diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java index e4509e082..e337e86e7 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -15,16 +15,15 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.Property; +import com.vaadin.data.Validator; import com.vaadin.data.util.ObjectProperty; import com.vaadin.data.validator.NullValidator; import com.vaadin.data.validator.RangeValidator; import com.vaadin.data.validator.RegexpValidator; import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.FormLayout; import com.vaadin.ui.OptionGroup; import com.vaadin.ui.TextField; @@ -44,6 +43,15 @@ public class GenerateDialog extends Window { private final FormLayout formLayout = new FormLayout(); + private final TextField namePrefixTextField; + private final TextField amountTextField; + private final TextField tenantTextField; + private final TextField pollDelayTextField; + private final TextField pollUrlTextField; + private final TextField gatewayTokenTextField; + private final OptionGroup protocolGroup; + private final Button buttonOk; + /** * Creates a new pop window for setting the configuration of simulating * devices. @@ -57,98 +65,43 @@ public class GenerateDialog extends Window { formLayout.setSpacing(true); formLayout.setMargin(true); - final TextField tf1 = new TextField("name prefix", "dmfSimulated"); - tf1.setIcon(FontAwesome.INFO); - tf1.setRequired(true); - tf1.addValidator(new NullValidator("Must be given", false)); + namePrefixTextField = createRequiredTextfield("name prefix", "dmfSimulated", FontAwesome.INFO, + new NullValidator("Must be given", false)); - final TextField tf2 = new TextField("amount", new ObjectProperty(10)); - tf2.setIcon(FontAwesome.GEAR); - tf2.setRequired(true); - tf2.addValidator(new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); + amountTextField = createRequiredTextfield("amount", new ObjectProperty(10), FontAwesome.GEAR, + new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); - final TextField tf3 = new TextField("tenant", "default"); - tf3.setIcon(FontAwesome.USER); - tf3.setRequired(true); - tf3.addValidator(new NullValidator("Must be given", false)); + tenantTextField = createRequiredTextfield("tenant", "default", FontAwesome.USER, + new NullValidator("Must be given", false)); - final TextField tf4 = new TextField("poll delay (sec)", new ObjectProperty(10)); - tf4.setIcon(FontAwesome.CLOCK_O); - tf4.setRequired(true); - tf4.setVisible(false); - tf4.addValidator(new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + pollDelayTextField = createRequiredTextfield("poll delay (sec)", new ObjectProperty(10), + FontAwesome.CLOCK_O, new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + pollDelayTextField.setVisible(false); - final TextField tf5 = new TextField("base poll URL endpoint", "http://localhost:8080"); - tf5.setColumns(50); - tf5.setIcon(FontAwesome.FLAG_O); - tf5.setRequired(true); - tf5.setVisible(false); - tf5.addValidator(new RegexpValidator("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", - "is not an URL")); + pollUrlTextField = createRequiredTextfield("base poll URL endpoint", "http://localhost:8080", + FontAwesome.FLAG_O, new RegexpValidator( + "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + pollUrlTextField.setColumns(50); + pollUrlTextField.setVisible(false); - final TextField tf6 = new TextField("gateway token", ""); - tf6.setColumns(50); - tf6.setIcon(FontAwesome.FLAG_O); - tf6.setRequired(true); - tf6.setVisible(false); + gatewayTokenTextField = createRequiredTextfield("gateway token", "", FontAwesome.FLAG_O, null); + gatewayTokenTextField.setColumns(50); + gatewayTokenTextField.setVisible(false); - final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); - protocolGroup.addItem(Protocol.DMF_AMQP); - protocolGroup.addItem(Protocol.DDI_HTTP); - protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); - protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); - protocolGroup.setNullSelectionAllowed(false); - protocolGroup.select(Protocol.DMF_AMQP); - protocolGroup.addValueChangeListener(new ValueChangeListener() { - private static final long serialVersionUID = 1L; + protocolGroup = createProtocolGroup(); + buttonOk = createOkButton(callback); - @Override - public void valueChange(final ValueChangeEvent event) { - if (event.getProperty().getValue().equals(Protocol.DDI_HTTP)) { - tf4.setVisible(true); - tf5.setVisible(true); - tf6.setVisible(true); - } else { - tf4.setVisible(false); - tf5.setVisible(false); - tf6.setVisible(false); - } - } - }); + namePrefixTextField.addValueChangeListener(event -> checkValid()); + amountTextField.addValueChangeListener(event -> checkValid()); + tenantTextField.addValueChangeListener(event -> checkValid()); - final Button buttonOk = new Button("generate"); - buttonOk.setImmediate(true); - buttonOk.setIcon(FontAwesome.GEARS); - buttonOk.addClickListener(new ClickListener() { - private static final long serialVersionUID = 1L; - - @Override - public void buttonClick(final ClickEvent event) { - try { - callback.okButton(tf1.getValue(), tf3.getValue(), - Integer.valueOf(tf2.getValue().replace(".", "").replace(",", "")), - Integer.valueOf(tf4.getValue().replace(".", "")), new URL(tf5.getValue()), tf6.getValue(), - (Protocol) protocolGroup.getValue()); - } catch (final NumberFormatException e) { - LOGGER.info(e.getMessage(), e); - } catch (final MalformedURLException e) { - LOGGER.info(e.getMessage(), e); - } - GenerateDialog.this.close(); - } - }); - - tf1.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - tf2.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - tf3.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - - formLayout.addComponent(tf1); - formLayout.addComponent(tf2); - formLayout.addComponent(tf3); + formLayout.addComponent(namePrefixTextField); + formLayout.addComponent(amountTextField); + formLayout.addComponent(tenantTextField); formLayout.addComponent(protocolGroup); - formLayout.addComponent(tf4); - formLayout.addComponent(tf5); - formLayout.addComponent(tf6); + formLayout.addComponent(pollDelayTextField); + formLayout.addComponent(pollUrlTextField); + formLayout.addComponent(gatewayTokenTextField); formLayout.addComponent(buttonOk); setCaption("Simulate Devices"); @@ -157,13 +110,10 @@ public class GenerateDialog extends Window { center(); } - private void checkValid(final TextField tf1, final TextField tf2, final TextField tf3, final TextField tf4, - final Button buttonOk) { - if (tf1.isValid() && tf2.isValid() && tf3.isValid() && tf4.isValid()) { - buttonOk.setEnabled(true); - } else { - buttonOk.setEnabled(false); - } + private void checkValid() { + buttonOk.setEnabled(namePrefixTextField.isValid() && amountTextField.isValid() && tenantTextField.isValid() + && pollDelayTextField.isValid()); + } @Override @@ -231,4 +181,66 @@ public class GenerateDialog extends Window { void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay, final URL basePollURL, final String gatewayToken, final Protocol protocol); } + + private OptionGroup createProtocolGroup() { + + final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); + protocolGroup.addItem(Protocol.DMF_AMQP); + protocolGroup.addItem(Protocol.DDI_HTTP); + protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); + protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); + protocolGroup.setNullSelectionAllowed(false); + protocolGroup.select(Protocol.DMF_AMQP); + protocolGroup.addValueChangeListener(event -> { + final boolean directDeviceOptionSelected = event.getProperty().getValue().equals(Protocol.DDI_HTTP); + pollDelayTextField.setVisible(directDeviceOptionSelected); + pollUrlTextField.setVisible(directDeviceOptionSelected); + gatewayTokenTextField.setVisible(directDeviceOptionSelected); + }); + return protocolGroup; + } + + private Button createOkButton(final GenerateDialogCallback callback) { + + final Button buttonOk = new Button("generate"); + buttonOk.setImmediate(true); + buttonOk.setIcon(FontAwesome.GEARS); + buttonOk.addClickListener(event -> { + try { + callback.okButton(namePrefixTextField.getValue(), tenantTextField.getValue(), + Integer.valueOf(amountTextField.getValue().replace(".", "").replace(",", "")), + Integer.valueOf(pollDelayTextField.getValue().replace(".", "")), + new URL(pollUrlTextField.getValue()), gatewayTokenTextField.getValue(), + (Protocol) protocolGroup.getValue()); + } catch (final NumberFormatException e) { + LOGGER.info(e.getMessage(), e); + } catch (final MalformedURLException e) { + LOGGER.info(e.getMessage(), e); + } + GenerateDialog.this.close(); + }); + return buttonOk; + } + + private TextField createRequiredTextfield(final String caption, final String value, final Resource icon, + final Validator validator) { + final TextField textField = new TextField(caption, value); + return addTextFieldValues(textField, icon, validator); + } + + private TextField createRequiredTextfield(final String caption, final Property dataSource, final Resource icon, + final Validator validator) { + final TextField textField = new TextField(caption, dataSource); + return addTextFieldValues(textField, icon, validator); + } + + private TextField addTextFieldValues(final TextField textField, final Resource icon, final Validator validator) { + textField.setIcon(icon); + textField.setRequired(true); + if (validator != null) { + textField.addValidator(validator); + } + return textField; + } + } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java index 25498cea7..e1d6bd15b 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java @@ -28,8 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Lists; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.BeanContainer; import com.vaadin.data.util.BeanItem; import com.vaadin.data.util.converter.Converter; @@ -76,8 +74,8 @@ public class SimulatorView extends VerticalLayout implements View { private final Label caption = new Label("DMF/DDI Simulated Devices"); private final HorizontalLayout toolbar = new HorizontalLayout(); private final Grid grid = new Grid(); - private final ComboBox responseComboBox = new ComboBox("", Lists.newArrayList(ResponseStatus.SUCCESSFUL, - ResponseStatus.ERROR)); + private final ComboBox responseComboBox = new ComboBox("", + Lists.newArrayList(ResponseStatus.SUCCESSFUL, ResponseStatus.ERROR)); private BeanContainer beanContainer; @@ -97,6 +95,9 @@ public class SimulatorView extends VerticalLayout implements View { grid.setSizeFull(); grid.setCellStyleGenerator(new CellStyleGenerator() { + + private static final long serialVersionUID = 1L; + @Override public String getStyle(final CellReference cellReference) { return cellReference.getPropertyId().equals("status") ? "centeralign" : null; @@ -118,86 +119,8 @@ public class SimulatorView extends VerticalLayout implements View { grid.getColumn("swversion").setHeaderCaption("SW Version"); grid.getColumn("responseStatus").setHeaderCaption("Response Update Status"); grid.getColumn("progress").setRenderer(new ProgressBarRenderer()); - grid.getColumn("protocol").setConverter(new Converter() { - @Override - public Protocol convertToModel(final String value, final Class targetType, - final Locale locale) { - return null; - } - - @Override - public String convertToPresentation(final Protocol value, final Class targetType, - final Locale locale) { - switch (value) { - case DDI_HTTP: - return "DDI API (http)"; - case DMF_AMQP: - return "DMF API (amqp)"; - default: - return "unknown"; - } - } - - @Override - public Class getModelType() { - return Protocol.class; - } - - @Override - public Class getPresentationType() { - return String.class; - } - }); - grid.getColumn("status").setRenderer(new HtmlRenderer(), new Converter() { - private static final long serialVersionUID = 1L; - - @Override - public Status convertToModel(final String value, final Class targetType, - final Locale locale) { - return null; - } - - @Override - public String convertToPresentation(final Status value, final Class targetType, - final Locale locale) { - String style = null; - switch (value) { - case UNKNWON: - style = "&#x" - + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; - break; - case PEDNING: - style = "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) - + ";"; - break; - case FINISH: - style = "&#x" - + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; - break; - case ERROR: - style = "&#x" - + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; - break; - default: - throw new IllegalStateException("unknown value"); - } - return style; - } - - @Override - public Class getModelType() { - return Status.class; - } - - @Override - public Class getPresentationType() { - return String.class; - } - }); + grid.getColumn("protocol").setConverter(createProtocolConverter()); + grid.getColumn("status").setRenderer(new HtmlRenderer(), createStatusConverter()); grid.removeColumn("tenant"); // grid combobox @@ -205,13 +128,9 @@ public class SimulatorView extends VerticalLayout implements View { responseComboBox.setItemIcon(ResponseStatus.ERROR, FontAwesome.EXCLAMATION_CIRCLE); responseComboBox.setNullSelectionAllowed(false); responseComboBox.setValue(ResponseStatus.SUCCESSFUL); - responseComboBox.addValueChangeListener(new ValueChangeListener() { - @Override - public void valueChange(final ValueChangeEvent event) { - beanContainer.getItemIds().forEach( - itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") - .setValue(event.getProperty().getValue())); - } + responseComboBox.addValueChangeListener(valueChangeEvent -> { + beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") + .setValue(valueChangeEvent.getProperty().getValue())); }); // add all components @@ -338,4 +257,90 @@ public class SimulatorView extends VerticalLayout implements View { } })); } + + private Converter createProtocolConverter() { + + return new Converter() { + + private static final long serialVersionUID = 1L; + + @Override + public Protocol convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Protocol value, final Class targetType, + final Locale locale) { + switch (value) { + case DDI_HTTP: + return "DDI API (http)"; + case DMF_AMQP: + return "DMF API (amqp)"; + default: + return "unknown"; + } + } + + @Override + public Class getModelType() { + return Protocol.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }; + + } + + private Converter createStatusConverter() { + return new Converter() { + private static final long serialVersionUID = 1L; + + @Override + public Status convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Status value, final Class targetType, + final Locale locale) { + switch (value) { + case UNKNWON: + return "&#x" + + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; + case PEDNING: + return "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) + + ";"; + case FINISH: + return "&#x" + + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; + case ERROR: + return "&#x" + + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; + default: + throw new IllegalStateException("unknown value"); + } + } + + @Override + public Class getModelType() { + return Status.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }; + } + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index aad0839cd..77f71bebf 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -302,13 +302,10 @@ public class DeploymentManagement { .collect(Collectors.toMap(TargetWithActionType::getTargetId, Function.identity())); // split tIDs length into max entries in-statement because many database - // have constraint of - // max entries in in-statements e.g. Oracle with maximum 1000 elements, - // so we need to split - // the entries here and execute multiple statements - // we take the target only into account if the requested operation is no - // duplicate of a - // previous one + // have constraint of max entries in in-statements e.g. Oracle with + // maximum 1000 elements, so we need to split the entries here and + // execute multiple statements we take the target only into account if + // the requested operation is no duplicate of a previous one final List targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream() .map(ids -> targetRepository .findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId()))) @@ -326,10 +323,9 @@ public class DeploymentManagement { targets.stream().map(Target::getId).collect(Collectors.toList()), Constants.MAX_ENTRIES_IN_STATEMENT); // override all active actions and set them into canceling state, we - // need to remember which - // one we have been switched to canceling state because for targets - // which we have changed to - // canceling we don't want to publish the new action update event. + // need to remember which one we have been switched to canceling state + // because for targets which we have changed to canceling we don't want + // to publish the new action update event. final Set targetIdsCancellList = new HashSet<>(); targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids))); @@ -349,27 +345,15 @@ public class DeploymentManagement { targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSet(set, System.currentTimeMillis(), currentUser, tIds)); targetIds.forEach(tIds -> targetInfoRepository.setTargetUpdateStatus(TargetUpdateStatus.PENDING, tIds)); + final Map targetIdsToActions = actionRepository + .save(targets.stream().map(t -> createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup)) + .collect(Collectors.toList())) + .stream().collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); - final Map targetIdsToActions = actionRepository.save(targets.stream().map(t -> { - final Action tAction = new Action(); - final TargetWithActionType targetWithActionType = targetsWithActionMap.get(t.getControllerId()); - tAction.setActionType(targetWithActionType.getActionType()); - tAction.setForcedTime(targetWithActionType.getForceTime()); - tAction.setActive(true); - tAction.setStatus(Status.RUNNING); - tAction.setTarget(t); - tAction.setDistributionSet(set); - tAction.setRollout(rollout); - tAction.setRolloutGroup(rolloutGroup); - return tAction; - }).collect(Collectors.toList())).stream() - .collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); - - // create initial action status when action is created so we - // remember the initial - // running status because we will change the status of the action itself - // and with this action - // status we have a nicer action history. + // create initial action status when action is created so we remember + // the initial running status because we will change the status + // of the action itself and with this action status we have a nicer + // action history. targetIdsToActions.values().forEach(action -> { final ActionStatus actionStatus = new ActionStatus(); actionStatus.setAction(action); @@ -405,6 +389,21 @@ public class DeploymentManagement { softwareModules)); } + private Action createTargetAction(final Map targetsWithActionMap, final Target target, + final DistributionSet set, final Rollout rollout, final RolloutGroup rolloutGroup) { + final Action actionForTarget = new Action(); + final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId()); + actionForTarget.setActionType(targetWithActionType.getActionType()); + actionForTarget.setForcedTime(targetWithActionType.getForceTime()); + actionForTarget.setActive(true); + actionForTarget.setStatus(Status.RUNNING); + actionForTarget.setTarget(target); + actionForTarget.setDistributionSet(set); + actionForTarget.setRollout(rollout); + actionForTarget.setRolloutGroup(rolloutGroup); + return actionForTarget; + } + /** * Sends the {@link TargetAssignDistributionSetEvent} for a specific target * to the {@link EventBus}.