From 7d09f455f8213c85cef30a1c2a64c01ec1e9195f Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 11 Apr 2024 14:24:25 -0400 Subject: [PATCH 01/59] Archiver Datasource protoype module --- app/trends/archive-datasource/pom.xml | 25 +++++++++++++++++++++++++ app/trends/pom.xml | 1 + 2 files changed, 26 insertions(+) create mode 100644 app/trends/archive-datasource/pom.xml diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml new file mode 100644 index 0000000000..f0ea19091d --- /dev/null +++ b/app/trends/archive-datasource/pom.xml @@ -0,0 +1,25 @@ + + + + parent + org.phoebus + 4.7.4-SNAPSHOT + + 4.0.0 + app-trends-archive-datasource + + + 17 + 17 + + + + + org.phoebus + app-databrowser + 4.7.4-SNAPSHOT + + + \ No newline at end of file diff --git a/app/trends/pom.xml b/app/trends/pom.xml index abdc14689a..83c350c27b 100644 --- a/app/trends/pom.xml +++ b/app/trends/pom.xml @@ -10,5 +10,6 @@ rich-adapters simple-adapters + archive-datasource From 52ccde63edd93f89aac77eedd253be8468c4d7a5 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 11 Apr 2024 14:25:44 -0400 Subject: [PATCH 02/59] Create an archiver appliance reader - reusing databrowser code --- .../pv/archive/ArchiveReaderService.java | 32 +++++++++++++++++++ .../org/phoebus/pv/archive/Preferences.java | 24 ++++++++++++++ ...ppliance_datasource_preferences.properties | 5 +++ 3 files changed, 61 insertions(+) create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderService.java create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java create mode 100644 app/trends/archive-datasource/src/main/resources/appliance_datasource_preferences.properties 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/Preferences.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java new file mode 100644 index 0000000000..231b3d8aa1 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/Preferences.java @@ -0,0 +1,24 @@ +package org.phoebus.pv.archive; + +import org.csstudio.trends.databrowser3.Activator; +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(Activator.class, Preferences.class, "/appliance_datasource_preferences.properties"); + } + +} 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 From 02fc4f3671c7513b86c83b3693d72f630e4ed78f Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 11 Apr 2024 15:08:35 -0400 Subject: [PATCH 03/59] Simple archive retrieval datasource, constant --- .../pv/archive/retrieve/ArchivePV.java | 61 ++++++++++++++++++ .../pv/archive/retrieve/ArchivePVFactory.java | 62 +++++++++++++++++++ .../services/org.phoebus.pv.PVFactory | 1 + 3 files changed, 124 insertions(+) create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java create mode 100644 app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory 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..19e4a83c85 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePV.java @@ -0,0 +1,61 @@ +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; + +/** + * A Connection to a PV in the archiver + * + * @author Kunal Shroff + */ +public class ArchivePV extends PV { + + ArchiveReaderService service = ArchiveReaderService.getService(); + + /** + * Timer for archive updates + */ + private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target -> + { + final Thread thread = new Thread(target, "ArchivePV"); + thread.setDaemon(true); + return thread; + }); + + public ArchivePV(String name) { + this(name, Instant.now()); + } + + public ArchivePV(String name, Instant instant) { + super(name); + try { + ValueIterator i = service.getReader().getRawValues(name, instant, instant); + + if (i.hasNext()) { + notifyListenersOfValue(i.next()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + 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..23ed1ca49b --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/retrieve/ArchivePVFactory.java @@ -0,0 +1,62 @@ +package org.phoebus.pv.archive.retrieve; + +import org.phoebus.pv.PV; +import org.phoebus.pv.PVFactory; + +import java.time.Instant; +import java.util.logging.Logger; + +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(pvName); + } else { + Instant time; + switch (parameters.length()) { + case 16 -> time = Instant.from(DATETIME_FORMAT.parse(parameters)); + case 19 -> time = Instant.from(SECONDS_FORMAT.parse(parameters)); + case 23 -> time = Instant.from(MILLI_FORMAT.parse(parameters)); + case 29 -> time = Instant.from(FULL_FORMAT.parse(parameters)); + default -> throw new Exception("Time value defined in a unknown formatt, '" + parameters + "'"); + } + return new ArchivePV(pvName, time); + } + } +} 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..63ecd477bd --- /dev/null +++ b/app/trends/archive-datasource/src/main/resources/META-INF/services/org.phoebus.pv.PVFactory @@ -0,0 +1 @@ +org.phoebus.pv.archive.retrieve.ArchivePVFactory \ No newline at end of file From 6c6f24daee68ad63912fd1b5ffcb9a92d84eca6d Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 22 May 2024 15:08:59 -0400 Subject: [PATCH 04/59] Add the archive datasource to the common product --- phoebus-product/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 4815bb3803..24b256a3d6 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -240,6 +240,12 @@ 4.7.4-SNAPSHOT true + + org.phoebus + app-trends-archive-datasource + 4.7.4-SNAPSHOT + true + org.phoebus app-channel-views From 8f7b46d954705d86cd5c010e3262ebfa6aafedf1 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Wed, 22 May 2024 15:09:20 -0400 Subject: [PATCH 05/59] starting some documentation on the usage of the aa datasources --- app/trends/archive-datasource/doc/index.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/trends/archive-datasource/doc/index.rst diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst new file mode 100644 index 0000000000..c7515037d6 --- /dev/null +++ b/app/trends/archive-datasource/doc/index.rst @@ -0,0 +1,21 @@ +Alarm Datasource +================ + +Overview +-------- +The archive datasource allows accessing historical data as a pv + + +PV syntax +--------- + +The standard prefix for the datasource is ``archive://`` which can be omitted if configured as the default datasource. +The archiver PV's are readonly and constants. + +archive://pv_name + +Retrieves the latest value in the archiver + +archive://pv_name(time) + +Retrieves the last value at or before the "time" From 4ef8bb4e07c24fc092e38c7d49684b997e3ef1fa Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 31 May 2024 10:12:53 -0400 Subject: [PATCH 06/59] Fix the parent for the archive datasource module --- app/trends/archive-datasource/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml index f0ea19091d..6bda3f309e 100644 --- a/app/trends/archive-datasource/pom.xml +++ b/app/trends/archive-datasource/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - parent + app-trends org.phoebus 4.7.4-SNAPSHOT From c252a8b227cb68d6b4222db04f34ef51e982f2ed Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 31 May 2024 11:07:22 -0400 Subject: [PATCH 07/59] Refactor archiver readers out of app-databrowser into independent module --- .../imports/ImportArchiveReaderFactory.java | 25 ++--- .../archive/vtype/TimestampHelper.java | 100 ------------------ ...us.archive.reader.spi.ArchiveReaderFactory | 5 - .../archive/vtype/TimestampHelperTest.java | 91 ---------------- .../phoebus/archive/reader/ArchiveReader.java | 0 .../archive/reader/ArchiveReaders.java | 0 .../archive/reader/AveragedValueIterator.java | 0 .../archive/reader/LinearValueIterator.java | 0 .../archive/reader/MergingValueIterator.java | 0 .../archive/reader/SpreadsheetIterator.java | 0 .../reader/UnknownChannelException.java | 0 .../phoebus/archive/reader/ValueIterator.java | 0 .../appliance/ApplianceArchiveReader.java | 0 .../ApplianceArchiveReaderConstants.java | 0 .../ApplianceArchiveReaderFactory.java | 0 .../appliance/ApplianceMeanValueIterator.java | 0 ...ianceNonNumericOptimizedValueIterator.java | 0 .../ApplianceOptimizedValueIterator.java | 0 .../appliance/AppliancePreferences.java | 0 .../appliance/ApplianceRawValueIterator.java | 0 .../ApplianceStatisticsValueIterator.java | 0 .../appliance/ApplianceValueIterator.java | 0 .../appliance/ArchiverApplianceException.java | 0 ...ArchiverApplianceInvalidTypeException.java | 0 .../reader/appliance/IteratorListener.java | 0 .../reader/channelarchiver/SeverityInfo.java | 0 .../channelarchiver/ValueRequestIterator.java | 0 .../channelarchiver/XMLRPCArchiveReader.java | 0 .../XMLRPCArchiveReaderFactory.java | 0 .../reader/channelarchiver/XmlRpc.java | 0 .../file/ArchiveFileBuffer.java | 0 .../file/ArchiveFileIndexReader.java | 0 .../file/ArchiveFileReader.java | 0 .../file/ArchiveFileReaderFactory.java | 0 .../file/ArchiveFileSampleReader.java | 0 .../channelarchiver/file/ArchiveFileTime.java | 0 .../channelarchiver/file/CtrlInfoReader.java | 0 .../channelarchiver/file/DataFileEntry.java | 0 .../channelarchiver/file/DataHeader.java | 0 .../channelarchiver/file/ListIndexReader.java | 0 .../channelarchiver/file/RTreeNode.java | 0 .../reader/rdb/AbstractRDBValueIterator.java | 0 .../archive/reader/rdb/RDBArchiveReader.java | 0 .../reader/rdb/RDBArchiveReaderFactory.java | 0 .../archive/reader/rdb/RDBPreferences.java | 0 .../archive/reader/rdb/RawSampleIterator.java | 0 .../org/phoebus/archive/reader/rdb/SQL.java | 0 .../rdb/StoredProcedureValueIterator.java | 0 .../reader/spi/ArchiveReaderFactory.java | 0 .../reader/util/ChannelAccessStatusUtil.java | 0 .../appliance_preferences.properties | 0 .../archive_reader_rdb_preferences.properties | 0 .../channelarchiver_preferences.properties | 0 .../archive/reader/DemoDataIterator.java | 0 .../reader/SpreadsheetIteratorUnitTest.java | 0 .../XMLRPCArchiveReaderDemo.java | 0 56 files changed, 13 insertions(+), 208 deletions(-) delete mode 100644 app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java delete mode 100644 app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory delete mode 100644 app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ArchiveReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ArchiveReaders.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/MergingValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/SpreadsheetIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/UnknownChannelException.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/ValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderConstants.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceArchiveReaderFactory.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceMeanValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceNonNumericOptimizedValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceOptimizedValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/AppliancePreferences.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceRawValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceStatisticsValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ApplianceValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceException.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/ArchiverApplianceInvalidTypeException.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/appliance/IteratorListener.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/SeverityInfo.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/ValueRequestIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderFactory.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/XmlRpc.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileBuffer.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileIndexReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileReaderFactory.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileSampleReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ArchiveFileTime.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/CtrlInfoReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataFileEntry.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/DataHeader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/ListIndexReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/channelarchiver/file/RTreeNode.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/AbstractRDBValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReader.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBArchiveReaderFactory.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RDBPreferences.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/RawSampleIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/SQL.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/rdb/StoredProcedureValueIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/spi/ArchiveReaderFactory.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/reader/util/ChannelAccessStatusUtil.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/resources/appliance_preferences.properties (100%) rename app/{databrowser => trends/archive-reader}/src/main/resources/archive_reader_rdb_preferences.properties (100%) rename app/{databrowser => trends/archive-reader}/src/main/resources/channelarchiver_preferences.properties (100%) rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/DemoDataIterator.java (100%) rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/SpreadsheetIteratorUnitTest.java (100%) rename app/{databrowser => trends/archive-reader}/src/test/java/org/phoebus/archive/reader/channelarchiver/XMLRPCArchiveReaderDemo.java (100%) 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..539728d809 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,13 +7,13 @@ ******************************************************************************/ package org.csstudio.trends.databrowser3.imports; -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; +import java.util.concurrent.ConcurrentHashMap; +// TODO resolve the dependency between this archive reader and the app-databrowser +//import org.csstudio.trends.databrowser3.model.ArchiveDataSource; + /** Factory for {@link ArchiveReader} that imports data from file * @author Kay Kasemir */ @@ -103,12 +103,13 @@ private ArchiveReader doCreateReader(final String url) } } - /** Removed cached data for given archive data sources - * @param sources {@link ArchiveDataSource}[] - */ - public static void removeCachedArchives(final Collection sources) - { - for (ArchiveDataSource source : sources) - cache.remove(source.getUrl()); - } + // TODO resolve the dependency without creating a cyclic dependency with app-databrowser +// /** Removed cached data for given archive data sources +// * @param sources {@link ArchiveDataSource}[] +// */ +// public static void removeCachedArchives(final Collection sources) +// { +// for (ArchiveDataSource source : sources) +// cache.remove(source.getUrl()); +// } } diff --git a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java b/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java deleted file mode 100644 index 49f6638374..0000000000 --- a/app/databrowser/src/main/java/org/phoebus/archive/vtype/TimestampHelper.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 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.archive.vtype; - -import java.sql.Timestamp; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.concurrent.TimeUnit; - -/** Time stamp helper */ -public class TimestampHelper -{ - /** Round time to next multiple of given duration - * @param time Original time stamp - * @param duration Duration to use for rounding - * @return Time stamp rounded up to next multiple of duration - */ - public static Instant roundUp(final Instant time, final Duration duration) - { - return roundUp(time, duration.getSeconds()); - } - - /** Seconds per hour */ - final public static long SECS_PER_HOUR = TimeUnit.HOURS.toSeconds(1); - /** Seconds per minute */ - final public static long SECS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1); - /** Seconds per day */ - final public static long SECS_PER_DAY = TimeUnit.DAYS.toSeconds(1); - - /** Round time to next multiple of given seconds - * @param time Original time stamp - * @param seconds Seconds to use for rounding - * @return Time stamp rounded up to next multiple of seconds - */ - public static Instant roundUp(final Instant time, final long seconds) - { - if (seconds <= 0) - return time; - - // Directly round seconds within an hour - if (seconds <= SECS_PER_HOUR) - { - long secs = time.getEpochSecond(); - if (time.getNano() > 0) - ++secs; - final long periods = secs / seconds; - secs = (periods + 1) * seconds; - return Instant.ofEpochSecond(secs, 0); - } - - // When rounding "2012/01/19 12:23:14" by 2 hours, - // the user likely expects "2012/01/19 14:00:00" - // because 12.xx rounded up by 2 is 14. - // - // In other words, rounding by 2 should result in an even hour, - // but this is in the user's time zone. - // When rounding based on the epoch seconds, which could differ - // by an odd number of hours from the local time zone, rounding by - // 2 hours could result in odd-numbered hours in local time. - // - // The addition of leap seconds can further confuse matters, - // so perform computations that go beyond an hour in local time, - // relative to midnight of the given time stamp. - final ZonedDateTime local = ZonedDateTime.ofInstant(time, ZoneId.systemDefault()); - final ZonedDateTime midnight = ZonedDateTime.of(local.getYear(), local.getMonthValue(), local.getDayOfMonth(), - 0, 0, 0, 0, local.getZone()); - - // Round the HH:MM within the day - long secs = local.getHour()* SECS_PER_HOUR + - local.getMinute() * SECS_PER_MINUTE; - final long periods = secs / seconds; - secs = (periods + 1) * seconds; - - // Create time for rounded HH:MM - return midnight.toInstant().plus(Duration.ofSeconds(secs)); - } - - /** @param time Instant - * @return SQL time stamp - */ - public static Timestamp toSQLTimestamp(final Instant time) - { - return java.sql.Timestamp.from(time); - } - - /** @param time SQL time stamp - * @return Instant - */ - public static Instant fromSQLTimestamp(final Timestamp time) - { - return time.toInstant(); - } -} 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 deleted file mode 100644 index 7c0cb8427b..0000000000 --- a/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory +++ /dev/null @@ -1,5 +0,0 @@ -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/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java b/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java deleted file mode 100644 index cfc68a31cb..0000000000 --- a/app/databrowser/src/test/java/org/phoebus/archive/vtype/TimestampHelperTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 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.archive.vtype; - -import org.junit.jupiter.api.Test; -import org.phoebus.util.time.TimeDuration; -import org.phoebus.util.time.TimestampFormats; - -import java.time.Instant; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -/** JUnit test of {@link TimestampHelper} - * @author Kay Kasemir - */ -@SuppressWarnings("nls") -public class TimestampHelperTest -{ - @Test - public void testRoundUp() throws Exception - { - final Instant orig = Instant.from(TimestampFormats.SECONDS_FORMAT.parse("2012-01-19 12:23:14")); - String text = TimestampFormats.SECONDS_FORMAT.format(orig); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 12:23:14")); - - Instant time; - - // Round within a few seconds - time = TimestampHelper.roundUp(orig, 10); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 12:23:20")); - - time = TimestampHelper.roundUp(orig, TimeDuration.ofSeconds(30)); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 12:23:30")); - - // .. to minute - time = TimestampHelper.roundUp(orig, 60); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 12:24:00")); - - // .. to hours - time = TimestampHelper.roundUp(orig, TimeDuration.ofHours(1.0)); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 13:00:00")); - - time = TimestampHelper.roundUp(orig, 2L*60*60); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-19 14:00:00")); - - // .. full day(s) - assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY)); - - time = TimestampHelper.roundUp(orig, TimestampHelper.SECS_PER_DAY); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-20 00:00:00")); - - time = TimestampHelper.roundUp(orig, 3*TimestampHelper.SECS_PER_DAY); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-22 00:00:00")); - - // Into next month - time = TimestampHelper.roundUp(orig, 13*TimestampHelper.SECS_PER_DAY); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-02-01 00:00:00")); - - // .. full day(s) - assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY)); - - // 1.5 days - time = TimestampHelper.roundUp(orig, (3*TimestampHelper.SECS_PER_DAY)/2); - text = TimestampFormats.SECONDS_FORMAT.format(time); - System.out.println(text); - assertThat(text, equalTo("2012-01-20 12:00:00")); - } -} 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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 100% 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 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/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 100% 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 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 From 05b9e40909c40a8cdd4564c519b5303bf43e7bf2 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 31 May 2024 11:08:16 -0400 Subject: [PATCH 08/59] moved to core-util with other utility classes for handling time --- .../phoebus/util/time/TimestampHelper.java | 100 ++++++++++++++++++ .../util/time/TimestampHelperTest.java | 92 ++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java create mode 100644 core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java diff --git a/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java new file mode 100644 index 0000000000..aa03c40bed --- /dev/null +++ b/core/util/src/main/java/org/phoebus/util/time/TimestampHelper.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2017 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.util.time; + +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +/** Time stamp helper */ +public class TimestampHelper +{ + /** Round time to next multiple of given duration + * @param time Original time stamp + * @param duration Duration to use for rounding + * @return Time stamp rounded up to next multiple of duration + */ + public static Instant roundUp(final Instant time, final Duration duration) + { + return roundUp(time, duration.getSeconds()); + } + + /** Seconds per hour */ + final public static long SECS_PER_HOUR = TimeUnit.HOURS.toSeconds(1); + /** Seconds per minute */ + final public static long SECS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1); + /** Seconds per day */ + final public static long SECS_PER_DAY = TimeUnit.DAYS.toSeconds(1); + + /** Round time to next multiple of given seconds + * @param time Original time stamp + * @param seconds Seconds to use for rounding + * @return Time stamp rounded up to next multiple of seconds + */ + public static Instant roundUp(final Instant time, final long seconds) + { + if (seconds <= 0) + return time; + + // Directly round seconds within an hour + if (seconds <= SECS_PER_HOUR) + { + long secs = time.getEpochSecond(); + if (time.getNano() > 0) + ++secs; + final long periods = secs / seconds; + secs = (periods + 1) * seconds; + return Instant.ofEpochSecond(secs, 0); + } + + // When rounding "2012/01/19 12:23:14" by 2 hours, + // the user likely expects "2012/01/19 14:00:00" + // because 12.xx rounded up by 2 is 14. + // + // In other words, rounding by 2 should result in an even hour, + // but this is in the user's time zone. + // When rounding based on the epoch seconds, which could differ + // by an odd number of hours from the local time zone, rounding by + // 2 hours could result in odd-numbered hours in local time. + // + // The addition of leap seconds can further confuse matters, + // so perform computations that go beyond an hour in local time, + // relative to midnight of the given time stamp. + final ZonedDateTime local = ZonedDateTime.ofInstant(time, ZoneId.systemDefault()); + final ZonedDateTime midnight = ZonedDateTime.of(local.getYear(), local.getMonthValue(), local.getDayOfMonth(), + 0, 0, 0, 0, local.getZone()); + + // Round the HH:MM within the day + long secs = local.getHour()* SECS_PER_HOUR + + local.getMinute() * SECS_PER_MINUTE; + final long periods = secs / seconds; + secs = (periods + 1) * seconds; + + // Create time for rounded HH:MM + return midnight.toInstant().plus(Duration.ofSeconds(secs)); + } + + /** @param time Instant + * @return SQL time stamp + */ + public static Timestamp toSQLTimestamp(final Instant time) + { + return java.sql.Timestamp.from(time); + } + + /** @param time SQL time stamp + * @return Instant + */ + public static Instant fromSQLTimestamp(final Timestamp time) + { + return time.toInstant(); + } +} diff --git a/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java new file mode 100644 index 0000000000..86bc71edee --- /dev/null +++ b/core/util/src/test/java/org/phoebus/util/time/TimestampHelperTest.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2017 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.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; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +/** JUnit test of {@link TimestampHelper} + * @author Kay Kasemir + */ +@SuppressWarnings("nls") +public class TimestampHelperTest +{ + @Test + public void testRoundUp() throws Exception + { + final Instant orig = Instant.from(TimestampFormats.SECONDS_FORMAT.parse("2012-01-19 12:23:14")); + String text = TimestampFormats.SECONDS_FORMAT.format(orig); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 12:23:14")); + + Instant time; + + // Round within a few seconds + time = TimestampHelper.roundUp(orig, 10); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 12:23:20")); + + time = TimestampHelper.roundUp(orig, TimeDuration.ofSeconds(30)); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 12:23:30")); + + // .. to minute + time = TimestampHelper.roundUp(orig, 60); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 12:24:00")); + + // .. to hours + time = TimestampHelper.roundUp(orig, TimeDuration.ofHours(1.0)); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 13:00:00")); + + time = TimestampHelper.roundUp(orig, 2L*60*60); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-19 14:00:00")); + + // .. full day(s) + assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY)); + + time = TimestampHelper.roundUp(orig, TimestampHelper.SECS_PER_DAY); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-20 00:00:00")); + + time = TimestampHelper.roundUp(orig, 3*TimestampHelper.SECS_PER_DAY); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-22 00:00:00")); + + // Into next month + time = TimestampHelper.roundUp(orig, 13*TimestampHelper.SECS_PER_DAY); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-02-01 00:00:00")); + + // .. full day(s) + assertThat(24L*60*60, equalTo(TimestampHelper.SECS_PER_DAY)); + + // 1.5 days + time = TimestampHelper.roundUp(orig, (3*TimestampHelper.SECS_PER_DAY)/2); + text = TimestampFormats.SECONDS_FORMAT.format(time); + System.out.println(text); + assertThat(text, equalTo("2012-01-20 12:00:00")); + } +} From b93d705d176e7ead1b19f4550a1e28597ec841ce Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 31 May 2024 11:09:12 -0400 Subject: [PATCH 09/59] Moving the very databrowser specific reader back to app-databrowser --- .../imports/ImportArchiveReaderFactory.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) 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 539728d809..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 @@ -10,9 +10,9 @@ import org.phoebus.archive.reader.ArchiveReader; import org.phoebus.archive.reader.spi.ArchiveReaderFactory; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; -// TODO resolve the dependency between this archive reader and the app-databrowser -//import org.csstudio.trends.databrowser3.model.ArchiveDataSource; +import org.csstudio.trends.databrowser3.model.ArchiveDataSource; /** Factory for {@link ArchiveReader} that imports data from file * @author Kay Kasemir @@ -103,13 +103,12 @@ private ArchiveReader doCreateReader(final String url) } } - // TODO resolve the dependency without creating a cyclic dependency with app-databrowser -// /** Removed cached data for given archive data sources -// * @param sources {@link ArchiveDataSource}[] -// */ -// public static void removeCachedArchives(final Collection sources) -// { -// for (ArchiveDataSource source : sources) -// cache.remove(source.getUrl()); -// } + /** Removed cached data for given archive data sources + * @param sources {@link ArchiveDataSource}[] + */ + public static void removeCachedArchives(final Collection sources) + { + for (ArchiveDataSource source : sources) + cache.remove(source.getUrl()); + } } From 57e20143e99ed77b3a7dc601d88eff474b70e8bc Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 31 May 2024 11:09:47 -0400 Subject: [PATCH 10/59] Add the SPI for the various archive readers --- .../org.phoebus.archive.reader.spi.ArchiveReaderFactory | 1 + .../org.phoebus.archive.reader.spi.ArchiveReaderFactory | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory create mode 100644 app/trends/archive-reader/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory 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 new file mode 100644 index 0000000000..3db81f793e --- /dev/null +++ b/app/databrowser/src/main/resources/META-INF/services/org.phoebus.archive.reader.spi.ArchiveReaderFactory @@ -0,0 +1 @@ +org.csstudio.trends.databrowser3.imports.ImportArchiveReaderFactory 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 From 024250acdadc8a2016b28c0adb7390ab6988647b Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 12:10:23 -0400 Subject: [PATCH 11/59] Move the archive vtype package to the new archive reader module --- .../main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java | 0 .../main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java | 0 .../main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java | 0 .../java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java | 0 .../java/org/phoebus/archive/vtype/StatisticsAccumulator.java | 0 .../main/java/org/phoebus/archive/vtype/StringVTypeFormat.java | 0 .../src/main/java/org/phoebus/archive/vtype/Style.java | 0 .../src/main/java/org/phoebus/archive/vtype/VTypeFormat.java | 0 .../src/main/java/org/phoebus/archive/vtype/VTypeHelper.java | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DecimalVTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DefaultVTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/DoubleVTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/ExponentialVTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/StatisticsAccumulator.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/StringVTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/Style.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/VTypeFormat.java (100%) rename app/{databrowser => trends/archive-reader}/src/main/java/org/phoebus/archive/vtype/VTypeHelper.java (100%) 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 From bab8778deba8211ad4f4a0141799070ca4957b89 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 12:14:00 -0400 Subject: [PATCH 12/59] Move the equivalent_pv_prefixes to RDB preference (only use case found) --- .../archive/reader/rdb/RDBArchiveReader.java | 2 +- .../archive/reader/rdb/RDBPreferences.java | 3 ++- .../archive_reader_rdb_preferences.properties | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/trends/archive-reader/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 index ede39a0d38..3966a422b5 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index 6bf5d1602d..abe5caa48d 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties b/app/trends/archive-reader/src/main/resources/archive_reader_rdb_preferences.properties index 71a36e7c40..a542079bcc 100644 --- a/app/trends/archive-reader/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 From 3d6b1dd9e9cb5604db14eff2f6152edf6c6b58a7 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 13:09:34 -0400 Subject: [PATCH 13/59] Fix import of TimestampHelper --- .../java/org/phoebus/archive/reader/AveragedValueIterator.java | 2 +- .../java/org/phoebus/archive/reader/LinearValueIterator.java | 2 +- .../archive/reader/appliance/ApplianceArchiveReader.java | 2 +- .../archive/reader/appliance/ApplianceMeanValueIterator.java | 2 +- .../reader/appliance/ApplianceOptimizedValueIterator.java | 2 +- .../reader/appliance/ApplianceStatisticsValueIterator.java | 2 +- .../archive/reader/appliance/ApplianceValueIterator.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/AveragedValueIterator.java index 64f8b12af7..31d31145d7 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java b/app/trends/archive-reader/src/main/java/org/phoebus/archive/reader/LinearValueIterator.java index fde8622b37..0b2d3400a3 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index f42e16d846..1887885028 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index b32d487851..4303995af9 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index f757fcba1d..91dbd5b750 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index 579eb63822..2140a62ce9 100644 --- a/app/trends/archive-reader/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/trends/archive-reader/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 index c070522fe2..d7feba56c5 100644 --- a/app/trends/archive-reader/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; /** * From 9b7f03ad6f37f23c8a65629daa0a2af2c6ba5ae4 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 13:43:22 -0400 Subject: [PATCH 14/59] remove the unused equivalent pv prefix preference from databrowser --- .../csstudio/trends/databrowser3/preferences/Preferences.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..18c52e22a1 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 @@ -87,8 +87,8 @@ public static class TimePreset @Preference public static boolean use_default_archives; /** Setting */ @Preference public static boolean drop_failed_archives; - /** Setting */ - @Preference public static String[] equivalent_pv_prefixes; +// /** Setting */ +// @Preference public static String[] equivalent_pv_prefixes; /** Setting */ @Preference public static boolean use_trace_names; /** Setting */ From ed7bb6d68e7813adbc8538cec00e612f90774f24 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 13:43:52 -0400 Subject: [PATCH 15/59] Update the pom files for the new trend modules --- app/databrowser/pom.xml | 13 ++---- app/trends/archive-reader/pom.xml | 69 +++++++++++++++++++++++++++++++ app/trends/pom.xml | 1 + 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 app/trends/archive-reader/pom.xml 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/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/trends/pom.xml b/app/trends/pom.xml index 83c350c27b..617c89db42 100644 --- a/app/trends/pom.xml +++ b/app/trends/pom.xml @@ -11,5 +11,6 @@ rich-adapters simple-adapters archive-datasource + archive-reader From 1eb9e3d786e9af0a91d074a8f0202b61e8e96f19 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 14:00:23 -0400 Subject: [PATCH 16/59] Undo the deleted preference ( needed by TS archive datasource ) --- .../trends/databrowser3/preferences/Preferences.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 18c52e22a1..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 @@ -87,8 +87,9 @@ public static class TimePreset @Preference public static boolean use_default_archives; /** Setting */ @Preference public static boolean drop_failed_archives; -// /** Setting */ -// @Preference public static String[] equivalent_pv_prefixes; + /** Setting */ + @Deprecated + @Preference public static String[] equivalent_pv_prefixes; /** Setting */ @Preference public static boolean use_trace_names; /** Setting */ From 1fd892bf6c96033d18abf340537582eb54d87e94 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 14:27:28 -0400 Subject: [PATCH 17/59] Add ant build scripts for the new trends modules --- app/trends/archive-datasource/build.xml | 19 +++++++++++++++++++ app/trends/archive-reader/build.xml | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 app/trends/archive-datasource/build.xml create mode 100644 app/trends/archive-reader/build.xml 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-reader/build.xml b/app/trends/archive-reader/build.xml new file mode 100644 index 0000000000..4ea1950c56 --- /dev/null +++ b/app/trends/archive-reader/build.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + From d9930a9e3fa49c0374204c9417d88c0feddc378c Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 3 Jun 2024 15:47:43 -0400 Subject: [PATCH 18/59] Fix typo in documentation --- app/trends/archive-datasource/doc/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst index c7515037d6..3e11c6ac3b 100644 --- a/app/trends/archive-datasource/doc/index.rst +++ b/app/trends/archive-datasource/doc/index.rst @@ -1,5 +1,5 @@ -Alarm Datasource -================ +Archive Datasource +================== Overview -------- From 0d0295e0ac657a4af2f8f5ecbe2f5dc995112b8a Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 4 Jun 2024 09:17:55 -0400 Subject: [PATCH 19/59] Fix the preference loading --- .../src/main/java/org/phoebus/pv/archive/Preferences.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 231b3d8aa1..3c0da79c2d 100644 --- 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 @@ -1,6 +1,5 @@ package org.phoebus.pv.archive; -import org.csstudio.trends.databrowser3.Activator; import org.phoebus.framework.preferences.AnnotatedPreferences; import org.phoebus.framework.preferences.Preference; import org.phoebus.framework.preferences.PreferencesReader; @@ -18,7 +17,7 @@ public class Preferences static { - final PreferencesReader prefs = AnnotatedPreferences.initialize(Activator.class, Preferences.class, "/appliance_datasource_preferences.properties"); + final PreferencesReader prefs = AnnotatedPreferences.initialize(Preferences.class, "/appliance_datasource_preferences.properties"); } } From 20b987f1089b8e2e38f503a460c6fddd3f7c3ccb Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Mon, 10 Jun 2024 09:22:48 +0200 Subject: [PATCH 20/59] Revert "CSSTUDIO-1987 Remove unused code." This reverts commit ffe810a8b56103ca4fb6eb9b9cf1e24fdc92a909. --- .../runtime/app/DockItemRepresentation.java | 20 +++++++++++++++++++ .../builder/runtime/script/ScriptUtil.java | 18 +++++++++++++++++ 2 files changed, 38 insertions(+) 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 From a633b75bc7ab35eaee34d37f3568e576bc0f0f9b Mon Sep 17 00:00:00 2001 From: kasemir Date: Wed, 12 Jun 2024 16:46:12 -0400 Subject: [PATCH 21/59] Implement In-Memory Preferences --- .../preferences/InMemoryPreferences.java | 111 ++++++++++++++++++ .../preferences/PhoebusPreferenceService.java | 4 +- 2 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java 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..7c600d5c73 --- /dev/null +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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.concurrent.ConcurrentHashMap; +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 ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + /** @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..d99e175dd5 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 @@ -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(); } } From 9c43b68372bba798be580d38cd7599d3085dd920 Mon Sep 17 00:00:00 2001 From: kasemir Date: Thu, 13 Jun 2024 09:44:40 -0400 Subject: [PATCH 22/59] R3emove unused file system preferences --- .../phoebus/framework/preferences/Base64.java | 261 ----- .../FilePreferencesXmlSupport.java | 426 -------- .../preferences/FileSystemPreferences.java | 964 ------------------ 3 files changed, 1651 deletions(-) delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/Base64.java delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/FilePreferencesXmlSupport.java delete mode 100644 core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java 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 From 57bcc055767e420474865c78526ea8a04e013acc Mon Sep 17 00:00:00 2001 From: kasemir Date: Thu, 13 Jun 2024 09:52:22 -0400 Subject: [PATCH 23/59] Documentation, cleanup --- .../preferences/PhoebusPreferenceService.java | 2 +- .../java/org/phoebus/ui/help/OpenAbout.java | 46 +------------------ docs/source/preferences.rst | 39 ++++++---------- 3 files changed, 17 insertions(+), 70 deletions(-) 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 d99e175dd5..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 * */ 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/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. From 7e174e795f5728fd945e7f2938a144fabc36ee77 Mon Sep 17 00:00:00 2001 From: kasemir Date: Thu, 13 Jun 2024 09:54:04 -0400 Subject: [PATCH 24/59] Documentation --- docs/source/locations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 4cc1642f0269ff07edcccd2807f55eaf962a2523 Mon Sep 17 00:00:00 2001 From: kasemir Date: Thu, 13 Jun 2024 10:01:14 -0400 Subject: [PATCH 25/59] Simplify locking --- .../framework/preferences/InMemoryPreferences.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 index 7c600d5c73..e0cea922f1 100644 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/InMemoryPreferences.java @@ -7,7 +7,8 @@ *******************************************************************************/ package org.phoebus.framework.preferences; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashMap; +import java.util.Map; import java.util.prefs.AbstractPreferences; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; @@ -21,8 +22,12 @@ class InMemoryPreferences extends AbstractPreferences private static final InMemoryPreferences prefs = new InMemoryPreferences(null, ""); /** Settings for this node in the preferences hierarchy */ - private ConcurrentHashMap cache = new ConcurrentHashMap<>(); + 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() { From 1deac1c16a192aca812bff5589f9406ff4632bc7 Mon Sep 17 00:00:00 2001 From: lcaouen Date: Fri, 14 Jun 2024 16:54:50 +0200 Subject: [PATCH 26/59] https://github.com/ControlSystemStudio/phoebus/issues/3047 Add Swagger-ui to phoebus-alarm-logger --- services/alarm-logger/pom.xml | 7 ++++ .../alarm/logging/rest/SearchController.java | 36 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) 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..68cba562b8 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,9 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo; import co.elastic.clients.elasticsearch.core.InfoResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.phoebus.alarm.logging.AlarmLoggingService; @@ -25,6 +28,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 +41,7 @@ */ @RestController @SuppressWarnings("unused") +@Tag(name = "Search controller") public class SearchController { static final Logger logger = Logger.getLogger(SearchController.class.getName()); @@ -44,6 +54,7 @@ public class SearchController { /** * @return Information about the alarm logging service */ + @Operation(summary = "Get Service Info") @GetMapping public String info() { @@ -74,22 +85,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") || From d66790be2e2370feb932d86ce7aa7664f2abef5b Mon Sep 17 00:00:00 2001 From: lcaouen Date: Fri, 14 Jun 2024 16:57:45 +0200 Subject: [PATCH 27/59] https://github.com/ControlSystemStudio/phoebus/issues/3047 Add Swagger-ui to phoebus-alarm-logger --- .../java/org/phoebus/alarm/logging/rest/SearchController.java | 2 -- 1 file changed, 2 deletions(-) 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 68cba562b8..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,8 +3,6 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo; import co.elastic.clients.elasticsearch.core.InfoResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; From 60614783d47c344798d4c5717b3242bf6cde6602 Mon Sep 17 00:00:00 2001 From: Conor Schofield Date: Fri, 14 Jun 2024 16:20:24 -0400 Subject: [PATCH 28/59] Adding Maven Action to fix github builds --- .github/workflows/build.yml | 7 ++++--- .github/workflows/build_latest.yml | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) 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 From 490cfc4999e641fc0fa57a2d4d9a7ce5d21f25ac Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 17 Jun 2024 11:40:29 -0400 Subject: [PATCH 29/59] Create a core utility class based on the google.guava String package --- .../java/org/phoebus/util/text/Strings.java | 20 ++++++++++ .../org/phoebus/util/text/StringsTest.java | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 core/util/src/main/java/org/phoebus/util/text/Strings.java create mode 100644 core/util/src/test/java/org/phoebus/util/text/StringsTest.java 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/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 From 3896ec59dc234dbae739bc9d27dc555f7f893c8b Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 17 Jun 2024 11:42:31 -0400 Subject: [PATCH 30/59] Refactor the use of google Strings with the Phoebus core-util version --- .../src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java | 2 -- 1 file changed, 2 deletions(-) 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..ebf34db574 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 @@ -11,8 +11,6 @@ import org.phoebus.util.time.TimeParser; -import com.google.common.base.Strings; - public class LogbookQueryUtil { // Ordered search keys From dee5a41528f704d5a722201b720e855956cc9e92 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 17 Jun 2024 11:42:41 -0400 Subject: [PATCH 31/59] Refactor the use of google Strings with the Phoebus core-util version --- .../src/main/java/org/phoebus/logbook/ui/LogbookQueryUtil.java | 1 + 1 file changed, 1 insertion(+) 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 ebf34db574..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,6 +9,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.phoebus.util.text.Strings; import org.phoebus.util.time.TimeParser; public class LogbookQueryUtil { From af4c161684b0144fc5c87d069fa3281694faf815 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 17 Jun 2024 11:47:04 -0400 Subject: [PATCH 32/59] [cont] Refactor use of google Strings with the Phoebus core-util version --- .../phoebus/logbook/olog/ui/AdvancedSearchViewController.java | 2 +- .../java/org/phoebus/logbook/olog/ui/LogEntryCalenderApp.java | 2 +- .../java/org/phoebus/logbook/olog/ui/LogEntryTableApp.java | 2 +- .../java/org/phoebus/logbook/olog/ui/LogbookQueryUtil.java | 3 +-- .../java/org/phoebus/logbook/olog/ui/SearchParameters.java | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) 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/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; From 8b53d7ca302e275442bd9172fb57e78a1bea2a70 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 17 Jun 2024 11:59:10 -0400 Subject: [PATCH 33/59] replacing the use of google guava with functionality from core java --- app/channel/channelfinder/pom.xml | 5 --- .../ChannelFinderClientImpl.java | 3 +- app/logbook/inmemory/pom.xml | 5 --- .../logbook/InMemoryLogClient.java | 37 +++++++++++++++---- app/logbook/olog/ui/pom.xml | 5 --- .../olog/ui/AttachmentsViewController.java | 3 +- app/logbook/ui/pom.xml | 5 --- 7 files changed, 33 insertions(+), 30 deletions(-) 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/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/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/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 From d2a382e316d34ed11ff4d1da599ce7712f622556 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 18 Jun 2024 10:19:07 -0400 Subject: [PATCH 34/59] Replacing use of guava ImmutableLists with Collections.unmodifiableList --- app/databrowser-json/pom.xml | 6 -- .../reader/json/internal/JsonVTypeReader.java | 64 +++++++++---------- dependencies/phoebus-target/pom.xml | 5 -- 3 files changed, 31 insertions(+), 44 deletions(-) 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/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index a545106292..9c9b495ce9 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 From 5b5ed1fd82079a9acac11d9699997cf40cff216d Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 18 Jun 2024 10:49:18 -0400 Subject: [PATCH 35/59] Fixing the unit test dependencies to guava --- .../reader/json/HttpServerTestBase.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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)); } /** From c4963ae8ec9a60ab5a75e16e127b386973424108 Mon Sep 17 00:00:00 2001 From: kasemir Date: Tue, 18 Jun 2024 15:15:14 -0400 Subject: [PATCH 36/59] Fix ant build broken by #3048 --- dependencies/phoebus-target/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml index a545106292..099ad4d1de 100644 --- a/dependencies/phoebus-target/pom.xml +++ b/dependencies/phoebus-target/pom.xml @@ -472,6 +472,11 @@ ${spring.boot-version} test + + org.springdoc + springdoc-openapi-ui + 1.7.0 + javax.validation From 01a260c9621bd81b15151619edae8a17a297c8b3 Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Wed, 19 Jun 2024 17:50:58 -0400 Subject: [PATCH 37/59] Add a 'methodCalled' event that can be fired with varargs for listeners to see the call stack at the time of firing --- .../builder/representation/ToolkitListener.java | 3 +++ .../representation/ToolkitRepresentation.java | 14 ++++++++++++++ .../runtime/app/DisplayRuntimeInstance.java | 2 ++ 3 files changed, 19 insertions(+) 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..2a0341e52b 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,7 @@ public interface ToolkitListener * @param value The value */ default public void handleWrite(Widget widget, Object value) {}; + + 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/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..a0887f0bd9 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 @@ -294,6 +294,8 @@ public void loadDisplayFile(final DisplayInfo info) // another instance dock_item.setInput(info.toURI()); + representation.fireMethodCall((Object)display_info); + // Now that old model is no longer represented, // show info. // Showing this info before disposeModel() From 84da2a3c45cd2f88890c9a411ab2fd690d679e39 Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Thu, 20 Jun 2024 16:13:23 -0400 Subject: [PATCH 38/59] Only fire event after successful display load, grabbing application thread's stack trace --- .../display/builder/runtime/app/DisplayRuntimeInstance.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 a0887f0bd9..0ce90879da 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 @@ -276,7 +276,7 @@ void close() } /** @return Current display info or null */ - DisplayInfo getDisplayInfo() + public DisplayInfo getDisplayInfo() { return display_info.orElse(null); } @@ -294,7 +294,7 @@ public void loadDisplayFile(final DisplayInfo info) // another instance dock_item.setInput(info.toURI()); - representation.fireMethodCall((Object)display_info); + StackTraceElement[] applicationThreadStackTrace = Thread.currentThread().getStackTrace(); // Now that old model is no longer represented, // show info. @@ -324,6 +324,7 @@ public void loadDisplayFile(final DisplayInfo info) { representation.awaitRepresentation(30, TimeUnit.SECONDS); representation_init.run(); + representation.fireMethodCall(info, applicationThreadStackTrace); logger.log(Level.FINE, "Done with representing model of " + info.getPath()); } catch (TimeoutException | InterruptedException ex) From 89c9591d219d2384cdcf19c57321b1f4d988e648 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 20 Jun 2024 16:43:51 -0400 Subject: [PATCH 39/59] Update the widget info to allow simpler selection of PV's --- .../javafx/WidgetInfoDialog.java | 90 +++++++++++++++---- .../types/TimeStampedProcessVariable.java | 3 + 2 files changed, 76 insertions(+), 17 deletions(-) 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/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 { From c76143d38ccf2b205ba473456695de79969a8a99 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Fri, 21 Jun 2024 13:04:58 -0400 Subject: [PATCH 40/59] Remove unused Executor --- .../org/phoebus/pv/archive/retrieve/ArchivePV.java | 10 ---------- 1 file changed, 10 deletions(-) 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 index 19e4a83c85..b10e9e4c5d 100644 --- 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 @@ -17,16 +17,6 @@ public class ArchivePV extends PV { ArchiveReaderService service = ArchiveReaderService.getService(); - /** - * Timer for archive updates - */ - private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, target -> - { - final Thread thread = new Thread(target, "ArchivePV"); - thread.setDaemon(true); - return thread; - }); - public ArchivePV(String name) { this(name, Instant.now()); } From 9416075617cb0c88def66766daa75776d3b8521d Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 24 Jun 2024 14:47:30 -0400 Subject: [PATCH 41/59] Use the complete name to ensure PVPool create proper references --- .../pv/archive/retrieve/ArchivePV.java | 19 ++++++++--- .../pv/archive/retrieve/ArchivePVFactory.java | 32 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) 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 index b10e9e4c5d..6540be32d7 100644 --- 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 @@ -17,12 +17,23 @@ public class ArchivePV extends PV { ArchiveReaderService service = ArchiveReaderService.getService(); - public ArchivePV(String name) { - this(name, Instant.now()); + /** + * + * @param completeName + * @param name + */ + public ArchivePV(String completeName, String name) { + this(completeName, name, Instant.now()); } - public ArchivePV(String name, Instant instant) { - super(name); + /** + * + * @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); 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 index 23ed1ca49b..d5083f1736 100644 --- 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 @@ -3,8 +3,12 @@ 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; @@ -46,17 +50,29 @@ public PV createPV(String name, String base_name) throws Exception { } if(parameters.isEmpty()) { - return new ArchivePV(pvName); + return new ArchivePV(name, pvName); } else { Instant time; - switch (parameters.length()) { - case 16 -> time = Instant.from(DATETIME_FORMAT.parse(parameters)); - case 19 -> time = Instant.from(SECONDS_FORMAT.parse(parameters)); - case 23 -> time = Instant.from(MILLI_FORMAT.parse(parameters)); - case 29 -> time = Instant.from(FULL_FORMAT.parse(parameters)); - default -> throw new Exception("Time value defined in a unknown formatt, '" + parameters + "'"); + 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 + "'"); } - return new ArchivePV(pvName, time); } } } From 999739d7b8c5376382114276713d9e71939db9c8 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 24 Jun 2024 14:47:59 -0400 Subject: [PATCH 42/59] Basic "replay" archive datasource --- app/trends/archive-datasource/pom.xml | 2 +- .../phoebus/pv/archive/replay/ReplayPV.java | 93 +++++++++++++++++++ .../pv/archive/replay/ReplayPVFactory.java | 64 +++++++++++++ .../services/org.phoebus.pv.PVFactory | 3 +- 4 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java diff --git a/app/trends/archive-datasource/pom.xml b/app/trends/archive-datasource/pom.xml index 6bda3f309e..988864edd9 100644 --- a/app/trends/archive-datasource/pom.xml +++ b/app/trends/archive-datasource/pom.xml @@ -18,7 +18,7 @@ org.phoebus - app-databrowser + app-trends-archive-reader 4.7.4-SNAPSHOT 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..a69298c5fd --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java @@ -0,0 +1,93 @@ +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) + { + 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(.01); + } + + /** 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) { + e.printStackTrace(); + close(); + } + } + + @Override + protected void close() + { + if (! 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..a979534525 --- /dev/null +++ b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPVFactory.java @@ -0,0 +1,64 @@ +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; + +/** + * 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()); + if (parameters.size() == 2) { + // start and end + + + } else if (parameters.size() == 3 ) { + // start, end, and rate + + } else { + throw new Exception("Incorrect number of parameters defined," + "'" + name + "'" + + " the replay datasource supports start, end, and optionally a rate parameter."); + + } + } + + return new ReplayPV(name, pvName, Instant.now(), Instant.now().minusSeconds(300)); + } +} 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 index 63ecd477bd..72db0eb20d 100644 --- 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 @@ -1 +1,2 @@ -org.phoebus.pv.archive.retrieve.ArchivePVFactory \ No newline at end of file +org.phoebus.pv.archive.retrieve.ArchivePVFactory +org.phoebus.pv.archive.replay.ReplayPVFactory \ No newline at end of file From 82f93bf7498feaeeee14d367be85ab8ef649d797 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 24 Jun 2024 16:48:24 -0400 Subject: [PATCH 43/59] Argument parsing for replay archive datasource --- .../phoebus/pv/archive/ArchiveReaderUtil.java | 40 +++++++++++++++++++ .../phoebus/pv/archive/replay/ReplayPV.java | 22 ++++++++-- .../pv/archive/replay/ReplayPVFactory.java | 18 +++++++-- 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/ArchiveReaderUtil.java 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/replay/ReplayPV.java b/app/trends/archive-datasource/src/main/java/org/phoebus/pv/archive/replay/ReplayPV.java index a69298c5fd..1ca5704d6a 100644 --- 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 @@ -34,13 +34,26 @@ public class ReplayPV extends PV 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); @@ -56,7 +69,7 @@ public ReplayPV(String completeName, final String name, Instant start, Instant e } catch (Exception e) { e.printStackTrace(); } - start(.01); + start(update); } /** Start periodic updates @@ -70,7 +83,8 @@ protected void start(final double update_seconds) } /** Called by periodic timer */ - protected void update() { + protected void update() + { try { if (i.hasNext()) { notifyListenersOfValue(i.next()); @@ -86,7 +100,7 @@ protected void update() { @Override protected void close() { - if (! task.cancel(false)) + 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 index a979534525..b436feb776 100644 --- 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 @@ -11,6 +11,8 @@ 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 @@ -45,20 +47,28 @@ public PV createPV(String name, String base_name) throws Exception { .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."); - } - } - return new ReplayPV(name, pvName, Instant.now(), Instant.now().minusSeconds(300)); + } } } From aeaf7a7fedbd2c89065ba5c4b6d83a5988687e5d Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Mon, 24 Jun 2024 16:52:59 -0400 Subject: [PATCH 44/59] Fix some formatting and logging ( more cleanup might be req ) --- .../phoebus/pv/archive/replay/ReplayPV.java | 2 +- .../pv/archive/replay/ReplayPVFactory.java | 6 ++++-- .../phoebus/pv/archive/retrieve/ArchivePV.java | 18 ++++++++++++------ .../pv/archive/retrieve/ArchivePVFactory.java | 17 ++++++++++++----- 4 files changed, 29 insertions(+), 14 deletions(-) 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 index 1ca5704d6a..c071f96667 100644 --- 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 @@ -92,7 +92,7 @@ protected void update() close(); } } catch (Exception e) { - e.printStackTrace(); + logger.log(Level.WARNING, "failed to update pv: " + getName() , e); 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 index b436feb776..df177b2fa9 100644 --- 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 @@ -23,12 +23,14 @@ public class ReplayPVFactory implements PVFactory final public static String TYPE = "replay"; @Override - public String getType() { + public String getType() + { return TYPE; } @Override - public PV createPV(String name, String base_name) throws Exception { + 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('('); 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 index 6540be32d7..32617fb8d3 100644 --- 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 @@ -7,6 +7,7 @@ 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 @@ -22,7 +23,8 @@ public class ArchivePV extends PV { * @param completeName * @param name */ - public ArchivePV(String completeName, String name) { + public ArchivePV(String completeName, String name) + { this(completeName, name, Instant.now()); } @@ -32,7 +34,8 @@ public ArchivePV(String completeName, String name) { * @param name * @param instant */ - public ArchivePV(String completeName, String name, Instant instant) { + public ArchivePV(String completeName, String name, Instant instant) + { super(completeName); try { ValueIterator i = service.getReader().getRawValues(name, instant, instant); @@ -41,21 +44,24 @@ public ArchivePV(String completeName, String name, Instant instant) { notifyListenersOfValue(i.next()); } } catch (Exception e) { - e.printStackTrace(); + logger.log(Level.WARNING, "failed to create pv: " + getName() , e); } } - public void disconnected() { + public void disconnected() + { notifyListenersOfDisconnect(); } @Override - protected void close() { + protected void close() + { super.close(); } @Override - public boolean isReadonly() { + 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 index d5083f1736..ea819e6278 100644 --- 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 @@ -26,7 +26,8 @@ public class ArchivePVFactory implements PVFactory final public static String TYPE = "archive"; @Override - public String getType() { + public String getType() + { return TYPE; } @@ -49,14 +50,18 @@ public PV createPV(String name, String base_name) throws Exception { parameters = base_name.substring(sep+1, end); } - if(parameters.isEmpty()) { + if(parameters.isEmpty()) + { return new ArchivePV(name, pvName); - } else { + } + else + { Instant time; List parameterList = Arrays.stream(parameters.split(",")) .map(String::strip) .collect(Collectors.toList()); - if(parameterList.size() == 1) { + if(parameterList.size() == 1) + { String timeParameter = parameterList.get(0); try { switch (parameterList.get(0).length()) { @@ -70,7 +75,9 @@ public PV createPV(String name, String base_name) throws Exception { } catch (DateTimeException e) { throw new Exception("Time value defined in a unknown format, '" + timeParameter + "'"); } - } else { + } + else + { throw new Exception("Incorrect number of parameters defined '" + name + "'"); } } From 14bd26c8c1545fc6fd412f65f68a2b833145ee4e Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Tue, 25 Jun 2024 11:32:08 -0400 Subject: [PATCH 45/59] Adding socumentation for the archive datasources --- app/trends/archive-datasource/doc/index.rst | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/app/trends/archive-datasource/doc/index.rst b/app/trends/archive-datasource/doc/index.rst index 3e11c6ac3b..5d63610b6b 100644 --- a/app/trends/archive-datasource/doc/index.rst +++ b/app/trends/archive-datasource/doc/index.rst @@ -1,21 +1,29 @@ -Archive Datasource -================== +Archive Datasources +=================== Overview -------- -The archive datasource allows accessing historical data as a pv +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. -PV syntax ---------- +Archive PV Syntax +----------------- -The standard prefix for the datasource is ``archive://`` which can be omitted if configured as the default datasource. -The archiver PV's are readonly and constants. +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 +- `archive://pv_name`: Retrieves the latest value in the archiver. +- `archive://pv_name(time)`: Retrieves the last value at or before the specified "time". -Retrieves the latest value in the archiver +Replay PV Syntax +---------------- -archive://pv_name(time) +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). -Retrieves the last value at or before the "time" From e6db4fd62ab76522eb3b0c4de1f01cdf6a3a4248 Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Wed, 26 Jun 2024 11:23:52 +0200 Subject: [PATCH 46/59] Drill down to alarm sub tree displays from alarm area panel Add query parameter to alarm URI. --- .../applications/alarm/ui/AlarmURI.java | 39 ++++- .../alarm/ui/area/AlarmAreaView.java | 34 ++-- .../alarm/ui/area/OpenTreeViewAction.java | 6 +- .../alarm/ui/tree/AlarmTreeInstance.java | 19 ++- .../alarm/ui/tree/AlarmTreeView.java | 17 +- .../applications/alarm/AlarmURITest.java | 146 ++++++++++++++++-- 6 files changed, 221 insertions(+), 40 deletions(-) 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..bffe9557a5 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 @@ -23,6 +23,9 @@ public class AlarmURI /** URI schema used to refer to an alarm config */ public static final String SCHEMA = "alarm"; + public static final String DELIMITER_QUERY_PARAMETERS = "&"; + public static final String DELIMITER_QUERY_PARAMETER_VALUE = "="; + /** @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" @@ -32,9 +35,18 @@ public static URI createURI(final String server, final String config_name) return URI.create(SCHEMA + "://" + server + "/" + config_name); } + /** @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" + */ + public static URI createURI(final String server, final String config_name, String rawQuery) { + return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + rawQuery); + } + /** 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 +67,31 @@ 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 }; } + + /** + * Extract raw query parameter value for given parameter. + * @param resource "alarm://localhost:9092/Accelerator" or "alarm://localhost:9092/Accelerator?param=value" + * @param queryParameter name of query parameter for which to extract value + * @return parameter value, null or "value" for examples above + */ + public static String getRawQueryParameterValue(URI resource, String queryParameter) { + String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split(AlarmURI.DELIMITER_QUERY_PARAMETERS) : null; + if (queryParametersValues != null) { + for (String queryParameterValue : queryParametersValues) { + if (queryParameterValue.startsWith(queryParameter)) { + String[] parameterValue = queryParameterValue.split(AlarmURI.DELIMITER_QUERY_PARAMETER_VALUE); + return parameterValue != null && parameterValue.length == 2 ? parameterValue[1] : null; + } + } + } + return null; + } + } 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..32b5f96b92 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; @@ -35,12 +37,10 @@ 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 +104,6 @@ public AlarmAreaView(final AlarmClient model) areaFilter = new AreaFilter(AlarmSystem.alarm_area_level); model.addListener(this); - - createContextMenu(); } // AlarmClientModelListener @@ -205,6 +203,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, "itemName=" + 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 +256,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..c0d7440937 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 @@ -26,11 +26,12 @@ public class OpenTreeViewAction extends MenuItem /** * Constructor * @param alarmConfigName The alarm configuration name + * @param alarmRawQuery raw query for alarm (null if no such information is available) */ - public OpenTreeViewAction(String alarmConfigName) + 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 +46,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..a4e7a71720 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; @@ -32,6 +34,8 @@ @SuppressWarnings("nls") class AlarmTreeInstance implements AppInstance { + public static final String ITEM_NAME = "itemName"; + private final AlarmTreeApplication app; private String server = null, config_name = null; @@ -46,7 +50,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.getRawQueryParameterValue(input, 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 +74,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 +87,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 +114,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..db1a7f9480 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,10 @@ 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); + } + public AlarmTreeView(final AlarmClient model, String itemName) { if (model.isRunning()) throw new IllegalStateException(); @@ -159,6 +163,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 +208,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 (String.valueOf(treeItem.getValue()).startsWith(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..1238717cef 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,8 @@ import org.phoebus.applications.alarm.ui.AlarmURI; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -21,39 +23,151 @@ * @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 getRawQueryParameterValue() throws Exception { + // create with URI.create + // with / without default port + String value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://localhost:9092/Accelerator"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=value"), "param"); + assertThat(value, equalTo("value")); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param"); + assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param"); + assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param2"); + assertThat(value, equalTo("value")); + + // create with AlarmURI.createURI + // with / without default port + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("localhost:9092", "Accelerator"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param"), "param"); + assertThat(value, equalTo(null)); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=value"), "param"); + assertThat(value, equalTo("value")); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param"); + assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param"); + assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + + value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param2"); + assertThat(value, equalTo("value")); + } + } From 4e6419ff34ef7eea9fdd4f9f99879ee38d482e34 Mon Sep 17 00:00:00 2001 From: Kunal Shroff Date: Thu, 27 Jun 2024 10:37:48 -0400 Subject: [PATCH 47/59] Add the archive readers to the common product --- phoebus-product/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 24b256a3d6..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 From 1e729ed567830b3327198245b52dc5825b0245a2 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Fri, 28 Jun 2024 12:08:41 +0200 Subject: [PATCH 48/59] CSSTUDIO-2472 Bugfix: Fix freezes of Display Runtime occurring when rapidly changing between embedded displays. --- .../EmbeddedDisplayRepresentation.java | 136 +++++++++--------- .../javafx/widgets/JFXBaseRepresentation.java | 1 - .../widgets/plots/XYPlotRepresentation.java | 1 - .../representation/WidgetRepresentation.java | 4 - 4 files changed, 65 insertions(+), 77 deletions(-) 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..1cd4e049e8 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; @@ -256,7 +257,7 @@ private void fileChanged(final WidgetProperty property, final Object old_valu /** Update to the next pending display * - *

Synchronized to serialize the background threads. + *

Executed on the JavaFX Application Thread to serialize the background threads. * *

Example: Displays A, B, C are requested in quick succession. * @@ -270,65 +271,56 @@ private void fileChanged(final WidgetProperty property, final Object old_valu * As thread C finally continues, it finds pending_display_and_group empty. * --> Showing A, then C, skipping B. */ - private synchronized void updatePendingDisplay(final JobMonitor monitor) - { - try - { - final DisplayAndGroup handle = pending_display_and_group.getAndSet(null); - if (handle == null) - { - // System.out.println("Nothing to handle"); - return; - } - if (inner == null) - { - // System.out.println("Aborted: " + handle); - return; - } + private void updatePendingDisplay(final JobMonitor monitor) { + Platform.runLater(() -> { + try { + final DisplayAndGroup handle = pending_display_and_group.getAndSet(null); + if (handle == null) { + // System.out.println("Nothing to handle"); + return; + } + if (inner == null) { + // System.out.println("Aborted: " + handle); + return; + } - monitor.beginTask("Load " + handle); - try - { // Load new model (potentially slow) - final DisplayModel new_model = loadDisplayModel(model_widget, handle); + monitor.beginTask("Load " + handle); + try { // Load new model (potentially slow) + final DisplayModel new_model = loadDisplayModel(model_widget, handle); - // Stop (old) runtime - // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime - model_widget.runtimePropEmbeddedModel().setValue(null); + // Stop (old) runtime + // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime + model_widget.runtimePropEmbeddedModel().setValue(null); - // Atomically update the 'active' model - final DisplayModel old_model = active_content_model.getAndSet(new_model); - new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener); + // Atomically update the 'active' model + final DisplayModel old_model = active_content_model.getAndSet(new_model); + new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener); - if (old_model != null) - { // Dispose old model + if (old_model != null) { // Dispose old model + final Future completion = toolkit.submit(() -> + { + toolkit.disposeRepresentation(old_model); + return null; + }); + checkCompletion(model_widget, completion, "timeout disposing old representation"); + } + // Represent new model on UI thread + toolkit.onRepresentationStarted(); final Future completion = toolkit.submit(() -> { - toolkit.disposeRepresentation(old_model); + representContent(new_model); return null; }); - checkCompletion(model_widget, completion, "timeout disposing old representation"); + checkCompletion(model_widget, completion, "timeout representing new content"); + // Allow EmbeddedWidgetRuntime to start the new runtime + model_widget.runtimePropEmbeddedModel().setValue(new_model); + } catch (Exception ex) { + logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex); } - // Represent new model on UI thread - toolkit.onRepresentationStarted(); - final Future completion = toolkit.submit(() -> - { - representContent(new_model); - return null; - }); - checkCompletion(model_widget, completion, "timeout representing new content"); - - // Allow EmbeddedWidgetRuntime to start the new runtime - model_widget.runtimePropEmbeddedModel().setValue(new_model); + } finally { + toolkit.onRepresentationFinished(); } - catch (Exception ex) - { - logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex); - } - } - finally - { - toolkit.onRepresentationFinished(); - } + }); } /** @param content_model Model to represent */ @@ -471,26 +463,28 @@ 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(); + public void 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/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; } } From 61aa0ceed4c8f04fba201f18a18d4c00d752c7c1 Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 28 Jun 2024 10:32:52 -0400 Subject: [PATCH 49/59] Fix ant --- app/databrowser-timescale/build.xml | 1 + app/databrowser/build.xml | 1 + app/trends/archive-reader/build.xml | 2 +- build.xml | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) 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/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/trends/archive-reader/build.xml b/app/trends/archive-reader/build.xml index 4ea1950c56..d086b50cb0 100644 --- a/app/trends/archive-reader/build.xml +++ b/app/trends/archive-reader/build.xml @@ -1,4 +1,4 @@ - + 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 @@ + From 53ebe6bc2c3bb18643e24296ae9660fccf7382b2 Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 28 Jun 2024 10:56:50 -0400 Subject: [PATCH 50/59] Fix eclipse --- app/databrowser-timescale/.classpath | 1 + app/databrowser/.classpath | 1 + app/display/representation-javafx/.classpath | 1 + app/trends/archive-reader/.classpath | 14 ++++++++++++++ app/trends/archive-reader/.project | 17 +++++++++++++++++ dependencies/phoebus-target/.classpath | 12 ++++++------ 6 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 app/trends/archive-reader/.classpath create mode 100644 app/trends/archive-reader/.project 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/.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/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/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/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 @@ - + From e4db60bb8ac54310ec5f48b394e9220ba7620508 Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 28 Jun 2024 11:03:59 -0400 Subject: [PATCH 51/59] Eclipse product: Add archive reader --- phoebus-product/.classpath | 1 + 1 file changed, 1 insertion(+) 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 @@ + From aa59a78ad88c0e6e8df9f7d015c0c11ababa489b Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 28 Jun 2024 12:56:07 -0400 Subject: [PATCH 52/59] RDB Archive: Correct writing of 'enum' metadata `Display.displayOf(VEnum)` or `..(VString)` returns a non-`null` display, which prevented the writing of enum metadata. Now VEnum is detected and its labels are written. --- services/archive-engine/.classpath | 2 + .../archive/writer/rdb/RDBArchiveWriter.java | 54 ++++++++++++------- 2 files changed, 37 insertions(+), 19 deletions(-) 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..157c6e02c2 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: Enum, numeric, string. + // // 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 // 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) From c3a53dd5529d1938c2acb730034dd218e61cf035 Mon Sep 17 00:00:00 2001 From: kasemir Date: Fri, 28 Jun 2024 13:21:49 -0400 Subject: [PATCH 53/59] typo --- .../org/csstudio/archive/writer/rdb/RDBArchiveWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 157c6e02c2..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 @@ -198,10 +198,10 @@ public void addSample(final WriteChannel channel, final VType sample) throws Exc */ private void writeMetaData(final RDBWriteChannel channel, final VType sample) throws Exception { - // Three cases: Enum, numeric, string. + // 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. From d2f099eaf4465783008453052f10dc68dc553ec8 Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 2 Jul 2024 11:26:44 +0200 Subject: [PATCH 54/59] Refactor handling of parameter names and values for raw query of alarm URI --- .../applications/alarm/ui/AlarmURI.java | 42 ++++++++----- .../alarm/ui/area/AlarmAreaView.java | 3 +- .../alarm/ui/area/OpenTreeViewAction.java | 14 +++-- .../alarm/ui/tree/AlarmTreeInstance.java | 4 +- .../alarm/ui/tree/AlarmTreeView.java | 7 ++- .../applications/alarm/AlarmURITest.java | 61 +++++++++---------- 6 files changed, 75 insertions(+), 56 deletions(-) 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 bffe9557a5..5fede08730 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 @@ -7,7 +7,11 @@ *******************************************************************************/ package org.phoebus.applications.alarm.ui; +import com.google.common.collect.ImmutableMap; + import java.net.URI; +import java.util.HashMap; +import java.util.Map; /** Alarm URI helpers * @@ -23,25 +27,27 @@ public class AlarmURI /** URI schema used to refer to an alarm config */ public static final String SCHEMA = "alarm"; - public static final String DELIMITER_QUERY_PARAMETERS = "&"; - public static final String DELIMITER_QUERY_PARAMETER_VALUE = "="; + /** 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" + * @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) { - return URI.create(SCHEMA + "://" + server + "/" + config_name + "?" + 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 @@ -76,22 +82,26 @@ public static String[] parseAlarmURI(final URI resource) throws Exception } /** - * Extract raw query parameter value for given parameter. + * 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" - * @param queryParameter name of query parameter for which to extract value - * @return parameter value, null or "value" for examples above + * @return map with parameter names as keys and parameter values as values for raw query */ - public static String getRawQueryParameterValue(URI resource, String queryParameter) { - String[] queryParametersValues = resource.getRawQuery() != null ? resource.getRawQuery().split(AlarmURI.DELIMITER_QUERY_PARAMETERS) : null; + 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) { - if (queryParameterValue.startsWith(queryParameter)) { - String[] parameterValue = queryParameterValue.split(AlarmURI.DELIMITER_QUERY_PARAMETER_VALUE); - return parameterValue != null && parameterValue.length == 2 ? parameterValue[1] : null; + 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 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 32b5f96b92..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 @@ -33,6 +33,7 @@ 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; @@ -215,7 +216,7 @@ private void recreateItems(final List items) } } - OpenTreeViewAction otva = new OpenTreeViewAction(alarmConfigName, "itemName=" + URLEncoder.encode(item_name, StandardCharsets.UTF_8)); + 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()); }); 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 c0d7440937..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,16 +20,22 @@ * @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) { + 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) - { + public OpenTreeViewAction(String alarmConfigName, String alarmRawQuery) { final AlarmTreeMenuEntry entry = new AlarmTreeMenuEntry(); entry.setResource(AlarmURI.createURI(AlarmSystem.server, alarmConfigName, alarmRawQuery)); setText(entry.getName()); 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 a4e7a71720..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 @@ -34,8 +34,6 @@ @SuppressWarnings("nls") class AlarmTreeInstance implements AppInstance { - public static final String ITEM_NAME = "itemName"; - private final AlarmTreeApplication app; private String server = null, config_name = null; @@ -52,7 +50,7 @@ public AlarmTreeInstance(final AlarmTreeApplication app, final URI input) throws // 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.getRawQueryParameterValue(input, ITEM_NAME); + 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); 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 db1a7f9480..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 @@ -154,6 +154,11 @@ public class AlarmTreeView extends BorderPane implements AlarmClientListener 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()) @@ -211,7 +216,7 @@ private void startup() // expand tree item if is matches item name if (tree_view.getRoot() != null && itemName != null) { for (TreeItem treeItem : tree_view.getRoot().getChildren()) { - if (String.valueOf(treeItem.getValue()).startsWith(itemName)) { + if (((AlarmTreeItem) treeItem.getValue()).getName().equals(itemName)) { expandAlarms(treeItem); break; } 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 1238717cef..0bdd4618eb 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 @@ -7,12 +7,14 @@ *******************************************************************************/ package org.phoebus.applications.alarm; +import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; 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; @@ -122,52 +124,49 @@ public void parseAlarmURI() throws Exception { } @Test - public void getRawQueryParameterValue() throws Exception { + public void getRawQueryParametersValues() throws Exception { // create with URI.create // with / without default port - String value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://localhost:9092/Accelerator"), "param"); - assertThat(value, equalTo(null)); + //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)); - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test"), "param"); - assertThat(value, equalTo(null)); + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test")); + assertThat(map.get("param"), equalTo(null)); - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param"), "param"); - assertThat(value, equalTo(null)); + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param")); + assertThat(map.get("param"), equalTo(null)); - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=value"), "param"); - assertThat(value, equalTo("value")); + map = AlarmURI.getRawQueryParametersValues(URI.create("alarm://host.my.site/Test?param=value")); + assertThat(map.get("param"), equalTo("value")); - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param"); - assertThat(value, 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))); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param"); - assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); - - value = AlarmURI.getRawQueryParameterValue(URI.create("alarm://host.my.site/Test?param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param2"); - assertThat(value, equalTo("value")); + 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 - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("localhost:9092", "Accelerator"), "param"); - assertThat(value, equalTo(null)); - - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test"), "param"); - assertThat(value, equalTo(null)); + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("localhost:9092", "Accelerator")); + assertThat(map.get("param"), equalTo(null)); - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param"), "param"); - assertThat(value, equalTo(null)); + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test")); + assertThat(map.get("param"), equalTo(null)); - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=value"), "param"); - assertThat(value, equalTo("value")); + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param")); + assertThat(map.get("param"), equalTo(null)); - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8)), "param"); - assertThat(value, equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); + map = AlarmURI.getRawQueryParametersValues(AlarmURI.createURI("host.my.site", "Test", "param=value")); + assertThat(map.get("param"), equalTo("value")); - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param"); - assertThat(value, 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))); + assertThat(map.get("param"), equalTo(URLEncoder.encode("abc def", StandardCharsets.UTF_8))); - value = AlarmURI.getRawQueryParameterValue(AlarmURI.createURI("host.my.site", "Test", "param=" + URLEncoder.encode("abc def", StandardCharsets.UTF_8) + "¶m2=value"), "param2"); - assertThat(value, equalTo("value")); + 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")); } } From 73a1c1c27409650aac5d793dd0e17370e7711858 Mon Sep 17 00:00:00 2001 From: Lars Johansson Date: Tue, 2 Jul 2024 17:10:23 +0200 Subject: [PATCH 55/59] Removed import statements --- .../main/java/org/phoebus/applications/alarm/ui/AlarmURI.java | 2 -- .../test/java/org/phoebus/applications/alarm/AlarmURITest.java | 1 - 2 files changed, 3 deletions(-) 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 5fede08730..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 @@ -7,8 +7,6 @@ *******************************************************************************/ package org.phoebus.applications.alarm.ui; -import com.google.common.collect.ImmutableMap; - import java.net.URI; import java.util.HashMap; import java.util.Map; 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 0bdd4618eb..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 @@ -7,7 +7,6 @@ *******************************************************************************/ package org.phoebus.applications.alarm; -import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; import org.phoebus.applications.alarm.ui.AlarmURI; From e432998913ebc555eeeafcad1d6aba213f15f481 Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Wed, 3 Jul 2024 10:40:44 -0400 Subject: [PATCH 56/59] core changes required for UX Analytics plugin Docstring, remove unused imports re-add imports which were necessary at compile time --- .../display/builder/representation/ToolkitListener.java | 5 +++++ .../builder/runtime/app/DisplayRuntimeInstance.java | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) 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 2a0341e52b..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 @@ -45,6 +45,11 @@ public interface ToolkitListener */ 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/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 0ce90879da..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; @@ -286,9 +287,12 @@ public 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 @@ -324,7 +328,9 @@ public void loadDisplayFile(final DisplayInfo info) { representation.awaitRepresentation(30, TimeUnit.SECONDS); representation_init.run(); - representation.fireMethodCall(info, applicationThreadStackTrace); + 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) From 86b178605375bf649f4d4673afbeeb7625a136b2 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 4 Jul 2024 11:15:18 +0200 Subject: [PATCH 57/59] CSSTUDIO-2472 Revert changes to "EmbeddedDisplayRepresentation". --- .../EmbeddedDisplayRepresentation.java | 136 +++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) 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 1cd4e049e8..e6b032542d 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,7 +15,6 @@ 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; @@ -257,7 +256,7 @@ private void fileChanged(final WidgetProperty property, final Object old_valu /** Update to the next pending display * - *

Executed on the JavaFX Application Thread to serialize the background threads. + *

Synchronized to serialize the background threads. * *

Example: Displays A, B, C are requested in quick succession. * @@ -271,56 +270,65 @@ private void fileChanged(final WidgetProperty property, final Object old_valu * As thread C finally continues, it finds pending_display_and_group empty. * --> Showing A, then C, skipping B. */ - private void updatePendingDisplay(final JobMonitor monitor) { - Platform.runLater(() -> { - try { - final DisplayAndGroup handle = pending_display_and_group.getAndSet(null); - if (handle == null) { - // System.out.println("Nothing to handle"); - return; - } - if (inner == null) { - // System.out.println("Aborted: " + handle); - return; - } + private synchronized void updatePendingDisplay(final JobMonitor monitor) + { + try + { + final DisplayAndGroup handle = pending_display_and_group.getAndSet(null); + if (handle == null) + { + // System.out.println("Nothing to handle"); + return; + } + if (inner == null) + { + // System.out.println("Aborted: " + handle); + return; + } - monitor.beginTask("Load " + handle); - try { // Load new model (potentially slow) - final DisplayModel new_model = loadDisplayModel(model_widget, handle); + monitor.beginTask("Load " + handle); + try + { // Load new model (potentially slow) + final DisplayModel new_model = loadDisplayModel(model_widget, handle); - // Stop (old) runtime - // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime - model_widget.runtimePropEmbeddedModel().setValue(null); + // Stop (old) runtime + // EmbeddedWidgetRuntime tracks this property to start/stop the embedded model's runtime + model_widget.runtimePropEmbeddedModel().setValue(null); - // Atomically update the 'active' model - final DisplayModel old_model = active_content_model.getAndSet(new_model); - new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener); + // Atomically update the 'active' model + final DisplayModel old_model = active_content_model.getAndSet(new_model); + new_model.propBackgroundColor().addUntypedPropertyListener(backgroundChangedListener); - if (old_model != null) { // Dispose old model - final Future completion = toolkit.submit(() -> - { - toolkit.disposeRepresentation(old_model); - return null; - }); - checkCompletion(model_widget, completion, "timeout disposing old representation"); - } - // Represent new model on UI thread - toolkit.onRepresentationStarted(); + if (old_model != null) + { // Dispose old model final Future completion = toolkit.submit(() -> { - representContent(new_model); + toolkit.disposeRepresentation(old_model); return null; }); - checkCompletion(model_widget, completion, "timeout representing new content"); - // Allow EmbeddedWidgetRuntime to start the new runtime - model_widget.runtimePropEmbeddedModel().setValue(new_model); - } catch (Exception ex) { - logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex); + checkCompletion(model_widget, completion, "timeout disposing old representation"); } - } finally { - toolkit.onRepresentationFinished(); + // Represent new model on UI thread + toolkit.onRepresentationStarted(); + final Future completion = toolkit.submit(() -> + { + representContent(new_model); + return null; + }); + checkCompletion(model_widget, completion, "timeout representing new content"); + + // Allow EmbeddedWidgetRuntime to start the new runtime + model_widget.runtimePropEmbeddedModel().setValue(new_model); } - }); + catch (Exception ex) + { + logger.log(Level.WARNING, "Failed to handle embedded display " + handle, ex); + } + } + finally + { + toolkit.onRepresentationFinished(); + } } /** @param content_model Model to represent */ @@ -463,28 +471,26 @@ else if (inner.getHeight() != 0.0 && inner.getWidth() != 0.0) } @Override - public void 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(); - }); + 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(); } } From 27cc9d27859aca6bb3b27a3b7cee142e7e8e56aa Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Thu, 4 Jul 2024 11:16:58 +0200 Subject: [PATCH 58/59] CSSTUDIO-2472 Run dispose() on the UI-thread. --- .../EmbeddedDisplayRepresentation.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) 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(); + }); } } From a0423e6189133e3601879969f38c6f8699ac1fd0 Mon Sep 17 00:00:00 2001 From: Abraham Wolk Date: Mon, 8 Jul 2024 14:39:07 +0200 Subject: [PATCH 59/59] CSSTUDIO-2460 Add a finalizer that deletes the temporary file containing an attachment when the corresponding 'fileAttachment' is garbage collected. --- .../logbook/olog/ui/SingleLogEntryDisplayController.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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());