diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a0b5dc04c..6f11682178 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,9 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v1 + - name: Setup Maven and Java Action + uses: s4u/setup-maven-action@v1.13.0 with: java-version: '17' + maven-version: '3.9.6' - name: Build - run: mvn --batch-mode install \ No newline at end of file + run: mvn --batch-mode install -DskipTests \ No newline at end of file diff --git a/.github/workflows/build_latest.yml b/.github/workflows/build_latest.yml index 5e5e0749e3..0779cb277c 100644 --- a/.github/workflows/build_latest.yml +++ b/.github/workflows/build_latest.yml @@ -15,10 +15,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v1 + - name: Setup Maven and Java Action + uses: s4u/setup-maven-action@v1.13.0 with: java-version: '17' + maven-version: '3.9.6' - name: Build run: mvn --batch-mode install -DskipTests diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java index 3441135480..f3046c8fce 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/AlarmURI.java @@ -8,6 +8,8 @@ package org.phoebus.applications.alarm.ui; import java.net.URI; +import java.util.HashMap; +import java.util.Map; /** Alarm URI helpers * @@ -23,18 +25,32 @@ public class AlarmURI /** URI schema used to refer to an alarm config */ public static final String SCHEMA = "alarm"; + /** Nme to use for item as query parameter */ + public static final String QUERY_PARAMETER_ITEM_NAME = "itemName"; + /** @param server Kafka server host:port * @param config_name Alarm configuration root * @return URI used to access that alarm configuration, "alarm://host:port/config_name" */ - public static URI createURI(final String server, final String config_name) - { - return URI.create(SCHEMA + "://" + server + "/" + config_name); + public static URI createURI(final String server, final String config_name) { + return createURI(server, config_name, null); + } + + /** @param server Kafka server host:port + * @param config_name Alarm configuration root + * @param rawQuery raw query for URI + * @return URI used to access that alarm configuration, "alarm://host:port/config_name?rawQuery" + */ + public static URI createURI(final String server, final String config_name, String rawQuery) { + if (rawQuery != null && rawQuery.length() > 0) + return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery); + else + return URI.create(SCHEMA + "://" + server + "/" + config_name); } /** Parse alarm configuration parameters from URI - * @param resource "alarm://localhost:9092/Accelerator" - * @return [ "localhost:9092", "Accelerator" ] + * @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value" + * @return ["localhost:9092", "Accelerator", null] or ["localhost:9092", "Accelerator", "param=value"] * @throws Exception on error */ public static String[] parseAlarmURI(final URI resource) throws Exception @@ -55,10 +71,35 @@ public static String[] parseAlarmURI(final URI resource) throws Exception if (port < 0) port = 9092; String config_name = resource.getPath(); + String rawQuery = resource.getRawQuery(); if (config_name.startsWith("/")) config_name = config_name.substring(1); if (config_name.isEmpty()) throw new Exception("Missing alarm config name in " + resource + ", expecting " + SCHEMA + "://{host}:{port}/{config_name}"); - return new String[] { resource.getHost() + ":" + port, config_name }; + return new String[] { resource.getHost() + ":" + port, config_name, rawQuery }; } + + /** + * Return a map with parameter names and values as key-value pairs for raw query of given resource. + * @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value" + * @return map with parameter names as keys and parameter values as values for raw query + */ + public static Map getRawQueryParametersValues(URI resource) { + Map map = new HashMap<>(); + String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split("&") : null; + if (queryParametersValues != null) { + for (String queryParameterValue : queryParametersValues) { + String[] parameterValue = queryParameterValue.split("="); + if (parameterValue != null) { + if (parameterValue.length == 2) { + map.put(parameterValue[0], parameterValue[1]); + } else if (parameterValue.length == 1) { + map.put(parameterValue[0], null); + } + } + } + } + return map; + } + } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java index 6bc83d1a10..63f5762453 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/AlarmAreaView.java @@ -9,6 +9,8 @@ import static org.phoebus.applications.alarm.AlarmSystem.logger; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -31,16 +33,15 @@ import org.phoebus.applications.alarm.model.AlarmTreeItem; import org.phoebus.applications.alarm.model.SeverityLevel; import org.phoebus.applications.alarm.ui.AlarmUI; +import org.phoebus.applications.alarm.ui.AlarmURI; import org.phoebus.ui.javafx.JFXUtil; import org.phoebus.ui.javafx.UpdateThrottle; import javafx.application.Platform; -import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; import javafx.scene.paint.Color; import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineJoin; @@ -104,8 +105,6 @@ public AlarmAreaView(final AlarmClient model) areaFilter = new AreaFilter(AlarmSystem.alarm_area_level); model.addListener(this); - - createContextMenu(); } // AlarmClientModelListener @@ -205,6 +204,23 @@ private void recreateItems(final List items) for (String item_name : items) { final Label view_item = newAreaLabel(item_name); + + // context menu content for alarm model item instead of alarm area + // link to item in tree view + view_item.setOnContextMenuRequested(event -> { + // need to clear and repopulate context menu since alarm area is recreated multiple times + // (but number of times unknown) + for (int i=menu.getItems().size()-1; i>0; i--) { + if (menu.getItems().get(i).getClass().equals(OpenTreeViewAction.class)) { + menu.getItems().remove(i); + } + } + + OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, AlarmURI.QUERY_PARAMETER_ITEM_NAME + "=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8)); + menu.getItems().add(otva); + menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY()); + }); + itemViewMap.put(item_name, view_item); updateItem(item_name); final int column = index % AlarmSystem.alarm_area_column_count; @@ -241,19 +257,10 @@ private void updateItem(final String item_name) view_item.setStyle("-fx-alignment: center; -fx-border-color: black; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-insets: 1; -fx-background-radius: 10; -fx-text-fill: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelColor(severityLevel)) + "; -fx-background-color: " + JFXUtil.webRGB(AlarmUI.getAlarmAreaPanelBackgroundColor(severityLevel))); } - private void createContextMenu() - { - final ObservableList menu_items = menu.getItems(); - - menu_items.add(new OpenTreeViewAction(alarmConfigName)); - this.setOnContextMenuRequested(event -> - menu.show(this.getScene().getWindow(), event.getScreenX(), event.getScreenY()) - ); - } - /** @return Context menu */ public ContextMenu getMenu() { return menu; } + } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java index fd99a80747..fcae14aae3 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/area/OpenTreeViewAction.java @@ -20,17 +20,24 @@ * @author Evan Smith */ @SuppressWarnings("nls") -public class OpenTreeViewAction extends MenuItem -{ +public class OpenTreeViewAction extends MenuItem { /** * Constructor * @param alarmConfigName The alarm configuration name */ - public OpenTreeViewAction(String alarmConfigName) - { + public OpenTreeViewAction(String alarmConfigName) { + this(alarmConfigName, null); + } + + /** + * Constructor + * @param alarmConfigName The alarm configuration name + * @param alarmRawQuery raw query for alarm (null if no such information is available) + */ + public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery) { final AlarmTreeMenuEntry entry = new AlarmTreeMenuEntry(); - entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName)); + entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName, alarmRawQuery)); setText(entry.getName()); setGraphic(new ImageView(entry.getIcon())); setOnAction(event -> @@ -45,4 +52,5 @@ public OpenTreeViewAction(String alarmConfigName) } }); } + } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java index a05ee7bb6a..f08fe1baa6 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeInstance.java @@ -10,6 +10,8 @@ import static org.phoebus.applications.alarm.AlarmSystem.logger; import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; @@ -46,7 +48,12 @@ public AlarmTreeInstance(final AlarmTreeApplication app, final URI input) throws { this.app = app; - tab = new DockItemWithInput(this, create(input), input, null, null); + // split input with/without (raw) query + final URI resource = new URI(input.getScheme(), input.getUserInfo(), input.getHost(), input.getPort(), input.getPath(), null, null); + String itemName = AlarmURI.getRawQueryParametersValues(input).get(AlarmURI.QUERY_PARAMETER_ITEM_NAME); + itemName = itemName != null ? URLDecoder.decode(itemName, StandardCharsets.UTF_8) : null; + + tab = new DockItemWithInput(this, create(resource, itemName), resource, null, null); Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName())); tab.addCloseCheck(() -> { @@ -65,10 +72,11 @@ public AppDescriptor getAppDescriptor() /** Create UI for input, starts alarm client * * @param input Alarm URI, will be parsed into `server` and `config_name` + * @param itemName item name that may be expanded or given focus * @return Alarm UI * @throws Exception */ - private Node create(final URI input) throws Exception + private Node create(final URI input, String itemName) throws Exception { final String[] parsed = AlarmURI.parseAlarmURI(input); server = parsed[0]; @@ -77,7 +85,7 @@ private Node create(final URI input) throws Exception try { client = new AlarmClient(server, config_name, AlarmSystem.kafka_properties); - final AlarmTreeView tree_view = new AlarmTreeView(client); + final AlarmTreeView tree_view = new AlarmTreeView(client, itemName); client.start(); if (AlarmSystem.config_names.length > 0) @@ -104,7 +112,8 @@ private void changeConfig(final String new_config_name) { // Use same server name, but new config_name final URI new_input = AlarmURI.createURI(server, new_config_name); - tab.setContent(create(new_input)); + // no need for initial item name + tab.setContent(create(new_input, null)); tab.setInput(new_input); Platform.runLater(() -> tab.setLabel(config_name + " " + app.getDisplayName())); } diff --git a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java index e7c0c4a413..83a3efd6aa 100644 --- a/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java +++ b/app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java @@ -88,6 +88,7 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener /** Model with current alarm tree, sends updates */ private final AlarmClient model; + private final String itemName; /** Latch for initially pausing model listeners * @@ -150,7 +151,15 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener // constant performance? /** @param model Model to represent. Must not be running, yet */ - public AlarmTreeView(final AlarmClient model) + public AlarmTreeView(final AlarmClient model) { + this(model, null); + } + + /** + * @param model Model to represent. Must not be running, yet + * @param itemName item name that may be expanded or given focus + */ + public AlarmTreeView(final AlarmClient model, String itemName) { if (model.isRunning()) throw new IllegalStateException(); @@ -159,6 +168,7 @@ public AlarmTreeView(final AlarmClient model) changing.setBackground(new Background(new BackgroundFill(Color.BLUE, CornerRadii.EMPTY, Insets.EMPTY))); this.model = model; + this.itemName = itemName; tree_view.setShowRoot(false); tree_view.setCellFactory(view -> new AlarmTreeViewCell()); @@ -203,6 +213,16 @@ private void startup() // Represent model that should by now be fairly complete tree_view.setRoot(createViewItem(model.getRoot())); + // expand tree item if is matches item name + if (tree_view.getRoot() != null && itemName != null) { + for (TreeItem treeItem : tree_view.getRoot().getChildren()) { + if (((AlarmTreeItem) treeItem.getValue()).getName().equals(itemName)) { + expandAlarms(treeItem); + break; + } + } + } + // Set change indicator so that it clears when there are no more changes indicateChange(); showServerState(model.isServerAlive()); diff --git a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java index 50682f48e5..37d96ca6fd 100644 --- a/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java +++ b/app/alarm/ui/src/test/java/org/phoebus/applications/alarm/AlarmURITest.java @@ -11,6 +11,9 @@ import org.phoebus.applications.alarm.ui.AlarmURI; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -21,39 +24,148 @@ * @author Kay Kasemir */ @SuppressWarnings("nls") -public class AlarmURITest -{ +public class AlarmURITest { + @Test - public void testCreateURI() - { - final URI uri = AlarmURI.createURI("localhost:9092", "Accelerator"); - System.out.println(uri); + public void createURI() { + URI uri = AlarmURI.createURI("localhost:9092", "Accelerator"); assertThat(uri.toString(), equalTo("alarm://localhost:9092/Accelerator")); + + uri = AlarmURI.createURI("localhost:9092", "Accelerator", "param=value"); + assertThat(uri.toString(), equalTo("alarm://localhost:9092/Accelerator?param=value")); } @Test - public void testParseURI() throws Exception - { + public void parseAlarmURI() throws Exception { + // create with URI.create + // with / without default port String[] parsed = AlarmURI.parseAlarmURI(URI.create("alarm://localhost:9092/Accelerator")); - assertThat(parsed.length, equalTo(2)); + assertThat(parsed.length, equalTo(3)); assertThat(parsed[0], equalTo("localhost:9092")); assertThat(parsed[1], equalTo("Accelerator")); + assertThat(parsed[2], equalTo(null)); - // Default port parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test")); - assertThat(parsed.length, equalTo(2)); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo(null)); + + parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param")); + + parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=value")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=value")); + + parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def"))); + + parsed = AlarmURI.parseAlarmURI(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); + + // create with AlarmURI.createURI + // with / without default port + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("localhost:9092", "Accelerator")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("localhost:9092")); + assertThat(parsed[1], equalTo("Accelerator")); + assertThat(parsed[2], equalTo(null)); + + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo(null)); + + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param")); + + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=value")); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=value")); + + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(parsed.length, equalTo(3)); + assertThat(parsed[0], equalTo("host.my.site:9092")); + assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def"))); + + parsed = AlarmURI.parseAlarmURI(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); + assertThat(parsed.length, equalTo(3)); assertThat(parsed[0], equalTo("host.my.site:9092")); assertThat(parsed[1], equalTo("Test")); + assertThat(parsed[2], equalTo("param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); - try - { + try { AlarmURI.parseAlarmURI(URI.create("alarm://server_but_no_config")); fail("Didn't catch missing config name"); - } - catch (Exception ex) - { + } catch (Exception ex) { // Expected assertThat(ex.getMessage(), containsString("expecting")); } } + + @Test + public void getRawQueryParametersValues() throws Exception { + // create with URI.create + // with / without default port + //ImmutableMap immutableMap = AlarmURI.getRawQueryParametersValues(URI.create("alarm://localhost:9092/Accelerator")); + Map map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://localhost:9092/Accelerator")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=value")); + assertThat(map.get("param"), equalTo("value")); + + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(map.get("param2"), equalTo("value")); + + // create with AlarmURI.createURI + // with / without default port + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("localhost:9092", "Accelerator")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param")); + assertThat(map.get("param"), equalTo(null)); + + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=value")); + assertThat(map.get("param"), equalTo("value")); + + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value")); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + assertThat(map.get("param2"), equalTo("value")); + } + } diff --git a/app/channel/channelfinder/pom.xml b/app/channel/channelfinder/pom.xml index 03ab9ec901..df93acc4e0 100644 --- a/app/channel/channelfinder/pom.xml +++ b/app/channel/channelfinder/pom.xml @@ -39,10 +39,5 @@ jersey-client 1.19 - - com.google.guava - guava - ${guava.version} - diff --git a/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java b/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java index b833bd9bca..8c212a9701 100644 --- a/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java +++ b/app/channel/channelfinder/src/main/java/org/phoebus/channelfinder/ChannelFinderClientImpl.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Joiner; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientResponse; @@ -1002,7 +1001,7 @@ public Collection findByTag(String pattern) throws ChannelFinderExcepti public Collection findByProperty(String property, String... pattern) throws ChannelFinderException { Map propertyPatterns = new HashMap(); if (pattern.length > 0) { - propertyPatterns.put(property, Joiner.on(",").join(pattern)); //$NON-NLS-1$ + propertyPatterns.put(property, Arrays.stream(pattern).collect(Collectors.joining(","))); //$NON-NLS-1$ } else { propertyPatterns.put(property, "*"); //$NON-NLS-1$ } diff --git a/app/databrowser-json/pom.xml b/app/databrowser-json/pom.xml index 9fffffa15d..bab56bf956 100644 --- a/app/databrowser-json/pom.xml +++ b/app/databrowser-json/pom.xml @@ -39,12 +39,6 @@ ${jackson.version} - - com.google.guava - guava - ${guava.version} - - org.epics epics-util diff --git a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java index 4febe23c57..499f947610 100644 --- a/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java +++ b/app/databrowser-json/src/main/java/org/phoebus/archive/reader/json/internal/JsonVTypeReader.java @@ -11,10 +11,6 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; -import com.google.common.primitives.ImmutableDoubleArray; -import com.google.common.primitives.ImmutableIntArray; -import com.google.common.primitives.ImmutableLongArray; -import org.epics.util.array.CollectionNumbers; import org.epics.util.array.ListDouble; import org.epics.util.array.ListInteger; import org.epics.util.array.ListLong; @@ -43,6 +39,8 @@ import java.math.BigInteger; import java.text.NumberFormat; import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -103,12 +101,12 @@ public static VType readValue( parser.getTokenLocation()); } Display display = null; - ImmutableDoubleArray double_value = null; + List double_value = null; EnumDisplay enum_display = null; - ImmutableIntArray enum_value = null; + List enum_value = null; String field_name = null; boolean found_value = false; - ImmutableLongArray long_value = null; + List long_value = null; Double maximum = null; Double minimum = null; String quality = null; @@ -280,13 +278,12 @@ public static VType readValue( if (display == null) { display = Display.none(); } - if (double_value.length() == 1) { + if (double_value.size() == 1) { return VDouble.of( double_value.get(0), alarm, time, display); } else { return VDoubleArray.of( - CollectionNumbers.toListDouble( - double_value.toArray()), + toListDouble(double_value), alarm, time, display); @@ -296,7 +293,7 @@ public static VType readValue( // Ensure that we have labels for all indices. int min_value = Integer.MAX_VALUE; int max_value = Integer.MIN_VALUE; - for (var i = 0; i < enum_value.length(); ++i) { + for (var i = 0; i < enum_value.size(); ++i) { final var value = enum_value.get(i); min_value = Math.min(min_value, value); max_value = Math.max(max_value, value); @@ -321,7 +318,7 @@ public static VType readValue( Range.undefined(), "", NumberFormats.precisionFormat(0)); - if (enum_value.length() == 1) { + if (enum_value.size() == 1) { return VInt.of( enum_value.get(0), alarm, @@ -335,7 +332,7 @@ public static VType readValue( display); } } - if (enum_value.length() == 1) { + if (enum_value.size() == 1) { return VEnum.of( enum_value.get(0), enum_display, alarm, time); } else { @@ -366,7 +363,7 @@ display. getAlarmRange(), NumberFormats.precisionFormat(0), display.getDescription()); } - if (long_value.length() == 1) { + if (long_value.size() == 1) { return VLong.of(long_value.get(0), alarm, time, display); } else { return VLongArray.of( @@ -386,7 +383,7 @@ display. getAlarmRange(), "Mandatory field is missing in object.", parser.getTokenLocation()); } - if (double_value.length() == 1) { + if (double_value.size() == 1) { return VStatistics.of( double_value.get(0), Double.NaN, @@ -459,9 +456,10 @@ private static boolean readBooleanValue(final JsonParser parser) return parser.getBooleanValue(); } - private static ImmutableDoubleArray readDoubleArray( + private static List readDoubleArray( final JsonParser parser) throws IOException { - final var array_builder = ImmutableDoubleArray.builder(1); + + final List values = new ArrayList<>(); var token = parser.getCurrentToken(); if (token != JsonToken.START_ARRAY) { throw new JsonParseException( @@ -477,9 +475,9 @@ private static ImmutableDoubleArray readDoubleArray( if (token == JsonToken.END_ARRAY) { break; } - array_builder.add(readDoubleValue(parser)); + values.add(readDoubleValue(parser)); } - return array_builder.build(); + return Collections.unmodifiableList(values); } private static double readDoubleValue(final JsonParser parser) @@ -515,9 +513,9 @@ private static Instant readInstant(final JsonParser parser) return bigIntegerToTimestamp(parser.getBigIntegerValue()); } - private static ImmutableIntArray readIntArray(final JsonParser parser) + private static List readIntArray(final JsonParser parser) throws IOException { - final var array_builder = ImmutableIntArray.builder(1); + final List values = new ArrayList<>(); var token = parser.getCurrentToken(); if (token != JsonToken.START_ARRAY) { throw new JsonParseException( @@ -533,9 +531,9 @@ private static ImmutableIntArray readIntArray(final JsonParser parser) if (token == JsonToken.END_ARRAY) { break; } - array_builder.add(readIntValue(parser)); + values.add(readIntValue(parser)); } - return array_builder.build(); + return Collections.unmodifiableList(values); } private static int readIntValue(final JsonParser parser) @@ -551,9 +549,9 @@ private static int readIntValue(final JsonParser parser) return parser.getIntValue(); } - private static ImmutableLongArray readLongArray(final JsonParser parser) + private static List readLongArray(final JsonParser parser) throws IOException { - final var array_builder = ImmutableLongArray.builder(1); + final List values = new ArrayList<>(); var token = parser.getCurrentToken(); if (token != JsonToken.START_ARRAY) { throw new JsonParseException( @@ -569,9 +567,9 @@ private static ImmutableLongArray readLongArray(final JsonParser parser) if (token == JsonToken.END_ARRAY) { break; } - array_builder.add(readLongValue(parser)); + values.add(readLongValue(parser)); } - return array_builder.build(); + return Collections.unmodifiableList(values); } private static long readLongValue(final JsonParser parser) @@ -888,7 +886,7 @@ private static double stringToSpecialDouble( }; } - private static ListDouble toListDouble(final ImmutableDoubleArray array) { + private static ListDouble toListDouble(final List array) { return new ListDouble() { @Override public double getDouble(int index) { @@ -897,12 +895,12 @@ public double getDouble(int index) { @Override public int size() { - return array.length(); + return array.size(); } }; } - private static ListInteger toListInteger(final ImmutableIntArray array) { + private static ListInteger toListInteger(final List array) { return new ListInteger() { @Override public int getInt(int index) { @@ -911,12 +909,12 @@ public int getInt(int index) { @Override public int size() { - return array.length(); + return array.size(); } }; } - private static ListLong toListLong(final ImmutableLongArray array) { + private static ListLong toListLong(final List array) { return new ListLong() { @Override public long getLong(int index) { @@ -925,7 +923,7 @@ public long getLong(int index) { @Override public int size() { - return array.length(); + return array.size(); } }; } diff --git a/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java index afa7d7437b..aaad15aa28 100644 --- a/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java +++ b/app/databrowser-json/src/test/java/org/phoebus/archive/reader/json/HttpServerTestBase.java @@ -8,8 +8,6 @@ package org.phoebus.archive.reader.json; -import com.google.common.base.Splitter; -import com.google.common.collect.Maps; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @@ -23,10 +21,12 @@ import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Base class for tests that need an HTTP server. @@ -62,12 +62,18 @@ public record HttpRequest( */ public static Map parseQueryString( final String query_string) { - return Maps.transformValues( - Splitter - .on('&') - .withKeyValueSeparator('=') - .split(query_string), - (value) -> URLDecoder.decode(value, StandardCharsets.UTF_8)); + + return Arrays.stream(query_string.split("&")) + .collect(Collectors.toMap( + k -> k.split("=")[0], + k -> URLDecoder.decode(k.split("=")[1], StandardCharsets.UTF_8))); +// +// return Maps.transformValues( +// Splitter +// .on('&') +// .withKeyValueSeparator('=') +// .split(query_string), +// (value) -> URLDecoder.decode(value, StandardCharsets.UTF_8)); } /** diff --git a/app/databrowser-timescale/.classpath b/app/databrowser-timescale/.classpath index 83b0af9e8b..68a49ce44a 100644 --- a/app/databrowser-timescale/.classpath +++ b/app/databrowser-timescale/.classpath @@ -10,5 +10,6 @@ + diff --git a/app/databrowser-timescale/build.xml b/app/databrowser-timescale/build.xml index f7c0c637d4..a7871b647d 100644 --- a/app/databrowser-timescale/build.xml +++ b/app/databrowser-timescale/build.xml @@ -8,6 +8,7 @@ + diff --git a/app/databrowser/.classpath b/app/databrowser/.classpath index ea5d410a5d..fdb5eaefc4 100644 --- a/app/databrowser/.classpath +++ b/app/databrowser/.classpath @@ -13,6 +13,7 @@ + diff --git a/app/databrowser/build.xml b/app/databrowser/build.xml index 402e587b29..58a661cb05 100644 --- a/app/databrowser/build.xml +++ b/app/databrowser/build.xml @@ -8,6 +8,7 @@ + diff --git a/app/databrowser/pom.xml b/app/databrowser/pom.xml index d0139b4640..9aee48ab32 100644 --- a/app/databrowser/pom.xml +++ b/app/databrowser/pom.xml @@ -56,24 +56,19 @@ org.phoebus - app-rtplot + app-trends-archive-reader 4.7.4-SNAPSHOT - com.google.protobuf - protobuf-java - 3.21.9 + org.phoebus + app-rtplot + 4.7.4-SNAPSHOT org.epics epics-util ${epics.util.version} - - org.epics - pbrawclient - 0.0.10 - org.apache.commons diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java index 8eb68bcd5b..c2aeb636b1 100644 --- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java +++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/imports/ImportArchiveReaderFactory.java @@ -7,12 +7,12 @@ ******************************************************************************/ package org.csstudio.trends.databrowser3.imports; +import org.phoebus.archive.reader.ArchiveReader; +import org.phoebus.archive.reader.spi.ArchiveReaderFactory; + import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; - import org.csstudio.trends.databrowser3.model.ArchiveDataSource; -import org.phoebus.archive.reader.ArchiveReader; -import org.phoebus.archive.reader.spi.ArchiveReaderFactory; /** Factory for {@link ArchiveReader} that imports data from file * @author Kay Kasemir diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java index 9e154a754d..18780336ba 100644 --- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java +++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/preferences/Preferences.java @@ -88,6 +88,7 @@ public static class TimePreset /** Setting */ @Preference public static boolean drop_failed_archives; /** Setting */ + @Deprecated @Preference public static String[] equivalent_pv_prefixes; /** Setting */ @Preference public static boolean use_trace_names; diff --git a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory index 7c0cb8427b..3db81f793e 100644 --- a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory +++ b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory @@ -1,5 +1 @@ -org.phoebus.archive.reader.appliance.ApplianceArchiveReaderFactory -org.phoebus.archive.reader.rdb.RDBArchiveReaderFactory -org.phoebus.archive.reader.channelarchiver.XMLRPCArchiveReaderFactory -org.phoebus.archive.reader.channelarchiver.file.ArchiveFileReaderFactory org.csstudio.trends.databrowser3.imports.ImportArchiveReaderFactory diff --git a/app/display/representation-javafx/.classpath b/app/display/representation-javafx/.classpath index b4875b35dc..f4a0fd70dd 100644 --- a/app/display/representation-javafx/.classpath +++ b/app/display/representation-javafx/.classpath @@ -10,6 +10,7 @@ + diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java index b685b56ce7..5adf641745 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/WidgetInfoDialog.java @@ -18,7 +18,14 @@ import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; - +import java.util.stream.Collectors; + +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.ContextMenuEvent; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.Widget; import org.csstudio.display.builder.model.WidgetDescriptor; @@ -31,8 +38,12 @@ import org.epics.vtype.AlarmSeverity; import org.epics.vtype.VNumberArray; import org.epics.vtype.VType; +import org.phoebus.core.types.ProcessVariable; import org.phoebus.core.vtypes.VTypeHelper; +import org.phoebus.framework.adapter.AdapterService; import org.phoebus.framework.macros.Macros; +import org.phoebus.framework.selection.SelectionService; +import org.phoebus.ui.application.ContextMenuService; import org.phoebus.ui.dialog.DialogHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.ui.javafx.ReadOnlyTextCell; @@ -61,6 +72,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; +import org.phoebus.ui.spi.ContextMenuEntry; /** Dialog for displaying widget information * @author Kay Kasemir @@ -75,16 +87,14 @@ public class WidgetInfoDialog extends Dialog private DisplayWidgetStats stats; /** PV info */ - public static class NameStateValue + public static class NameStateValue extends ProcessVariable { - /** PV Name */ - public final String name; /** State, incl. read-only or writable? */ - public final String state; + private final String state; /** Last known value */ - public final VType value; + private final VType value; /** Path to Widget within display that uses the PV */ - public final String path; + private final String path; /** @param name PV Name * @param state State, incl. read-only or writable? @@ -93,11 +103,23 @@ public static class NameStateValue */ public NameStateValue(final String name, final String state, final VType value, final String path) { - this.name = name; + super(name); this.state = state; this.value = value; this.path = path; } + + public String getState() { + return state; + } + + public VType getValue() { + return value; + } + + public String getPath() { + return path; + } } /** Cell with text colored based on alarm severity */ @@ -209,8 +231,8 @@ private void exportToCSV(File file){ buffer.append("PVS (name, state, value, widget path)").append(System.lineSeparator()) .append(horizontalRuler).append(System.lineSeparator()); - pvs.stream().sorted(Comparator.comparing(pv -> pv.name)).forEach(pv -> { - buffer.append(pv.name).append(itemSeparator) + pvs.stream().sorted(Comparator.comparing(pv -> pv.getName())).forEach(pv -> { + buffer.append(pv.getName()).append(itemSeparator) .append(pv.state) .append(itemSeparator) .append(getPVValue(pv.value)) @@ -292,16 +314,13 @@ private Tab createPVs(final Collection pvs) { // Use text field to allow users to copy the name, value to clipboard final TableColumn name = new TableColumn<>(Messages.WidgetInfoDialog_Name); - name.setCellFactory(col -> new ReadOnlyTextCell<>()); - name.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().name)); + name.setCellValueFactory(new PropertyValueFactory("name")); final TableColumn state = new TableColumn<>(Messages.WidgetInfoDialog_State); - state.setCellFactory(col -> new ReadOnlyTextCell<>()); - state.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().state)); + state.setCellValueFactory(new PropertyValueFactory("state")); final TableColumn path = new TableColumn<>(Messages.WidgetInfoDialog_Path); - path.setCellFactory(col -> new ReadOnlyTextCell<>()); - path.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().path)); + path.setCellValueFactory(new PropertyValueFactory("path")); final TableColumn value = new TableColumn<>(Messages.WidgetInfoDialog_Value); value.setCellFactory(col -> new AlarmColoredCell()); @@ -312,7 +331,7 @@ private Tab createPVs(final Collection pvs) }); final ObservableList pv_data = FXCollections.observableArrayList(pvs); - pv_data.sort(Comparator.comparing(a -> a.name)); + pv_data.sort(Comparator.comparing(a -> a.getName())); final TableView table = new TableView<>(pv_data); table.getColumns().add(name); table.getColumns().add(state); @@ -320,6 +339,43 @@ private Tab createPVs(final Collection pvs) table.getColumns().add(path); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + table.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> { + if (newSelection != null) { + SelectionService.getInstance().setSelection(table, table.getSelectionModel().getSelectedItems()); + } + }); + + table.setOnContextMenuRequested(event -> { + + final ContextMenu contextMenu = new ContextMenu(); + + List contextEntries = ContextMenuService.getInstance().listSupportedContextMenuEntries(); + + contextEntries.forEach(entry -> { + MenuItem item = new MenuItem(entry.getName(), new ImageView(entry.getIcon())); + item.setOnAction(e -> { + try { + ObservableList old = table.getSelectionModel().getSelectedItems(); + + List selectedPVs = SelectionService.getInstance().getSelection().getSelections().stream().map(s -> { + return AdapterService.adapt(s, entry.getSupportedType()).get(); + }).collect(Collectors.toList()); + // set the selection + SelectionService.getInstance().setSelection(table, selectedPVs); + entry.call(SelectionService.getInstance().getSelection()); + // reset the selection + SelectionService.getInstance().setSelection(table, old); + } catch (Exception ex) { + //logger.log(Level.WARNING, "Failed to execute action " + entry.getName(), ex); + } + }); + contextMenu.getItems().add(item); + }); + + table.setContextMenu(contextMenu); + }); + return new Tab(Messages.WidgetInfoDialog_TabPVs, table); } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java index e6b032542d..dde8845324 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/EmbeddedDisplayRepresentation.java @@ -15,6 +15,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; +import javafx.application.Platform; import org.csstudio.display.builder.model.DirtyFlag; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.UntypedWidgetPropertyListener; @@ -473,24 +474,26 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0) @Override public void dispose() { - // When the file name is changed, updatePendingDisplay() - // will atomically update the active_content_model, - // represent the new model, and then set runtimePropEmbeddedModel. - // - // Fetching the embedded model from active_content_model - // could dispose a representation that hasn't been represented, yet. - // Fetching the embedded model from runtimePropEmbeddedModel - // could fail to dispose what's just now being represented. - // - // --> Very unlikely to happen because runtime has been stopped, - // so nothing is changing the file name right now. - final DisplayModel em = active_content_model.getAndSet(null); - model_widget.runtimePropEmbeddedModel().setValue(null); - - if (inner != null && em != null) - toolkit.disposeRepresentation(em); - inner = null; - - super.dispose(); + Platform.runLater(() -> { + // When the file name is changed, updatePendingDisplay() + // will atomically update the active_content_model, + // represent the new model, and then set runtimePropEmbeddedModel. + // + // Fetching the embedded model from active_content_model + // could dispose a representation that hasn't been represented, yet. + // Fetching the embedded model from runtimePropEmbeddedModel + // could fail to dispose what's just now being represented. + // + // --> Very unlikely to happen because runtime has been stopped, + // so nothing is changing the file name right now. + final DisplayModel em = active_content_model.getAndSet(null); + model_widget.runtimePropEmbeddedModel().setValue(null); + + if (inner != null && em != null) + toolkit.disposeRepresentation(em); + inner = null; + + super.dispose(); + }); } } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java index 3ba4f67132..40f03b8a0c 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java @@ -251,7 +251,6 @@ public void dispose() logger.log(Level.WARNING, "Missing JFX parent for " + model_widget); else JFXRepresentation.getChildren(parent).remove(jfx_node); - jfx_node = null; } /** Get parent that would be used for child-widgets. diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java index 9ae909fced..11105cf3fa 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/plots/XYPlotRepresentation.java @@ -736,6 +736,5 @@ public void dispose() { super.dispose(); plot.dispose(); - plot = null; } } diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java index 90360b9007..7164b691f1 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitListener.java @@ -44,4 +44,12 @@ public interface ToolkitListener * @param value The value */ default public void handleWrite(Widget widget, Object value) {}; + + /** + * A method was called from the UI that other listeners might be interested in. + * @param user_args Zero or more objects relevant to what was called. + * Case-specific Implementations should expect and check these. + */ + default public void handleMethodCalled(Object... user_args) {}; + } diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java index 81da7b057e..f5fc3a0fa1 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java @@ -689,6 +689,20 @@ public void fireWrite(final Widget widget, final Object value) } } + public void fireMethodCall(Object... user_args) { + for (final ToolkitListener listener : listeners) + { + try + { + listener.handleMethodCalled(user_args); + } + catch (final Throwable ex) + { + logger.log(Level.WARNING, "Failure when firing method-call event for " + Thread.currentThread().getStackTrace()[1].getMethodName(), ex); + } + } + }; + /** Close the toolkit's "window" that displays a model * @param model Model that has been represented in this toolkit * @throws Exception on error diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java index e4a8018ce0..cc9515bf1e 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/WidgetRepresentation.java @@ -86,9 +86,5 @@ public void initialize(final ToolkitRepresentation toolkit, void destroy() { dispose(); - - // Speedup GC - model_widget = null; - toolkit = null; } } diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java index ddca7128e8..3c01b44f37 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DisplayRuntimeInstance.java @@ -11,6 +11,7 @@ import java.awt.geom.Rectangle2D; +import java.util.ArrayList; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; @@ -276,7 +277,7 @@ void close() } /** @return Current display info or null */ - DisplayInfo getDisplayInfo() + public DisplayInfo getDisplayInfo() { return display_info.orElse(null); } @@ -286,14 +287,19 @@ DisplayInfo getDisplayInfo() */ public void loadDisplayFile(final DisplayInfo info) { + DisplayInfo old_info = display_info.orElse(null); // If already executing another display, shut it down disposeModel(); + ArrayList dst_src = new ArrayList<>(); + // Set input ASAP so that other requests to open this // resource will find this instance and not start // another instance dock_item.setInput(info.toURI()); + StackTraceElement[] applicationThreadStackTrace = Thread.currentThread().getStackTrace(); + // Now that old model is no longer represented, // show info. // Showing this info before disposeModel() @@ -322,6 +328,9 @@ public void loadDisplayFile(final DisplayInfo info) { representation.awaitRepresentation(30, TimeUnit.SECONDS); representation_init.run(); + dst_src.add(info); + dst_src.add(old_info); + representation.fireMethodCall(dst_src, applicationThreadStackTrace); logger.log(Level.FINE, "Done with representing model of " + info.getPath()); } catch (TimeoutException | InterruptedException ex) diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java index fc23c0dce4..5a13862a88 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/DockItemRepresentation.java @@ -141,4 +141,24 @@ public void representModel(final Parent model_parent, final DisplayModel model) app_instance.trackCurrentModel(model); super.representModel(model_parent, model); } + + @Override + public void closeWindow(final DisplayModel model) throws Exception + { + // Is called from ScriptUtil, i.e. scripts, from background thread + final Parent model_parent = Objects.requireNonNull(model.getUserData(Widget.USER_DATA_TOOLKIT_PARENT)); + if (model_parent.getProperties().get(DisplayRuntimeInstance.MODEL_PARENT_DISPLAY_RUNTIME) == app_instance) + { + // Prepare-to-close, which might take time and must be called off the UI thread + final DisplayRuntimeInstance instance = (DisplayRuntimeInstance) app_instance.getRepresentation().getModelParent().getProperties().get(DisplayRuntimeInstance.MODEL_PARENT_DISPLAY_RUNTIME); + if (instance != null) + instance.getDockItem().prepareToClose(); + else + logger.log(Level.SEVERE, "Missing DisplayRuntimeInstance to prepare closing", new Exception("Stack Trace")); + // 'close' on the UI thread + execute(() -> app_instance.close()); + } + else + throw new Exception("Wrong model"); + } } diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java index 600fff19bd..7f6b4b2a60 100644 --- a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/script/ScriptUtil.java @@ -141,6 +141,24 @@ public static void openDisplay(final Widget widget, final String file, final Str ActionUtil.handleAction(widget, open); } + /** Close a display + * + * @param widget Widget within the display to close + */ + public static void closeDisplay(final Widget widget) + { + try + { + final DisplayModel model = widget.getTopDisplayModel(); + final ToolkitRepresentation toolkit = ToolkitRepresentation.getToolkit(model); + toolkit.closeWindow(model); + } + catch (Throwable ex) + { + logger.log(Level.WARNING, "Cannot close display", ex); + } + } + // ==================== // public alert dialog utils diff --git a/app/logbook/inmemory/pom.xml b/app/logbook/inmemory/pom.xml index 448a2effac..63e28235f4 100644 --- a/app/logbook/inmemory/pom.xml +++ b/app/logbook/inmemory/pom.xml @@ -12,10 +12,5 @@ core-logbook 4.7.4-SNAPSHOT - - com.google.guava - guava - ${guava.version} - diff --git a/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java b/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java index a9f8f9eb9a..468e545180 100644 --- a/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java +++ b/app/logbook/inmemory/src/main/java/org/phoebus/applications/logbook/InMemoryLogClient.java @@ -1,24 +1,47 @@ package org.phoebus.applications.logbook; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.net.URLConnection; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.time.Instant; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.phoebus.logbook.*; +import org.phoebus.logbook.Attachment; +import org.phoebus.logbook.AttachmentImpl; +import org.phoebus.logbook.LogClient; +import org.phoebus.logbook.LogEntry; +import org.phoebus.logbook.LogEntryImpl; import org.phoebus.logbook.LogEntryImpl.LogEntryBuilder; - -import com.google.common.io.Files; +import org.phoebus.logbook.Logbook; +import org.phoebus.logbook.LogbookException; +import org.phoebus.logbook.LogbookImpl; +import org.phoebus.logbook.Property; +import org.phoebus.logbook.PropertyImpl; +import org.phoebus.logbook.SearchResult; +import org.phoebus.logbook.Tag; +import org.phoebus.logbook.TagImpl; /** * A logbook which maintains logentries in memory. It is mainly for testing and debugging purpose. */ -public class InMemoryLogClient implements LogClient{ +public class InMemoryLogClient implements LogClient { private static Logger logger = Logger.getLogger(InMemoryLogClient.class.getName()); private final AtomicInteger logIdCounter; private final Map logEntries; @@ -105,7 +128,7 @@ public LogEntry set(LogEntry log) { ext = file.getName().substring(i); } File tempFile = File.createTempFile(prefix, ext); - Files.copy(file, tempFile); + Files.copy(file.toPath(), tempFile.toPath()); tempFile.deleteOnExit(); String mimeType = URLConnection.guessContentTypeFromName(tempFile.getName()); return AttachmentImpl.of(tempFile, mimeType != null ? mimeType : ext, false); diff --git a/app/logbook/olog/ui/pom.xml b/app/logbook/olog/ui/pom.xml index 3adee8c0f0..9df3fa0dc3 100644 --- a/app/logbook/olog/ui/pom.xml +++ b/app/logbook/olog/ui/pom.xml @@ -40,11 +40,6 @@ core-security 4.7.4-SNAPSHOT - - com.google.guava - guava - ${guava.version} - org.jfxtras jfxtras-agenda diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java index 19871335af..e97816cd58 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AdvancedSearchViewController.java @@ -18,7 +18,6 @@ package org.phoebus.logbook.olog.ui; -import com.google.common.base.Strings; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; @@ -42,6 +41,7 @@ import org.phoebus.ui.dialog.ListSelectionPopOver; import org.phoebus.ui.dialog.PopOver; import org.phoebus.ui.time.TimeRelativeIntervalPane; +import org.phoebus.util.text.Strings; import org.phoebus.util.time.TimeParser; import org.phoebus.util.time.TimeRelativeInterval; import org.phoebus.util.time.TimestampFormats; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java index 07beeea29a..5a791d621e 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java @@ -60,6 +60,7 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -306,7 +307,7 @@ private void copyAttachment(File targetFolder, Attachment attachment) { if (targetFile.exists()) { throw new Exception("Target file " + targetFile.getAbsolutePath() + " exists"); } - Files.copy(attachment.getFile().toPath(), targetFile.toPath()); + Files.copy(attachment.getFile().toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { ExceptionDetailsErrorDialog.openError(splitPane.getParent(), Messages.FileSave, Messages.FileSaveFailed, e); } diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java index 4fa8ca4194..3eb48128d3 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java @@ -1,6 +1,5 @@ package org.phoebus.logbook.olog.ui; -import com.google.common.base.Strings; import javafx.scene.image.Image; import org.phoebus.framework.spi.AppInstance; import org.phoebus.framework.spi.AppResourceDescriptor; @@ -9,6 +8,7 @@ import org.phoebus.logbook.LogService; import org.phoebus.logbook.LogbookPreferences; import org.phoebus.ui.javafx.ImageCache; +import org.phoebus.util.text.Strings; import java.net.URI; import java.util.logging.Logger; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java index c50ca88d73..deaadb5baf 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java @@ -1,11 +1,11 @@ package org.phoebus.logbook.olog.ui; -import com.google.common.base.Strings; import javafx.scene.image.Image; import org.phoebus.framework.spi.AppInstance; import org.phoebus.framework.spi.AppResourceDescriptor; import org.phoebus.logbook.*; import org.phoebus.ui.javafx.ImageCache; +import org.phoebus.util.text.Strings; import java.net.URI; import java.util.logging.Logger; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java index 097e623b9d..99d9802e75 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java @@ -1,7 +1,6 @@ package org.phoebus.logbook.olog.ui; -import com.google.common.base.Strings; -import org.checkerframework.checker.units.qual.K; +import org.phoebus.util.text.Strings; import org.phoebus.util.time.TimeParser; import java.net.URI; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java index 795e515a42..f50ba2e095 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SearchParameters.java @@ -18,13 +18,13 @@ package org.phoebus.logbook.olog.ui; -import com.google.common.base.Strings; import javafx.beans.InvalidationListener; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import org.phoebus.logbook.olog.ui.LogbookQueryUtil.Keys; +import org.phoebus.util.text.Strings; import java.util.ArrayList; import java.util.HashMap; diff --git a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java index 2ef0956433..ba6960d68f 100644 --- a/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java +++ b/app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/SingleLogEntryDisplayController.java @@ -188,7 +188,14 @@ private void fetchAttachments() { Collection attachments = logEntry.getAttachments().stream() .filter((attachment) -> attachment.getName() != null && !attachment.getName().isEmpty()) .map((attachment) -> { - OlogAttachment fileAttachment = new OlogAttachment(); + OlogAttachment fileAttachment = new OlogAttachment() { + @Override + protected void finalize() { + if (getFile() != null && getFile().exists()) { + getFile().delete(); + } + } + }; fileAttachment.setContentType(attachment.getContentType()); fileAttachment.setThumbnail(false); fileAttachment.setFileName(attachment.getName()); diff --git a/app/logbook/ui/pom.xml b/app/logbook/ui/pom.xml index 049e5dd541..530e494d43 100644 --- a/app/logbook/ui/pom.xml +++ b/app/logbook/ui/pom.xml @@ -32,11 +32,6 @@ core-security 4.7.4-SNAPSHOT - - com.google.guava - guava - ${guava.version} - org.jfxtras jfxtras-agenda diff --git a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java index c8c20dac81..64c5741dea 100644 --- a/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java +++ b/app/logbook/ui/src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java @@ -9,10 +9,9 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.phoebus.util.text.Strings; import org.phoebus.util.time.TimeParser; -import com.google.common.base.Strings; - public class LogbookQueryUtil { // Ordered search keys diff --git a/app/trends/archive-datasource/build.xml b/app/trends/archive-datasource/build.xml new file mode 100644 index 0000000000..707746f875 --- /dev/null +++ b/app/trends/archive-datasource/build.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst new file mode 100644 index 0000000000..5d63610b6b --- /dev/null +++ b/app/trends/archive-datasource/doc/index.rst @@ -0,0 +1,29 @@ +Archive Datasources +=================== + +Overview +-------- +The archive datasources allow accessing historical data as a PV. There are two types of archive datasources: + +1. `archive`: Retrieves a single archived value at a particular instant of time. +2. `replay`: Creates a PV that recreates changes in values based on data from the archive. + +Archive PV Syntax +----------------- + +The prefix for the datasource is ``archive://``, which can be omitted if configured as the default datasource. +The archiver PVs are read-only and constant. + +- `archive://pv_name`: Retrieves the latest value in the archiver. +- `archive://pv_name(time)`: Retrieves the last value at or before the specified "time". + +Replay PV Syntax +---------------- + +The prefix for this datasource is ``replay://``. +The replay PVs are read-only and constant. + +- `replay://pv_name`: Retrieves the last 5 minutes of data for this PV from the archiver and replays them at 10Hz. +- `replay://pv_name(start, end)`: Recreates the PV value changes using the data from the archiver between the specific start and end times. +- `replay://pv_name(start, end, update_rate)`: Recreates the PV value changes using the data from the archiver between the specified start and end times. Updates occur at the rate specified by `update_rate` (a value defined in seconds). + diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml new file mode 100644 index 0000000000..988864edd9 --- /dev/null +++ b/app/trends/archive-datasource/pom.xml @@ -0,0 +1,25 @@ + + + + app-trends + org.phoebus + 4.7.4-SNAPSHOT + + 4.0.0 + app-trends-archive-datasource + + + 17 + 17 + + + + + org.phoebus + app-trends-archive-reader + 4.7.4-SNAPSHOT + + + \ No newline at end of file diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java new file mode 100644 index 0000000000..7a7f43674d --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java @@ -0,0 +1,32 @@ +package org.phoebus.pv.archive; + +import org.phoebus.archive.reader.ArchiveReader; +import org.phoebus.archive.reader.appliance.ApplianceArchiveReader; + +public class ArchiveReaderService { + + /** + * Singleton + */ + private static final ArchiveReaderService INSTANCE = new ArchiveReaderService(); + + private final ArchiveReader reader; + + public static ArchiveReaderService getService() { + return INSTANCE; + } + + private ArchiveReaderService() { + // Might have to add support for multiple AA URL's + reader = createReader(Preferences.archive_url); + } + + private ArchiveReader createReader(final String url) { + final ApplianceArchiveReader reader = new ApplianceArchiveReader(url, false, true); + return reader; + } + + public ArchiveReader getReader() { + return reader; + } +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java new file mode 100644 index 0000000000..07fa49a8c9 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java @@ -0,0 +1,40 @@ +package org.phoebus.pv.archive; + +import java.time.DateTimeException; +import java.time.Instant; + +import static org.phoebus.util.time.TimestampFormats.DATETIME_FORMAT; +import static org.phoebus.util.time.TimestampFormats.FULL_FORMAT; +import static org.phoebus.util.time.TimestampFormats.MILLI_FORMAT; +import static org.phoebus.util.time.TimestampFormats.SECONDS_FORMAT; + +public class ArchiveReaderUtil { + + /** + * A utility method to parse a subset of supported time formats used by the archive datasources + * Support formats include + * FULL_PATTERN = "yyyy-MM-dd HH:mm:ss.nnnnnnnnn"; + * MILLI_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; + * SECONDS_PATTERN = "yyyy-MM-dd HH:mm:ss"; + * DATETIME_PATTERN = "yyyy-MM-dd HH:mm"; + * + * @param timeValue + * @return + */ + public static Instant parseSupportedTimeFormat(String timeValue) throws Exception { + Instant time; + try { + switch (timeValue.length()) { + case 16 -> time = Instant.from(DATETIME_FORMAT.parse(timeValue)); + case 19 -> time = Instant.from(SECONDS_FORMAT.parse(timeValue)); + case 23 -> time = Instant.from(MILLI_FORMAT.parse(timeValue)); + case 29 -> time = Instant.from(FULL_FORMAT.parse(timeValue)); + default -> throw new Exception("Time value defined in a unknown format, '" + timeValue + "'"); + } + } catch ( + DateTimeException e) { + throw new Exception("Time value defined in a unknown format, '" + timeValue + "'"); + } + return time; + } +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java new file mode 100644 index 0000000000..3c0da79c2d --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java @@ -0,0 +1,23 @@ +package org.phoebus.pv.archive; + +import org.phoebus.framework.preferences.AnnotatedPreferences; +import org.phoebus.framework.preferences.Preference; +import org.phoebus.framework.preferences.PreferencesReader; + +/** Helper for reading preference settings + * + * @author Kunal Shroff + */ +@SuppressWarnings("nls") +public class Preferences +{ + + /** Setting */ + @Preference public static String archive_url; + + static + { + final PreferencesReader prefs = AnnotatedPreferences.initialize(Preferences.class, "/appliance_datasource_preferences.properties"); + } + +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java new file mode 100644 index 0000000000..c071f96667 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java @@ -0,0 +1,107 @@ +package org.phoebus.pv.archive.replay; + +import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.pv.PV; +import org.phoebus.pv.archive.ArchiveReaderService; + +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +/** Base for replay PVs + * + * @author Kunal Shroff, Kay Kasemir, based on similar code in org.csstudio.utility.pv and diirt + */ +@SuppressWarnings("nls") +public class ReplayPV extends PV +{ + ArchiveReaderService service = ArchiveReaderService.getService(); + + /** Timer for replaying updates */ + private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target -> + { + final Thread thread = new Thread(target, "ReplayPV"); + thread.setDaemon(true); + return thread; + }); + + /** Task that was submitted for periodic updates */ + private ScheduledFuture task; + + private ValueIterator i; + + /** + * + * @param completeName + * @param name + * @param start + * @param end + */ + public ReplayPV(String completeName, final String name, Instant start, Instant end) + { + this(completeName, name, start, end, .1); + } + + /** + * + * @param completeName + * @param name + * @param start + * @param end + * @param update + */ + public ReplayPV(String completeName, final String name, Instant start, Instant end, double update) + { + super(completeName); + + // ReplayPV PVs are read-only + notifyListenersOfPermissions(true); + + try { + i = service.getReader().getRawValues(name, start, end); + + if (i.hasNext()) { + notifyListenersOfValue(i.next()); + } + } catch (Exception e) { + e.printStackTrace(); + } + start(update); + } + + /** Start periodic updates + * @param update_seconds Update period in seconds + */ + protected void start(final double update_seconds) + { + // Limit rate to 100 Hz + final long milli = Math.round(Math.max(update_seconds, 0.01) * 1000); + task = executor.scheduleAtFixedRate(this::update, milli, milli, TimeUnit.MILLISECONDS); + } + + /** Called by periodic timer */ + protected void update() + { + try { + if (i.hasNext()) { + notifyListenersOfValue(i.next()); + } else { + close(); + } + } catch (Exception e) { + logger.log(Level.WARNING, "failed to update pv: " + getName() , e); + close(); + } + } + + @Override + protected void close() + { + if (!task.isDone() && !task.cancel(false)) + logger.log(Level.WARNING, "Cannot cancel updates for " + getName()); + super.close(); + } +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java new file mode 100644 index 0000000000..df177b2fa9 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java @@ -0,0 +1,76 @@ +package org.phoebus.pv.archive.replay; + +import org.phoebus.pv.PV; +import org.phoebus.pv.PVFactory; +import org.phoebus.pv.archive.retrieve.ArchivePVFactory; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static org.phoebus.pv.archive.ArchiveReaderUtil.parseSupportedTimeFormat; + +/** + * A datasource for the replaying archived PV's + * @author Kunal Shroff + */ +public class ReplayPVFactory implements PVFactory +{ + final public static Logger logger = Logger.getLogger(ReplayPVFactory.class.getName()); + final public static String TYPE = "replay"; + + @Override + public String getType() + { + return TYPE; + } + + @Override + public PV createPV(String name, String base_name) throws Exception + { + // Determine simulation function name and (optional) parameters + final String pvName; + int sep = base_name.indexOf('('); + if (sep < 0) + { + pvName = base_name; + return new ReplayPV(name, pvName, Instant.now().minusSeconds(300), Instant.now()); + } + else + { + final int end = base_name.lastIndexOf(')'); + if (end < 0) + throw new Exception("Missing closing bracket for parameters in '" + name + "'"); + pvName = base_name.substring(0, sep); + final List parameters = Arrays + .stream(base_name.substring(sep+1, end).split(",")) + .map(String::strip) + .collect(Collectors.toList()); + + Instant startTime; + Instant endTime; + if (parameters.size() == 2) { + // start and end + startTime = parseSupportedTimeFormat(parameters.get(0)); + endTime = parseSupportedTimeFormat(parameters.get(1)); + return new ReplayPV(name, pvName, startTime, endTime); + + } else if (parameters.size() == 3 ) { + // start, end, and rate + startTime = parseSupportedTimeFormat(parameters.get(0)); + endTime = parseSupportedTimeFormat(parameters.get(1)); + double rate = Double.parseDouble(parameters.get(2)); + + return new ReplayPV(name, pvName, startTime, endTime, rate); + + } else { + throw new Exception("Incorrect number of parameters defined," + "'" + name + "'" + + " the replay datasource supports start, end, and optionally a rate parameter."); + } + + } + } +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java new file mode 100644 index 0000000000..32617fb8d3 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java @@ -0,0 +1,68 @@ +package org.phoebus.pv.archive.retrieve; + +import org.phoebus.archive.reader.ValueIterator; +import org.phoebus.pv.PV; +import org.phoebus.pv.archive.ArchiveReaderService; + +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; + +/** + * A Connection to a PV in the archiver + * + * @author Kunal Shroff + */ +public class ArchivePV extends PV { + + ArchiveReaderService service = ArchiveReaderService.getService(); + + /** + * + * @param completeName + * @param name + */ + public ArchivePV(String completeName, String name) + { + this(completeName, name, Instant.now()); + } + + /** + * + * @param completeName + * @param name + * @param instant + */ + public ArchivePV(String completeName, String name, Instant instant) + { + super(completeName); + try { + ValueIterator i = service.getReader().getRawValues(name, instant, instant); + + if (i.hasNext()) { + notifyListenersOfValue(i.next()); + } + } catch (Exception e) { + logger.log(Level.WARNING, "failed to create pv: " + getName() , e); + } + } + + public void disconnected() + { + notifyListenersOfDisconnect(); + } + + @Override + protected void close() + { + super.close(); + } + + @Override + public boolean isReadonly() + { + return true; + } + +} diff --git a/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java new file mode 100644 index 0000000000..ea819e6278 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java @@ -0,0 +1,85 @@ +package org.phoebus.pv.archive.retrieve; + +import org.phoebus.pv.PV; +import org.phoebus.pv.PVFactory; + +import java.time.DateTimeException; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static org.phoebus.util.time.TimestampFormats.DATETIME_FORMAT; +import static org.phoebus.util.time.TimestampFormats.SECONDS_FORMAT; +import static org.phoebus.util.time.TimestampFormats.MILLI_FORMAT; +import static org.phoebus.util.time.TimestampFormats.FULL_FORMAT; + +/** + * A datasource for the retrieval of archived PV's + * @author Kunal Shroff + */ +public class ArchivePVFactory implements PVFactory +{ + + final public static Logger logger = Logger.getLogger(ArchivePVFactory.class.getName()); + final public static String TYPE = "archive"; + + @Override + public String getType() + { + return TYPE; + } + + @Override + public PV createPV(String name, String base_name) throws Exception { + // Determine simulation function name and (optional) parameters + final String pvName, parameters; + int sep = base_name.indexOf('('); + if (sep < 0) + { + pvName = base_name; + parameters = ""; + } + else + { + final int end = base_name.lastIndexOf(')'); + if (end < 0) + throw new Exception("Missing closing bracket for parameters in '" + name + "'"); + pvName = base_name.substring(0, sep); + parameters = base_name.substring(sep+1, end); + } + + if(parameters.isEmpty()) + { + return new ArchivePV(name, pvName); + } + else + { + Instant time; + List parameterList = Arrays.stream(parameters.split(",")) + .map(String::strip) + .collect(Collectors.toList()); + if(parameterList.size() == 1) + { + String timeParameter = parameterList.get(0); + try { + switch (parameterList.get(0).length()) { + case 16 -> time = Instant.from(DATETIME_FORMAT.parse(timeParameter)); + case 19 -> time = Instant.from(SECONDS_FORMAT.parse(timeParameter)); + case 23 -> time = Instant.from(MILLI_FORMAT.parse(timeParameter)); + case 29 -> time = Instant.from(FULL_FORMAT.parse(timeParameter)); + default -> throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'"); + } + return new ArchivePV(name, pvName, time); + } catch (DateTimeException e) { + throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'"); + } + } + else + { + throw new Exception("Incorrect number of parameters defined '" + name + "'"); + } + } + } +} diff --git a/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory new file mode 100644 index 0000000000..72db0eb20d --- /dev/null +++ b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1,2 @@ +org.phoebus.pv.archive.retrieve.ArchivePVFactory +org.phoebus.pv.archive.replay.ReplayPVFactory \ No newline at end of file diff --git a/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties b/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties new file mode 100644 index 0000000000..83b8ef61b9 --- /dev/null +++ b/app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties @@ -0,0 +1,5 @@ +# ---------------------------------------- +# Package org.phoebus.pv.archive +# ---------------------------------------- + +archive_url=http://localhost:10068/retrieval diff --git a/app/trends/archive-reader/.classpath b/app/trends/archive-reader/.classpath new file mode 100644 index 0000000000..0b6599fa79 --- /dev/null +++ b/app/trends/archive-reader/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/trends/archive-reader/.project b/app/trends/archive-reader/.project new file mode 100644 index 0000000000..4e243167d2 --- /dev/null +++ b/app/trends/archive-reader/.project @@ -0,0 +1,17 @@ + + + app-trends-archive-reader + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/app/trends/archive-reader/build.xml b/app/trends/archive-reader/build.xml new file mode 100644 index 0000000000..d086b50cb0 --- /dev/null +++ b/app/trends/archive-reader/build.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/trends/archive-reader/pom.xml b/app/trends/archive-reader/pom.xml new file mode 100644 index 0000000000..21ce6b584f --- /dev/null +++ b/app/trends/archive-reader/pom.xml @@ -0,0 +1,69 @@ + + + + app-trends + org.phoebus + 4.7.4-SNAPSHOT + + 4.0.0 + + app-trends-archive-reader + + + 17 + 17 + + + + + org.phoebus + core-pv + 4.7.4-SNAPSHOT + + + org.phoebus + core-types + 4.7.4-SNAPSHOT + + + org.phoebus + core-util + 4.7.4-SNAPSHOT + + + com.google.protobuf + protobuf-java + 3.21.9 + + + org.epics + epics-util + ${epics.util.version} + + + org.epics + pbrawclient + 0.0.10 + + + org.phoebus + core-ui + 4.7.4-SNAPSHOT + compile + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + \ No newline at end of file diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java index 64f8b12af7..31d31145d7 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java @@ -22,10 +22,10 @@ import org.epics.vtype.VStatistics; import org.epics.vtype.VType; import org.phoebus.archive.vtype.StatisticsAccumulator; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.archive.vtype.VTypeHelper; import org.phoebus.pv.TimeHelper; import org.phoebus.util.time.TimestampFormats; +import org.phoebus.util.time.TimestampHelper; /** Averaging sample iterator. * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java index fde8622b37..0b2d3400a3 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java @@ -18,10 +18,10 @@ import org.epics.vtype.VStatistics; import org.epics.vtype.VType; import org.phoebus.archive.vtype.StatisticsAccumulator; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.archive.vtype.VTypeHelper; import org.phoebus.pv.TimeHelper; import org.phoebus.util.time.TimeDuration; +import org.phoebus.util.time.TimestampHelper; /** {@link ValueIterator} that performs linear interpolation * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/ValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/ValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/ValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java index f42e16d846..1887885028 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java @@ -21,8 +21,8 @@ import org.phoebus.archive.reader.ArchiveReader; import org.phoebus.archive.reader.UnknownChannelException; import org.phoebus.archive.reader.ValueIterator; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.ui.text.RegExHelper; +import org.phoebus.util.time.TimestampHelper; /** * Appliance archive reader which reads data from EPICS archiver appliance. diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java similarity index 98% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java index b32d487851..4303995af9 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java @@ -8,10 +8,10 @@ import org.epics.archiverappliance.retrieval.client.EpicsMessage; import org.epics.archiverappliance.retrieval.client.GenMsgIterator; import org.epics.vtype.Display; -import org.phoebus.archive.vtype.TimestampHelper; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; +import org.phoebus.util.time.TimestampHelper; /** * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java index f757fcba1d..91dbd5b750 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java @@ -14,11 +14,11 @@ import org.epics.vtype.VNumber; import org.epics.vtype.VStatistics; import org.epics.vtype.VType; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.pv.TimeHelper; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; +import org.phoebus.util.time.TimestampHelper; /** * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java index 579eb63822..2140a62ce9 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java @@ -12,10 +12,10 @@ import org.epics.vtype.Display; import org.epics.vtype.VStatistics; import org.epics.vtype.VType; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.pv.TimeHelper; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; +import org.phoebus.util.time.TimestampHelper; /** * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java index c070522fe2..d7feba56c5 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java @@ -32,7 +32,6 @@ import org.epics.vtype.VType; import org.phoebus.archive.reader.ValueIterator; import org.phoebus.archive.reader.util.ChannelAccessStatusUtil; -import org.phoebus.archive.vtype.TimestampHelper; import org.phoebus.pv.TimeHelper; import com.google.protobuf.ByteString; @@ -41,6 +40,7 @@ import edu.stanford.slac.archiverappliance.PB.EPICSEvent.FieldValue; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadType; +import org.phoebus.util.time.TimestampHelper; /** * diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java index ede39a0d38..3966a422b5 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java @@ -340,7 +340,7 @@ int getChannelID(final String name) throws UnknownChannelException, Exception if (RDBPreferences.timeout_secs > 0) statement.setQueryTimeout(RDBPreferences.timeout_secs); // Loop over variants - for (String variant : PVPool.getNameVariants(name, org.csstudio.trends.databrowser3.preferences.Preferences.equivalent_pv_prefixes)) + for (String variant : PVPool.getNameVariants(name, RDBPreferences.equivalent_pv_prefixes)) { statement.setString(1, variant); try (final ResultSet result = statement.executeQuery()) diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java similarity index 86% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java index 6bf5d1602d..abe5caa48d 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java +++ b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java @@ -22,9 +22,10 @@ public class RDBPreferences @Preference static String stored_procedure; @Preference static String starttime_function; @Preference static int fetch_size; + @Preference static String[] equivalent_pv_prefixes; static { - AnnotatedPreferences.initialize(RDBPreferences.class, "/archive_reader_rdb_preferences.properties"); + AnnotatedPreferences.initialize(RDBPreferences.class, "/archive_reader_rdb_preferences.properties"); } } diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/SQL.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/SQL.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/SQL.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/SQL.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/Style.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/Style.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/Style.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/Style.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java similarity index 100% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java rename to app/trends/archive-reader/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java diff --git a/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory b/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory new file mode 100644 index 0000000000..737fe3cf43 --- /dev/null +++ b/app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory @@ -0,0 +1,4 @@ +org.phoebus.archive.reader.appliance.ApplianceArchiveReaderFactory +org.phoebus.archive.reader.rdb.RDBArchiveReaderFactory +org.phoebus.archive.reader.channelarchiver.XMLRPCArchiveReaderFactory +org.phoebus.archive.reader.channelarchiver.file.ArchiveFileReaderFactory diff --git a/app/databrowser/src/main/resources/appliance_preferences.properties b/app/trends/archive-reader/src/main/resources/appliance_preferences.properties similarity index 100% rename from app/databrowser/src/main/resources/appliance_preferences.properties rename to app/trends/archive-reader/src/main/resources/appliance_preferences.properties diff --git a/app/databrowser/src/main/resources/archive_reader_rdb_preferences.properties b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties similarity index 71% rename from app/databrowser/src/main/resources/archive_reader_rdb_preferences.properties rename to app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties index 71a36e7c40..a542079bcc 100644 --- a/app/databrowser/src/main/resources/archive_reader_rdb_preferences.properties +++ b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties @@ -56,3 +56,24 @@ starttime_function= # Tests resulted in a speed increase up to fetch sizes of 1000. # On the other hand, bigger numbers can result in java.lang.OutOfMemoryError. fetch_size=1000 + +# With EPICS IOCs from release 7 on, the PVs +# "xxx", "ca://xxx" and "pva://xxx" all refer +# to the same record "xxx" on the IOC. +# +# When the plot requests "pva://xxx", the archive might still +# trace that channel as "ca://xxx" or "xxx". +# Alternatively, the archive might already track the channel +# as "pva://xxx" while data browser plots still use "ca://xxx" +# or just "xxx". +# This preference setting instructs the data browser +# to try all equivalent variants. If any types are listed, +# just "xxx" without any prefix will also be checked in addition +# to the listed types. +# +# The default of setting of "ca, pva" supports the seamless +# transition between the key protocols. +# +# When `equivalent_pv_prefixes` is empty, +# the PV name is used as is without looking for any equivalent names. +equivalent_pv_prefixes=ca, pva diff --git a/app/databrowser/src/main/resources/channelarchiver_preferences.properties b/app/trends/archive-reader/src/main/resources/channelarchiver_preferences.properties similarity index 100% rename from app/databrowser/src/main/resources/channelarchiver_preferences.properties rename to app/trends/archive-reader/src/main/resources/channelarchiver_preferences.properties diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java similarity index 100% rename from app/databrowser/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java similarity index 100% rename from app/databrowser/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java diff --git a/app/databrowser/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java b/app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java similarity index 100% rename from app/databrowser/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java rename to app/trends/archive-reader/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java diff --git a/app/trends/pom.xml b/app/trends/pom.xml index abdc14689a..617c89db42 100644 --- a/app/trends/pom.xml +++ b/app/trends/pom.xml @@ -10,5 +10,7 @@ rich-adapters simple-adapters + archive-datasource + archive-reader diff --git a/build.xml b/build.xml index f9c6667abc..0ee3e74f6b 100644 --- a/build.xml +++ b/build.xml @@ -33,6 +33,7 @@ + @@ -117,6 +118,7 @@ + diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java b/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java deleted file mode 100644 index 288ff8941f..0000000000 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -package org.phoebus.framework.preferences; - -/** - * Static methods for translating Base64 encoded strings to byte arrays - * and vice-versa. - * - * @author Josh Bloch - * @see Preferences - * @since 1.4 - */ -class Base64 { - /** - * Translates the specified byte array into a Base64 string as per - * Preferences.put(byte[]). - */ - static String byteArrayToBase64(byte[] a) { - return byteArrayToBase64(a, false); - } - - /** - * Translates the specified byte array into an "alternate representation" - * Base64 string. This non-standard variant uses an alphabet that does - * not contain the uppercase alphabetic characters, which makes it - * suitable for use in situations where case-folding occurs. - */ - static String byteArrayToAltBase64(byte[] a) { - return byteArrayToBase64(a, true); - } - - private static String byteArrayToBase64(byte[] a, boolean alternate) { - int aLen = a.length; - int numFullGroups = aLen/3; - int numBytesInPartialGroup = aLen - 3*numFullGroups; - int resultLen = 4*((aLen + 2)/3); - StringBuffer result = new StringBuffer(resultLen); - char[] intToAlpha = (alternate ? intToAltBase64 : intToBase64); - - // Translate all full groups from byte array elements to Base64 - int inCursor = 0; - for (int i=0; i> 2]); - result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); - result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]); - result.append(intToAlpha[byte2 & 0x3f]); - } - - // Translate partial group if present - if (numBytesInPartialGroup != 0) { - int byte0 = a[inCursor++] & 0xff; - result.append(intToAlpha[byte0 >> 2]); - if (numBytesInPartialGroup == 1) { - result.append(intToAlpha[(byte0 << 4) & 0x3f]); - result.append("=="); - } else { - // assert numBytesInPartialGroup == 2; - int byte1 = a[inCursor++] & 0xff; - result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); - result.append(intToAlpha[(byte1 << 2)&0x3f]); - result.append('='); - } - } - // assert inCursor == a.length; - // assert result.length() == resultLen; - return result.toString(); - } - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Base64 Alphabet" equivalents as specified - * in Table 1 of RFC 2045. - */ - private static final char intToBase64[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Alternate Base64 Alphabet" equivalents. - * This is NOT the real Base64 Alphabet as per in Table 1 of RFC 2045. - * This alternate alphabet does not use the capital letters. It is - * designed for use in environments where "case folding" occurs. - */ - private static final char intToAltBase64[] = { - '!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':', - ';', '<', '>', '@', '[', ']', '^', '`', '_', '{', '|', '}', '~', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '?' - }; - - /** - * Translates the specified Base64 string (as per Preferences.get(byte[])) - * into a byte array. - * - * @throw IllegalArgumentException if s is not a valid Base64 - * string. - */ - static byte[] base64ToByteArray(String s) { - return base64ToByteArray(s, false); - } - - /** - * Translates the specified "alternate representation" Base64 string - * into a byte array. - * - * @throw IllegalArgumentException or ArrayOutOfBoundsException - * if s is not a valid alternate representation - * Base64 string. - */ - static byte[] altBase64ToByteArray(String s) { - return base64ToByteArray(s, true); - } - - private static byte[] base64ToByteArray(String s, boolean alternate) { - byte[] alphaToInt = (alternate ? altBase64ToInt : base64ToInt); - int sLen = s.length(); - int numGroups = sLen/4; - if (4*numGroups != sLen) - throw new IllegalArgumentException( - "String length must be a multiple of four."); - int missingBytesInLastGroup = 0; - int numFullGroups = numGroups; - if (sLen != 0) { - if (s.charAt(sLen-1) == '=') { - missingBytesInLastGroup++; - numFullGroups--; - } - if (s.charAt(sLen-2) == '=') - missingBytesInLastGroup++; - } - byte[] result = new byte[3*numGroups - missingBytesInLastGroup]; - - // Translate all full groups from base64 to byte array elements - int inCursor = 0, outCursor = 0; - for (int i=0; i> 4)); - result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); - result[outCursor++] = (byte) ((ch2 << 6) | ch3); - } - - // Translate partial group, if present - if (missingBytesInLastGroup != 0) { - int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt); - int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt); - result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4)); - - if (missingBytesInLastGroup == 1) { - int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt); - result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); - } - } - // assert inCursor == s.length()-missingBytesInLastGroup; - // assert outCursor == result.length; - return result; - } - - /** - * Translates the specified character, which is assumed to be in the - * "Base 64 Alphabet" into its equivalent 6-bit positive integer. - * - * @throw IllegalArgumentException or ArrayOutOfBoundsException if - * c is not in the Base64 Alphabet. - */ - private static int base64toInt(char c, byte[] alphaToInt) { - int result = alphaToInt[c]; - if (result < 0) - throw new IllegalArgumentException("Illegal character " + c); - return result; - } - - /** - * This array is a lookup table that translates unicode characters - * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) - * into their 6-bit positive integer equivalents. Characters that - * are not in the Base64 alphabet but fall within the bounds of the - * array are translated to -1. - */ - private static final byte base64ToInt[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; - - /** - * This array is the analogue of base64ToInt, but for the nonstandard - * variant that avoids the use of uppercase alphabetic characters. - */ - private static final byte altBase64ToInt[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, - 2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1 , 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 22, 23, 24, 25 - }; - - public static void main(String args[]) { - int numRuns = Integer.parseInt(args[0]); - int numBytes = Integer.parseInt(args[1]); - java.util.Random rnd = new java.util.Random(); - for (int i=0; i" + - - "" + - - "" + "" + - - "" + "" + - - "" + "" + - - "" + "" - + "" + ""; - /** - * Version number for the format exported preferences files. - */ - private static final String EXTERNAL_XML_VERSION = "1.0"; - - /* - * Version number for the internal map files. - */ - private static final String MAP_XML_VERSION = "1.0"; - - /** - * Export the specified preferences node and, if subTree is true, all subnodes, - * to the specified output stream. Preferences are exported as an XML document - * conforming to the definition in the Preferences spec. - * - * @throws IOException - * if writing to the specified output stream results in an - * IOException. - * @throws BackingStoreException - * if preference data cannot be read from backing store. - * @throws IllegalStateException - * if this node (or an ancestor) has been removed with the - * {@link #removeNode()} method. - */ - static void export(OutputStream os, final Preferences p, boolean subTree) - throws IOException, BackingStoreException { - if (((FileSystemPreferences) p).isRemoved()) - throw new IllegalStateException("Node has been removed"); - Document doc = createPrefsDoc("preferences"); - Element preferences = doc.getDocumentElement(); - preferences.setAttribute("EXTERNAL_XML_VERSION", EXTERNAL_XML_VERSION); - Element xmlRoot = (Element) preferences.appendChild(doc.createElement("root")); - xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system")); - - // Get bottom-up list of nodes from p to root, excluding root - List ancestors = new ArrayList(); - - for (Preferences kid = p, dad = kid.parent(); dad != null; kid = dad, dad = kid.parent()) { - ancestors.add(kid); - } - Element e = xmlRoot; - for (int i = ancestors.size() - 1; i >= 0; i--) { - e.appendChild(doc.createElement("map")); - e = (Element) e.appendChild(doc.createElement("node")); - e.setAttribute("name", ((Preferences) ancestors.get(i)).name()); - } - putPreferencesInXml(e, doc, p, subTree); - - writeDoc(doc, os); - } - - /** - * Put the preferences in the specified Preferences node into the specified XML - * element which is assumed to represent a node in the specified XML document - * which is assumed to conform to PREFS_DTD. If subTree is true, create children - * of the specified XML node conforming to all of the children of the specified - * Preferences node and recurse. - * - * @throws BackingStoreException - * if it is not possible to read the preferences or children out of - * the specified preferences node. - */ - private static void putPreferencesInXml(Element elt, Document doc, Preferences prefs, boolean subTree) - throws BackingStoreException { - Preferences[] kidsCopy = null; - String[] kidNames = null; - - // Node is locked to export its contents and get a - // copy of children, then lock is released, - // and, if subTree = true, recursive calls are made on children - - // to remove a node we need an exclusive lock - - // to remove a node we need an exclusive lock - if (!((FileSystemPreferences) prefs).lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - // Check if this node was concurrently removed. If yes - // remove it from XML Document and return. - if (((FileSystemPreferences) prefs).isRemoved()) { - elt.getParentNode().removeChild(elt); - return; - } - // Put map in xml element - String[] keys = prefs.keys(); - Element map = (Element) elt.appendChild(doc.createElement("map")); - for (int i = 0; i < keys.length; i++) { - Element entry = (Element) map.appendChild(doc.createElement("entry")); - entry.setAttribute("key", keys[i]); - // NEXT STATEMENT THROWS NULL PTR EXC INSTEAD OF ASSERT FAIL - entry.setAttribute("value", prefs.get(keys[i], null)); - } - // Recurse if appropriate - if (subTree) { - /* Get a copy of kids while lock is held */ - kidNames = prefs.childrenNames(); - kidsCopy = new Preferences[kidNames.length]; - for (int i = 0; i < kidNames.length; i++) - kidsCopy[i] = prefs.node(kidNames[i]); - } - } finally { - ((FileSystemPreferences) prefs).unlockFile(); - } - if (subTree) { - for (int i = 0; i < kidNames.length; i++) { - Element xmlKid = (Element) elt.appendChild(doc.createElement("node")); - xmlKid.setAttribute("name", kidNames[i]); - putPreferencesInXml(xmlKid, doc, kidsCopy[i], subTree); - } - } - } - - /** - * Import preferences from the specified input stream, which is assumed to - * contain an XML document in the format described in the Preferences spec. - * - * @throws IOException - * if reading from the specified output stream results in an - * IOException. - * @throws InvalidPreferencesFormatException - * Data on input stream does not constitute a valid XML document - * with the mandated document type. - */ - static void importPreferences(InputStream is) throws IOException, InvalidPreferencesFormatException { - try { - Document doc = loadPrefsDoc(is); - String xmlVersion = doc.getDocumentElement().getAttribute("EXTERNAL_XML_VERSION"); - if (xmlVersion.compareTo(EXTERNAL_XML_VERSION) > 0) - throw new InvalidPreferencesFormatException("Exported preferences file format version " + xmlVersion - + " is not supported. This java installation can read" + " versions " + EXTERNAL_XML_VERSION - + " or older. You may need" + " to install a newer version of JDK."); - - Element xmlRoot = (Element) doc.getDocumentElement().getChildNodes().item(0); - Preferences prefsRoot = (xmlRoot.getAttribute("type").equals("user") ? Preferences.userRoot() - : Preferences.systemRoot()); - ImportSubtree(prefsRoot, xmlRoot); - } catch (SAXException | BackingStoreException e) { - throw new InvalidPreferencesFormatException(e); - } - } - - /** - * Create a new prefs XML document. - */ - private static Document createPrefsDoc(String qname) { - try { - DOMImplementation di = DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation(); - DocumentType dt = di.createDocumentType(qname, null, PREFS_DTD_URI); - return di.createDocument(null, qname, dt); - } catch (ParserConfigurationException e) { - throw new AssertionError(e); - } - } - - /** - * Load an XML document from specified input stream, which must have the - * requisite DTD URI. - */ - private static Document loadPrefsDoc(InputStream in) throws SAXException, IOException { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setIgnoringElementContentWhitespace(true); - dbf.setValidating(true); - dbf.setCoalescing(true); - dbf.setIgnoringComments(true); - try { - DocumentBuilder db = dbf.newDocumentBuilder(); - db.setEntityResolver(new Resolver()); - db.setErrorHandler(new EH()); - return db.parse(new InputSource(in)); - } catch (ParserConfigurationException e) { - throw new AssertionError(e); - } - } - - /** - * Write XML document to the specified output stream. - */ - private static final void writeDoc(Document doc, OutputStream out) throws IOException { - try { - TransformerFactory tf = TransformerFactory.newInstance(); - try { - tf.setAttribute("indent-number", Integer.valueOf(2)); - } catch (IllegalArgumentException iae) { - // Ignore the IAE. Should not fail the writeout even the - // transformer provider does not support "indent-number". - } - Transformer t = tf.newTransformer(); - t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId()); - t.setOutputProperty(OutputKeys.INDENT, "yes"); - // Transformer resets the "indent" info if the "result" is a StreamResult with - // an OutputStream object embedded, creating a Writer object on top of that - // OutputStream object however works. - t.transform(new DOMSource(doc), new StreamResult(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")))); - } catch (TransformerException e) { - throw new AssertionError(e); - } - } - - /** - * Recursively traverse the specified preferences node and store the described - * preferences into the system or current user preferences tree, as appropriate. - * @throws BackingStoreException - */ - private static void ImportSubtree(Preferences prefsNode, Element xmlNode) throws BackingStoreException { - NodeList xmlKids = xmlNode.getChildNodes(); - int numXmlKids = xmlKids.getLength(); - /* - * We first lock the node, import its contents and get child nodes. Then we - * unlock the node and go to children Since some of the children might have been - * concurrently deleted we check for this. - */ - Preferences[] prefsKids; - /* Lock the node */ - // to remove a node we need an exclusive lock - if (!((FileSystemPreferences) prefsNode).lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - // If removed, return silently - if (((FileSystemPreferences) prefsNode).isRemoved()) - return; - - // Import any preferences at this node - Element firstXmlKid = (Element) xmlKids.item(0); - ImportPrefs(prefsNode, firstXmlKid); - prefsKids = new Preferences[numXmlKids - 1]; - - // Get involved children - for (int i = 1; i < numXmlKids; i++) { - Element xmlKid = (Element) xmlKids.item(i); - prefsKids[i - 1] = prefsNode.node(xmlKid.getAttribute("name")); - } - } finally { - // unlocked the node - ((FileSystemPreferences) prefsNode).unlockFile(); - } - - // import children - for (int i = 1; i < numXmlKids; i++) - ImportSubtree(prefsKids[i - 1], (Element) xmlKids.item(i)); - } - - /** - * Import the preferences described by the specified XML element (a map from a - * preferences document) into the specified preferences node. - */ - private static void ImportPrefs(Preferences prefsNode, Element map) { - NodeList entries = map.getChildNodes(); - for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) { - Element entry = (Element) entries.item(i); - prefsNode.put(entry.getAttribute("key"), entry.getAttribute("value")); - } - } - - /** - * Export the specified Map to a map document on the specified - * OutputStream as per the prefs DTD. This is used as the internal - * (undocumented) format for FileSystemPrefs. - * - * @throws IOException - * if writing to the specified output stream results in an - * IOException. - */ - static void exportMap(OutputStream os, Map map) throws IOException { - Document doc = createPrefsDoc("map"); - Element xmlMap = doc.getDocumentElement(); - xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION); - - for (Iterator i = map.entrySet().iterator(); i.hasNext();) { - Map.Entry e = (Map.Entry) i.next(); - Element xe = (Element) xmlMap.appendChild(doc.createElement("entry")); - xe.setAttribute("key", (String) e.getKey()); - xe.setAttribute("value", (String) e.getValue()); - } - - writeDoc(doc, os); - } - - /** - * Import Map from the specified input stream, which is assumed to contain a map - * document as per the prefs DTD. This is used as the internal (undocumented) - * format for FileSystemPrefs. The key-value pairs specified in the XML document - * will be put into the specified Map. (If this Map is empty, it will contain - * exactly the key-value pairs int the XML-document when this method returns.) - * - * @throws IOException - * if reading from the specified output stream results in an - * IOException. - * @throws InvalidPreferencesFormatException - * Data on input stream does not constitute a valid XML document - * with the mandated document type. - */ - static void importMap(InputStream is, Map m) throws IOException, InvalidPreferencesFormatException { - try { - Document doc = loadPrefsDoc(is); - Element xmlMap = doc.getDocumentElement(); - // check version - String mapVersion = xmlMap.getAttribute("MAP_XML_VERSION"); - if (mapVersion.compareTo(MAP_XML_VERSION) > 0) - throw new InvalidPreferencesFormatException("Preferences map file format version " + mapVersion - + " is not supported. This java installation can read" + " versions " + MAP_XML_VERSION - + " or older. You may need" + " to install a newer version of JDK."); - - NodeList entries = xmlMap.getChildNodes(); - for (int i = 0, numEntries = entries.getLength(); i < numEntries; i++) { - Element entry = (Element) entries.item(i); - m.put(entry.getAttribute("key"), entry.getAttribute("value")); - } - } catch (SAXException e) { - throw new InvalidPreferencesFormatException(e); - } - } - - private static class Resolver implements EntityResolver { - public InputSource resolveEntity(String pid, String sid) throws SAXException { - if (sid.equals(PREFS_DTD_URI)) { - InputSource is; - is = new InputSource(new StringReader(PREFS_DTD)); - is.setSystemId(PREFS_DTD_URI); - return is; - } - throw new SAXException("Invalid system identifier: " + sid); - } - } - - private static class EH implements ErrorHandler { - public void error(SAXParseException x) throws SAXException { - throw x; - } - - public void fatalError(SAXParseException x) throws SAXException { - throw x; - } - - public void warning(SAXParseException x) throws SAXException { - throw x; - } - } -} \ No newline at end of file diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java deleted file mode 100644 index 534ed13bdb..0000000000 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java +++ /dev/null @@ -1,964 +0,0 @@ -/* - * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -package org.phoebus.framework.preferences; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileLock; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.AbstractPreferences; -import java.util.prefs.BackingStoreException; -import java.util.prefs.InvalidPreferencesFormatException; -import java.util.prefs.Preferences; - -import org.phoebus.framework.workbench.Locations; - -/** - * Preferences implementation for Unix. Preferences are stored in the file - * system, with one directory per preferences node. All of the preferences at - * each node are stored in a single file. Atomic file system operations (e.g. - * File.renameTo) are used to ensure integrity. An in-memory cache of the - * "explored" portion of the tree is maintained for performance, and written - * back to the disk periodically. File-locking is used to ensure reasonable - * behavior when multiple VMs are running at the same time. (The file lock is - * obtained only for sync(), flush() and removeNode().) - * - * @author Josh Bloch - * @see Preferences - * @since 1.4 - */ - -@SuppressWarnings({"unchecked", "nls"}) -class FileSystemPreferences extends AbstractPreferences { - - /** - * Sync interval in seconds. - */ - private static final int SYNC_INTERVAL = Math.max(1, - Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction() { - @Override - public String run() { - return System.getProperty("java.util.prefs.syncInterval", "30"); - } - }))); - - /** - * Returns logger for error messages. Backing store exceptions are logged at - * WARNING level. - */ - private static Logger getLogger() { - return Logger.getLogger("java.util.prefs"); - } - - /** - * Directory for system preferences. - */ - private static File systemRootDir; - - /* - * Flag, indicating whether systemRoot directory is writable - */ - private static boolean isSystemRootWritable; - - /** - * Directory for user preferences. - */ - private static File userRootDir; - - /* - * Flag, indicating whether userRoot directory is writable - */ - private static boolean isUserRootWritable; - - /** - * The user root. - */ - static Preferences userRoot = null; - - static synchronized Preferences getUserRoot() { - if (userRoot == null) { - setupUserRoot(); - userRoot = new FileSystemPreferences(true); - } - return userRoot; - } - - private static void setupUserRoot() { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - - // If Phoebus locations have been set, - // place the user preferences in the user directory that also stores mementos etc. - // Otherwise fall back to original FileSystemPreferences behavior - if (System.getProperty(Locations.PHOEBUS_USER, "").length() > 0) - userRootDir = new File(System.getProperty(Locations.PHOEBUS_USER), ".userPrefs"); - else - userRootDir = new File(System.getProperty("java.util.prefs.userRoot", System.getProperty("user.home")), - ".phoebus/.userPrefs"); - - // Attempt to create root dir if it does not yet exist. - if (!userRootDir.exists()) { - if (userRootDir.mkdirs()) { - getLogger().info("Created user preferences directory."); - } else - getLogger().warning( - "Couldn't create user preferences" + " directory. User preferences are unusable."); - } - isUserRootWritable = userRootDir.canWrite(); - String USER_NAME = System.getProperty("user.name"); - userLockFile = new File(userRootDir, ".user.lock." + USER_NAME); - userRootModFile = new File(userRootDir, ".userRootModFile." + USER_NAME); - if (!userRootModFile.exists()) - try { - // create if does not exist. - userRootModFile.createNewFile(); - } catch (IOException e) { - getLogger().warning(e.toString()); - } - userRootModTime = userRootModFile.lastModified(); - return null; - } - }); - } - - /** - * The system root. - */ - static Preferences systemRoot; - - static synchronized Preferences getSystemRoot() { - if (systemRoot == null) { - setupSystemRoot(); - systemRoot = new FileSystemPreferences(false); - } - return systemRoot; - } - - private static void setupSystemRoot() { - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - String systemPrefsDirName = System.getProperty("java.util.prefs.systemRoot", "/etc/.java"); - systemRootDir = new File(systemPrefsDirName, ".systemPrefs"); - // Attempt to create root dir if it does not yet exist. - if (!systemRootDir.exists()) { - // system root does not exist in /etc/.java - // Switching to java.home - systemRootDir = new File(System.getProperty("java.home"), ".systemPrefs"); - if (!systemRootDir.exists()) { - if (systemRootDir.mkdirs()) { - getLogger().info("Created system preferences directory " + "in java.home."); - } else { - getLogger().warning("Could not create " + "system preferences directory. System " - + "preferences are unusable."); - } - } - } - isSystemRootWritable = systemRootDir.canWrite(); - systemLockFile = new File(systemRootDir, ".system.lock"); - systemRootModFile = new File(systemRootDir, ".systemRootModFile"); - if (!systemRootModFile.exists() && isSystemRootWritable) - try { - // create if does not exist. - systemRootModFile.createNewFile(); - } catch (IOException e) { - getLogger().warning(e.toString()); - } - systemRootModTime = systemRootModFile.lastModified(); - return null; - } - }); - } - - /** - * The lock file for the user tree. - */ - static File userLockFile; - - /** - * The lock file for the system tree. - */ - static File systemLockFile; - - /** - * Unix lock handle for userRoot. Zero, if unlocked. - */ - - private static RandomAccessFile userRootLockFile = null; - private static FileLock userRootLockHandle = null; - - /** - * Unix lock handle for systemRoot. Zero, if unlocked. - */ - - private static RandomAccessFile systemRootLockFile = null; - private static FileLock systemRootLockHandle = null; - - /** - * The directory representing this preference node. There is no guarantee that - * this directory exits, as another VM can delete it at any time that it (the - * other VM) holds the file-lock. While the root node cannot be deleted, it may - * not yet have been created, or the underlying directory could have been - * deleted accidentally. - */ - private final File dir; - - /** - * The file representing this preference node's preferences. The file format is - * undocumented, and subject to change from release to release, but I'm sure - * that you can figure it out if you try real hard. - */ - private final File prefsFile; - - /** - * A temporary file used for saving changes to preferences. As part of the sync - * operation, changes are first saved into this file, and then atomically - * renamed to prefsFile. This results in an atomic state change from one valid - * set of preferences to another. The the file-lock is held for the duration of - * this transformation. - */ - private final File tmpFile; - - /** - * File, which keeps track of global modifications of userRoot. - */ - private static File userRootModFile; - - /** - * Flag, which indicated whether userRoot was modified by another VM - */ - private static boolean isUserRootModified = false; - - /** - * Keeps track of userRoot modification time. This time is reset to zero after - * UNIX reboot, and is increased by 1 second each time userRoot is modified. - */ - private static long userRootModTime; - - /* - * File, which keeps track of global modifications of systemRoot - */ - private static File systemRootModFile; - /* - * Flag, which indicates whether systemRoot was modified by another VM - */ - private static boolean isSystemRootModified = false; - - /** - * Keeps track of systemRoot modification time. This time is reset to zero after - * system reboot, and is increased by 1 second each time systemRoot is modified. - */ - private static long systemRootModTime; - - /** - * Locally cached preferences for this node (includes uncommitted changes). This - * map is initialized with from disk when the first get or put operation occurs - * on this node. It is synchronized with the corresponding disk file (prefsFile) - * by the sync operation. The initial value is read *without* acquiring the - * file-lock. - */ - private Map prefsCache = null; - - /** - * The last modification time of the file backing this node at the time that - * prefCache was last synchronized (or initially read). This value is set - * *before* reading the file, so it's conservative; the actual timestamp could - * be (slightly) higher. A value of zero indicates that we were unable to - * initialize prefsCache from the disk, or have not yet attempted to do so. (If - * prefsCache is non-null, it indicates the former; if it's null, the latter.) - */ - private long lastSyncTime = 0; - - /** - * Unix error code for locked file. - */ - private static final int EAGAIN = 11; - - /** - * Unix error code for denied access. - */ - private static final int EACCES = 13; - - /** - * A list of all uncommitted preference changes. The elements in this list are - * of type PrefChange. If this node is concurrently modified on disk by another - * VM, the two sets of changes are merged when this node is sync'ed by - * overwriting our prefsCache with the preference map last written out to disk - * (by the other VM), and then replaying this change log against that map. The - * resulting map is then written back to the disk. - */ - final List changeLog = new ArrayList(); - - /** - * Represents a change to a preference. - */ - private abstract class Change { - /** - * Reapplies the change to prefsCache. - */ - abstract void replay(); - }; - - /** - * Represents a preference put. - */ - private class Put extends Change { - String key, value; - - Put(String key, String value) { - this.key = key; - this.value = value; - } - - @Override - void replay() { - prefsCache.put(key, value); - } - } - - /** - * Represents a preference remove. - */ - private class Remove extends Change { - String key; - - Remove(String key) { - this.key = key; - } - - @Override - void replay() { - prefsCache.remove(key); - } - } - - /** - * Represents the creation of this node. - */ - private class NodeCreate extends Change { - /** - * Performs no action, but the presence of this object in changeLog will force - * the node and its ancestors to be made permanent at the next sync. - */ - @Override - void replay() { - } - } - - /** - * NodeCreate object for this node. - */ - NodeCreate nodeCreate = null; - - /** - * Replay changeLog against prefsCache. - */ - private void replayChanges() { - for (int i = 0, n = changeLog.size(); i < n; i++) - ((Change) changeLog.get(i)).replay(); - } - - private static Timer syncTimer = new Timer(true); // Daemon Thread - - static { - // Add periodic timer task to periodically sync cached prefs - syncTimer.schedule(new TimerTask() { - @Override - public void run() { - syncWorld(); - } - }, SYNC_INTERVAL * 1000, SYNC_INTERVAL * 1000); - - // Add shutdown hook to flush cached prefs on normal termination - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - syncTimer.cancel(); - syncWorld(); - } - }); - return null; - } - }); - } - - private static void syncWorld() { - /* - * Synchronization necessary because userRoot and systemRoot are lazily - * initialized. - */ - Preferences userRt; - Preferences systemRt; - synchronized (FileSystemPreferences.class) { - userRt = userRoot; - systemRt = systemRoot; - } - - try { - if (userRt != null) - userRt.flush(); - } catch (BackingStoreException e) { - getLogger().warning("Couldn't flush user prefs: " + e); - } - - try { - if (systemRt != null) - systemRt.flush(); - } catch (BackingStoreException e) { - getLogger().warning("Couldn't flush system prefs: " + e); - } - } - - private final boolean isUserNode; - - /** - * Special constructor for roots (both user and system). This constructor will - * only be called twice, by the static initializer. - */ - private FileSystemPreferences(boolean user) { - super(null, ""); - isUserNode = user; - dir = (user ? userRootDir : systemRootDir); - prefsFile = new File(dir, "prefs.xml"); - tmpFile = new File(dir, "prefs.tmp"); - } - - /** - * Construct a new FileSystemPreferences instance with the specified parent node - * and name. This constructor, called from childSpi, is used to make every node - * except for the two //roots. - */ - private FileSystemPreferences(FileSystemPreferences parent, String name) { - super(parent, name); - isUserNode = parent.isUserNode; - dir = new File(parent.dir, dirName(name)); - prefsFile = new File(dir, "prefs.xml"); - tmpFile = new File(dir, "prefs.tmp"); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - newNode = !dir.exists(); - return null; - } - }); - if (newNode) { - // These 2 things guarantee node will get wrtten at next flush/sync - prefsCache = new TreeMap(); - nodeCreate = new NodeCreate(); - changeLog.add(nodeCreate); - } - } - - @Override - public boolean isUserNode() { - return isUserNode; - } - - @Override - protected void putSpi(String key, String value) { - initCacheIfNecessary(); - changeLog.add(new Put(key, value)); - prefsCache.put(key, value); - } - - @Override - protected String getSpi(String key) { - initCacheIfNecessary(); - return (String) prefsCache.get(key); - } - - @Override - protected void removeSpi(String key) { - initCacheIfNecessary(); - changeLog.add(new Remove(key)); - prefsCache.remove(key); - } - - /** - * Initialize prefsCache if it has yet to be initialized. When this method - * returns, prefsCache will be non-null. If the data was successfully read from - * the file, lastSyncTime will be updated. If prefsCache was null, but it was - * impossible to read the file (because it didn't exist or for any other reason) - * prefsCache will be initialized to an empty, modifiable Map, and lastSyncTime - * remain zero. - */ - private void initCacheIfNecessary() { - if (prefsCache != null) - return; - - try { - loadCache(); - } catch (Exception e) { - // assert lastSyncTime == 0; - prefsCache = new TreeMap(); - } - } - - /** - * Attempt to load prefsCache from the backing store. If the attempt succeeds, - * lastSyncTime will be updated (the new value will typically correspond to the - * data loaded into the map, but it may be less, if another VM is updating this - * node concurrently). If the attempt fails, a BackingStoreException is thrown - * and both prefsCache and lastSyncTime are unaffected by the call. - */ - private void loadCache() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - Map m = new TreeMap(); - long newLastSyncTime = 0; - try { - newLastSyncTime = prefsFile.lastModified(); - FileInputStream fis = new FileInputStream(prefsFile); - FilePreferencesXmlSupport.importMap(fis, m); - fis.close(); - } catch (Exception e) { - if (e instanceof InvalidPreferencesFormatException) { - getLogger().warning("Invalid preferences format in " + prefsFile.getPath()); - prefsFile.renameTo(new File(prefsFile.getParentFile(), "IncorrectFormatPrefs.xml")); - m = new TreeMap(); - } else if (e instanceof FileNotFoundException) { - getLogger().warning("Prefs file removed in background " + prefsFile.getPath()); - } else { - throw new BackingStoreException(e); - } - } - // Attempt succeeded; update state - prefsCache = m; - lastSyncTime = newLastSyncTime; - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - /** - * Attempt to write back prefsCache to the backing store. If the attempt - * succeeds, lastSyncTime will be updated (the new value will correspond exactly - * to the data thust written back, as we hold the file lock, which prevents a - * concurrent write. If the attempt fails, a BackingStoreException is thrown and - * both the backing store (prefsFile) and lastSyncTime will be unaffected by - * this call. This call will NEVER leave prefsFile in a corrupt state. - */ - private void writeBackCache() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - try { - if (!dir.exists() && !dir.mkdirs()) - throw new BackingStoreException(dir + " create failed."); - FileOutputStream fos = new FileOutputStream(tmpFile); - FilePreferencesXmlSupport.exportMap(fos, prefsCache); - fos.close(); - Files.move(tmpFile.toPath(), prefsFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (Exception e) { - throw new BackingStoreException(e); - } - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - @Override - protected String[] keysSpi() { - initCacheIfNecessary(); - return (String[]) prefsCache.keySet().toArray(new String[prefsCache.size()]); - } - - @Override - protected String[] childrenNamesSpi() { - return (String[]) AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - List result = new ArrayList(); - File[] dirContents = dir.listFiles(); - if (dirContents != null) { - for (int i = 0; i < dirContents.length; i++) - if (dirContents[i].isDirectory()) - result.add(nodeName(dirContents[i].getName())); - } - return result.toArray(EMPTY_STRING_ARRAY); - } - }); - } - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - @Override - protected AbstractPreferences childSpi(String name) { - return new FileSystemPreferences(this, name); - } - - @Override - public void removeNode() throws BackingStoreException { - synchronized (isUserNode() ? userLockFile : systemLockFile) { - // to remove a node we need an exclusive lock - if (!lockFile(false)) - throw (new BackingStoreException("Couldn't get file lock.")); - try { - super.removeNode(); - } finally { - unlockFile(); - } - } - } - - /** - * Called with file lock held (in addition to node locks). - */ - @Override - protected void removeNodeSpi() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - if (changeLog.contains(nodeCreate)) { - changeLog.remove(nodeCreate); - nodeCreate = null; - return null; - } - if (!dir.exists()) - return null; - prefsFile.delete(); - tmpFile.delete(); - // dir should be empty now. If it's not, empty it - File[] junk = dir.listFiles(); - if (junk.length != 0) { - getLogger().warning("Found extraneous files when removing node: " + Arrays.asList(junk)); - for (int i = 0; i < junk.length; i++) - junk[i].delete(); - } - if (!dir.delete()) - throw new BackingStoreException("Couldn't delete dir: " + dir); - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - @Override - public synchronized void sync() throws BackingStoreException { - boolean userNode = isUserNode(); - boolean shared; - - if (userNode) { - shared = false; /* use exclusive lock for user prefs */ - } else { - /* - * if can write to system root, use exclusive lock. otherwise use shared lock. - */ - shared = !isSystemRootWritable; - } - synchronized (isUserNode() ? userLockFile : systemLockFile) { - if (!lockFile(shared)) - throw (new BackingStoreException("Couldn't get file lock.")); - final Long newModTime = (Long) AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - long nmt; - if (isUserNode()) { - nmt = userRootModFile.lastModified(); - isUserRootModified = userRootModTime == nmt; - } else { - nmt = systemRootModFile.lastModified(); - isSystemRootModified = systemRootModTime == nmt; - } - return Long.valueOf(nmt); - } - }); - try { - super.sync(); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - if (isUserNode()) { - userRootModTime = newModTime.longValue() + 1000; - userRootModFile.setLastModified(userRootModTime); - } else { - systemRootModTime = newModTime.longValue() + 1000; - systemRootModFile.setLastModified(systemRootModTime); - } - return null; - } - }); - } finally { - unlockFile(); - } - } - } - - @Override - protected void syncSpi() throws BackingStoreException { - try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws BackingStoreException { - syncSpiPrivileged(); - return null; - } - }); - } catch (PrivilegedActionException e) { - throw (BackingStoreException) e.getException(); - } - } - - private void syncSpiPrivileged() throws BackingStoreException { - if (isRemoved()) - throw new IllegalStateException("Node has been removed"); - if (prefsCache == null) - return; // We've never been used, don't bother syncing - long lastModifiedTime; - if ((isUserNode() ? isUserRootModified : isSystemRootModified)) { - lastModifiedTime = prefsFile.lastModified(); - if (lastModifiedTime != lastSyncTime) { - // Prefs at this node were externally modified; read in node and - // playback any local mods since last sync - loadCache(); - replayChanges(); - lastSyncTime = lastModifiedTime; - } - } else if (lastSyncTime != 0 && !dir.exists()) { - // This node was removed in the background. Playback any changes - // against a virgin (empty) Map. - prefsCache = new TreeMap(); - replayChanges(); - } - if (!changeLog.isEmpty()) { - writeBackCache(); // Creates directory & file if necessary - /* - * Attempt succeeded; it's barely possible that the call to lastModified might - * fail (i.e., return 0), but this would not be a disaster, as lastSyncTime is - * allowed to lag. - */ - lastModifiedTime = prefsFile.lastModified(); - /* - * If lastSyncTime did not change, or went back increment by 1 second. Since we - * hold the lock lastSyncTime always monotonically encreases in the atomic - * sense. - */ - if (lastSyncTime <= lastModifiedTime) { - lastSyncTime = lastModifiedTime + 1000; - prefsFile.setLastModified(lastSyncTime); - } - changeLog.clear(); - } - } - - @Override - public void flush() throws BackingStoreException { - if (isRemoved()) - return; - sync(); - } - - @Override - protected void flushSpi() throws BackingStoreException { - // assert false; - } - - @Override - protected boolean isRemoved() { - return super.isRemoved(); - } - - /** - * Returns true if the specified character is appropriate for use in Unix - * directory names. A character is appropriate if it's a printable ASCII - * character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f), dot ('.', - * 0x2e), or underscore ('_', 0x5f). - */ - private static boolean isDirChar(char ch) { - return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_'; - } - - /** - * Returns the directory name corresponding to the specified node name. - * Generally, this is just the node name. If the node name includes - * inappropriate characters (as per isDirChar) it is translated to Base64. with - * the underscore character ('_', 0x5f) prepended. - */ - private static String dirName(String nodeName) { - for (int i = 0, n = nodeName.length(); i < n; i++) - if (!isDirChar(nodeName.charAt(i))) - return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName)); - return nodeName; - } - - /** - * Translate a string into a byte array by translating each character into two - * bytes, high-byte first ("big-endian"). - */ - private static byte[] byteArray(String s) { - int len = s.length(); - byte[] result = new byte[2 * len]; - for (int i = 0, j = 0; i < len; i++) { - char c = s.charAt(i); - result[j++] = (byte) (c >> 8); - result[j++] = (byte) c; - } - return result; - } - - /** - * Returns the node name corresponding to the specified directory name. (Inverts - * the transformation of dirName(String). - */ - private static String nodeName(String dirName) { - if (dirName.charAt(0) != '_') - return dirName; - byte a[] = Base64.altBase64ToByteArray(dirName.substring(1)); - StringBuffer result = new StringBuffer(a.length / 2); - for (int i = 0; i < a.length;) { - int highByte = a[i++] & 0xff; - int lowByte = a[i++] & 0xff; - result.append((char) ((highByte << 8) | lowByte)); - } - return result.toString(); - } - - /** - * Try to acquire the appropriate file lock (user or system). If the initial - * attempt fails, several more attempts are made using an exponential backoff - * strategy. If all attempts fail, this method returns false. - * - * @throws SecurityException - * if file access denied. - */ - boolean lockFile(boolean shared) throws SecurityException { - boolean usernode = isUserNode(); - int errorCode = 0; - File lockFile = (usernode ? userLockFile : systemLockFile); - long sleepTime = INIT_SLEEP_TIME; - for (int i = 0; i < MAX_ATTEMPTS; i++) { - try { - RandomAccessFile file = new RandomAccessFile(lockFile.getCanonicalPath(), "rw"); - FileLock lock = file.getChannel().lock(); - - if (lock != null) { - if (usernode) { - userRootLockFile = file; - userRootLockHandle = lock; - } else { - systemRootLockFile = file; - systemRootLockHandle = lock; - } - return true; - } - } catch (IOException e) { - } - - try { - Thread.sleep(sleepTime); - } catch (InterruptedException e) { - checkLockFile0ErrorCode(errorCode); - return false; - } - sleepTime *= 2; - } - checkLockFile0ErrorCode(errorCode); - return false; - } - - /** - * Checks if unlockFile0() returned an error. Throws a SecurityException, if - * access denied. Logs a warning otherwise. - */ - private void checkLockFile0ErrorCode(int errorCode) throws SecurityException { - if (errorCode == EACCES) - throw new SecurityException( - "Could not lock " + (isUserNode() ? "User prefs." : "System prefs.") + " Lock file access denied."); - if (errorCode != EAGAIN) - getLogger().warning("Could not lock " + (isUserNode() ? "User prefs. " : "System prefs.") - + " Unix error code " + errorCode + "."); - } - - /** - * Initial time between lock attempts, in ms. The time is doubled after each - * failing attempt (except the first). - */ - private static int INIT_SLEEP_TIME = 50; - - /** - * Maximum number of lock attempts. - */ - private static int MAX_ATTEMPTS = 5; - - /** - * Release the the appropriate file lock (user or system). - * - * @throws SecurityException - * if file access denied. - */ - void unlockFile() { - boolean usernode = isUserNode(); - RandomAccessFile rootLockFile = (usernode ? userRootLockFile : systemRootLockFile); - FileLock lockHandle = (usernode ? userRootLockHandle : systemRootLockHandle); - try { - lockHandle.close(); - } catch (IOException e) { - getLogger().warning("Unlock: zero lockHandle for " + (usernode ? "user" : "system") + " preferences.)"); - return; - } finally { - try { - rootLockFile.close(); - } catch (IOException e) { - getLogger().log(Level.WARNING, "Cannot close lock file " + rootLockFile, e); - } - } - - } -} \ No newline at end of file diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java new file mode 100644 index 0000000000..e0cea922f1 --- /dev/null +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2024 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.phoebus.framework.preferences; + +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** Java Preferences that are held in memory, not persisted + * @author Kay Kasemir + */ +class InMemoryPreferences extends AbstractPreferences +{ + /** Preferences for both "user" and "system" */ + private static final InMemoryPreferences prefs = new InMemoryPreferences(null, ""); + + /** Settings for this node in the preferences hierarchy */ + private Map cache = new HashMap<>(); + + // Javadoc for all ..Spi calls includes + // "This method is invoked with the lock on this node held." + // so no need for ConcurrentHashMap or our own locking + + /** @return User preferences */ + public static Preferences getUserRoot() + { + return prefs; + } + + /** @return System preferences */ + public static Preferences getSystemRoot() + { + return prefs; + } + + InMemoryPreferences(final InMemoryPreferences parent, final String name) + { + super(parent, name); + } + + /** @inheritDoc */ + @Override + protected void putSpi(String key, String value) + { + cache.put(key, value); + } + + /** @inheritDoc */ + @Override + protected String getSpi(String key) + { + return cache.get(key); + } + + /** @inheritDoc */ + @Override + protected void removeSpi(String key) + { + cache.remove(key); + } + + /** @inheritDoc */ + @Override + protected void removeNodeSpi() throws BackingStoreException + { + // Nothing to remove in the file system + cache.clear(); + } + + /** @inheritDoc */ + @Override + protected String[] keysSpi() throws BackingStoreException + { + return cache.keySet().toArray(new String[cache.size()]); + } + + /** @inheritDoc */ + @Override + protected String[] childrenNamesSpi() throws BackingStoreException + { + // This method need not return the names of any nodes already cached + // by the AbstractPreferences + return new String[0]; + } + + /** @inheritDoc */ + @Override + protected AbstractPreferences childSpi(String name) + { + // AbstractPreferences guaranteed that the named node has not been returned + // by a previous invocation of this method or {@link #getChild(String)}, + // so only called once and can then create the one and only new child + return new InMemoryPreferences(this, name); + } + + /** @inheritDoc */ + @Override + protected void syncSpi() throws BackingStoreException + { + // Nothing to sync + } + + /** @inheritDoc */ + @Override + protected void flushSpi() throws BackingStoreException + { + // Nothing to sync + } +} diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java index fe4f6fb453..fe9b303ccc 100644 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/PhoebusPreferenceService.java @@ -4,7 +4,7 @@ import java.util.prefs.PreferencesFactory; /** - * A service which enabled the use of the file backed implementation of java {@link Preferences}. + * A service which enabled the use of the in-memory implementation of java {@link Preferences}. * @author Kunal Shroff * */ @@ -22,13 +22,13 @@ public static Preferences userNodeForClass(final Class clazz) @Override public Preferences userRoot() { - return FileSystemPreferences.getUserRoot(); + return InMemoryPreferences.getUserRoot(); } @Override public Preferences systemRoot() { - return FileSystemPreferences.getSystemRoot(); + return InMemoryPreferences.getSystemRoot(); } } diff --git a/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java b/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java index f407f7558d..87566c421f 100644 --- a/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java +++ b/core/types/src/main/java/org/phoebus/core/types/TimeStampedProcessVariable.java @@ -2,6 +2,9 @@ import java.time.Instant; +/** + * A PV with a Timestamp + */ @SuppressWarnings("nls") public class TimeStampedProcessVariable extends ProcessVariable { diff --git a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java index 6583817b53..0add9484a5 100644 --- a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java +++ b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2020 Oak Ridge National Laboratory. + * Copyright (c) 2017-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,34 +11,27 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.util.Arrays; import java.util.List; import java.util.logging.Level; -import org.phoebus.framework.jobs.JobManager; -import org.phoebus.framework.preferences.PropertyPreferenceLoader; import org.phoebus.framework.preferences.PropertyPreferenceWriter; import org.phoebus.framework.workbench.ApplicationService; import org.phoebus.framework.workbench.Locations; import org.phoebus.ui.application.Messages; import org.phoebus.ui.dialog.DialogHelper; -import org.phoebus.ui.dialog.OpenFileDialog; import org.phoebus.ui.docking.DockPane; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.ui.javafx.ReadOnlyTextCell; import org.phoebus.ui.spi.MenuEntry; -import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TableCell; @@ -47,10 +40,8 @@ import javafx.scene.control.TextArea; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import javafx.stage.FileChooser.ExtensionFilter; /** Menu entry to open 'about' * @author Kay Kasemir @@ -216,44 +207,11 @@ private Node createDetailSection() area = new TextArea(prefs_buf.toString()); area.setEditable(false); - final Button import_prefs = new Button("Import Preferences"); - import_prefs.setOnAction(event -> import_preferences()); - final HBox bottom_row = new HBox(import_prefs); - bottom_row.setAlignment(Pos.BASELINE_RIGHT); - VBox.setVgrow(area, Priority.ALWAYS); - final Tab prefs = new Tab(Messages.HelpAboutPrefs, new VBox(5, area, bottom_row)); + final Tab prefs = new Tab(Messages.HelpAboutPrefs, area); final TabPane tabs = new TabPane(apps, envs, props, prefs); return tabs; } - - /** Prompt for settings.ini to import, then offer restart */ - private void import_preferences() - { - final DockPane parent = DockPane.getActiveDockPane(); - - final ExtensionFilter[] ini = new ExtensionFilter[] { new ExtensionFilter("Preference settings.ini", List.of("*.ini")) }; - final File file = new OpenFileDialog().promptForFile(parent.getScene().getWindow(), Messages.Open, null, ini); - if (file == null) - return; - - JobManager.schedule("Load preferences", monitor -> - { - PropertyPreferenceLoader.load(new FileInputStream(file)); - Platform.runLater(() -> - { - final Alert restart = new Alert(AlertType.CONFIRMATION); - restart.setHeaderText("Restart to activate loaded settings"); - restart.setContentText("For performance reasons, preference settings are only loaded once on startup.\n" + - "Exit application so you can then start it again?"); - restart.getDialogPane().setPrefSize(500, 300); - restart.setResizable(true); - DialogHelper.positionDialog(restart, parent, -400, -300); - if (restart.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) - System.exit(0); - }); - }); - } } diff --git a/core/util/src/main/java/org/phoebus/util/text/Strings.java b/core/util/src/main/java/org/phoebus/util/text/Strings.java new file mode 100644 index 0000000000..03328eb7da --- /dev/null +++ b/core/util/src/main/java/org/phoebus/util/text/Strings.java @@ -0,0 +1,20 @@ +package org.phoebus.util.text; + +import java.util.Objects; + +public class Strings { + + /** + * Based on the the google String.isNullOrEmpty. + * + * @param str + * @return true if the string is null or empty + */ + public static boolean isNullOrEmpty(String str) { + if(Objects.isNull(str) || str.isBlank()) { + return true; + } else { + return false; + } + } +} diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java similarity index 99% rename from app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java rename to core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java index 49f6638374..aa03c40bed 100644 --- a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java +++ b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.archive.vtype; +package org.phoebus.util.time; import java.sql.Timestamp; import java.time.Duration; diff --git a/core/util/src/test/java/org/phoebus/util/text/StringsTest.java b/core/util/src/test/java/org/phoebus/util/text/StringsTest.java new file mode 100644 index 0000000000..95595b3505 --- /dev/null +++ b/core/util/src/test/java/org/phoebus/util/text/StringsTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.phoebus.util.text; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit test for {@link Strings}. + * + * Based on the tests in google guava @author Kevin Bourrillion + */ +public class StringsTest { + + + @Test + public void testIsNullOrEmpty() { + assertTrue(Strings.isNullOrEmpty(null)); + assertTrue(Strings.isNullOrEmpty("")); + assertFalse(Strings.isNullOrEmpty("a")); + } + +} \ No newline at end of file diff --git a/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java similarity index 97% rename from app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java rename to core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java index cfc68a31cb..86bc71edee 100644 --- a/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java +++ b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java @@ -5,11 +5,12 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package org.phoebus.archive.vtype; +package org.phoebus.util.time; import org.junit.jupiter.api.Test; import org.phoebus.util.time.TimeDuration; import org.phoebus.util.time.TimestampFormats; +import org.phoebus.util.time.TimestampHelper; import java.time.Instant; diff --git a/dependencies/phoebus-target/.classpath b/dependencies/phoebus-target/.classpath index 529c1da696..276b6b4c62 100644 --- a/dependencies/phoebus-target/.classpath +++ b/dependencies/phoebus-target/.classpath @@ -42,11 +42,11 @@ - - + + - + @@ -56,7 +56,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -135,7 +135,7 @@ - + diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index a545106292..f3f2951dd9 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -217,11 +217,6 @@ javax.activation 1.2.0 - - com.google.guava - guava - ${guava.version} - com.fasterxml.jackson.core @@ -472,6 +467,11 @@ ${spring.boot-version} test + + org.springdoc + springdoc-openapi-ui + 1.7.0 + javax.validation diff --git a/docs/source/locations.rst b/docs/source/locations.rst index 2c0ee43a56..4eace4c04e 100644 --- a/docs/source/locations.rst +++ b/docs/source/locations.rst @@ -19,7 +19,7 @@ set when starting the product. ``phoebus.user``: Location where phoebus keeps the memento - and preferences. + and saved layouts. Defaults to ``.phoebus`` in the user's home directory. Site-Specific Branding and Settings diff --git a/docs/source/preferences.rst b/docs/source/preferences.rst index 67357bd0a6..df70bd01c9 100644 --- a/docs/source/preferences.rst +++ b/docs/source/preferences.rst @@ -64,22 +64,15 @@ that lists all preference settings in the same format that is used by the ``settings.ini`` file. You can copy settings that you need to change from the display into your settings file. -The same details pane that lists current preference settings also -offers an ``Import Preferences`` button for loading a ``settings.ini`` -file. You may use that as an alternative to the command line ``-settings ..`` option, -but note that settings loaded via this button only become effective -after a restart. - -Settings loaded via either the ``-settings ..`` command line option -or the ``Import Preferences`` button are stored in the user location (see :ref:`locations`). -They remain effective until different settings are loaded or the user location is deleted. -It is therefore not necessary to always run the application with the same -``-settings ..`` command line option. Just invoking with the command line option -once or using the ``Import Preferences`` button once suffices to load settings. -In practice, however, it is advisable to include the ``-settings ..`` command line option -in a site-specific application start script. -This way, new users do not need to remember to once start with the option, -and existing users will benefit from changes to the settings file. +Settings loaded via the ``-settings ..`` command line option +only remain effective while the application is running. +It is therefore necessary to always run the application with the same +``-settings ..`` command line option to get the same results. +In practice, it is advisable to include the ``-settings ..`` command line option +in a site-specific application start script or add them to a site-specific +product as detailed below. +This way, new users do not need to remember any command line settings +because they are applied in the launcher script or bundled into the product. Conceptually, preference settings are meant to hold critical configuration parameters like the control system network configuration. @@ -94,7 +87,7 @@ like context menus or configuration dialogs. When you package phoebus for distribution at your site, you can also place a file ``settings.ini`` in the installation location (see :ref:`locations`). At startup, Phoebus will automatically load the file ``settings.ini`` -from the installation location, eliminating the need for your users +from the installation location, eliminating the need for your users or a launcher script to add the ``-settings ..`` command line option. @@ -145,7 +138,7 @@ In your application code, you can most conveniently access them like this:: The ``AnnotatedPreferences`` helper will read your ``*preferences.properties``, -apply updates from ``java.util.prefs.Preferences``, and then set the values +apply updates from ``java.util.prefs.Preferences`` that have been added via ``-settings ..``, and then set the values of all static fields annotated with ``@Preference``. It handles basic types like ``int``, ``long``, ``double``, ``boolean``, ``String``, ``File``. It can also parse comma-separated items into ``int[]`` or ``String[]``. @@ -169,10 +162,6 @@ returns a ``PreferencesReader``, or you could directly use that lower level API // and parse as desired. The ``PreferencesReader`` loads defaults from the property file, -then allows overrides via the ``java.util.prefs.Preferences`` API. -By default, the user settings are stored in a ``.phoebus`` folder -in the home directory. -This location can be changed by setting the Java property ``phoebus.user``. - -In the future, a preference UI might be added, but as mentioned -the preference settings are not meant to be adjusted by end users. +then allows overrides via the ``java.util.prefs.Preferences`` API +that is used when loading a ``settings.ini`` in the installation location +and by the ``-settings ..`` provided on the command line. diff --git a/phoebus-product/.classpath b/phoebus-product/.classpath index d4138ff3a5..0ee9f7c14a 100644 --- a/phoebus-product/.classpath +++ b/phoebus-product/.classpath @@ -43,6 +43,7 @@ + diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 4815bb3803..ab311a58af 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -116,6 +116,11 @@ app-databrowser 4.7.4-SNAPSHOT + + org.phoebus + app-trends-archive-reader + 4.7.4-SNAPSHOT + org.phoebus app-databrowser-json @@ -240,6 +245,12 @@ 4.7.4-SNAPSHOT true + + org.phoebus + app-trends-archive-datasource + 4.7.4-SNAPSHOT + true + org.phoebus app-channel-views diff --git a/services/alarm-logger/pom.xml b/services/alarm-logger/pom.xml index 7007ddef65..4e98977b0f 100644 --- a/services/alarm-logger/pom.xml +++ b/services/alarm-logger/pom.xml @@ -141,6 +141,13 @@ test + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + diff --git a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java index 614483b024..95ab4aba3b 100644 --- a/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java +++ b/services/alarm-logger/src/main/java/org/phoebus/alarm/logging/rest/SearchController.java @@ -3,6 +3,7 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo; import co.elastic.clients.elasticsearch.core.InfoResponse; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.phoebus.alarm.logging.AlarmLoggingService; @@ -25,6 +26,12 @@ import java.util.logging.Level; import java.util.logging.Logger; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; + /** * A REST service for querying the alarm message history * @@ -32,6 +39,7 @@ */ @RestController @SuppressWarnings("unused") +@Tag(name = "Search controller") public class SearchController { static final Logger logger = Logger.getLogger(SearchController.class.getName()); @@ -44,6 +52,7 @@ public class SearchController { /** * @return Information about the alarm logging service */ + @Operation(summary = "Get Service Info") @GetMapping public String info() { @@ -74,22 +83,41 @@ public String info() { } } + @Operation(summary = "Search alarms") + @Parameters({ + @Parameter(name = "pv", description = "PV name", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "severity", description = "Alarm severity", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "message", description = "Alarm message", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "current_severity", description = "PV severity", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "current_message", description = "PV message", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "user", description = "User", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "host", description = "Host", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "command", description = "Command", schema = @Schema(type = "string"), required = false, example = "*"), + @Parameter(name = "start", description = "Start time", schema = @Schema(type = "string"), required = false, example = "2024-06-12"), + @Parameter(name = "end", description = "End time", schema = @Schema(type = "string"), required = false, example = "2024-06-14"), + }) @RequestMapping(value = "/search/alarm", method = RequestMethod.GET) - public List search(@RequestParam Map allRequestParams) { + public List search(@Parameter(hidden = true) @RequestParam Map allRequestParams) { List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), allRequestParams); return result; } + @Operation(summary = "Search alarms by PV name") @RequestMapping(value = "/search/alarm/pv/{pv}", method = RequestMethod.GET) - public List searchPv(@PathVariable String pv) { + public List searchPv(@Parameter(description = "PV name") @PathVariable String pv) { Map searchParameters = new HashMap<>(); searchParameters.put("pv", pv); List result = AlarmLogSearchUtil.search(ElasticClientHelper.getInstance().getClient(), searchParameters); return result; } + @Operation(summary = "Search alarm config") + @Schema(name = "config", example = "/Accelerator/compteur", required = true) + @Parameters({ + @Parameter(name = "config", description = "Config path", schema = @Schema(type = "string"), required = false, example = "/Accelerator/pvname"), + }) @RequestMapping(value = "/search/alarm/config", method = RequestMethod.GET) - public List searchConfig(@RequestParam Map allRequestParams) { + public List searchConfig(@Parameter(hidden = true) @RequestParam Map allRequestParams) { if(allRequestParams == null || allRequestParams.isEmpty() || !allRequestParams.containsKey("config") || diff --git a/services/archive-engine/.classpath b/services/archive-engine/.classpath index 2225c2c8b6..12e57c1a54 100644 --- a/services/archive-engine/.classpath +++ b/services/archive-engine/.classpath @@ -7,6 +7,8 @@ + + diff --git a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java index 02eda778c4..38c54216ee 100644 --- a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java +++ b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011-2020 Oak Ridge National Laboratory. + * Copyright (c) 2011-2024 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -178,11 +178,18 @@ public WriteChannel getChannel(final String name) throws Exception @Override public void addSample(final WriteChannel channel, final VType sample) throws Exception { - final RDBWriteChannel rdb_channel = (RDBWriteChannel) channel; - writeMetaData(rdb_channel, sample); - batchSample(rdb_channel, sample); - batched_channel.add(rdb_channel); - batched_samples.add(sample); + try + { + final RDBWriteChannel rdb_channel = (RDBWriteChannel) channel; + writeMetaData(rdb_channel, sample); + batchSample(rdb_channel, sample); + batched_channel.add(rdb_channel); + batched_samples.add(sample); + } + catch (Exception ex) + { // Wrap with channel info + throw new Exception("Cannot add sample for " + channel, ex); + } } /** Write meta data if it was never written or has changed @@ -191,12 +198,33 @@ public void addSample(final WriteChannel channel, final VType sample) throws Exc */ private void writeMetaData(final RDBWriteChannel channel, final VType sample) throws Exception { + // Three cases: String, enum, numeric. + // // Note that Strings have no meta data. But we don't know at this point - // if it's really a string channel, or of this is just a special + // if it's really a string channel, or if this is just a special // string value like "disconnected". // In order to not delete any existing meta data, - // we just do nothing for strings + // we just do nothing for strings. + if (sample instanceof VString) + return; + + if (sample instanceof VEnum) + { + final List labels = ((VEnum)sample).getDisplay().getChoices(); + if (MetaDataHelper.equals(labels, channel.getMetadata())) + return; + + // Clear numeric meta data, set enumerated in RDB + NumericMetaDataHelper.delete(connection, sql, channel); + EnumMetaDataHelper.delete(connection, sql, channel); + EnumMetaDataHelper.insert(connection, sql, channel, labels); + channel.setMetaData(labels); + return; + } + + // Note that Display.displayOf(VEnum) or ..(VString) will return a non-null display, + // but we already handled those cases final Display display = Display.displayOf(sample); if (display != null) { @@ -209,18 +237,6 @@ private void writeMetaData(final RDBWriteChannel channel, final VType sample) th NumericMetaDataHelper.insert(connection, sql, channel, display); channel.setMetaData(display); } - else if (sample instanceof VEnum) - { - final List labels = ((VEnum)sample).getDisplay().getChoices(); - if (MetaDataHelper.equals(labels, channel.getMetadata())) - return; - - // Clear numeric meta data, set enumerated in RDB - NumericMetaDataHelper.delete(connection, sql, channel); - EnumMetaDataHelper.delete(connection, sql, channel); - EnumMetaDataHelper.insert(connection, sql, channel, labels); - channel.setMetaData(labels); - } } private static Instant getTimestamp(final VType value)