diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/AdminUIModule.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/AdminUIModule.java index a307ea801..506bf9370 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/AdminUIModule.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/AdminUIModule.java @@ -40,6 +40,7 @@ import org.sensorhub.api.sensor.SensorConfig; import org.sensorhub.impl.database.system.SystemDriverDatabaseConfig; import org.sensorhub.impl.datastore.view.ObsSystemDatabaseViewConfig; +import org.sensorhub.impl.processing.SMLProcessConfig; import org.sensorhub.impl.security.BasicSecurityRealmConfig; import org.sensorhub.impl.service.AbstractHttpServiceModule; import org.sensorhub.impl.service.HttpServer; @@ -101,7 +102,8 @@ public void setConfiguration(AdminUIConfig config) customForms.put(CommandStreamFilter.class.getCanonicalName(), DatabaseFilterConfigForm.class); customForms.put(ObsFilter.class.getCanonicalName(), DatabaseFilterConfigForm.class); customForms.put(CommandFilter.class.getCanonicalName(), DatabaseFilterConfigForm.class); - + + // custom form builders defined in config for (CustomUIConfig customForm: config.customForms) { diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessAdminPanel.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessAdminPanel.java index 32136e95a..6e3761f15 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessAdminPanel.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessAdminPanel.java @@ -14,45 +14,45 @@ package org.sensorhub.ui; -import net.opengis.sensorml.v20.AbstractProcess; -import net.opengis.sensorml.v20.AggregateProcess; +import com.vaadin.event.Action; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.*; +import com.vaadin.v7.data.Item; +import com.vaadin.v7.ui.ComboBox; +import com.vaadin.v7.ui.TreeTable; +import com.vaadin.v7.ui.VerticalLayout; +import net.opengis.OgcProperty; +import net.opengis.OgcPropertyList; +import net.opengis.gml.v32.impl.CodeWithAuthorityImpl; +import net.opengis.sensorml.v20.*; import net.opengis.sensorml.v20.Link; -import net.opengis.sensorml.v20.Settings; -import net.opengis.sensorml.v20.SimpleProcess; +import net.opengis.sensorml.v20.impl.SettingsImpl; +import net.opengis.sensorml.v20.impl.ValueSettingImpl; +import net.opengis.swe.v20.AbstractSWEIdentifiable; import net.opengis.swe.v20.DataComponent; -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.OutputStream; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + import org.sensorhub.api.command.IStreamingControlInterface; -import org.sensorhub.api.data.IDataProducer; -import org.sensorhub.api.module.IModule; import org.sensorhub.api.module.ModuleConfig; import org.sensorhub.api.processing.IProcessModule; -import org.sensorhub.api.processing.ProcessingException; +import org.sensorhub.impl.processing.CommandStreamSink; +import org.sensorhub.impl.processing.DataStreamSource; import org.sensorhub.impl.processing.SMLProcessConfig; import org.sensorhub.impl.processing.SMLProcessImpl; -import org.sensorhub.impl.processing.StreamDataSource; -import org.sensorhub.ui.ModuleInstanceSelectionPopup.ModuleInstanceSelectionCallback; -import org.sensorhub.ui.ProcessFlowDiagram.Connection; -import org.sensorhub.ui.ProcessFlowDiagram.ProcessBlock; -import org.sensorhub.ui.ProcessFlowDiagram.ProcessFlowState; import org.sensorhub.ui.ProcessSelectionPopup.ProcessSelectionCallback; import org.sensorhub.ui.data.MyBeanItem; import org.sensorhub.utils.FileUtils; import org.vast.data.DataRecordImpl; import org.vast.process.ProcessInfo; -import org.vast.sensorML.SMLHelper; +import org.vast.sensorML.AggregateProcessImpl; +import org.vast.sensorML.LinkImpl; import org.vast.sensorML.SMLUtils; import org.vast.swe.SWEHelper; -import com.vaadin.ui.Alignment; -import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Button.ClickListener; -import com.vaadin.ui.Component; -import com.vaadin.ui.HorizontalLayout; -import com.vaadin.ui.Label; -import com.vaadin.ui.Layout; -import com.vaadin.ui.Panel; /** @@ -65,13 +65,31 @@ * @author Alex Robin * @since 1.0 */ -@SuppressWarnings("serial") +@SuppressWarnings({"serial", "deprecation"}) public class ProcessAdminPanel extends DataSourceAdminPanel> { - Panel inputCommandsPanel, paramCommandsPanel, processFlowPanel; - ProcessFlowDiagram diagram; + Panel inputCommandsPanel, paramCommandsPanel; SMLProcessConfig config; - + MyBeanItem configBean = null; + com.vaadin.v7.ui.TextField smlPathTextField = null; + TreeTable processTable; + AggregateProcess aggregateProcess; + + private static final String PROP_INPUTS = "Inputs"; + private static final String PROP_OUTPUTS = "Outputs"; + private static final String PROP_PARAMS = "Parameters"; + private static final String PROP_COMPS = "Components"; + private static final String PROP_CONNS = "Connections"; + private static final String PROP_NAME = "Name"; + private static final String PROP_VALUE = "Value"; + + private static final Action ADD_COMPONENT_ACTION = new Action("Add Component"); + private static final Action ADD_IO_ACTION = new Action("Add Input/Output"); + private static final Action ADD_PARAMETER_ACTION = new Action("Add Parameter"); + private static final Action ADD_CONNECTION_ACTION = new Action("Add Connection"); + private static final Action CONFIGURE_PARAMS_ACTION = new Action("Configure Parameters"); + private static final Action EDIT_CONNECTION_ACTION = new Action("Edit Connection"); + private static final Action DELETE_ACTION = new Action("Delete"); @Override public void build(final MyBeanItem beanItem, final IProcessModule module) @@ -119,8 +137,29 @@ public void build(final MyBeanItem beanItem, final IProcessModule< // process flow section if (module instanceof SMLProcessImpl) { - //addProcessFlowEditor((SMLProcessImpl)module); + addProcessEditor(); this.config = (SMLProcessConfig)beanItem.getBean(); + this.configBean = beanItem; + + var tabs = this.configTabs.getComponentIterator(); + GenericConfigForm configForm = null; + while (tabs.hasNext()) { + var tab = tabs.next(); + if (tab instanceof GenericConfigForm) { + configForm = (GenericConfigForm) tab; + break; + } + } + + if (configForm != null) { + // SensorML path is the last text box in this config form + this.smlPathTextField = (com.vaadin.v7.ui.TextField) configForm.textBoxes.get(configForm.textBoxes.size()-1); + } + var process = getProcessChainFromFile(); + if (process == null) + process = new AggregateProcessImpl(); + process.setUniqueIdentifier(config.id); + this.aggregateProcess = process; } } @@ -176,164 +215,672 @@ protected void buildParamInputsPanels(IProcessModule module) } - protected void addProcessFlowEditor(final SMLProcessImpl module) + protected void addProcessEditor() { HorizontalLayout buttonBar = new HorizontalLayout(); buttonBar.setSpacing(true); - addComponent(buttonBar); - - // add data source button - Button addDatasrcBtn = new Button("Datasource", ADD_ICON); - addDatasrcBtn.addStyleName(STYLE_SMALL); - buttonBar.addComponent(addDatasrcBtn); - addDatasrcBtn.addClickListener(new ClickListener() { - @Override - public void buttonClick(ClickEvent event) - { - // show popup to select among available module types - final ModuleInstanceSelectionCallback callback = new ModuleInstanceSelectionCallback() { - @Override - @SuppressWarnings("rawtypes") - public void onSelected(IModule module) throws ProcessingException - { - diagram.addNewDataSource((IDataProducer)module); - } - }; - - // popup the list so the user can select what he wants - ModuleInstanceSelectionPopup popup = new ModuleInstanceSelectionPopup(IDataProducer.class, callback); - popup.setModal(true); - getUI().addWindow(popup); - } - }); - + addComponent(buttonBar); + // add process button - Button addProcessBtn = new Button("Process", ADD_ICON); + Button addProcessBtn = new Button("Save Process", FontAwesome.SAVE); addProcessBtn.addStyleName(STYLE_SMALL); buttonBar.addComponent(addProcessBtn); - addProcessBtn.addClickListener(new ClickListener() { + addProcessBtn.addClickListener( event -> { + saveProcessChain(); + }); + + Button addLoadProcessBtn = new Button("Load Process", FontAwesome.UPLOAD); + addLoadProcessBtn.addStyleName(STYLE_SMALL); + buttonBar.addComponent(addLoadProcessBtn); + addLoadProcessBtn.addClickListener( event -> { + var aggy = getProcessChainFromFile(); + if (aggy == null) + getOshLogger().error("Error loading process chain, file name is null"); + this.aggregateProcess = aggy; + refreshTable(); + }); + + buildProcessTable(); + addComponent(processTable); + } + + protected void addDataSource(final String name, final String producerURI, String outputName) { + DataStreamSource dataSource = new DataStreamSource(); + SimpleProcess process = (SimpleProcess) SMLUtils.wrapWithProcessDescription(dataSource); + Settings paramSettings = new SettingsImpl(); + paramSettings.addSetValue(new ValueSettingImpl("parameters/systemUID", producerURI)); + process.setConfiguration(paramSettings); + + CodeWithAuthorityImpl codeWithAuthority = new CodeWithAuthorityImpl(); + codeWithAuthority.setValue(process.getName()); + process.addName(codeWithAuthority); + + aggregateProcess.addComponent(name, process); + + if (outputName.contains("/")) { + outputName = outputName.substring(0, outputName.indexOf("/")); + } + + for (var param : dataSource.getParameterList()) + ((DataComponent) param).assignNewDataBlock(); + dataSource.getParameterList() + .getComponent(DataStreamSource.SYSTEM_UID_PARAM) + .getData() + .setStringValue(producerURI); + dataSource.getParameterList() + .getComponent(DataStreamSource.OUTPUT_NAME_PARAM) + .getData() + .setStringValue(outputName); + dataSource.setParentHub(getParentHub()); + dataSource.notifyParamChange(); + } + + + protected void addCommandSink(final String name, final String consumerURI, final String inputName) { + CommandStreamSink sink = new CommandStreamSink(); + for (var param : sink.getParameterList()) + ((DataComponent) param).assignNewDataBlock(); + sink.getParameterList() + .getComponent(CommandStreamSink.SYSTEM_UID_PARAM) + .getData() + .setStringValue(consumerURI); + sink.getParameterList() + .getComponent(CommandStreamSink.OUTPUT_NAME_PARAM) + .getData() + .setStringValue(inputName); + sink.setParentHub(getParentHub()); + sink.notifyParamChange(); + + var process = SMLUtils.wrapWithProcessDescription(sink); + CodeWithAuthorityImpl codeWithAuthority = new CodeWithAuthorityImpl(); + codeWithAuthority.setValue(process.getName()); + process.addName(codeWithAuthority); + + aggregateProcess.addComponent(name, process); + } + + protected void addProcess(final String name, final ProcessInfo processInfo) { + try { + var process = SMLUtils.wrapWithProcessDescription(processInfo.getImplementationClass().newInstance()); + + CodeWithAuthorityImpl codeWithAuthority = new CodeWithAuthorityImpl(); + codeWithAuthority.setValue(process.getName()); + process.addName(codeWithAuthority); + + aggregateProcess.addComponent(name, process); + } catch (InstantiationException | IllegalAccessException e) { + DisplayUtils.showErrorPopup("Error instantiating process implementation", e); + } + } + + protected void addConnection(final String src, final String dest) { + aggregateProcess.addConnection(new LinkImpl(src, dest)); + } + + protected void addInput(DataComponent component) { + component.renewDataBlock(); + aggregateProcess.addInput(component.getName(), component); + } + + protected void addOutput(DataComponent component) { + component.renewDataBlock(); + aggregateProcess.addOutput(component.getName(), component); + } + + protected void addParameter(DataComponent component) { + component.renewDataBlock(); + aggregateProcess.addParameter(component.getName(), component); + } + + protected void buildProcessTable() { + // create table to display process flow + TreeTable table = new TreeTable("Aggregate Process"); + table.setWidth(100, Unit.PERCENTAGE); + table.addStyleName(STYLE_SMALL); + table.setSelectable(true); + processTable = table; + + // TODO: Set values from aggregateProcess + // TODO: Add columns for inputs, outputs, parameters, components, connections + table.addContainerProperty(PROP_NAME, String.class, ""); + table.addContainerProperty(PROP_VALUE, String.class, ""); + + addBaseItem(PROP_INPUTS, "0"); + addBaseItem(PROP_OUTPUTS, "0"); + addBaseItem(PROP_PARAMS, "0"); + addBaseItem(PROP_COMPS, "0"); + addBaseItem(PROP_CONNS, "0"); + + table.addActionHandler(new Action.Handler() { @Override - public void buttonClick(ClickEvent event) - { - // show popup to select among available module types - final ProcessSelectionCallback callback = new ProcessSelectionCallback() { - @Override - public void onSelected(String name, ProcessInfo info) throws ProcessingException - { - diagram.addNewProcess(name, info); + public Action[] getActions(Object target, Object sender) { + List actions = new ArrayList<>(); + + if (target == null) + return actions.toArray(new Action[0]); + + if (target == PROP_INPUTS || target == PROP_OUTPUTS) { + actions.add(ADD_IO_ACTION); + } else if (target == PROP_PARAMS) { + actions.add(ADD_PARAMETER_ACTION); + } else if (target == PROP_COMPS) { + actions.add(ADD_COMPONENT_ACTION); + } else if (target == PROP_CONNS) { + actions.add(ADD_CONNECTION_ACTION); + } else { + String parentName = table.getItem(table.getParent(target)).getItemProperty(PROP_NAME).getValue().toString(); + if (parentName.equals(PROP_COMPS)) + actions.add(CONFIGURE_PARAMS_ACTION); + else if (parentName.equals(PROP_CONNS)) + actions.add(EDIT_CONNECTION_ACTION); + actions.add(DELETE_ACTION); + } + + return actions.toArray(new Action[0]); + } + + @Override + public void handleAction(Action action, Object sender, Object target) { + String parentName; + if (target != null) + parentName = table.getItem(target).getItemProperty(PROP_NAME).getValue().toString(); + else { + parentName = null; + } + + if (action == ADD_COMPONENT_ACTION) { + // show popup to select among available module types + final ProcessSelectionCallback callback = (name, info) -> { + // If data source, show popup for selecting source system + if (info.equals(DataStreamSource.INFO)) { + SystemSelectionPopup popup = new SystemSelectionPopup(800, value -> { + String producerURI = (String) value; + var outputComponentPopup = new SystemIOSelectionPopup( + recordStructure -> { + // TODO: Use full path for name in case user selects nested component. then we need to resolve parent as output name + addDataSource(name, producerURI, SWEHelper.getComponentPath(((DataComponent) recordStructure))); + refreshTable(); + }, + getParentHub().getDatabaseRegistry().getFederatedDatabase(), + producerURI + ); + + outputComponentPopup.setModal(true); + getUI().addWindow(outputComponentPopup); + }, getParentHub().getDatabaseRegistry().getFederatedDatabase()); + popup.setModal(true); + getUI().addWindow(popup); + // If command, show system and IO popups + } else if (info.equals(CommandStreamSink.INFO)) { + SystemSelectionPopup popup = new SystemSelectionPopup(800, value -> { + String consumerURI = (String) value; + var inputComponentPopup = new SystemIOSelectionPopup( + recordStructure -> { + addCommandSink(name, consumerURI, ((DataComponent) recordStructure).getName()); + refreshTable(); + }, + getParentHub().getDatabaseRegistry().getFederatedDatabase(), + consumerURI + ); + inputComponentPopup.setModal(true); + getUI().addWindow(inputComponentPopup); + }, getParentHub().getDatabaseRegistry().getFederatedDatabase()); + popup.setModal(true); + getUI().addWindow(popup); + } else { + addProcess(name, info); + } + refreshTable(); + }; + + // popup the list so the user can select what he wants + ProcessSelectionPopup popup = new ProcessSelectionPopup(getParentHub().getProcessingManager().getAllProcessingPackages(), callback); + popup.setModal(true); + getUI().addWindow(popup); + } else if (action == CONFIGURE_PARAMS_ACTION) { + DataRecordImpl params = new DataRecordImpl(); + params.setName("Parameters"); + for (AbstractSWEIdentifiable param : aggregateProcess.getComponent(target.toString()).getParameterList()) { + DataComponent dataComponent = (DataComponent) param; + params.addComponent(dataComponent.getName(), dataComponent); } - }; - - // popup the list so the user can select what he wants - ProcessSelectionPopup popup = new ProcessSelectionPopup(getParentHub().getProcessingManager().getAllProcessingPackages(), callback); - popup.setModal(true); - getUI().addWindow(popup); + params.combineDataBlocks(); + + Window popup = new Window(); + Component sweForm = new SWEControlForm(params, event -> { + popup.close(); + refreshTable(); + }); + VerticalLayout layout = new VerticalLayout(); + layout.addComponent(sweForm); + layout.setMargin(true); + + popup.setContent(layout); + popup.setModal(true); + + getUI().addWindow(popup); + } else if (action == ADD_IO_ACTION) { + ValueEntryPopup.ValueCallback ioSelectionCallback = value -> { + if (parentName == null) + return; + if (Objects.equals(parentName, PROP_INPUTS)) { + addInput((DataComponent) value); + } else if (Objects.equals(parentName, PROP_OUTPUTS)) { + addOutput((DataComponent) value); + } else if (Objects.equals(parentName, PROP_PARAMS)) { + addParameter((DataComponent) value); + } + refreshTable(); + }; + + SystemSelectionPopup systemSelectionPopup = new SystemSelectionPopup(800, value -> { + SystemIOSelectionPopup ioSelectionPopup = new SystemIOSelectionPopup(ioSelectionCallback, getParentHub().getDatabaseRegistry().getFederatedDatabase(), value.toString()); + ioSelectionPopup.setModal(true); + getUI().addWindow(ioSelectionPopup); + }, getParentHub().getDatabaseRegistry().getFederatedDatabase()); + + systemSelectionPopup.setModal(true); + + getUI().addWindow(systemSelectionPopup); + } else if (action == ADD_PARAMETER_ACTION) { + ValueEntryPopup.ValueCallback ioSelectionCallback = value -> { + if (parentName == null) + return; + if (Objects.equals(parentName, PROP_PARAMS)) { + addParameter((DataComponent) value); + } + refreshTable(); + }; + + SystemSelectionPopup systemSelectionPopup = new SystemSelectionPopup(800, value -> { + SystemIOSelectionPopup ioSelectionPopup = new SystemIOSelectionPopup(ioSelectionCallback, getParentHub().getDatabaseRegistry().getFederatedDatabase(), value.toString()); + ioSelectionPopup.setModal(true); + getUI().addWindow(ioSelectionPopup); + }, getParentHub().getDatabaseRegistry().getFederatedDatabase()); + + systemSelectionPopup.setModal(true); + + getUI().addWindow(systemSelectionPopup); + } else if (action == ADD_CONNECTION_ACTION) { + Window popup = new Window(); + popup.setWidth(300, Unit.PIXELS); + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + + ComboBox source = new ComboBox("Source"); + source.setWidth(100, Unit.PERCENTAGE); + ComboBox destination = new ComboBox("Destination"); + destination.setWidth(100, Unit.PERCENTAGE); + + List possibleConnectionItems = getPossibleConnectionItems(aggregateProcess); + + source.addItems(possibleConnectionItems); + destination.addItems(possibleConnectionItems); + + Button okButton = new Button("OK"); + okButton.addClickListener(e -> { + addConnection(source.getValue().toString(), destination.getValue().toString()); + popup.close(); + refreshTable(); + }); + + layout.addComponents(source, destination, okButton); + + popup.setModal(true); + popup.setContent(layout); + popup.center(); + getUI().addWindow(popup); + } else if (action == EDIT_CONNECTION_ACTION) { + String itemId = target.toString(); + + Link connectionToEdit = null; + + int connectionIndexToRemove = -1; + for (int i = 0; i < aggregateProcess.getNumConnections(); i++) { + Link conn = aggregateProcess.getConnectionList().get(i); + String connId = conn.getSource().toString() + "-" + conn.getDestination().toString(); + + if (connId.equals(itemId)) { + connectionToEdit = conn; + connectionIndexToRemove = i; + break; + } + } + + + Window popup = new Window("Edit Connection"); + popup.setWidth(300, Unit.PIXELS); + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + + ComboBox source = new ComboBox("Source"); + source.setWidth(100, Unit.PERCENTAGE); + ComboBox destination = new ComboBox("Destination"); + destination.setWidth(100, Unit.PERCENTAGE); + + List possibleConnectionItems = getPossibleConnectionItems(aggregateProcess); + + source.addItems(possibleConnectionItems); + destination.addItems(possibleConnectionItems); + + source.setValue(connectionToEdit.getSource()); + destination.setValue(connectionToEdit.getDestination()); + + final int finalIndex = connectionIndexToRemove; + Button updateButton = new Button("Update"); + updateButton.addClickListener(e -> { + aggregateProcess.getConnectionList().get(finalIndex).setSource(source.getValue().toString()); + aggregateProcess.getConnectionList().get(finalIndex).setDestination(destination.getValue().toString()); + + popup.close(); + refreshTable(); + }); + + Button cancelButton = new Button("Cancel"); + cancelButton.addClickListener(e -> popup.close()); + + HorizontalLayout buttonLayout = new HorizontalLayout(updateButton, cancelButton); + buttonLayout.setSpacing(true); + layout.addComponents(source, destination, buttonLayout); + + popup.setModal(true); + popup.setContent(layout); + popup.center(); + getUI().addWindow(popup); + + } else if (action == DELETE_ACTION) { + String itemId = target.toString(); + + Object parent = table.getParent(target); + String propName = table.getItem(parent).getItemProperty(PROP_NAME).getValue().toString(); + + switch (propName) { + case PROP_COMPS: + if (parentName.contains(itemId)) { + aggregateProcess.getComponentList().remove(itemId); + break; + } + break; + case PROP_PARAMS: + if (itemId.contains(parentName)) { + aggregateProcess.getParameterList().remove(parentName); + break; + } + break; + case PROP_CONNS: + int idToRemove = -1; + for (int i = 0; i < aggregateProcess.getNumConnections(); i++) { + Link conn = aggregateProcess.getConnectionList().get(i); + String connId = conn.getSource().toString() + "-" + conn.getDestination().toString(); + if (itemId.equals(connId)) { + idToRemove = i; + break; + } + } + + if (idToRemove >= 0) { + aggregateProcess.getConnectionList().remove(idToRemove); + } + break; + case PROP_INPUTS: + if (itemId.contains(parentName)) { + aggregateProcess.getInputList().remove(parentName); + break; + } + break; + case PROP_OUTPUTS: + if (itemId.contains(parentName)) { + aggregateProcess.getOutputList().remove(parentName); + break; + } + break; + default: + getOshLogger().warn("Unknown type, cannot delete item ", parentName); + break; + } + + table.removeItem(itemId); + } + refreshTable(); } }); - - // add process flow diagram - diagram = new ProcessFlowDiagram(module.getProcessChain()); - addComponent(diagram); } - - - @Override - protected void beforeUpdateConfig() throws ProcessingException - { - //if (module instanceof SMLProcessImpl) - // saveProcessChain(); + + private List getPossibleConnectionItems(AbstractProcess process) { + List possibleConnectionItems = new ArrayList<>(); + + collectFromIOList("inputs", process.getInputList(), possibleConnectionItems); + collectFromIOList("outputs", process.getOutputList(), possibleConnectionItems); + collectFromIOList("parameters", process.getParameterList(), possibleConnectionItems); + + if (process instanceof AggregateProcess aggregate) { + OgcPropertyList components = aggregate.getComponentList(); + for (int i = 0; i < aggregate.getNumComponents(); i++) { + var component = components.getProperties().get(i); + String prefix = "components/" + component.getName() + "/"; + List possibleComponentPaths = getPossibleConnectionItems(component.getValue()); + possibleConnectionItems.addAll(possibleComponentPaths.stream() + .map(s -> prefix + s) + .toList()); + } + } + + return possibleConnectionItems; } - - - protected void saveProcessChain() throws ProcessingException - { - AggregateProcess processChain = null; - - try - { - ProcessFlowState state = diagram.getState(); - - // do nothing if diagram is empty - // maybe we just want to read from file - if (state.processBlocks.isEmpty()) + + private void collectFromIOList(String prefix, IOPropertyList ioList, List paths) { + for (int i = 0; i < ioList.size(); i++) { + DataComponent component = ioList.getComponent(i); + String topLevelPath = prefix + "/" + component.getName(); + paths.add(topLevelPath); // inputs/sensorLocation + collectNestedPaths(topLevelPath, component, paths); + } + } + + private void collectNestedPaths(String parentPath, DataComponent component, List paths) { + for (int i = 0; i < component.getComponentCount(); i++) { + DataComponent child = component.getComponent(i); + String childPath = parentPath + "/" + child.getName(); + paths.add(childPath); + collectNestedPaths(childPath, child, paths); + } + } + + private void addBaseItem(String name, String value) { + // Add a new item to the process table + Item newItem = processTable.addItem(name); + newItem.getItemProperty(PROP_NAME).setValue(name); + newItem.getItemProperty(PROP_VALUE).setValue(value); + processTable.setChildrenAllowed(name, false); + } + + private void addOrUpdateIOComponent(String itemId, DataComponent component) { + Item item = processTable.addItem(itemId); + if (item == null) + item = processTable.getItem(itemId); + item.getItemProperty(PROP_NAME).setValue(component.getName()); + String dataType = "UNKNOWN"; + try { + dataType = component.getData().getDataType().toString(); + } catch (Exception e) {} + item.getItemProperty(PROP_VALUE).setValue(dataType); + } + + protected void refreshTable() { + try { + // TODO: Refresh the process table with the current state of the aggregate process + // This should update the table with the current inputs, outputs, parameters, components, and connections + if (aggregateProcess == null) return; - - // convert diagram state to SensorML process chain - String uid = module.getUniqueIdentifier(); - SMLHelper sml = new SMLHelper(); - - processChain = sml.createAggregateProcess() - .uniqueID(uid) - .build(); - - // data sources - for (ProcessBlock block: state.dataSources.values()) - { - SimpleProcess p = sml.createSimpleProcess() - .uniqueID(block.id) - .typeOf(block.uri) - .build(); - Settings settings = sml.getFactory().newSettings(); - settings.addSetValue("parameters/"+StreamDataSource.PRODUCER_URI_PARAM, block.id); - p.setConfiguration(settings); - saveShape(block, p); - - processChain.addComponent(block.name, p); + + processTable.removeAllItems(); + addBaseItem(PROP_INPUTS, "0"); + addBaseItem(PROP_OUTPUTS, "0"); + addBaseItem(PROP_PARAMS, "0"); + addBaseItem(PROP_COMPS, "0"); + addBaseItem(PROP_CONNS, "0"); + + if (aggregateProcess.getNumInputs() >= 0) { + if (aggregateProcess.getNumInputs() > 0) + processTable.setChildrenAllowed(PROP_INPUTS, true); + processTable.getItem(PROP_INPUTS).getItemProperty(PROP_VALUE).setValue(String.valueOf(aggregateProcess.getNumInputs())); + for (int i = 0; i < aggregateProcess.getNumInputs(); i++) { + DataComponent input = aggregateProcess.getInputList().getComponent(i); + var itemId = "inputs/" + SWEHelper.getComponentPath(input); + addOrUpdateIOComponent(itemId, input); + processTable.setParent(itemId, PROP_INPUTS); + processTable.setChildrenAllowed(itemId, false); + } } - - // process blocks - for (ProcessBlock block: state.processBlocks.values()) - { - SimpleProcess p = sml.createSimpleProcess() - .uniqueID(block.id) - .typeOf(block.uri) - .build(); - - saveShape(block, p); - processChain.addComponent(block.name, p); - processChain.addOutput("test", new SWEHelper().newQuantity()); + if (aggregateProcess.getNumOutputs() >= 0) { + if (aggregateProcess.getNumOutputs() > 0) + processTable.setChildrenAllowed(PROP_OUTPUTS, true); + processTable.getItem(PROP_OUTPUTS).getItemProperty(PROP_VALUE).setValue(String.valueOf(aggregateProcess.getNumOutputs())); + for (int i = 0; i < aggregateProcess.getNumOutputs(); i++) { + DataComponent output = aggregateProcess.getOutputList().getComponent(i); + var itemId = "outputs/" + SWEHelper.getComponentPath(output); + addOrUpdateIOComponent(itemId, output); + processTable.setParent(itemId, PROP_OUTPUTS); + processTable.setChildrenAllowed(itemId, false); + } } - - // connections - for (Connection c: state.connections.values()) - { - Link link = sml.getFactory().newLink(); - link.setSource(c.src); - link.setDestination(c.dest); - processChain.addConnection(link); + if (aggregateProcess.getNumParameters() >= 0) { + if (aggregateProcess.getNumParameters() > 0) + processTable.setChildrenAllowed(PROP_PARAMS, true); + processTable.getItem(PROP_PARAMS).getItemProperty(PROP_VALUE).setValue(String.valueOf(aggregateProcess.getNumParameters())); + for (int i = 0; i < aggregateProcess.getNumParameters(); i++) { + DataComponent param = aggregateProcess.getParameterList().getComponent(i); + var itemId = "parameters/" + SWEHelper.getComponentPath(param); + addOrUpdateIOComponent(itemId, param); + processTable.setParent(itemId, PROP_PARAMS); + processTable.setChildrenAllowed(itemId, false); + } } + if (aggregateProcess.getNumComponents() >= 0) { + if (aggregateProcess.getNumComponents() > 0) + processTable.setChildrenAllowed(PROP_COMPS, true); + processTable.getItem(PROP_COMPS).getItemProperty(PROP_VALUE).setValue(String.valueOf(aggregateProcess.getNumComponents())); + OgcPropertyList comps = aggregateProcess.getComponentList(); + for (int i = 0; i < aggregateProcess.getNumComponents(); i++) { + OgcProperty component = comps.getProperties().get(i); + AbstractProcess process = component.getValue(); + Item item = processTable.addItem(component.getName()); + if (item == null) + item = processTable.getItem(component.getName()); + item.getItemProperty(PROP_NAME).setValue(process.getName() + " (" + component.getName() + ")"); + if (Objects.equals(process.getTypeOf().getHref(), DataStreamSource.INFO.getUri())) { + // If data source, set value to the source UID + item.getItemProperty(PROP_VALUE).setValue(((DataComponent)process.getParameter(DataStreamSource.SYSTEM_UID_PARAM)).getData().getStringValue()); + } else if (Objects.equals(process.getTypeOf().getHref(), CommandStreamSink.INFO.getUri())) { + // If data source, set value to the target UID / inputName + item.getItemProperty(PROP_VALUE).setValue( + ((DataComponent)process.getParameter(CommandStreamSink.SYSTEM_UID_PARAM)).getData().getStringValue() + "#" + + ((DataComponent)process.getParameter(CommandStreamSink.OUTPUT_NAME_PARAM)).getData().getStringValue()); + } else { + StringBuilder paramsList = new StringBuilder(); + for (int paramIndex = 0; paramIndex < process.getParameterList().size(); paramIndex++) { + DataComponent param = process.getParameterList().getComponent(paramIndex); + paramsList.append(param.getName()); + if (paramIndex != process.getParameterList().size() - 1) + paramsList.append(", "); + } + item.getItemProperty(PROP_VALUE).setValue(paramsList.toString()); + } + processTable.setParent(component.getName(), PROP_COMPS); + processTable.setChildrenAllowed(component.getName(), false); + } + } + if (aggregateProcess.getNumConnections() >= 0) { + if (aggregateProcess.getNumConnections() > 0) + processTable.setChildrenAllowed(PROP_CONNS, true); + processTable.getItem(PROP_CONNS).getItemProperty(PROP_VALUE).setValue(String.valueOf(aggregateProcess.getNumConnections())); + for (int i = 0; i < aggregateProcess.getNumConnections(); i++) { + Link connection = aggregateProcess.getConnectionList().get(i); + String itemId = connection.getSource() + "-" + connection.getDestination(); + Item item = processTable.addItem(itemId); + if (item == null) + item = processTable.getItem(itemId); + item.getItemProperty(PROP_NAME).setValue("Source Component: " + connection.getSource()); + item.getItemProperty(PROP_VALUE).setValue("Destination Component: " + connection.getDestination()); + processTable.setParent(itemId, PROP_CONNS); + processTable.setChildrenAllowed(itemId, false); + } + } + } catch (Exception e) { + getOshLogger().error("Error refreshing table", e); } - catch (Exception e) - { - throw new ProcessingException("Cannot create SensorML process chain", e); - } - - // save SensorML file + } + + protected AggregateProcess getProcessChainFromFile() { + // Read process as SensorML file if exists String smlPath = config.getSensorMLPath(); - if (smlPath == null || !FileUtils.isSafeFilePath(smlPath)) - throw new ProcessingException("Cannot save process chain: A valid SensorML file path must be provided"); - - try (OutputStream os = new BufferedOutputStream(new FileOutputStream(smlPath))) - { - new SMLUtils(SMLUtils.V2_0).writeProcess(os, processChain, true); + if (!FileUtils.isSafeFilePath(smlPath)) + return null; + + AggregateProcess process; + try (InputStream is = new FileInputStream(smlPath)) { + process = (AggregateProcess) new SMLUtils(SMLUtils.V2_0).readProcess(is); + } catch (Exception e) { + process = null; } - catch (Exception e) - { - throw new ProcessingException(String.format("Cannot write SensorML description at '%s'", smlPath), e); - } + return process; } - - protected void saveShape(ProcessBlock block, AbstractProcess p) + protected void saveProcessChain() { + + // save SensorML file + AtomicReference smlPath = new AtomicReference<>(config.getSensorMLPath()); + if (!FileUtils.isSafeFilePath(smlPath.get())) { + Window popup = new Window(); + popup.setWidth(300, Unit.PIXELS); + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + + TextField newFilePath = new TextField("SensorML File path"); + newFilePath.setWidth(100, Unit.PERCENTAGE); + + Button okButton = new Button("OK"); + okButton.addClickListener(event -> { + smlPath.set(newFilePath.getValue()); + + writeProcess(smlPath.get()); + + popup.close(); + }); + + layout.addComponents(newFilePath, okButton); + + popup.setModal(true); + popup.setContent(layout); + popup.center(); + getUI().addWindow(popup); + } else { + writeProcess(smlPath.get()); + } } - - - /*@Override - protected void refreshContent() + + private void writeProcess(String path) { - ProcessFlowDiagram oldDiagram = diagram; - diagram = new ProcessFlowDiagram(((SMLProcessImpl)module).getProcessChain()); - replaceComponent(oldDiagram, diagram); - }*/ + // Clear component I/O in case process implementation needs to use params to add I/O. Also, not needed in XML + for (var component : aggregateProcess.getComponentList()) + { + component.getOutputList().clear(); + component.getInputList().clear(); + } + + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(path))) + { + new SMLUtils(SMLUtils.V2_0).writeProcess(os, aggregateProcess, true); + DisplayUtils.showOperationSuccessful("Saved SensorML description to " + path, 1000); + + if (this.smlPathTextField != null) + this.smlPathTextField.setValue(path); + } + catch (Exception e) + { + DisplayUtils.showErrorPopup(String.format("Cannot write SensorML description at '%s'", path), e); + } + } + } diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessFlowDiagram.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessFlowDiagram.java index a9a3de7d5..c46976569 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessFlowDiagram.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessFlowDiagram.java @@ -28,6 +28,7 @@ import org.vast.process.ProcessInfo; import org.vast.sensorML.AbstractProcessImpl; import org.vast.sensorML.AggregateProcessImpl; +import org.vast.sensorML.LinkImpl; import org.vast.sensorML.SMLUtils; import org.vast.swe.SWEHelper; import com.rits.cloning.Cloner; @@ -178,27 +179,28 @@ protected void setupCallbacks() { addFunction("onChangeLink", new JavaScriptFunction() { @Override - public void call(JsonArray args) - { + public void call(JsonArray args) { Connection conn = new Connection(); conn.id = args.getString(0); conn.src = args.getString(1); conn.dest = args.getString(2); - addConnection(conn); + addConnection(conn); // updates UI state + upsertProcessChainLink(conn); // <-- persist to processChain notifyListeners(); - } + } }); - + addFunction("onRemoveLink", new JavaScriptFunction() { @Override - public void call(JsonArray args) - { + public void call(JsonArray args) { String id = args.getString(0); getState().connections.remove(id); + removeProcessChainLink(id); // <-- mirror removal notifyListeners(); - } + } }); - + + addFunction("onChangeElement", new JavaScriptFunction() { @Override public void call(JsonArray args) @@ -226,34 +228,86 @@ public void call(JsonArray args) notifyListeners(); } }); - + addFunction("onContextMenu", new JavaScriptFunction() { @Override - public void call(JsonArray args) - { + public void call(JsonArray args) { String action = args.getString(0); String blockName = args.getString(1); String portName = args.getString(2); - if ("addInput".equals(action)) - addExternalInput(blockName, portName); - else if ("setInput".equals(action)) - setInputValues(blockName, portName); - else if ("setParam".equals(action)) - setParamValues(blockName, portName); + + // normalize action names coming from the UI + switch (action) { + case "addInput": + case "exposeInput": + case "exposeAsInput": + case "Expose as input": + addExternalInput(blockName, portName); + break; + + case "setInput": + case "setValue": + case "Set value": + setInputValues(blockName, portName); + break; + + case "setParam": + case "setParameter": + setParamValues(blockName, portName); + break; + + default: + // ignore unknowns to avoid NPEs + return; + } notifyListeners(); - } + } }); + } - - - protected void addExternalInput(String blockName, String portName) - { + + protected void upsertProcessChainLink(Connection conn) { + // replace if same id exists + for (int i = 0; i < processChain.getConnectionList().size(); i++) { + Link l = processChain.getConnectionList().get(i); + if (conn.id != null && conn.id.equals(l.getId())) { + l.setSource(conn.src); + l.setDestination(conn.dest); + return; + } + } + // otherwise add a new link + Link l = new LinkImpl(); + l.setId(conn.id != null ? conn.id : UUID.randomUUID().toString()); + l.setSource(conn.src); + l.setDestination(conn.dest); + processChain.getConnectionList().add(l); + } + + protected void removeProcessChainLink(String id) { + if (id == null) return; + List links = processChain.getConnectionList(); + for (int i = links.size() - 1; i >= 0; i--) { + Link l = links.get(i); + if (id.equals(l.getId())) { + links.remove(i); + } + } + } + + + + protected void addExternalInput(String blockName, String portName) { + for (Port p : getState().inputs) { + if (p.path.equals(portName)) return; // already exposed + } Port port = new Port(); port.path = portName; getState().inputs.add(port); } - - + + + protected void addExternalParam(String blockName, String portName) { @@ -264,40 +318,33 @@ protected void addExternalOutput(String blockName, String portName) { } - - - protected void setInputValues(String blockName, String portName) - { + + + protected void setInputValues(String blockName, String portName) { AbstractProcess process = processChain.getComponent(blockName); - DataComponent paramPort = process.getParameterComponent(portName); - editValues(paramPort); + DataComponent inputPort = process.getInputComponent(portName); // <-- FIXED + editValues("Set Input", inputPort); } - - - protected void setParamValues(String blockName, String portName) - { + + protected void setParamValues(String blockName, String portName) { AbstractProcess process = processChain.getComponent(blockName); DataComponent paramPort = process.getParameterComponent(portName); - editValues(paramPort); + editValues("Set Parameter", paramPort); } - - - protected void editValues(DataComponent component) - { - Window popup = new Window("Set Parameter"); + + protected void editValues(String title, DataComponent component) { + Window popup = new Window(title); // <-- dynamic title VerticalLayout content = new VerticalLayout(); popup.setContent(content); popup.center(); - - // retrieve param component + SWEParamForm form = new SWEParamForm(component); content.addComponent(form); - - // Open it in the UI getUI().addWindow(popup); } - - + + + protected void addDataSource(ProcessBlock b) { getState().dataSources.put(b.name, b); diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessSelectionPopup.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessSelectionPopup.java index b54feb592..da90dbdaf 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessSelectionPopup.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/ProcessSelectionPopup.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import org.sensorhub.api.processing.IProcessProvider; import org.sensorhub.api.processing.ProcessingException; +import org.sensorhub.impl.processing.StreamDataSource; import org.sensorhub.ui.api.UIConstants; import org.vast.process.ProcessInfo; import com.vaadin.ui.Alignment; @@ -65,7 +66,7 @@ public ProcessSelectionPopup(Collection providers, final Proce setWidth(1000.0f, Unit.PIXELS); buildDialog(providers, callback); } - + protected void buildDialog(Collection providers, final ProcessSelectionCallback callback) { @@ -81,7 +82,7 @@ protected void buildDialog(Collection providers, final Process table.addContainerProperty(PROP_DESC, String.class, null); table.addContainerProperty(PROP_VERSION, String.class, null); table.addContainerProperty(PROP_AUTHOR, String.class, null); - table.setColumnHeaders(new String[] {"Name", "Description", "Version", "Author"}); + table.setColumnHeaders("Name", "Description", "Version", "Author"); table.setColumnWidth(PROP_NAME, 250); table.setPageLength(10); table.setMultiSelect(false); @@ -97,8 +98,8 @@ protected void buildDialog(Collection providers, final Process for (ProcessInfo info: provider.getProcessMap().values()) { - // skip data sources as they are inserted separately - if (info.getUri().contains(":datasource:")) + // For simplicity, don't let users choose this one + if (info == StreamDataSource.INFO) continue; Object id = table.addItem(new Object[] { @@ -112,20 +113,21 @@ protected void buildDialog(Collection providers, final Process } layout.addComponent(table); - // link to more modules - Button installNew = new Button("Install More Packages..."); - installNew.setStyleName(STYLE_LINK); - layout.addComponent(installNew); - layout.setComponentAlignment(installNew, Alignment.MIDDLE_RIGHT); - installNew.addClickListener(new ClickListener() - { - @Override - public void buttonClick(ClickEvent event) - { - //close(); - getUI().addWindow(new DownloadModulesPopup()); - } - }); +// // link to more modules +// Button installNew = new Button("Install More Packages..."); +// installNew.setStyleName(STYLE_LINK); +// layout.addComponent(installNew); +// layout.setComponentAlignment(installNew, Alignment.MIDDLE_RIGHT); + // TODO: Use check OSGi logic +// installNew.addClickListener(new ClickListener() +// { +// @Override +// public void buttonClick(ClickEvent event) +// { +// //close(); +// getUI().addWindow(new DownloadModulesPopup()); +// } +// }); // buttons bar HorizontalLayout buttons = new HorizontalLayout(); diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SMLProcessPanel.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SMLProcessPanel.java new file mode 100644 index 000000000..1ff4eb1f3 --- /dev/null +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SMLProcessPanel.java @@ -0,0 +1,4 @@ +package org.sensorhub.ui; + +public class SMLProcessPanel { +} diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SWEControlForm.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SWEControlForm.java index d22c36454..930a9adbb 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SWEControlForm.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SWEControlForm.java @@ -38,25 +38,27 @@ public class SWEControlForm extends SWEEditForm transient Random random = new SecureRandom(); - public SWEControlForm(final IStreamingControlInterface controlInput) + public SWEControlForm(final IStreamingControlInterface controlInput) { this(controlInput, null); } + + public SWEControlForm(final IStreamingControlInterface controlInput, final ClickListener submitListener) { super(controlInput.getCommandDescription().copy()); this.controlInput = controlInput; this.component.assignNewDataBlock(); - buildForm(); + buildForm(submitListener); } - - public SWEControlForm(final DataComponent params) + public SWEControlForm(final DataComponent params) { this(params, null); } + + public SWEControlForm(final DataComponent params, ClickListener submitListener) { super(params.copy()); this.addSpacing = true; this.controlSink = params; this.component.setData(params.getData()); - buildForm(); + buildForm(submitListener); } - @Override public void attach() { @@ -77,8 +79,12 @@ public void attach() } } + @Override + protected void buildForm() { + buildForm(null); + } - protected void buildForm() + protected void buildForm(ClickListener submitListener) { super.buildForm(); @@ -116,5 +122,8 @@ public void buttonClick(ClickEvent event) } } }); + + if (submitListener != null) + sendBtn.addClickListener(submitListener); } } diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOList.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOList.java new file mode 100644 index 000000000..a6ce7f918 --- /dev/null +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOList.java @@ -0,0 +1,110 @@ +package org.sensorhub.ui; + +import com.vaadin.ui.Component; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.v7.data.Item; +import com.vaadin.v7.ui.TreeTable; +import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; +import net.opengis.swe.v20.DataComponent; +import org.sensorhub.api.database.IObsSystemDatabase; +import org.sensorhub.api.datastore.command.CommandStreamFilter; +import org.sensorhub.api.datastore.obs.DataStreamFilter; +import org.sensorhub.api.datastore.system.SystemFilter; +import org.sensorhub.ui.api.UIConstants; + +public class SystemIOList extends VerticalLayout { + + static final String PROP_NAME = "name"; + static final String PROP_DESCRIPTION = "description"; + static final String PROP_STRUCT = "recordStructure"; + + TreeTable table; + + public SystemIOList(final IObsSystemDatabase db, final String systemUID, final ItemClickListener selectionListener) { + setMargin(false); + + addComponent(buildIOTable(selectionListener)); + + updateTable(db, systemUID); + } + + protected Component buildIOTable(final ItemClickListener selectionListener) { + table = new TreeTable(); + table.setWidth(100, Unit.PERCENTAGE); + table.setSelectable(true); + table.addStyleName(UIConstants.STYLE_SMALL); + table.addContainerProperty(PROP_NAME, String.class, null); + table.addContainerProperty(PROP_DESCRIPTION, String.class, null); + table.addContainerProperty(PROP_STRUCT, DataComponent.class, null); + table.setVisibleColumns(PROP_NAME, PROP_DESCRIPTION); + + table.addItemClickListener(selectionListener); + + return table; + } + + protected void updateTable(final IObsSystemDatabase db, final String systemUID) { + table.removeAllItems(); + + DataStreamFilter dsFilter = new DataStreamFilter.Builder() + .withSystems(new SystemFilter.Builder().withUniqueIDs(systemUID).build()) + .build(); + + // TODO Add nested IO also + db.getDataStreamStore().select(dsFilter) + .forEach(ds -> { + String itemId = ds.getFullName(); + Item item = table.addItem(itemId); + table.setChildrenAllowed(itemId, true); + + if (item != null) { + DataComponent recordStructure = ds.getRecordStructure(); + item.getItemProperty(PROP_NAME).setValue(ds.getOutputName()); + item.getItemProperty(PROP_DESCRIPTION).setValue(recordStructure.getDescription()); + item.getItemProperty(PROP_STRUCT).setValue(recordStructure); + + for (int i = 0; i < recordStructure.getComponentCount(); i++) + { + var child = recordStructure.getComponent(i); + String childId = itemId + "/" + child.getName(); + Item childItem = table.addItem(childId); + table.setChildrenAllowed(childId, false); + childItem.getItemProperty(PROP_NAME).setValue(child.getName()); + childItem.getItemProperty(PROP_DESCRIPTION).setValue(child.getDescription()); + childItem.getItemProperty(PROP_STRUCT).setValue(child); + table.setParent(childId, itemId); + } + } + }); + + CommandStreamFilter csFilter = new CommandStreamFilter.Builder() + .withSystems(new SystemFilter.Builder().withUniqueIDs(systemUID).build()) + .build(); + + db.getCommandStreamStore().select(csFilter) + .forEach(cs -> { + String itemId = cs.getFullName(); + Item item = table.addItem(itemId); + table.setChildrenAllowed(itemId, true); + + if (item != null) { + DataComponent recordStructure = cs.getRecordStructure(); + item.getItemProperty(PROP_NAME).setValue(cs.getControlInputName()); + item.getItemProperty(PROP_DESCRIPTION).setValue(recordStructure.getDescription()); + item.getItemProperty(PROP_STRUCT).setValue(recordStructure); + + for (int i = 0; i < recordStructure.getComponentCount(); i++) + { + var child = recordStructure.getComponent(i); + String childId = itemId + "/" + child.getName(); + Item childItem = table.addItem(childId); + table.setChildrenAllowed(childId, false); + childItem.getItemProperty(PROP_NAME).setValue(child.getName()); + childItem.getItemProperty(PROP_DESCRIPTION).setValue(child.getDescription()); + childItem.getItemProperty(PROP_STRUCT).setValue(child); + table.setParent(childId, itemId); + } + } + }); + } +} diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOSelectionPopup.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOSelectionPopup.java new file mode 100644 index 000000000..ee7f767b5 --- /dev/null +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemIOSelectionPopup.java @@ -0,0 +1,36 @@ +package org.sensorhub.ui; + +import com.vaadin.ui.Button; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; +import net.opengis.swe.v20.DataComponent; +import org.sensorhub.api.database.IObsSystemDatabase; +import org.sensorhub.ui.api.UIConstants; + +public class SystemIOSelectionPopup extends Window { + + DataComponent dataComponent; + + public SystemIOSelectionPopup(final ValueEntryPopup.ValueCallback callback, IObsSystemDatabase db, String systemUID) { + super("Select an Input/Output Component"); + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + + var ioTable = new SystemIOList(db, systemUID, event -> + dataComponent = (DataComponent) event.getItem().getItemProperty(SystemIOList.PROP_STRUCT).getValue()); + layout.addComponent(ioTable); + + Button okButton = new Button("OK"); + okButton.addStyleName(UIConstants.STYLE_SMALL); + okButton.addClickListener( e -> { + if (dataComponent == null) + DisplayUtils.showErrorPopup("Please select an IO component!", new IllegalArgumentException()); + callback.newValue(dataComponent); + close(); + }); + layout.addComponent(okButton); + setContent(layout); + center(); + } + +} diff --git a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemSelectionPopup.java b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemSelectionPopup.java index c4d6eae12..b709e7a68 100644 --- a/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemSelectionPopup.java +++ b/sensorhub-webui-core/src/main/java/org/sensorhub/ui/SystemSelectionPopup.java @@ -15,6 +15,7 @@ package org.sensorhub.ui; import org.sensorhub.api.database.IObsSystemDatabase; +import org.sensorhub.api.event.EventUtils; import org.sensorhub.ui.ValueEntryPopup.ValueCallback; import org.sensorhub.ui.api.UIConstants; import com.vaadin.v7.data.Property.ValueChangeEvent; @@ -34,7 +35,7 @@ public class SystemSelectionPopup extends Window { String sysUID; - + public SystemSelectionPopup(int width, final ValueCallback callback, IObsSystemDatabase db) { super("Select an Observing System");