diff --git a/examples/hawkbit-device-simulator/README.md b/examples/hawkbit-device-simulator/README.md index 1ba29bb7e..0d73f3f3f 100644 --- a/examples/hawkbit-device-simulator/README.md +++ b/examples/hawkbit-device-simulator/README.md @@ -24,6 +24,12 @@ The simulator has user authentication enabled in **cloud profile**. Default cred This can be configured/disabled by spring boot properties +## hawkBit APIs + +The simulator supports `DDI` as well as the `DMF` integration APIs. +In case there is no AMQP message broker (like rabbitMQ) running, you can disable the AMQP support for the device simulator, so the simulator is not trying to connect to an amqp message broker. +Configuration property `hawkbit.device.simulator.amqp.enabled=true` + ## Usage ### Graphical User Interface diff --git a/examples/hawkbit-device-simulator/cf/manifest.yml b/examples/hawkbit-device-simulator/cf/manifest.yml index 51a43ace6..188ef7780 100644 --- a/examples/hawkbit-device-simulator/cf/manifest.yml +++ b/examples/hawkbit-device-simulator/cf/manifest.yml @@ -17,6 +17,4 @@ applications: services: - dmf-rabbit env: - SPRING_PROFILES_ACTIVE: cloud,amqp - CF_STAGING_TIMEOUT: 15 - CF_STARTUP_TIMEOUT: 15 + SPRING_PROFILES_ACTIVE: cloud diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 9c4c43ad2..01a9231e3 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -8,9 +8,12 @@ */ package org.eclipse.hawkbit.simulator; +import static java.util.concurrent.Executors.newScheduledThreadPool; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.security.DigestOutputStream; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -20,7 +23,6 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -56,9 +58,10 @@ import com.google.common.io.ByteStreams; */ @Service public class DeviceSimulatorUpdater { + private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSimulatorUpdater.class); - private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(8); + private static final ScheduledExecutorService threadPool = newScheduledThreadPool(8); @Autowired private SpSenderService spSenderService; @@ -118,14 +121,9 @@ public class DeviceSimulatorUpdater { } private static final class DeviceSimulatorUpdateThread implements Runnable { - /** - * - */ + private static final String BUT_GOT_LOG_MESSAGE = " but got: "; - /** - * - */ private static final String DOWNLOAD_LOG_MESSAGE = "Download "; private static final int MINIMUM_TOKENLENGTH_FOR_HINT = 6; @@ -279,13 +277,17 @@ public class DeviceSimulatorUpdater { private static long getOverallRead(final CloseableHttpResponse response, final MessageDigest md) throws IOException { + long overallread; - try (final BufferedOutputStream bdos = new BufferedOutputStream( - new DigestOutputStream(ByteStreams.nullOutputStream(), md))) { + + try (final OutputStream os = ByteStreams.nullOutputStream(); + final BufferedOutputStream bos = new BufferedOutputStream(new DigestOutputStream(os, md))) { + try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { - overallread = ByteStreams.copy(bis, bdos); + overallread = ByteStreams.copy(bis, bos); } } + return overallread; } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java index 8d5c5e0b1..3a8690cb3 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java @@ -72,6 +72,7 @@ public class SimulatedDeviceFactory { final int pollDelaySec, final URL baseEndpoint, final String gatewayToken) { switch (protocol) { case DMF_AMQP: + spSenderService.createOrUpdateThing(tenant, id); return new DMFSimulatedDevice(id, tenant, spSenderService, pollDelaySec); case DDI_HTTP: final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger()) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index 43f954c00..b100cb28f 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -8,11 +8,13 @@ */ package org.eclipse.hawkbit.simulator; +import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX; + import java.net.MalformedURLException; import java.net.URL; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; -import org.eclipse.hawkbit.simulator.amqp.SpSenderService; +import org.eclipse.hawkbit.simulator.amqp.AmqpProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -21,22 +23,19 @@ import org.springframework.web.bind.annotation.RestController; /** * REST endpoint for controlling the device simulator. - * - * - * */ @RestController public class SimulationController { - @Autowired - private SpSenderService spSenderService; - @Autowired private DeviceSimulatorRepository repository; @Autowired private SimulatedDeviceFactory deviceFactory; + @Autowired + private AmqpProperties amqpProperties; + /** * The start resource to start a device creation. * @@ -82,6 +81,12 @@ public class SimulationController { return ResponseEntity.badRequest().body("query param api only allows value of 'dmf' or 'ddi'"); } + if (protocol == Protocol.DMF_AMQP && isDmfDisabled()) { + return ResponseEntity.badRequest() + .body("The AMQP interface has been disabled, to use DMF protocol you need to enable the AMQP interface via '" + + CONFIGURATION_PREFIX + ".enabled=true'"); + } + for (int i = 0; i < amount; i++) { final String deviceId = name + i; repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint), @@ -90,4 +95,8 @@ public class SimulationController { return ResponseEntity.ok("Updated " + amount + " DMF connected targets!"); } + + private boolean isDmfDisabled() { + return !amqpProperties.isEnabled(); + } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java index 486eb4b5e..69ff765d0 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatorStartup.java @@ -11,7 +11,7 @@ package org.eclipse.hawkbit.simulator; import java.net.MalformedURLException; import java.net.URL; -import org.eclipse.hawkbit.simulator.amqp.SpSenderService; +import org.eclipse.hawkbit.simulator.amqp.AmqpProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,24 +32,27 @@ public class SimulatorStartup implements ApplicationListener { for (int i = 0; i < autostart.getAmount(); i++) { final String deviceId = autostart.getName() + i; try { - repository.add(deviceFactory.createSimulatedDevice(deviceId, autostart.getTenant(), - autostart.getApi(), autostart.getPollDelay(), new URL(autostart.getEndpoint()), - autostart.getGatewayToken())); + if (amqpProperties.isEnabled()) { + repository.add(deviceFactory.createSimulatedDevice(deviceId, autostart.getTenant(), + autostart.getApi(), autostart.getPollDelay(), new URL(autostart.getEndpoint()), + autostart.getGatewayToken())); + } + } catch (final MalformedURLException e) { LOGGER.error("Creation of simulated device at startup failed.", e); } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java index edaf9d11a..5e9b3e049 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.simulator.amqp; +import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX; + import java.time.Duration; import java.util.HashMap; import java.util.Map; @@ -27,6 +29,7 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,6 +42,7 @@ import org.springframework.retry.support.RetryTemplate; */ @Configuration @EnableConfigurationProperties(AmqpProperties.class) +@ConditionalOnProperty(prefix = CONFIGURATION_PREFIX, name = "enabled") public class AmqpConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(AmqpConfiguration.class); diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java index 8fba3ac5b..3730a3350 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java @@ -19,6 +19,17 @@ import org.springframework.stereotype.Component; @Component @ConfigurationProperties("hawkbit.device.simulator.amqp") public class AmqpProperties { + + /** + * The prefix for this configuration. + */ + public static final String CONFIGURATION_PREFIX = "hawkbit.device.simulator.amqp"; + + /** + * Indicates if the AMQP interface is enabled for the device simulator. + */ + private boolean enabled; + /** * Queue for receiving DMF messages from update server. */ @@ -84,4 +95,12 @@ public class AmqpProperties { public void setDeadLetterTtl(final int deadLetterTtl) { this.deadLetterTtl = deadLetterTtl; } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java index 7d55fb375..8dac6d4bc 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.simulator.amqp; +import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX; + import java.util.Map; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; @@ -22,6 +24,7 @@ import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; @@ -32,6 +35,7 @@ import com.google.common.collect.Lists; * */ @Component +@ConditionalOnProperty(prefix = CONFIGURATION_PREFIX, name = "enabled") public class SpReceiverService extends ReceiverService { private static final Logger LOGGER = LoggerFactory.getLogger(ReceiverService.class); 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 b50098829..6d1806463 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 @@ -52,6 +52,7 @@ public class GenerateDialog extends Window { private final TextField gatewayTokenTextField; private OptionGroup protocolGroup; private Button buttonOk; + private final boolean dmfEnabled; /** * Creates a new pop window for setting the configuration of simulating @@ -60,9 +61,12 @@ public class GenerateDialog extends Window { * @param callback * the callback which is called when the dialog has been * successfully confirmed. + * @param dmfEnabled + * indicates if the AMQP/DMF interface is enabled by + * configuration and if the option DMF should be enabled or not */ - public GenerateDialog(final GenerateDialogCallback callback) { - + public GenerateDialog(final GenerateDialogCallback callback, final boolean dmfEnabled) { + this.dmfEnabled = dmfEnabled; formLayout.setSpacing(true); formLayout.setMargin(true); @@ -187,15 +191,19 @@ public class GenerateDialog extends Window { this.protocolGroup = new OptionGroup("Simulated Device Protocol"); protocolGroup.addItem(Protocol.DMF_AMQP); protocolGroup.addItem(Protocol.DDI_HTTP); + protocolGroup.select(Protocol.DMF_AMQP); 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); pollUrlTextField.setVisible(directDeviceOptionSelected); gatewayTokenTextField.setVisible(directDeviceOptionSelected); }); + protocolGroup.setItemEnabled(Protocol.DMF_AMQP, dmfEnabled); + if (!dmfEnabled) { + protocolGroup.select(Protocol.DDI_HTTP); + } } private void createOkButton(final GenerateDialogCallback callback) { 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 686945407..4a81547c5 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 @@ -17,6 +17,7 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status; import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository; import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory; import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus; +import org.eclipse.hawkbit.simulator.amqp.AmqpProperties; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.eclipse.hawkbit.simulator.event.InitUpdate; import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; @@ -90,6 +91,9 @@ public class SimulatorView extends VerticalLayout implements View { @Autowired private transient EventBus eventbus; + @Autowired + private transient AmqpProperties amqpProperties; + private final Label caption = new Label("DMF/DDI Simulated Devices"); private final HorizontalLayout toolbar = new HorizontalLayout(); private final Grid grid = new Grid(); @@ -266,9 +270,8 @@ public class SimulatorView extends VerticalLayout implements View { final String deviceId = namePrefix + index; beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken))); - spSenderService.createOrUpdateThing(tenant, deviceId); } - })); + }, amqpProperties.isEnabled())); } private ProtocolConverter createProtocolConverter() { diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index 5d3f04be7..60fa817e2 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -8,6 +8,7 @@ # ## Configuration for DMF communication +hawkbit.device.simulator.amqp.enabled=true hawkbit.device.simulator.amqp.receiverConnectorQueueFromSp=simulator_receiver hawkbit.device.simulator.amqp.deadLetterQueue=simulator_deadletter hawkbit.device.simulator.amqp.deadLetterExchange=simulator.deadletter diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java index 574ce61e2..d8fd4281c 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java @@ -16,9 +16,10 @@ import java.util.Map; * An interface for declaring the name of the field described in the database * which is used as string representation of the field, e.g. for sorting the * fields over REST. - * */ +@FunctionalInterface public interface FieldNameProvider { + /** * Separator for the sub attributes */ @@ -32,64 +33,18 @@ public interface FieldNameProvider { /** * Contains the sub entity the given field. - * + * * @param propertyField * the given field * @return contains contains not */ default boolean containsSubEntityAttribute(final String propertyField) { - return FieldNameProvider.containsSubEntityAttribute(propertyField, getSubEntityAttributes()); - } - /** - * - * @return all sub entities attributes. - */ - default List getSubEntityAttributes() { - return Collections.emptyList(); - } - - /** - * the database column for the key - * - * @return key fieldname - */ - default String getKeyFieldName() { - return null; - } - - /** - * the database column for the value - * - * @return key fieldname - */ - default String getValueFieldName() { - return null; - } - - /** - * Is the entity field a {@link Map}. - * - * @return is a map is not a map - */ - default boolean isMap() { - return getKeyFieldName() != null; - } - - /** - * Check if a sub attribute exists. - * - * @param propertyField - * the sub property field. - * @param subEntityAttribues - * the list of available properties - * @return property exists not exists - */ - static boolean containsSubEntityAttribute(final String propertyField, final List subEntityAttribues) { - if (subEntityAttribues.contains(propertyField)) { + final List subEntityAttributes = getSubEntityAttributes(); + if (subEntityAttributes.contains(propertyField)) { return true; } - for (final String attribute : subEntityAttribues) { + for (final String attribute : subEntityAttributes) { final String[] graph = attribute.split("\\" + SUB_ATTRIBUTE_SEPERATOR); for (final String subAttribute : graph) { @@ -102,4 +57,37 @@ public interface FieldNameProvider { return false; } + /** + * @return all sub entities attributes. + */ + default List getSubEntityAttributes() { + return Collections.emptyList(); + } + + /** + * The database column for the key + * + * @return key fieldname + */ + default String getKeyFieldName() { + return null; + } + + /** + * The database column for the value + * + * @return key fieldname + */ + default String getValueFieldName() { + return null; + } + + /** + * Is the entity field a {@link Map}. + * + * @return is a map is not a map + */ + default boolean isMap() { + return getKeyFieldName() != null; + } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 8906bc96c..871a4973a 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -41,6 +41,7 @@ import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; +import org.eclipse.hawkbit.repository.exception.ToManyStatusEntriesException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; @@ -153,8 +154,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService { return handleAuthentifiactionMessage(message); } catch (final IllegalArgumentException ex) { throw new AmqpRejectAndDontRequeueException("Invalid message!", ex); - } catch (final TenantNotExistException teex) { - throw new AmqpRejectAndDontRequeueException(teex); + } catch (final TenantNotExistException | ToManyStatusEntriesException e) { + throw new AmqpRejectAndDontRequeueException(e); } finally { SecurityContextHolder.setContext(oldContext); } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetAttributes.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetAttributes.java index dad8e868f..ea01b87ca 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetAttributes.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetAttributes.java @@ -8,8 +8,9 @@ import java.util.Map; /** * {@link Map} with attributes of SP Target. - * */ public class MgmtTargetAttributes extends HashMap { + private static final long serialVersionUID = 1L; + } diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index f647106af..57e2c096d 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -52,9 +52,7 @@ import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.eclipse.hawkbit.rest.util.JsonBuilder; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.eclipse.hawkbit.util.IpUtil; -import org.json.JSONException; import org.json.JSONObject; -import org.junit.Ignore; import org.junit.Test; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; @@ -80,7 +78,6 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { private static final String TARGET_DESCRIPTION_TEST = "created in test"; - // json paths private static final String JSON_PATH_ROOT = "$"; // fields, attributes @@ -633,69 +630,8 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { final String knownName = "someName"; createSingleTarget(knownControllerId, knownName); - // test - mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId + "/installedDS")) .andExpect(status().isNoContent()).andExpect(content().string("")); - - } - - @Test - @Ignore - public void getInstalledDistributionSetOfTarget() throws JSONException, Exception { - // create first a target which can be retrieved by rest interface - final String knownControllerId = "1"; - final String knownName = "someName"; - createSingleTarget(knownControllerId, knownName); - final DistributionSet ds = testdataFactory.createDistributionSet(""); - // assign ds to target - final Long actionId = deploymentManagement.assignDistributionSet(ds.getId(), knownControllerId).getActions() - .get(0); - // give feedback, so installedDS is in SNYC - feedbackToByInSync(actionId); - // test - - final SoftwareModule os = ds.findFirstModuleByType(osType); - final SoftwareModule jvm = ds.findFirstModuleByType(runtimeType); - final SoftwareModule bApp = ds.findFirstModuleByType(appType); - mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId + "/installedDS")) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath(JSON_PATH_ID, equalTo(ds.getId().intValue()))) - .andExpect(jsonPath(JSON_PATH_NAME, equalTo(ds.getName()))) - .andExpect(jsonPath(JSON_PATH_DESCRIPTION, equalTo(ds.getDescription()))) - // os - .andExpect( - jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].id", equalTo(os.getId().intValue()))) - .andExpect(jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].name", equalTo(os.getName()))) - .andExpect(jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].description", - equalTo(os.getDescription()))) - .andExpect( - jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].version", equalTo(os.getVersion()))) - .andExpect(jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].vendor", equalTo(os.getVendor()))) - .andExpect(jsonPath("$modules.[?(@.type==" + osType.getKey() + ")][0].type", equalTo("os"))) - // jvm - .andExpect(jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].id", - equalTo(jvm.getId().intValue()))) - .andExpect( - jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].name", equalTo(jvm.getName()))) - .andExpect(jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].description", - equalTo(jvm.getDescription()))) - .andExpect(jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].version", - equalTo(jvm.getVersion()))) - .andExpect(jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].vendor", - equalTo(jvm.getVendor()))) - .andExpect(jsonPath("$modules.[?(@.type==" + runtimeType.getKey() + ")][0].type", equalTo("runtime"))) - // baseApp - .andExpect(jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].id", - equalTo(bApp.getId().intValue()))) - .andExpect(jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].name", equalTo(bApp.getName()))) - .andExpect(jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].description", - equalTo(bApp.getDescription()))) - .andExpect(jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].version", - equalTo(bApp.getVersion()))) - .andExpect( - jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].vendor", equalTo(bApp.getVendor()))) - .andExpect(jsonPath("$modules.[?(@.type==" + appType.getKey() + ")][0].type", equalTo("application"))); } @Test diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java index 1784606bd..d8a6f54fe 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java @@ -281,6 +281,16 @@ public interface DistributionSetManagement { Page findDistributionSetMetadataByDistributionSetId(@NotNull Long distributionSetId, @NotNull Pageable pageable); + /** + * Finds all meta data by the given distribution set id. + * + * @param distributionSetId + * the distribution set id to retrieve the meta data from + * @return list of distributionSetMetadata for a given distribution set Id. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) + List findDistributionSetMetadataByDistributionSetId(@NotNull Long distributionSetId); + /** * finds all meta data by the given distribution set id. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java index bc3c50004..03027598e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java @@ -483,5 +483,23 @@ public interface SoftwareManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) SoftwareModuleType updateSoftwareModuleType(@NotNull SoftwareModuleType sm); + + /** + * Finds all meta data by the given software module id. + * + * @param softwareModuleId + * the software module id to retrieve the meta data from + + * + * @throws RSQLParameterUnsupportedFieldException + * if a field in the RSQL string is used but not provided by the + * given {@code fieldNameProvider} + * @throws RSQLParameterSyntaxException + * if the RSQL syntax is wrong + * @return result of all meta data entries for a given software + * module id. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) + List findSoftwareModuleMetadataBySoftwareModuleId(Long softwareModuleId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/AbstractPropertyChangeEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/AbstractPropertyChangeEvent.java index 97cf13b66..2520ebc9c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/AbstractPropertyChangeEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/AbstractPropertyChangeEvent.java @@ -20,18 +20,18 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; public class AbstractPropertyChangeEvent extends AbstractBaseEntityEvent { private static final long serialVersionUID = -3671601415138242311L; - private final transient Map changeSet; + private final transient Map changeSet; /** * Initialize base entity and property changed with old and new value. - * + * * @param baseEntity * entity changed * @param changeSetValues * details of properties changed and old value and new value of * the changed properties */ - public AbstractPropertyChangeEvent(final E baseEntity, final Map changeSetValues) { + public AbstractPropertyChangeEvent(final E baseEntity, final Map changeSetValues) { super(baseEntity); this.changeSet = changeSetValues; } @@ -39,27 +39,27 @@ public class AbstractPropertyChangeEvent extend /** * @return the changeSet */ - public Map getChangeSet() { + public Map getChangeSet() { return changeSet; } /** * Carries old value and new value of a property . - * */ - public class Values { + public static class PropertyChange { + private final Object oldValue; private final Object newValue; /** * Initialize old value and new changes value of property. - * + * * @param oldValue * old value before change * @param newValue * new value after change */ - public Values(final Object oldValue, final Object newValue) { + public PropertyChange(final Object oldValue, final Object newValue) { super(); this.oldValue = oldValue; this.newValue = newValue; @@ -78,6 +78,5 @@ public class AbstractPropertyChangeEvent extend public Object getNewValue() { return newValue; } - } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/ActionPropertyChangeEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/ActionPropertyChangeEvent.java index 84487547e..5197bd9a0 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/ActionPropertyChangeEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/ActionPropertyChangeEvent.java @@ -22,8 +22,7 @@ public class ActionPropertyChangeEvent extends AbstractPropertyChangeEvent.Values> changeSetValues) { + public ActionPropertyChangeEvent(final Action action, final Map changeSetValues) { super(action, changeSetValues); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionCreatedEvent.java new file mode 100644 index 000000000..20515521d --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionCreatedEvent.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.eventbus.event; + +import org.eclipse.hawkbit.repository.model.DistributionSet; + +/** + * Defines the {@link AbstractBaseEntityEvent} of creating a new {@link DistributionSet}. + * + */ +public class DistributionCreatedEvent extends AbstractBaseEntityEvent { + + private static final long serialVersionUID = 1L; + + /** + * @param distributionSet + * the distributionSet which has been created + */ + public DistributionCreatedEvent(final DistributionSet distributionSet) { + super(distributionSet); + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionDeletedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionDeletedEvent.java new file mode 100644 index 000000000..47c8c8435 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionDeletedEvent.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.eventbus.event; + +import org.eclipse.hawkbit.eventbus.event.AbstractDistributedEvent; +import org.eclipse.hawkbit.repository.model.DistributionSet; + +/** + * Defines the {@link AbstractDistributedEvent} for deletion of + * {@link DistributionSet}. + */ +public class DistributionDeletedEvent extends AbstractDistributedEvent { + private static final long serialVersionUID = -3308850381757843098L; + private final Long distributionId; + + /** + * @param tenant + * the tenant for this event + * @param distributionId + * the ID of the distribution set which has been deleted + */ + public DistributionDeletedEvent(final String tenant, final Long distributionId) { + super(-1, tenant); + this.distributionId = distributionId; + } + + public Long getDistributionSetId() { + return distributionId; + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionSetUpdateEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionSetUpdateEvent.java new file mode 100644 index 000000000..d060e2ee5 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DistributionSetUpdateEvent.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.eventbus.event; + +import org.eclipse.hawkbit.repository.model.DistributionSet; + +/** + * Defines the {@link AbstractBaseEntityEvent} for update a {@link DistributionSet}. + * + */ +public class DistributionSetUpdateEvent extends AbstractBaseEntityEvent { + + private static final long serialVersionUID = 1L; + + + /** + * Constructor + * @param ds Distribution Set + */ + public DistributionSetUpdateEvent(final DistributionSet ds) { + super(ds); + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutGroupPropertyChangeEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutGroupPropertyChangeEvent.java index 887a1c2ff..b0b31b79a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutGroupPropertyChangeEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutGroupPropertyChangeEvent.java @@ -20,12 +20,12 @@ public class RolloutGroupPropertyChangeEvent extends AbstractPropertyChangeEvent private static final long serialVersionUID = 4026477044419472686L; /** - * + * * @param rolloutGroup * @param changeSetValues */ public RolloutGroupPropertyChangeEvent(final RolloutGroup rolloutGroup, - final Map.Values> changeSetValues) { + final Map changeSetValues) { super(rolloutGroup, changeSetValues); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutPropertyChangeEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutPropertyChangeEvent.java index 9287a0fb3..9bb975c3a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutPropertyChangeEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/RolloutPropertyChangeEvent.java @@ -19,12 +19,11 @@ public class RolloutPropertyChangeEvent extends AbstractPropertyChangeEvent.Values> changeSetValues) { + public RolloutPropertyChangeEvent(final Rollout rollout, final Map changeSetValues) { super(rollout, changeSetValues); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetDeletedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetDeletedEvent.java new file mode 100644 index 000000000..637a0dcc5 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetDeletedEvent.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.eventbus.event; + +import org.eclipse.hawkbit.eventbus.event.AbstractDistributedEvent; +import org.eclipse.hawkbit.repository.model.Target; + +/** + * + * Defines the {@link AbstractBaseEntityEvent} of deleting a {@link Target}. + */ +public class TargetDeletedEvent extends AbstractDistributedEvent { + + private static final long serialVersionUID = 1L; + private final long targetId; + + /** + * @param tenant + * the tenant for this event + * @param targetId + * the ID of the target which has been deleted + */ + public TargetDeletedEvent(final String tenant, final long targetId) { + super(-1, tenant); + this.targetId = targetId; + } + + /** + * @return the targetId + */ + public long getTargetId() { + return targetId; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetUpdatedEvent.java new file mode 100644 index 000000000..8ae5718e9 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/TargetUpdatedEvent.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.eventbus.event; + +import org.eclipse.hawkbit.repository.model.Target; + +/** + * Defines the {@link AbstractBaseEntityEvent} of updating a {@link Target}. + * + */ +public class TargetUpdatedEvent extends AbstractBaseEntityEvent { + + private static final long serialVersionUID = 5665118668865832477L; + + /** + * Constructor + * + * @param baseEntity + * Target entity + */ + public TargetUpdatedEvent(final Target baseEntity) { + super(baseEntity); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/AbstractReportSeries.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/AbstractReportSeries.java index 747cd8279..9ba5ba2da 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/AbstractReportSeries.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/AbstractReportSeries.java @@ -12,16 +12,9 @@ import java.io.Serializable; /** * An abstract report series. - * - * - * - * */ public class AbstractReportSeries implements Serializable { - /** - * - */ private static final long serialVersionUID = 1L; private final String name; diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/ListReportSeries.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/ListReportSeries.java index 18b011420..27f5fb3ec 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/ListReportSeries.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/ListReportSeries.java @@ -14,13 +14,11 @@ import java.util.List; /** * A simple list report series which just contains a list of values of a report. - * - * - * - * */ public class ListReportSeries extends AbstractReportSeries { + private static final long serialVersionUID = 1L; + private final List data = new ArrayList<>(); /** @@ -50,8 +48,8 @@ public class ListReportSeries extends AbstractReportSeries { * @param values */ private void setData(final Number... values) { - this.data.clear(); - Collections.addAll(this.data, values); + data.clear(); + Collections.addAll(data, values); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 6236e8d6f..5a2c6fdda 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -82,7 +82,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Modifying; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -599,11 +598,16 @@ public class JpaDeploymentManagement implements DeploymentManagement { @Override public Page findActionsByTarget(final String rsqlParam, final Target target, final Pageable pageable) { - final Specification specification = RSQLUtility.parse(rsqlParam, ActionFields.class); - return convertAcPage(actionRepository.findAll((Specification) (root, query, cb) -> cb - .and(specification.toPredicate(root, query, cb), cb.equal(root.get(JpaAction_.target), target)), - pageable), pageable); + final Specification byTargetSpec = createSpecificationFor(target, rsqlParam); + final Page actions = actionRepository.findAll(byTargetSpec, pageable); + return convertAcPage(actions, pageable); + } + + private Specification createSpecificationFor(final Target target, final String rsqlParam) { + final Specification spec = RSQLUtility.parse(rsqlParam, ActionFields.class); + return (root, query, cb) -> cb.and(spec.toPredicate(root, query, cb), + cb.equal(root.get(JpaAction_.target), target)); } private static Page convertAcPage(final Page findAll, final Pageable pageable) { @@ -642,10 +646,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { @Override public Long countActionsByTarget(final String rsqlParam, final Target target) { - final Specification spec = RSQLUtility.parse(rsqlParam, ActionFields.class); - - return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb), - cb.equal(root.get(JpaAction_.target), target))); + return actionRepository.count(createSpecificationFor(target, rsqlParam)); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java index 0e97d6ff5..e170ccad7 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; import org.eclipse.hawkbit.repository.DistributionSetTypeFields; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TagManagement; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetTagAssigmentResultEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityLockedException; @@ -54,6 +55,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -102,6 +104,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Autowired private AfterTransactionCommitExecutor afterCommit; + @Autowired + private TenantAware tenantAware; + @Override public DistributionSet findDistributionSetByIdWithDetails(final Long distid) { return distributionSetRepository.findOne(DistributionSetSpecification.byId(distid)); @@ -193,6 +198,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { // handle the empty list distributionSetRepository.deleteByIdIn(toHardDelete); } + + Arrays.stream(distributionSetIDs) + .forEach(dsId -> eventBus.post(new DistributionDeletedEvent(tenantAware.getCurrentTenant(), dsId))); } @Override @@ -475,11 +483,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { if (distributionSetMetadataRepository.exists(metadata.getId())) { throwMetadataKeyAlreadyExists(metadata.getId().getKey()); } - // merge base distribution set so optLockRevision gets updated and audit - // log written because - // modifying metadata is modifying the base distribution set itself for - // auditing purposes. - entityManager.merge((JpaDistributionSet) metadata.getDistributionSet()).setLastModifiedAt(0L); + + touch(metadata.getDistributionSet()); return distributionSetMetadataRepository.save(metadata); } @@ -494,7 +499,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { for (final JpaDistributionSetMetadata distributionSetMetadata : metadata) { checkAndThrowAlreadyIfDistributionSetMetadataExists(distributionSetMetadata.getId()); } - metadata.forEach(m -> entityManager.merge((JpaDistributionSet) m.getDistributionSet()).setLastModifiedAt(0L)); + metadata.forEach(m -> touch(m.getDistributionSet())); return new ArrayList<>( (Collection) distributionSetMetadataRepository.save(metadata)); @@ -510,7 +515,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { findOne(metadata.getDistributionSet(), metadata.getKey()); // touch it to update the lock revision because we are modifying the // DS indirectly - entityManager.merge((JpaDistributionSet) metadata.getDistributionSet()).setLastModifiedAt(0L); + touch(metadata.getDistributionSet()); return distributionSetMetadataRepository.save(metadata); } @@ -518,13 +523,29 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Modifying public void deleteDistributionSetMetadata(final DistributionSet distributionSet, final String key) { + touch(distributionSet); distributionSetMetadataRepository.delete(new DsMetadataCompositeKey(distributionSet, key)); } + /** + * Method to get the latest distribution set based on ds ID after the + * metadata changes for that distribution set. + * + * @param distributionSet + * Distribution set + */ + private void touch(final DistributionSet distributionSet) { + final DistributionSet latestDistributionSet = findDistributionSetById(distributionSet.getId()); + // merge base distribution set so optLockRevision gets updated and audit + // log written because + // modifying metadata is modifying the base distribution set itself for + // auditing purposes. + entityManager.merge((JpaDistributionSet) latestDistributionSet).setLastModifiedAt(0L); + } + @Override public Page findDistributionSetMetadataByDistributionSetId(final Long distributionSetId, final Pageable pageable) { - return convertMdPage(distributionSetMetadataRepository .findAll((Specification) (root, query, cb) -> cb.equal( root.get(JpaDistributionSetMetadata_.distributionSet).get(JpaDistributionSet_.id), @@ -532,6 +553,14 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { pageable); } + @Override + public List findDistributionSetMetadataByDistributionSetId(final Long distributionSetId) { + return new ArrayList<>(distributionSetMetadataRepository + .findAll((Specification) (root, query, cb) -> cb.equal( + root.get(JpaDistributionSetMetadata_.distributionSet).get(JpaDistributionSet_.id), + distributionSetId))); + } + @Override public Page findDistributionSetMetadataByDistributionSetId(final Long distributionSetId, final String rsqlParam, final Pageable pageable) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index 239302a70..3a903ea89 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -599,7 +599,7 @@ public class JpaRolloutManagement implements RolloutManagement { /** * Get count of targets in different status in rollout. * - * @param page + * @param pageable * the page request to sort and limit the result * @return a list of rollouts with details of targets count for different * statuses diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java index b561667a9..3f8c1a54b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java @@ -621,6 +621,16 @@ public class JpaSoftwareManagement implements SoftwareManagement { pageable); } + @Override + public List findSoftwareModuleMetadataBySoftwareModuleId(final Long softwareModuleId) { + return new ArrayList<>( + softwareModuleMetadataRepository + .findAll((Specification) (root, query, + cb) -> cb.and(cb.equal( + root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), + softwareModuleId)))); + } + @Override public SoftwareModuleMetadata findSoftwareModuleMetadata(final SoftwareModule softwareModule, final String key) { return findSoftwareModuleMetadata(new SwMetadataCompositeKey(softwareModule, key)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java index 65ed6faa4..6438e19f6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java @@ -26,7 +26,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specifications; import org.springframework.data.jpa.repository.Modifying; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 1cd2ffc3c..225c0b4b2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -29,6 +29,7 @@ import javax.persistence.criteria.Root; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.eventbus.event.TargetDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetTagAssigmentResultEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; @@ -48,6 +49,7 @@ import org.eclipse.hawkbit.repository.model.TargetIdName; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; @@ -94,6 +96,9 @@ public class JpaTargetManagement implements TargetManagement { @Autowired private EventBus eventBus; + @Autowired + private TenantAware tenantAware; + @Autowired private AfterTransactionCommitExecutor afterCommit; @@ -206,6 +211,8 @@ public class JpaTargetManagement implements TargetManagement { targetInfoRepository.deleteByTargetIdIn(targetsForCurrentTenant); targetRepository.deleteByIdIn(targetsForCurrentTenant); } + targetsForCurrentTenant + .forEach(targetId -> eventBus.post(new TargetDeletedEvent(tenantAware.getCurrentTenant(), targetId))); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java index be21cdfe7..c18f614fd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java @@ -46,6 +46,7 @@ import org.springframework.transaction.TransactionSystemException; */ @Aspect public class ExceptionMappingAspectHandler implements Ordered { + private static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingAspectHandler.class); private static final Map EXCEPTION_MAPPING = new HashMap<>(); @@ -63,6 +64,7 @@ public class ExceptionMappingAspectHandler implements Ordered { private final SQLStateSQLExceptionTranslator sqlStateExceptionTranslator = new SQLStateSQLExceptionTranslator(); static { + MAPPED_EXCEPTION_ORDER.add(DuplicateKeyException.class); MAPPED_EXCEPTION_ORDER.add(DataIntegrityViolationException.class); MAPPED_EXCEPTION_ORDER.add(ConcurrencyFailureException.class); @@ -90,9 +92,11 @@ public class ExceptionMappingAspectHandler implements Ordered { + " || execution( * org.eclipse.hawkbit.controller.*.*(..)) " + " || execution( * org.eclipse.hawkbit.rest.resource.*.*(..)) " + " || execution( * org.eclipse.hawkbit.service.*.*(..)) )", throwing = "ex") - // Exception squid:S00112 - Is aspectJ proxy - @SuppressWarnings({ "squid:S00112" }) + // Exception for squid:S00112, squid:S1162 + // It is a AspectJ proxy which deals with exceptions. + @SuppressWarnings({ "squid:S00112", "squid:S1162" }) public void catchAndWrapJpaExceptionsService(final Exception ex) throws Throwable { + LOG.trace("exception occured", ex); Exception translatedAccessException = translateEclipseLinkExceptionIfPossible(ex); @@ -122,6 +126,7 @@ public class ExceptionMappingAspectHandler implements Ordered { break; } } + LOG.trace("mapped exception {} to {}", translatedAccessException.getClass(), mappingException.getClass()); throw mappingException; } @@ -138,7 +143,7 @@ public class ExceptionMappingAspectHandler implements Ordered { * translate the exception by the sql error code. Luckily, there we can use * {@link SQLStateSQLExceptionTranslator} if we can get a * {@link SQLException}. - * + * * @param accessException * the base access exception from jpa * @return the translated accessException diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/eventbus/EntityChangeEventListener.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/eventbus/EntityChangeEventListener.java deleted file mode 100644 index 5f41063d2..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/eventbus/EntityChangeEventListener.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * 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.eclipse.hawkbit.repository.jpa.eventbus; - -import java.util.Collection; - -import javax.persistence.EntityManager; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.eclipse.hawkbit.eventbus.event.TargetDeletedEvent; -import org.eclipse.hawkbit.repository.eventbus.event.TargetCreatedEvent; -import org.eclipse.hawkbit.repository.eventbus.event.TargetInfoUpdateEvent; -import org.eclipse.hawkbit.repository.jpa.TargetRepository; -import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetInfo; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.google.common.eventbus.EventBus; - -/** - * An aspect implementation which wraps the necessary repository services for - * saving {@link TenantAwareBaseEntity}s to publish create or update events. - * - * - * - * - */ -@Service -@Aspect -public class EntityChangeEventListener { - - @Autowired - private EventBus eventBus; - - @Autowired - private TenantAware tenantAware; - - @Autowired - private EntityManager entityManager; - - @Autowired - private AfterTransactionCommitExecutor afterCommit; - - /** - * In case the a {@link Target} is created a corresponding - * {@link TargetInfo} is created as well. We need the {@link TargetInfo} - * information in the target created event. So we are listening to the - * {@link TargetInfo} creation to indicate if an Target has been created. - * - * @param joinpoint - * the aspect join point - * @return the object of the {@link ProceedingJoinPoint#proceed()} - * @throws Throwable - * in case exception happens in the - * {@link ProceedingJoinPoint#proceed()} - */ - @Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetInfoRepository.save(..))") - // Exception squid:S00112 - Is aspectJ proxy - @SuppressWarnings({ "squid:S00112" }) - public Object targetCreated(final ProceedingJoinPoint joinpoint) throws Throwable { - final boolean isNew = isTargetInfoNew(joinpoint.getArgs()[0]); - final Object result = joinpoint.proceed(); - if (result instanceof TargetInfo) { - if (isNew) { - notifyTargetCreated(entityManager.merge(entityManager.merge(((TargetInfo) result).getTarget()))); - } else { - notifyTargetInfoChanged((TargetInfo) result); - } - } - return result; - } - - /** - * Proxy method around the delete method of the {@link TargetRepository} to - * notify the {@link TargetDeletedEvent} in case targets has been deleted. - * - * @param joinpoint - * the aspect join point - * @return the object of the {@link ProceedingJoinPoint#proceed()} - * @throws Throwable - * in case exception happens in the - * {@link ProceedingJoinPoint#proceed()} - */ - @Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.deleteByIdIn(..))") - // Exception squid:S00112 - Is aspectJ proxy - @SuppressWarnings({ "squid:S00112" }) - public Object targetDeletedById(final ProceedingJoinPoint joinpoint) throws Throwable { - final String currentTenant = tenantAware.getCurrentTenant(); - final Object result = joinpoint.proceed(); - final Collection targetIds = (Collection) joinpoint.getArgs()[0]; - targetIds.forEach(targetId -> notifyTargetDeleted(currentTenant, targetId)); - return result; - } - - /** - * Proxy method around the delete method of the {@link TargetRepository} to - * notify the {@link TargetDeletedEvent} in case targets has been deleted. - * - * @param joinpoint - * the aspect join point - * @return the object of the {@link ProceedingJoinPoint#proceed()} - * @throws Throwable - * in case exception happens in the - * {@link ProceedingJoinPoint#proceed()} - */ - @Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.delete(..))") - // Exception squid:S00112 - Is aspectJ proxy - @SuppressWarnings({ "squid:S00112", "unchecked" }) - public Object targetDeleted(final ProceedingJoinPoint joinpoint) throws Throwable { - final String currentTenant = tenantAware.getCurrentTenant(); - final Object result = joinpoint.proceed(); - final Object param = joinpoint.getArgs()[0]; - // delete by id - if (param instanceof Long) { - notifyTargetDeleted(currentTenant, (Long) param); - } else if (param instanceof Target) { - notifyTargetDeleted(currentTenant, ((Target) param).getId()); - } else if (param instanceof Iterable) { - ((Iterable) param).forEach(target -> notifyTargetDeleted(currentTenant, target.getId())); - } - return result; - } - - private void notifyTargetCreated(final Target t) { - afterCommit.afterCommit(() -> eventBus.post(new TargetCreatedEvent(t))); - - } - - private void notifyTargetInfoChanged(final TargetInfo targetInfo) { - afterCommit.afterCommit(() -> eventBus.post(new TargetInfoUpdateEvent(targetInfo))); - } - - private void notifyTargetDeleted(final String tenant, final Long targetId) { - afterCommit.afterCommit(() -> eventBus.post(new TargetDeletedEvent(tenant, targetId))); - } - - private static boolean isTargetInfoNew(final Object targetInfo) { - return ((JpaTargetInfo) targetInfo).isNew(); - } - -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java index ca8ac60df..ef1868c3f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaBaseEntity.java @@ -179,4 +179,5 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity { return true; } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityPropertyChangeListener.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityPropertyChangeListener.java index 2dd0fdb40..429c128dd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityPropertyChangeListener.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EntityPropertyChangeListener.java @@ -8,79 +8,47 @@ */ package org.eclipse.hawkbit.repository.jpa.model; -import java.util.Map; -import java.util.stream.Collectors; - -import org.eclipse.hawkbit.repository.eventbus.event.AbstractPropertyChangeEvent; -import org.eclipse.hawkbit.repository.eventbus.event.ActionCreatedEvent; -import org.eclipse.hawkbit.repository.eventbus.event.ActionPropertyChangeEvent; -import org.eclipse.hawkbit.repository.eventbus.event.RolloutGroupPropertyChangeEvent; -import org.eclipse.hawkbit.repository.eventbus.event.RolloutPropertyChangeEvent; -import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; -import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; -import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.Rollout; -import org.eclipse.hawkbit.repository.model.RolloutGroup; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEventAdapter; -import org.eclipse.persistence.internal.sessions.ObjectChangeSet; -import org.eclipse.persistence.queries.UpdateObjectQuery; -import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; - -import com.google.common.eventbus.EventBus; /** - * Listens to change in property values of an entity. + * Listens to change in property values of an entity and calls the corresponding + * {@link EventAwareEntity}. * */ public class EntityPropertyChangeListener extends DescriptorEventAdapter { @Override public void postInsert(final DescriptorEvent event) { - if (event.getObject().getClass().equals(Action.class)) { - final Action action = (Action) event.getObject(); - if (action.getRollout() != null) { - final EventBus eventBus = getEventBus(); - final AfterTransactionCommitExecutor afterCommit = getAfterTransactionCommmitExecutor(); - afterCommit.afterCommit(() -> eventBus.post(new ActionCreatedEvent(action))); - } + final Object object = event.getObject(); + if (isEventAwareEntity(object)) { + doNotifiy(() -> ((EventAwareEntity) object).fireCreateEvent(event)); } - } @Override public void postUpdate(final DescriptorEvent event) { - if (event.getObject().getClass().equals(JpaAction.class)) { - getAfterTransactionCommmitExecutor().afterCommit(() -> getEventBus().post( - new ActionPropertyChangeEvent((Action) event.getObject(), getChangeSet(Action.class, event)))); - } else if (event.getObject().getClass().equals(JpaRollout.class)) { - getAfterTransactionCommmitExecutor().afterCommit(() -> getEventBus().post( - new RolloutPropertyChangeEvent((Rollout) event.getObject(), getChangeSet(Rollout.class, event)))); - } else if (event.getObject().getClass().equals(JpaRolloutGroup.class)) { - getAfterTransactionCommmitExecutor().afterCommit( - () -> getEventBus().post(new RolloutGroupPropertyChangeEvent((RolloutGroup) event.getObject(), - getChangeSet(RolloutGroup.class, event)))); + final Object object = event.getObject(); + if (isEventAwareEntity(object)) { + doNotifiy(() -> ((EventAwareEntity) object).fireUpdateEvent(event)); } } - private Map.Values> getChangeSet( - final Class clazz, final DescriptorEvent event) { - final T rolloutGroup = clazz.cast(event.getObject()); - final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); - return changeSet.getChanges().stream().filter(record -> record instanceof DirectToFieldChangeRecord) - .map(record -> (DirectToFieldChangeRecord) record) - .collect(Collectors.toMap(record -> record.getAttribute(), - record -> new AbstractPropertyChangeEvent(rolloutGroup, null).new Values( - record.getOldValue(), record.getNewValue()))); + @Override + public void postDelete(final DescriptorEvent event) { + final Object object = event.getObject(); + if (isEventAwareEntity(object)) { + doNotifiy(() -> ((EventAwareEntity) object).fireDeleteEvent(event)); + } } - private AfterTransactionCommitExecutor getAfterTransactionCommmitExecutor() { - return AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit(); + private static boolean isEventAwareEntity(final Object object) { + return object instanceof EventAwareEntity; } - private EventBus getEventBus() { - return EventBusHolder.getInstance().getEventBus(); + private static void doNotifiy(final Runnable runnable) { + AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(runnable); } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EventAwareEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EventAwareEntity.java new file mode 100644 index 000000000..889a61796 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/EventAwareEntity.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.jpa.model; + +import org.eclipse.persistence.descriptors.DescriptorEvent; + +/** + * Interfaces which can be implemented by entities to be called when the entity + * should fire an event because the entity has been created, updated or deleted. + */ +public interface EventAwareEntity { + + /** + * Fired for the Entity creation. + * + * @param descriptorEvent + */ + void fireCreateEvent(DescriptorEvent descriptorEvent); + + /** + * Fired for the Entity updation. + * + * @param descriptorEvent + */ + void fireUpdateEvent(DescriptorEvent descriptorEvent); + + /** + * Fired for the Entity deletion. + * + * @param descriptorEvent + */ + void fireDeleteEvent(DescriptorEvent descriptorEvent); +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 5fa2c3bed..72ef46ba3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -28,6 +28,10 @@ import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; import javax.persistence.Table; +import org.eclipse.hawkbit.repository.eventbus.event.ActionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.ActionPropertyChangeEvent; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityPropertyChangeHelper; +import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -35,6 +39,7 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.persistence.annotations.CascadeOnDelete; +import org.eclipse.persistence.descriptors.DescriptorEvent; /** * JPA implementation of {@link Action}. @@ -49,7 +54,7 @@ import org.eclipse.persistence.annotations.CascadeOnDelete; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") -public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action { +public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity { private static final long serialVersionUID = 1L; @ManyToOne(fetch = FetchType.LAZY) @@ -171,4 +176,20 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio return "Action [distributionSet=" + distributionSet + ", getId()=" + getId() + "]"; } + @Override + public void fireCreateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new ActionCreatedEvent(this)); + } + + @Override + public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new ActionPropertyChangeEvent(this, + EntityPropertyChangeHelper.getChangeSet(Action.class, descriptorEvent))); + } + + @Override + public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { + // there is no action deletion + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java index 3a8cb8683..e1843eb33 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java @@ -98,8 +98,8 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements * the status for this action status * @param occurredAt * the occurred timestamp - * @param messages - * the messages which should be added to this action status + * @param message + * the message which should be added to this action status */ public JpaActionStatus(final JpaAction action, final Status status, final Long occurredAt, final String message) { this.action = action; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index 676215424..661474668 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -13,6 +13,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -33,8 +34,14 @@ import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import org.eclipse.hawkbit.repository.eventbus.event.AbstractPropertyChangeEvent.PropertyChange; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetUpdateEvent; import org.eclipse.hawkbit.repository.exception.DistributionSetTypeUndefinedException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityPropertyChangeHelper; +import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; @@ -45,6 +52,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.persistence.annotations.CascadeOnDelete; +import org.eclipse.persistence.descriptors.DescriptorEvent; /** * Jpa implementation of {@link DistributionSet}. @@ -61,9 +69,13 @@ import org.eclipse.persistence.annotations.CascadeOnDelete; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") -public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implements DistributionSet { +public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implements DistributionSet, EventAwareEntity { private static final long serialVersionUID = 1L; + private static final String COMPLETE_PROPERTY = "complete"; + + private static final String DELETED_PROPERTY = "deleted"; + @Column(name = "required_migration_step") private boolean requiredMigrationStep; @@ -279,4 +291,30 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen return complete; } + @Override + public void fireCreateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new DistributionCreatedEvent(this)); + } + + @Override + public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { + + final Map changeSet = EntityPropertyChangeHelper.getChangeSet(JpaDistributionSet.class, + descriptorEvent); + EventBusHolder.getInstance().getEventBus().post(new DistributionSetUpdateEvent(this)); + + if (changeSet.containsKey(DELETED_PROPERTY)) { + final Boolean newDeleted = (Boolean) changeSet.get(DELETED_PROPERTY).getNewValue(); + if (newDeleted) { + EventBusHolder.getInstance().getEventBus().post(new DistributionDeletedEvent(getTenant(), getId())); + } + } + + } + + @Override + public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new DistributionDeletedEvent(getTenant(), getId())); + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java index 3cbe65717..85395e27b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java @@ -25,13 +25,17 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; +import org.eclipse.hawkbit.repository.eventbus.event.RolloutPropertyChangeEvent; import org.eclipse.hawkbit.repository.jpa.cache.CacheField; import org.eclipse.hawkbit.repository.jpa.cache.CacheKeys; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityPropertyChangeHelper; +import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.persistence.descriptors.DescriptorEvent; /** * JPA implementation of a {@link Rollout}. @@ -44,7 +48,7 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") -public class JpaRollout extends AbstractJpaNamedEntity implements Rollout { +public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity { private static final long serialVersionUID = 1L; @@ -197,4 +201,21 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout { + ", getName()=" + getName() + ", getId()=" + getId() + "]"; } + @Override + public void fireCreateEvent(final DescriptorEvent descriptorEvent) { + // there is no rollout creation event + } + + @Override + public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new RolloutPropertyChangeEvent(this, + EntityPropertyChangeHelper.getChangeSet(Rollout.class, descriptorEvent))); + + } + + @Override + public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { + // there is no rollout deletion event + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java index 79da6630a..9bb8644f4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java @@ -25,9 +25,13 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; +import org.eclipse.hawkbit.repository.eventbus.event.RolloutGroupPropertyChangeEvent; +import org.eclipse.hawkbit.repository.jpa.model.helper.EntityPropertyChangeHelper; +import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.persistence.descriptors.DescriptorEvent; /** * JPA entity definition of persisting a group of an rollout. @@ -40,7 +44,7 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") -public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGroup { +public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGroup, EventAwareEntity { private static final long serialVersionUID = 1L; @@ -236,4 +240,19 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr + ", getId()=" + getId() + "]"; } + @Override + public void fireCreateEvent(final DescriptorEvent descriptorEvent) { + // there is no RolloutGroup created event + } + + @Override + public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new RolloutGroupPropertyChangeEvent(this, + EntityPropertyChangeHelper.getChangeSet(RolloutGroup.class, descriptorEvent))); + } + + @Override + public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { + // there is no RolloutGroup deleted event + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index 183405470..c32a5e673 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -36,6 +36,10 @@ import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.eventbus.event.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetDeletedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.jpa.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityChecker; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder; @@ -45,6 +49,7 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.persistence.annotations.CascadeOnDelete; +import org.eclipse.persistence.descriptors.DescriptorEvent; import org.springframework.data.domain.Persistable; /** @@ -64,7 +69,8 @@ import org.springframework.data.domain.Persistable; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") -public class JpaTarget extends AbstractJpaNamedEntity implements Persistable, Target { +public class JpaTarget extends AbstractJpaNamedEntity implements Persistable, Target, EventAwareEntity { + private static final long serialVersionUID = 1L; @Column(name = "controller_id", length = 64) @@ -180,7 +186,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable, TargetInfo { +@EntityListeners(EntityPropertyChangeListener.class) +public class JpaTargetInfo implements Persistable, TargetInfo, EventAwareEntity { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(TargetInfo.class); @@ -117,7 +122,7 @@ public class JpaTargetInfo implements Persistable, TargetInfo { private boolean requestControllerAttributes = true; /** - * Constructor for {@link TargetStatus}. + * Constructor for {@link JpaTargetInfo}. * * @param target * related to this status. @@ -144,7 +149,7 @@ public class JpaTargetInfo implements Persistable, TargetInfo { } /** - * @param isNew + * @param entityNew * the isNew to set */ public void setNew(final boolean entityNew) { @@ -321,4 +326,19 @@ public class JpaTargetInfo implements Persistable, TargetInfo { } return true; } + + @Override + public void fireCreateEvent(final DescriptorEvent descriptorEvent) { + // there is no target info created event + } + + @Override + public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { + EventBusHolder.getInstance().getEventBus().post(new TargetInfoUpdateEvent(this)); + } + + @Override + public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { + // there is no target info deleted event + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityPropertyChangeHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityPropertyChangeHelper.java new file mode 100644 index 000000000..9e759640d --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/helper/EntityPropertyChangeHelper.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.jpa.model.helper; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.repository.eventbus.event.AbstractPropertyChangeEvent.PropertyChange; +import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +import org.eclipse.persistence.descriptors.DescriptorEvent; +import org.eclipse.persistence.internal.sessions.ObjectChangeSet; +import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; + +/** + * Helper class to get the change set for the property changes in the Entity. + * + * @param + */ +public class EntityPropertyChangeHelper { + + /** + * To get the map of entity property change set + * + * @param clazz + * @param event + * @return the map of the changeSet + */ + public static Map getChangeSet(final Class clazz, + final DescriptorEvent event) { + final T rolloutGroup = clazz.cast(event.getObject()); + final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); + return changeSet.getChanges().stream().filter(record -> record instanceof DirectToFieldChangeRecord) + .map(record -> (DirectToFieldChangeRecord) record) + .collect(Collectors.toMap(record -> record.getAttribute(), + record -> new PropertyChange(record.getOldValue(), record.getNewValue()))); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java index 91e03342e..90dd039f5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLUtility.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.repository.jpa.rsql; +import static org.eclipse.hawkbit.repository.FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -15,7 +17,6 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; @@ -49,7 +50,7 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor; * A utility class which is able to parse RSQL strings into an spring data * {@link Specification} which then can be enhanced sql queries to filter * entities. RSQL parser library: https://github.com/jirutka/rsql-parser - * + * *
    *
  • Equal to : ==
  • *
  • Not equal to : !=
  • @@ -83,14 +84,12 @@ public final class RSQLUtility { /** * parses an RSQL valid string into an JPA {@link Specification} which then * can be used to filter for JPA entities with the given RSQL query. - * + * * @param rsql * the rsql query * @param fieldNameProvider * the enum class type which implements the * {@link FieldNameProvider} - * @param entityManager - * {@link EntityManager} * @return an specification which can be used with JPA * @throws RSQLParameterUnsupportedFieldException * if a field in the RSQL string is used but not provided by the @@ -105,10 +104,10 @@ public final class RSQLUtility { /** * Validate the given rsql string regarding existence and correct syntax. - * + * * @param rsql * the rsql string to get validated - * + * */ public static void isValid(final String rsql) { parseRsql(rsql); @@ -156,7 +155,7 @@ public final class RSQLUtility { /** * An implementation of the {@link RSQLVisitor} to visit the parsed tokens * and build jpa where clauses. - * + * * * * @param @@ -205,7 +204,7 @@ public final class RSQLUtility { } private String getAndValidatePropertyFieldName(final A propertyEnum, final ComparisonNode node) { - String finalProperty = propertyEnum.getFieldName(); + final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); validateMapParamter(propertyEnum, node, graph); @@ -215,9 +214,12 @@ public final class RSQLUtility { throw createRSQLParameterUnsupportedException(node); } + final StringBuilder fieldNameBuilder = new StringBuilder(propertyEnum.getFieldName()); + for (int i = 1; i < graph.length; i++) { + final String propertyField = graph[i]; - finalProperty += FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR + propertyField; + fieldNameBuilder.append(SUB_ATTRIBUTE_SEPERATOR).append(propertyField); // the key of map is not in the graph if (propertyEnum.isMap() && graph.length == (i + 1)) { @@ -229,7 +231,7 @@ public final class RSQLUtility { } } - return finalProperty; + return fieldNameBuilder.toString(); } private void validateMapParamter(final A propertyEnum, final ComparisonNode node, final String[] graph) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/eventbus/RepositoryEntityEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/eventbus/RepositoryEntityEventTest.java new file mode 100644 index 000000000..0aa305d52 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/eventbus/RepositoryEntityEventTest.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.repository.jpa.eventbus; + +import static org.fest.assertions.Assertions.assertThat; + +import java.net.URI; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.hawkbit.eventbus.event.Event; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetDeletedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetInfoUpdateEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.fest.assertions.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Repository") +@Stories("Entity Events") +public class RepositoryEntityEventTest extends AbstractJpaIntegrationTest { + + @Autowired + private EventBus eventBus; + + private final MyEventListener eventListener = new MyEventListener(); + + @Before + public void beforeTest() { + eventListener.queue.clear(); + eventBus.register(eventListener); + } + + @Test + @Description("Verifies that the target created event is published when a target has been created") + public void targetCreatedEventIsPublished() throws InterruptedException { + final Target createdTarget = targetManagement.createTarget(entityFactory.generateTarget("12345")); + + final TargetCreatedEvent targetCreatedEvent = eventListener.waitForEvent(TargetCreatedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(targetCreatedEvent).isNotNull(); + assertThat(targetCreatedEvent.getEntity().getId()).isEqualTo(createdTarget.getId()); + } + + @Test + @Description("Verifies that the target update event is published when a target has been updated") + public void targetUpdateEventIsPublished() throws InterruptedException { + final Target createdTarget = targetManagement.createTarget(entityFactory.generateTarget("12345")); + createdTarget.setName("updateName"); + targetManagement.updateTarget(createdTarget); + + final TargetUpdatedEvent targetUpdatedEvent = eventListener.waitForEvent(TargetUpdatedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(targetUpdatedEvent).isNotNull(); + assertThat(targetUpdatedEvent.getEntity().getId()).isEqualTo(createdTarget.getId()); + } + + @Test + @Description("Verifies that the target info update event is published when a target info has been updated") + public void targetInfoUpdateEventIsPublished() throws InterruptedException { + final Target createdTarget = targetManagement.createTarget(entityFactory.generateTarget("12345")); + controllerManagament.updateTargetStatus(createdTarget.getTargetInfo(), TargetUpdateStatus.PENDING, + System.currentTimeMillis(), URI.create("http://127.0.0.1")); + + final TargetInfoUpdateEvent targetInfoUpdatedEvent = eventListener.waitForEvent(TargetInfoUpdateEvent.class, 1, + TimeUnit.SECONDS); + assertThat(targetInfoUpdatedEvent).isNotNull(); + assertThat(targetInfoUpdatedEvent.getEntity().getTarget().getId()).isEqualTo(createdTarget.getId()); + } + + @Test + @Description("Verifies that the target deleted event is published when a target has been deleted") + public void targetDeletedEventIsPublished() throws InterruptedException { + final Target createdTarget = targetManagement.createTarget(entityFactory.generateTarget("12345")); + + targetManagement.deleteTargets(createdTarget.getId()); + + final TargetDeletedEvent targetDeletedEvent = eventListener.waitForEvent(TargetDeletedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(targetDeletedEvent).isNotNull(); + assertThat(targetDeletedEvent.getTargetId()).isEqualTo(createdTarget.getId()); + } + + @Test + @Description("Verifies that the distribution set created event is published when a distribution set has been created") + public void distributionSetCreatedEventIsPublished() throws InterruptedException { + final DistributionSet generateDistributionSet = entityFactory.generateDistributionSet(); + generateDistributionSet.setName("dsEventTest"); + generateDistributionSet.setVersion("1"); + final DistributionSet createDistributionSet = distributionSetManagement + .createDistributionSet(generateDistributionSet); + + final DistributionCreatedEvent dsCreatedEvent = eventListener.waitForEvent(DistributionCreatedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(dsCreatedEvent).isNotNull(); + assertThat(dsCreatedEvent.getEntity().getId()).isEqualTo(createDistributionSet.getId()); + } + + @Test + @Description("Verifies that the distribution set deleted event is published when a distribution set has been deleted") + public void distributionSetDeletedEventIsPublished() throws InterruptedException { + + final DistributionSet generateDistributionSet = entityFactory.generateDistributionSet(); + generateDistributionSet.setName("dsEventTest"); + generateDistributionSet.setVersion("1"); + final DistributionSet createDistributionSet = distributionSetManagement + .createDistributionSet(generateDistributionSet); + + distributionSetManagement.deleteDistributionSet(createDistributionSet); + + final DistributionDeletedEvent dsDeletedEvent = eventListener.waitForEvent(DistributionDeletedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(dsDeletedEvent).isNotNull(); + assertThat(dsDeletedEvent.getDistributionSetId()).isEqualTo(createDistributionSet.getId()); + } + + private class MyEventListener { + + private final BlockingQueue queue = new LinkedBlockingQueue<>(); + + @Subscribe + public void onEvent(final Event event) { + queue.offer(event); + } + + public T waitForEvent(final Class eventType, final long timeout, final TimeUnit timeUnit) + throws InterruptedException { + Event event = null; + while ((event = queue.poll(timeout, timeUnit)) != null) { + if (event.getClass().isAssignableFrom(eventType)) { + return (T) event; + } + } + Assertions.fail("Missing event " + eventType + " within timeout " + timeout + " " + timeUnit); + return null; + } + } + +} diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java index 0c9dd39c5..7e2fbed7c 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/util/RestResourceConversionHelper.java @@ -9,11 +9,24 @@ package org.eclipse.hawkbit.rest.util; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.net.HttpHeaders.ACCEPT_RANGES; +import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION; +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.CONTENT_RANGE; +import static com.google.common.net.HttpHeaders.ETAG; +import static com.google.common.net.HttpHeaders.IF_RANGE; +import static com.google.common.net.HttpHeaders.LAST_MODIFIED; +import static java.math.RoundingMode.DOWN; +import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT; +import static org.eclipse.hawkbit.rest.util.ByteRange.MULTIPART_BOUNDARY; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.PARTIAL_CONTENT; +import static org.springframework.http.HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -27,23 +40,19 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import com.google.common.math.DoubleMath; -import com.google.common.net.HttpHeaders; /** * Utility class for the Rest Source API. - * */ public final class RestResourceConversionHelper { + private static final Logger LOG = LoggerFactory.getLogger(RestResourceConversionHelper.class); private static final int BUFFER_SIZE = 4096; - // utility class, private constructor. private RestResourceConversionHelper() { } @@ -92,7 +101,8 @@ public final class RestResourceConversionHelper { * * @return http code * - * @see https://tools.ietf.org/html/rfc7233 + * @see https://tools.ietf.org + * /html/rfc7233 */ public static ResponseEntity writeFileResponse(final LocalArtifact artifact, final HttpServletResponse response, final HttpServletRequest request, final DbArtifact file, @@ -107,11 +117,11 @@ public final class RestResourceConversionHelper { response.reset(); response.setBufferSize(BUFFER_SIZE); - response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + artifact.getFilename()); - response.setHeader(HttpHeaders.ETAG, etag); - response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); - response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified); - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setHeader(CONTENT_DISPOSITION, "attachment;filename=" + artifact.getFilename()); + response.setHeader(ETAG, etag); + response.setHeader(ACCEPT_RANGES, "bytes"); + response.setDateHeader(LAST_MODIFIED, lastModified); + response.setContentType(APPLICATION_OCTET_STREAM_VALUE); final ByteRange full = new ByteRange(0, length - 1, length); final List ranges = new ArrayList<>(); @@ -123,9 +133,9 @@ public final class RestResourceConversionHelper { // Range header matches"bytes=n-n,n-n,n-n..." if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); + response.setHeader(CONTENT_RANGE, "bytes */" + length); LOG.debug("range header for filename ({}) is not satisfiable: ", artifact.getFilename()); - return new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + return new ResponseEntity<>(REQUESTED_RANGE_NOT_SATISFIABLE); } // RFC: if the representation is unchanged, send me the part(s) that @@ -144,32 +154,31 @@ public final class RestResourceConversionHelper { // full request - no range if (ranges.isEmpty() || ranges.get(0).equals(full)) { LOG.debug("filename ({}) results into a full request: ", artifact.getFilename()); - fullfileRequest(artifact, response, file, controllerManagement, statusId, full); - result = new ResponseEntity<>(HttpStatus.OK); + handleFullFileRequest(artifact, response, file, controllerManagement, statusId, full); + result = new ResponseEntity<>(OK); } // standard range request else if (ranges.size() == 1) { LOG.debug("filename ({}) results into a standard range request: ", artifact.getFilename()); - standardRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); - result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT); + handleStandardRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); + result = new ResponseEntity<>(PARTIAL_CONTENT); } // multipart range request else { LOG.debug("filename ({}) results into a multipart range request: ", artifact.getFilename()); - multipartRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); - result = new ResponseEntity<>(HttpStatus.PARTIAL_CONTENT); + handleMultipartRangeRequest(artifact, response, file, controllerManagement, statusId, ranges); + result = new ResponseEntity<>(PARTIAL_CONTENT); } return result; - } - private static void fullfileRequest(final LocalArtifact artifact, final HttpServletResponse response, + private static void handleFullFileRequest(final LocalArtifact artifact, final HttpServletResponse response, final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, final ByteRange full) { final ByteRange r = full; - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); - response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength())); + response.setHeader(CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); + response.setHeader(CONTENT_LENGTH, String.valueOf(r.getLength())); try { copyStreams(file.getFileInputStream(), response.getOutputStream(), controllerManagement, statusId, @@ -182,7 +191,7 @@ public final class RestResourceConversionHelper { private static ResponseEntity extractRange(final HttpServletResponse response, final long length, final List ranges, final String range) { - ResponseEntity result = null; + if (ranges.isEmpty()) { for (final String part : range.substring(6).split(",")) { long start = sublong(part, 0, part.indexOf('-')); @@ -198,9 +207,8 @@ public final class RestResourceConversionHelper { // Check if Range is syntactically valid. If not, then return // 416. if (start > end) { - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + length); - result = new ResponseEntity<>(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); - return result; + response.setHeader(CONTENT_RANGE, "bytes */" + length); + return new ResponseEntity<>(REQUESTED_RANGE_NOT_SATISFIABLE); } // Add range. @@ -218,10 +226,10 @@ public final class RestResourceConversionHelper { private static void checkForShortcut(final HttpServletRequest request, final String etag, final long lastModified, final ByteRange full, final List ranges) { - final String ifRange = request.getHeader(HttpHeaders.IF_RANGE); + final String ifRange = request.getHeader(IF_RANGE); if (ifRange != null && !ifRange.equals(etag)) { try { - final long ifRangeTime = request.getDateHeader(HttpHeaders.IF_RANGE); + final long ifRangeTime = request.getDateHeader(IF_RANGE); if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { ranges.add(full); } @@ -232,17 +240,17 @@ public final class RestResourceConversionHelper { } } - private static void multipartRangeRequest(final LocalArtifact artifact, final HttpServletResponse response, + private static void handleMultipartRangeRequest(final LocalArtifact artifact, final HttpServletResponse response, final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, final List ranges) { - response.setContentType("multipart/byteranges; boundary=" + ByteRange.MULTIPART_BOUNDARY); - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); + response.setStatus(SC_PARTIAL_CONTENT); try { for (final ByteRange r : ranges) { // Add multipart boundary and header fields for every range. response.getOutputStream().println(); - response.getOutputStream().println("--" + ByteRange.MULTIPART_BOUNDARY); + response.getOutputStream().println("--" + MULTIPART_BOUNDARY); response.getOutputStream() .println("Content-Range: bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); @@ -253,20 +261,20 @@ public final class RestResourceConversionHelper { // End with final multipart boundary. response.getOutputStream().println(); - response.getOutputStream().print("--" + ByteRange.MULTIPART_BOUNDARY + "--"); + response.getOutputStream().print("--" + MULTIPART_BOUNDARY + "--"); } catch (final IOException e) { LOG.error("multipartRangeRequest of file ({}) failed!", artifact.getFilename(), e); throw new FileSteamingFailedException(artifact.getFilename()); } } - private static void standardRangeRequest(final LocalArtifact artifact, final HttpServletResponse response, + private static void handleStandardRangeRequest(final LocalArtifact artifact, final HttpServletResponse response, final DbArtifact file, final ControllerManagement controllerManagement, final Long statusId, final List ranges) { final ByteRange r = ranges.get(0); - response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); - response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(r.getLength())); - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + response.setHeader(CONTENT_RANGE, "bytes " + r.getStart() + "-" + r.getEnd() + "/" + r.getTotal()); + response.setHeader(CONTENT_LENGTH, String.valueOf(r.getLength())); + response.setStatus(SC_PARTIAL_CONTENT); try { copyStreams(file.getFileInputStream(), response.getOutputStream(), controllerManagement, statusId, @@ -315,7 +323,7 @@ public final class RestResourceConversionHelper { } if (controllerManagement != null) { - final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, RoundingMode.DOWN); + final int newPercent = DoubleMath.roundToInt(total * 100.0 / length, DOWN); // every 10 percent an event if (newPercent == 100 || newPercent > progressPercent + 10) { diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java index 008aa3e95..89d0f4387 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DosFilter.java @@ -100,7 +100,8 @@ public class DosFilter extends OncePerRequestFilter { boolean processChain; - final String ip = IpUtil.getClientIpFromRequest(request, forwardHeader, true).getHost(); + final String ip = IpUtil.getClientIpFromRequest(request, forwardHeader).getHost(); + if (checkIpFails(ip)) { processChain = handleMissingIpAddress(response); } else { @@ -121,7 +122,6 @@ public class DosFilter extends OncePerRequestFilter { if (processChain) { filterChain.doFilter(request, response); } - } /** diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java index c7778f776..30306526a 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java @@ -44,7 +44,7 @@ public final class IpUtil { * {@link HttpServletRequest} by either the * {@link HttpHeaders#X_FORWARDED_FOR} or by the * {@link HttpServletRequest#getRemoteAddr()} methods. - * + * * @param request * the {@link HttpServletRequest} to determine the IP address * where this request has been sent from @@ -65,21 +65,23 @@ public final class IpUtil { * {@link HttpServletRequest} by either the * {@link HttpHeaders#X_FORWARDED_FOR} or by the * {@link HttpServletRequest#getRemoteAddr()} methods. - * + * * @param request * the {@link HttpServletRequest} to determine the IP address * where this request has been sent from * @param forwardHeader * the header name containing the IP address e.g. forwarded by a * proxy {@code x-forwarded-for} - * - * @param trackRemoteIp - * to true if remote IP should be tracked. * @return the {@link URI} based IP address from the client which sent the * request */ - public static URI getClientIpFromRequest(final HttpServletRequest request, final String forwardHeader, + public static URI getClientIpFromRequest(final HttpServletRequest request, final String forwardHeader) { + return getClientIpFromRequest(request, forwardHeader, true); + } + + private static URI getClientIpFromRequest(final HttpServletRequest request, final String forwardHeader, final boolean trackRemoteIp) { + String ip; if (trackRemoteIp) { @@ -113,7 +115,7 @@ public final class IpUtil { /** * Create a {@link URI} with scheme and host. - * + * * @param scheme * the scheme * @param host @@ -132,7 +134,7 @@ public final class IpUtil { /** * Create a {@link URI} with amqp scheme and host. - * + * * @param host * the host * @param exchange @@ -147,7 +149,7 @@ public final class IpUtil { /** * Create a {@link URI} with http scheme and host. - * + * * @param host * the host * @return the {@link URI} @@ -160,7 +162,7 @@ public final class IpUtil { /** * Check if scheme contains http and uri ist not null. - * + * * @param uri * the uri * @return true = is http host false = not @@ -171,7 +173,7 @@ public final class IpUtil { /** * Check if host scheme amqp and uri ist not null. - * + * * @param uri * the uri * @return true = is http host false = not @@ -183,7 +185,7 @@ public final class IpUtil { /** * Check if the IP address of that {@link URI} is known, i.e. not an AQMP * exchange in DMF case and not HIDDEN_IP in DDI case. - * + * * @param uri * the uri * @return true if IP address is actually known by the server diff --git a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java index 0f4a01d26..836a9f13e 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.util; +import static com.google.common.net.HttpHeaders.X_FORWARDED_FOR; import static org.fest.assertions.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -21,13 +22,13 @@ import java.net.URI; import javax.servlet.http.HttpServletRequest; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties.Clients; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import com.google.common.net.HttpHeaders; - import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @@ -37,69 +38,73 @@ import ru.yandex.qatools.allure.annotations.Stories; @Stories("IP Util Test") public class IpUtilTest { + private static final String KNOWN_REQUEST_HEADER = "bumlux"; + @Mock private HttpServletRequest requestMock; + @Mock + private Clients clientMock; + + @Mock + private HawkbitSecurityProperties securityPropertyMock; + @Test @Description("Tests create uri from request") public void getRemoteAddrFromRequestIfForwaredHeaderNotPresent() { - // known values + final URI knownRemoteClientIP = IpUtil.createHttpUri("127.0.0.1"); - // mock - when(requestMock.getHeader(HttpHeaders.X_FORWARDED_FOR)).thenReturn(null); + when(requestMock.getHeader(X_FORWARDED_FOR)).thenReturn(null); when(requestMock.getRemoteAddr()).thenReturn(knownRemoteClientIP.getHost()); - // test - final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "bumlux", true); + final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, KNOWN_REQUEST_HEADER); // verify assertThat(remoteAddr).as("The remote address should be as the known client IP address") .isEqualTo(knownRemoteClientIP); - verify(requestMock, times(1)).getHeader("bumlux"); + verify(requestMock, times(1)).getHeader(KNOWN_REQUEST_HEADER); verify(requestMock, times(1)).getRemoteAddr(); } @Test @Description("Tests create uri from request with masked IP when IP tracking is disabled") public void maskRemoteAddrIfDisabled() { - // known values + final URI knownRemoteClientIP = IpUtil.createHttpUri("***"); - // mock - when(requestMock.getHeader(HttpHeaders.X_FORWARDED_FOR)).thenReturn(null); + when(requestMock.getHeader(X_FORWARDED_FOR)).thenReturn(null); when(requestMock.getRemoteAddr()).thenReturn(knownRemoteClientIP.getHost()); + when(securityPropertyMock.getClients()).thenReturn(clientMock); + when(clientMock.getRemoteIpHeader()).thenReturn(KNOWN_REQUEST_HEADER); + when(clientMock.isTrackRemoteIp()).thenReturn(false); - // test - final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "bumlux", false); + final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, securityPropertyMock); - // verify assertThat(remoteAddr).as("The remote address should be as the known client IP address") .isEqualTo(knownRemoteClientIP); - verify(requestMock, times(0)).getHeader("bumlux"); + verify(requestMock, times(0)).getHeader(KNOWN_REQUEST_HEADER); verify(requestMock, times(0)).getRemoteAddr(); } @Test @Description("Tests create uri from x forward header") public void getRemoteAddrFromXForwardedForHeader() { - // known values + final URI knownRemoteClientIP = IpUtil.createHttpUri("10.99.99.1"); - // mock - when(requestMock.getHeader(HttpHeaders.X_FORWARDED_FOR)).thenReturn(knownRemoteClientIP.getHost()); + when(requestMock.getHeader(X_FORWARDED_FOR)).thenReturn(knownRemoteClientIP.getHost()); when(requestMock.getRemoteAddr()).thenReturn(null); - // test - final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "X-Forwarded-For", true); + final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "X-Forwarded-For"); - // verify assertThat(remoteAddr).as("The remote address should be as the known client IP address") .isEqualTo(knownRemoteClientIP); - verify(requestMock, times(1)).getHeader(HttpHeaders.X_FORWARDED_FOR); + verify(requestMock, times(1)).getHeader(X_FORWARDED_FOR); verify(requestMock, times(0)).getRemoteAddr(); } @Test @Description("Tests create http uri ipv4 and ipv6") public void testCreateHttpUri() { + final String ipv4 = "10.99.99.1"; URI httpUri = IpUtil.createHttpUri(ipv4); assertHttpUri(ipv4, httpUri); @@ -111,7 +116,6 @@ public class IpUtilTest { final String ipv6 = "0:0:0:0:0:0:0:1"; httpUri = IpUtil.createHttpUri(ipv6); assertHttpUri("[" + ipv6 + "]", httpUri); - } private void assertHttpUri(final String host, final URI httpUri) { @@ -124,6 +128,7 @@ public class IpUtilTest { @Test @Description("Tests create amqp uri ipv4 and ipv6") public void testCreateAmqpUri() { + final String ipv4 = "10.99.99.1"; URI amqpUri = IpUtil.createAmqpUri(ipv4, "path"); assertAmqpUri(ipv4, amqpUri); @@ -138,6 +143,7 @@ public class IpUtilTest { } private void assertAmqpUri(final String host, final URI amqpUri) { + assertTrue("The given URI is an AMQP scheme", IpUtil.isAmqpUri(amqpUri)); assertFalse("The given URI is not an HTTP scheme", IpUtil.isHttpUri(amqpUri)); assertEquals("The given host matches the URI host", host, amqpUri.getHost()); @@ -148,17 +154,19 @@ public class IpUtilTest { @Test @Description("Tests create invalid uri") public void testCreateInvalidUri() { + final String host = "10.99.99.1"; final URI testUri = IpUtil.createUri("test", host); + assertFalse("The given URI is not an AMQP address", IpUtil.isAmqpUri(testUri)); assertFalse("The given URI is not an HTTP address", IpUtil.isHttpUri(testUri)); assertEquals("The given host matches the URI host", host, testUri.getHost()); + try { IpUtil.createUri(":/", host); fail("Missing expected IllegalArgumentException due invalid URI"); } catch (final IllegalArgumentException e) { // expected } - } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitEventProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitEventProvider.java index cca4bdd4c..f064a76a1 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitEventProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitEventProvider.java @@ -12,25 +12,29 @@ import java.util.HashSet; import java.util.Set; import org.eclipse.hawkbit.eventbus.event.Event; -import org.eclipse.hawkbit.eventbus.event.TargetDeletedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetTagCreatedBulkEvent; import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetTagDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetTagUpdateEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionSetUpdateEvent; import org.eclipse.hawkbit.repository.eventbus.event.RolloutChangeEvent; import org.eclipse.hawkbit.repository.eventbus.event.RolloutGroupChangeEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetInfoUpdateEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetTagCreatedBulkEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetTagDeletedEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetTagUpdateEvent; +import org.eclipse.hawkbit.repository.eventbus.event.TargetUpdatedEvent; /** * The default hawkbit event provider. */ public class HawkbitEventProvider implements UIEventProvider { - private static final Set> SINGLE_EVENTS = new HashSet<>(6); - private static final Set> BULK_EVENTS = new HashSet<>(3); + private static final Set> SINGLE_EVENTS = new HashSet<>(9); + private static final Set> BULK_EVENTS = new HashSet<>(5); static { SINGLE_EVENTS.add(TargetTagCreatedBulkEvent.class); @@ -41,10 +45,14 @@ public class HawkbitEventProvider implements UIEventProvider { SINGLE_EVENTS.add(RolloutGroupChangeEvent.class); SINGLE_EVENTS.add(RolloutChangeEvent.class); SINGLE_EVENTS.add(TargetTagUpdateEvent.class); + SINGLE_EVENTS.add(DistributionSetUpdateEvent.class); BULK_EVENTS.add(TargetCreatedEvent.class); BULK_EVENTS.add(TargetInfoUpdateEvent.class); BULK_EVENTS.add(TargetDeletedEvent.class); + BULK_EVENTS.add(DistributionDeletedEvent.class); + BULK_EVENTS.add(DistributionCreatedEvent.class); + BULK_EVENTS.add(TargetUpdatedEvent.class); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactBeanQuery.java index 4ba722284..d08474a5a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactBeanQuery.java @@ -8,6 +8,11 @@ */ package org.eclipse.hawkbit.ui.artifacts.details; +import static org.apache.commons.lang3.ArrayUtils.isEmpty; +import static org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil.isNotNullOrEmpty; +import static org.springframework.data.domain.Sort.Direction.ASC; +import static org.springframework.data.domain.Sort.Direction.DESC; + import java.util.List; import java.util.Map; @@ -15,7 +20,6 @@ import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.model.LocalArtifact; -import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.springframework.data.domain.Page; @@ -39,7 +43,7 @@ public class ArtifactBeanQuery extends AbstractBeanQuery { /** * Parametric Constructor. - * + * * @param definition * as Def * @param queryConfig @@ -51,18 +55,18 @@ public class ArtifactBeanQuery extends AbstractBeanQuery { */ public ArtifactBeanQuery(final QueryDefinition definition, final Map queryConfig, final Object[] sortIds, final boolean[] sortStates) { + super(definition, queryConfig, sortIds, sortStates); - if (HawkbitCommonUtil.mapCheckStrKey(queryConfig)) { + if (isNotNullOrEmpty(queryConfig)) { baseSwModuleId = (Long) queryConfig.get(SPUIDefinitions.BY_BASE_SOFTWARE_MODULE); } - if (HawkbitCommonUtil.checkBolArray(sortStates)) { - // Initalize Sor - sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortIds[0]); - // Add sort. + if (!isEmpty(sortStates)) { + sort = new Sort(sortStates[0] ? ASC : DESC, (String) sortIds[0]); + for (int targetId = 1; targetId < sortIds.length; targetId++) { - sort.and(new Sort(sortStates[targetId] ? Direction.ASC : Direction.DESC, (String) sortIds[targetId])); + sort.and(new Sort(sortStates[targetId] ? ASC : DESC, (String) sortIds[targetId])); } } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java index c62637117..8408f7c74 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java @@ -24,6 +24,7 @@ import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent.SoftwareModuleEventType; import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.common.ConfirmationDialog; +import org.eclipse.hawkbit.ui.common.builder.LabelBuilder; import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; import org.eclipse.hawkbit.ui.components.SPUIButton; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; @@ -149,8 +150,8 @@ public class ArtifactDetailsLayout extends VerticalLayout { final SoftwareModule softwareModule = artifactUploadState.getSelectedBaseSoftwareModule().get(); labelStr = HawkbitCommonUtil.getFormattedNameVersion(softwareModule.getName(), softwareModule.getVersion()); } - titleOfArtifactDetails = SPUIComponentProvider.getLabel( - HawkbitCommonUtil.getArtifactoryDetailsLabelId(labelStr), SPUILabelDefinitions.SP_WIDGET_CAPTION); + titleOfArtifactDetails = new LabelBuilder().name(HawkbitCommonUtil.getArtifactoryDetailsLabelId(labelStr)) + .buildCaptionLabel(); titleOfArtifactDetails.setContentMode(ContentMode.HTML); titleOfArtifactDetails.setSizeFull(); titleOfArtifactDetails.setImmediate(true); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java index 3b4767c60..4a8d0a747 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java @@ -7,6 +7,7 @@ * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.hawkbit.ui.artifacts.event; + /** * * Holds the upload file status. @@ -14,24 +15,36 @@ package org.eclipse.hawkbit.ui.artifacts.event; */ public class UploadStatusEvent { + /** + * Event type definition of events during the artifact upload life-cycle + * from receiving the upload until the process end. + */ public enum UploadStatusEventType { - UPLOAD_FAILED, UPLOAD_IN_PROGRESS, UPLOAD_STARTED, UPLOAD_FINISHED, UPLOAD_SUCCESSFUL, UPLOAD_STREAMING_FAILED, UPLOAD_STREAMING_FINISHED, ABORT_UPLOAD + RECEIVE_UPLOAD, UPLOAD_FAILED, UPLOAD_IN_PROGRESS, UPLOAD_STARTED, UPLOAD_FINISHED, UPLOAD_SUCCESSFUL, UPLOAD_STREAMING_FAILED, UPLOAD_STREAMING_FINISHED, ABORT_UPLOAD } - private UploadStatusEventType uploadProgressEventType; + private final UploadStatusEventType uploadProgressEventType; private UploadFileStatus uploadStatus; - public UploadStatusEvent(UploadStatusEventType eventType, UploadFileStatus entity) { + /** + * Constructor. + * + * @param eventType + * the type of the event + * @param uploadStatus + * the upload status of this event + */ + public UploadStatusEvent(final UploadStatusEventType eventType, final UploadFileStatus uploadStatus) { this.uploadProgressEventType = eventType; - this.uploadStatus = entity; + this.uploadStatus = uploadStatus; } public UploadFileStatus getUploadStatus() { return uploadStatus; } - public void setUploadStatus(UploadFileStatus uploadStatus) { + public void setUploadStatus(final UploadFileStatus uploadStatus) { this.uploadStatus = uploadStatus; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java index 29bea30eb..103481f89 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java @@ -43,7 +43,7 @@ public class BaseSwModuleBeanQuery extends AbstractBeanQuery queryConfig, final Object[] sortIds, final boolean[] sortStates) { super(definition, queryConfig, sortIds, sortStates); - if (HawkbitCommonUtil.mapCheckStrKey(queryConfig)) { + if (HawkbitCommonUtil.isNotNullOrEmpty(queryConfig)) { type = (SoftwareModuleType) queryConfig.get(SPUIDefinitions.BY_SOFTWARE_MODULE_TYPE); searchText = (String) queryConfig.get(SPUIDefinitions.FILTER_BY_TEXT); if (!Strings.isNullOrEmpty(searchText)) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java index c8eb74313..a437c0039 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java @@ -18,9 +18,11 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.SoftwareModuleTypeBeanQuery; +import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder; +import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder; +import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; -import org.eclipse.hawkbit.ui.decorators.SPUIWindowDecorator; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; @@ -118,26 +120,17 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se } private void createRequiredComponents() { - /* name textfield */ - nameTextField = SPUIComponentProvider.getTextField(i18n.get("textfield.name"), "", ValoTheme.TEXTFIELD_TINY, - true, null, i18n.get("textfield.name"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); - nameTextField.setId(SPUIComponentIdProvider.SOFT_MODULE_NAME); - /* version text field */ - versionTextField = SPUIComponentProvider.getTextField(i18n.get("textfield.version"), "", - ValoTheme.TEXTFIELD_TINY, true, null, i18n.get("textfield.version"), true, - SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); - versionTextField.setId(SPUIComponentIdProvider.SOFT_MODULE_VERSION); + nameTextField = createTextField("textfield.name", SPUIComponentIdProvider.SOFT_MODULE_NAME); - /* Vendor text field */ - vendorTextField = SPUIComponentProvider.getTextField(i18n.get("textfield.vendor"), "", ValoTheme.TEXTFIELD_TINY, - false, null, i18n.get("textfield.vendor"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); - vendorTextField.setId(SPUIComponentIdProvider.SOFT_MODULE_VENDOR); + versionTextField = createTextField("textfield.version", SPUIComponentIdProvider.SOFT_MODULE_VERSION); - descTextArea = SPUIComponentProvider.getTextArea(i18n.get("textfield.description"), "text-area-style", - ValoTheme.TEXTAREA_TINY, false, null, i18n.get("textfield.description"), - SPUILabelDefinitions.TEXT_AREA_MAX_LENGTH); - descTextArea.setId(SPUIComponentIdProvider.ADD_SW_MODULE_DESCRIPTION); + vendorTextField = createTextField("textfield.vendor", SPUIComponentIdProvider.SOFT_MODULE_VENDOR); + vendorTextField.setRequired(false); + + descTextArea = new TextAreaBuilder().caption(i18n.get("textfield.description")).style("text-area-style") + .prompt(i18n.get("textfield.description")).id(SPUIComponentIdProvider.ADD_SW_MODULE_DESCRIPTION) + .buildTextComponent(); typeComboBox = SPUIComponentProvider.getComboBox(i18n.get("upload.swmodule.type"), "", "", null, null, true, null, i18n.get("upload.swmodule.type")); @@ -148,6 +141,11 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se populateTypeNameCombo(); } + private TextField createTextField(final String in18Key, final String id) { + return new TextFieldBuilder().caption(i18n.get(in18Key)).required(true).prompt(i18n.get(in18Key)) + .immediate(true).id(id).buildTextComponent(); + } + private void populateTypeNameCombo() { typeComboBox.setContainerDataSource(HawkbitCommonUtil.createLazyQueryContainer( new BeanQueryFactory(SoftwareModuleTypeBeanQuery.class))); @@ -181,8 +179,11 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se setCompositionRoot(formLayout); - window = SPUIWindowDecorator.getWindow(i18n.get("upload.caption.add.new.swmodule"), null, - SPUIDefinitions.CREATE_UPDATE_WINDOW, this, event -> saveOrUpdate(), null, null, formLayout, i18n); + window = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) + .caption(i18n.get("upload.caption.add.new.swmodule")).content(this) + .saveButtonClickListener(event -> saveOrUpdate()).layout(formLayout).i18n(i18n) + .buildCommonDialogWindow(); + window.getButtonsLayout().removeStyleName("actionButtonsMargin"); nameTextField.setEnabled(!editSwModule); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java index 8233f47ff..98e309396 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java @@ -51,8 +51,8 @@ import com.vaadin.ui.UI; /** * Header of Software module table. */ -@SpringComponent @ViewScope +@SpringComponent public class SoftwareModuleTable extends AbstractNamedVersionTable { private static final long serialVersionUID = 6469417305487144809L; @@ -161,7 +161,7 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable refreshFilter()); + UI.getCurrent().access(this::refreshFilter); } } @@ -196,7 +196,7 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable showMetadataDetails((Long) itemId, nameVersionStr)); + manageMetaDataBtn.addClickListener(event -> showMetadataDetails((Long) itemId)); return manageMetaDataBtn; } }); @@ -244,7 +244,7 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable updateProgress(event.getUploadStatus().getFileName(), event.getUploadStatus().getBytesRead(), - event.getUploadStatus().getContentLength(), event.getUploadStatus().getSoftwareModule())); - } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STARTED) { - UI.getCurrent().access(() -> onStartOfUpload(event)); - } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FAILED) { - ui.access(() -> uploadFailed(event.getUploadStatus().getFileName(), event.getUploadStatus() - .getFailureReason(), event.getUploadStatus().getSoftwareModule())); - } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_SUCCESSFUL) { - UI.getCurrent().access( - () -> uploadSucceeded(event.getUploadStatus().getFileName(), event.getUploadStatus() - .getSoftwareModule())); - } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FINISHED) { - ui.access(() -> uploadSucceeded(event.getUploadStatus().getFileName(), event.getUploadStatus() - .getSoftwareModule())); + + final UploadFileStatus uploadStatus = event.getUploadStatus(); + switch (event.getUploadProgressEventType()) { + case UPLOAD_IN_PROGRESS: + ui.access(() -> updateProgress(uploadStatus.getFileName(), uploadStatus.getBytesRead(), + uploadStatus.getContentLength(), uploadStatus.getSoftwareModule())); + break; + case UPLOAD_STARTED: + ui.access(() -> onStartOfUpload(event)); + break; + case UPLOAD_STREAMING_FAILED: + ui.access(() -> uploadFailed(uploadStatus.getFileName(), uploadStatus.getFailureReason(), + uploadStatus.getSoftwareModule())); + break; + case UPLOAD_SUCCESSFUL: + // fall through here + case UPLOAD_STREAMING_FINISHED: + ui.access(() -> uploadSucceeded(uploadStatus.getFileName(), uploadStatus.getSoftwareModule())); + break; + case RECEIVE_UPLOAD: + uploadRecevied(uploadStatus.getFileName(), uploadStatus.getSoftwareModule()); + break; + case UPLOAD_FINISHED: + case ABORT_UPLOAD: + case UPLOAD_FAILED: + default: } } - private void onStartOfUpload(UploadStatusEvent event) { + private void onStartOfUpload(final UploadStatusEvent event) { uploadSessionStarted(); uploadStarted(event.getUploadStatus().getFileName(), event.getUploadStatus().getSoftwareModule()); } @@ -169,19 +176,23 @@ public class UploadStatusInfoWindow extends Window { } private void restoreState() { - Indexed container = grid.getContainerDataSource(); + final Indexed container = grid.getContainerDataSource(); if (container.getItemIds().isEmpty()) { container.removeAllItems(); - for (UploadStatusObject statusObject : artifactUploadState.getUploadedFileStatusList()) { - Item item = container.addItem(getItemid(statusObject.getFilename(), - statusObject.getSelectedSoftwareModule())); + for (final UploadStatusObject statusObject : artifactUploadState.getUploadedFileStatusList()) { + final Item item = container + .addItem(getItemid(statusObject.getFilename(), statusObject.getSelectedSoftwareModule())); item.getItemProperty(REASON).setValue(statusObject.getReason() != null ? statusObject.getReason() : ""); - item.getItemProperty(STATUS).setValue(statusObject.getStatus()); - item.getItemProperty(PROGRESS).setValue(statusObject.getProgress()); + if (statusObject.getStatus() != null) { + item.getItemProperty(STATUS).setValue(statusObject.getStatus()); + } + if (statusObject.getProgress() != null) { + item.getItemProperty(PROGRESS).setValue(statusObject.getProgress()); + } item.getItemProperty(FILE_NAME).setValue(statusObject.getFilename()); - SoftwareModule sw = statusObject.getSelectedSoftwareModule(); - item.getItemProperty(SPUILabelDefinitions.NAME_VERSION).setValue( - HawkbitCommonUtil.getFormattedNameVersion(sw.getName(), sw.getVersion())); + final SoftwareModule sw = statusObject.getSelectedSoftwareModule(); + item.getItemProperty(SPUILabelDefinitions.NAME_VERSION) + .setValue(HawkbitCommonUtil.getFormattedNameVersion(sw.getName(), sw.getVersion())); } if (artifactUploadState.isUploadCompleted()) { minimizeButton.setEnabled(false); @@ -209,7 +220,7 @@ public class UploadStatusInfoWindow extends Window { } private Grid createGrid() { - Grid statusGrid = new Grid(uploads); + final Grid statusGrid = new Grid(uploads); statusGrid.addStyleName(SPUIStyleDefinitions.UPLOAD_STATUS_GRID); statusGrid.setSelectionMode(SelectionMode.NONE); statusGrid.setHeaderVisible(true); @@ -219,7 +230,7 @@ public class UploadStatusInfoWindow extends Window { } private IndexedContainer getGridContainer() { - IndexedContainer uploadContainer = new IndexedContainer(); + final IndexedContainer uploadContainer = new IndexedContainer(); uploadContainer.addContainerProperty(STATUS, String.class, "Active"); uploadContainer.addContainerProperty(FILE_NAME, String.class, null); uploadContainer.addContainerProperty(PROGRESS, Double.class, 0D); @@ -321,72 +332,72 @@ public class UploadStatusInfoWindow extends Window { restoreState(); } - void uploadStarted(final String filename, final SoftwareModule softwareModule) { + private void uploadRecevied(final String filename, final SoftwareModule softwareModule) { final Item item = uploads.addItem(getItemid(filename, softwareModule)); if (item != null) { item.getItemProperty(FILE_NAME).setValue(filename); item.getItemProperty(SPUILabelDefinitions.NAME_VERSION).setValue( HawkbitCommonUtil.getFormattedNameVersion(softwareModule.getName(), softwareModule.getVersion())); + final UploadStatusObject uploadStatus = new UploadStatusObject(filename, softwareModule); + uploadStatus.setStatus("Active"); + artifactUploadState.getUploadedFileStatusList().add(uploadStatus); } - grid.scrollToEnd(); - UploadStatusObject uploadStatus = new UploadStatusObject(filename, softwareModule); - uploadStatus.setStatus("Active"); - artifactUploadState.getUploadedFileStatusList().add(uploadStatus); + } + + void uploadStarted(final String filename, final SoftwareModule softwareModule) { + grid.scrollTo(getItemid(filename, softwareModule)); } void updateProgress(final String filename, final long readBytes, final long contentLength, final SoftwareModule softwareModule) { final Item item = uploads.getItem(getItemid(filename, softwareModule)); - double progress = (double) readBytes / (double) contentLength; + final double progress = (double) readBytes / (double) contentLength; if (item != null) { item.getItemProperty(PROGRESS).setValue(progress); } - List uploadStatusObjectList = (List) artifactUploadState - .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) - .collect(Collectors.toList()); + final List uploadStatusObjectList = artifactUploadState.getUploadedFileStatusList().stream() + .filter(e -> e.getFilename().equals(filename)).collect(Collectors.toList()); if (!uploadStatusObjectList.isEmpty()) { - UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + final UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); uploadStatusObject.setProgress(progress); } } /** * Called when each file upload is success. - * + * * @param filename * of the uploaded file. * @param softwareModule * selected software module */ - public void uploadSucceeded(final String filename, SoftwareModule softwareModule) { + public void uploadSucceeded(final String filename, final SoftwareModule softwareModule) { final Item item = uploads.getItem(getItemid(filename, softwareModule)); - String status = "Finished"; + final String status = "Finished"; if (item != null) { item.getItemProperty(STATUS).setValue(status); } - List uploadStatusObjectList = (List) artifactUploadState - .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) - .collect(Collectors.toList()); + final List uploadStatusObjectList = artifactUploadState.getUploadedFileStatusList().stream() + .filter(e -> e.getFilename().equals(filename)).collect(Collectors.toList()); if (!uploadStatusObjectList.isEmpty()) { - UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + final UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); uploadStatusObject.setStatus(status); uploadStatusObject.setProgress(1d); } } - void uploadFailed(final String filename, final String errorReason, SoftwareModule softwareModule) { + void uploadFailed(final String filename, final String errorReason, final SoftwareModule softwareModule) { errorOccured = true; - String status = "Failed"; + final String status = "Failed"; final Item item = uploads.getItem(getItemid(filename, softwareModule)); if (item != null) { item.getItemProperty(REASON).setValue(errorReason); item.getItemProperty(STATUS).setValue(status); } - List uploadStatusObjectList = (List) artifactUploadState - .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) - .collect(Collectors.toList()); + final List uploadStatusObjectList = artifactUploadState.getUploadedFileStatusList().stream() + .filter(e -> e.getFilename().equals(filename)).collect(Collectors.toList()); if (!uploadStatusObjectList.isEmpty()) { - UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + final UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); uploadStatusObject.setStatus(status); uploadStatusObject.setReason(errorReason); } @@ -428,8 +439,8 @@ public class UploadStatusInfoWindow extends Window { return resizeBtn; } - private void resizeWindow(ClickEvent event) { - if (event.getButton().getIcon() == FontAwesome.EXPAND) { + private void resizeWindow(final ClickEvent event) { + if (FontAwesome.EXPAND.equals(event.getButton().getIcon())) { event.getButton().setIcon(FontAwesome.COMPRESS); setWindowMode(WindowMode.MAXIMIZED); resetColumnWidth(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java index ff1cc3db1..9283af715 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/AbstractMetadataPopupLayout.java @@ -16,15 +16,17 @@ import javax.annotation.PostConstruct; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.NamedVersionedEntity; +import org.eclipse.hawkbit.ui.common.builder.LabelBuilder; +import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder; +import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder; +import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlButtonRenderer; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; -import org.eclipse.hawkbit.ui.decorators.SPUIWindowDecorator; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; -import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UINotification; import org.springframework.beans.factory.annotation.Autowired; @@ -48,7 +50,6 @@ import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.renderers.ClickableRenderer.RendererClickEvent; -import com.vaadin.ui.themes.ValoTheme; /** * @@ -115,9 +116,11 @@ public abstract class AbstractMetadataPopupLayout onSave(), event -> onCancel(), null, mainLayout, - i18n); + + metadataWindow = new WindowBuilder(SPUIDefinitions.CUSTOM_METADATA_WINDOW) + .caption(getMetadataCaption(nameVersion)).content(this).saveButtonClickListener(event -> onSave()) + .cancelButtonClickListener(event -> onCancel()).layout(mainLayout).i18n(i18n).buildCommonDialogWindow(); + metadataWindow.setId(SPUIComponentIdProvider.METADATA_POPUP_ID); metadataWindow.setHeight(550, Unit.PIXELS); metadataWindow.setWidth(800, Unit.PIXELS); @@ -204,9 +207,9 @@ public abstract class AbstractMetadataPopupLayout onKeyChange(event)); keyField.setTextChangeEventMode(TextChangeEventMode.EAGER); keyField.setWidth("100%"); @@ -214,9 +217,9 @@ public abstract class AbstractMetadataPopupLayout 110) { - v = 1f - (y - 110f) / 110f; + v = 1F - (y - 110F) / 110F; } return new Color(Color.HSVtoRGB(h, s, v)); } - private int calculateYCoordinateOfColor(final float[] hsv) { + private static int calculateYCoordinateOfColor(final float[] hsv) { int y; // lower half /* Assuming hsv[] array value will have in the range of 0 to 1 */ - if (hsv[1] < 1f) { - y = Math.round(hsv[1] * 110f); + if (hsv[1] < 1F) { + y = Math.round(hsv[1] * 110F); } else { - y = Math.round(110f - (hsv[1] + hsv[2]) * 110f); + y = Math.round(110F - (hsv[1] + hsv[2]) * 110F); } return y; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/AbstractTextFieldBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/AbstractTextFieldBuilder.java new file mode 100644 index 000000000..591983650 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/AbstractTextFieldBuilder.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.ui.common.builder; + +import org.apache.commons.lang3.StringUtils; + +import com.vaadin.ui.AbstractTextField; + +/** + * Abstract Text field builder. + * + * @param + * the concrete text component + */ +public abstract class AbstractTextFieldBuilder { + + private String caption; + private String style; + private String styleName; + private String prompt; + private String id; + private boolean immediate; + private boolean required; + private int maxLengthAllowed; + + /** + * @param caption + * the caption to set + * @return the builder + */ + public AbstractTextFieldBuilder caption(final String caption) { + this.caption = caption; + return this; + } + + /** + * @param style + * the style to set * @return the builder + * @return the builder + */ + public AbstractTextFieldBuilder style(final String style) { + this.style = style; + return this; + } + + /** + * @param styleName + * the styleName to set + * @return the builder + */ + public AbstractTextFieldBuilder styleName(final String styleName) { + this.styleName = styleName; + return this; + } + + /** + * @param required + * the required to set + * @return the builder + */ + public AbstractTextFieldBuilder required(final boolean required) { + this.required = required; + return this; + } + + /** + * @param prompt + * the prompt to set + * @return the builder + */ + public AbstractTextFieldBuilder prompt(final String prompt) { + this.prompt = prompt; + return this; + } + + /** + * @param immediate + * the immediate to set + * @return the builder + */ + public AbstractTextFieldBuilder immediate(final boolean immediate) { + this.immediate = immediate; + return this; + } + + /** + * @param maxLengthAllowed + * the maxLengthAllowed to set + * @return the builder + */ + public AbstractTextFieldBuilder maxLengthAllowed(final int maxLengthAllowed) { + this.maxLengthAllowed = maxLengthAllowed; + return this; + } + + /** + * @param id + * the id to set + * @return the builder + */ + public AbstractTextFieldBuilder id(final String id) { + this.id = id; + return this; + } + + /** + * Build a textfield + * + * @return textfield + */ + public E buildTextComponent() { + final E textComponent = createTextComponent(); + + textComponent.setRequired(required); + textComponent.setImmediate(immediate); + + if (StringUtils.isNotEmpty(caption)) { + textComponent.setCaption(caption); + } + + if (StringUtils.isNotEmpty(style)) { + textComponent.setStyleName(style); + } + + if (StringUtils.isNotEmpty(styleName)) { + textComponent.addStyleName(styleName); + } + if (StringUtils.isNotEmpty(prompt)) { + textComponent.setInputPrompt(prompt); + } + + if (maxLengthAllowed > 0) { + textComponent.setMaxLength(maxLengthAllowed); + } + + if (StringUtils.isNotEmpty(id)) { + textComponent.setId(id); + } + + return textComponent; + } + + protected abstract E createTextComponent(); + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/LabelBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/LabelBuilder.java new file mode 100644 index 000000000..12d50149a --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/LabelBuilder.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.ui.common.builder; + +import com.vaadin.ui.Label; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Label Builder. + * + */ +public class LabelBuilder { + + private String name; + + private String id; + + private boolean visible = true; + + /** + * @param name + * the name to set + * @return builder + */ + public LabelBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param id + * the id to set + * @return builder + */ + public LabelBuilder id(final String id) { + this.id = id; + return this; + } + + /** + * @param visible + * the visible to set + * @return builder + */ + public LabelBuilder visible(final boolean visible) { + this.visible = visible; + return this; + } + + /** + * Build caption label. + * + * @return Label + */ + public Label buildCaptionLabel() { + final Label label = createLabel(); + label.setValue(name); + label.addStyleName("header-caption"); + return label; + } + + /** + * Build label. + * + * @return Label + */ + public Label buildLabel() { + final Label label = createLabel(); + label.setImmediate(false); + label.setWidth("-1px"); + label.setHeight("-1px"); + + return label; + } + + private Label createLabel() { + final Label label = new Label(name); + label.setVisible(visible); + final StringBuilder style = new StringBuilder(ValoTheme.LABEL_SMALL); + style.append(' '); + style.append(ValoTheme.LABEL_BOLD); + label.addStyleName(style.toString()); + if (id != null) { + label.setId(id); + } + + return label; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/TextAreaBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/TextAreaBuilder.java new file mode 100644 index 000000000..d75b408d4 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/builder/TextAreaBuilder.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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.eclipse.hawkbit.ui.common.builder; + +import com.vaadin.ui.TextArea; +import com.vaadin.ui.themes.ValoTheme; + +/** + * TextArea builder. + * + */ +public class TextAreaBuilder extends AbstractTextFieldBuilder