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 81237beb7..69ed4fd4a 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 @@ -10,8 +10,6 @@ package org.eclipse.hawkbit.simulator; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.security.DigestOutputStream; import java.security.KeyManagementException; @@ -34,6 +32,7 @@ import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.eclipse.hawkbit.dmf.json.model.Artifact; +import org.eclipse.hawkbit.dmf.json.model.Artifact.UrlProtocol; import org.eclipse.hawkbit.dmf.json.model.SoftwareModule; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus; @@ -59,7 +58,7 @@ import com.google.common.io.ByteStreams; public class DeviceSimulatorUpdater { private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSimulatorUpdater.class); - private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); + private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(8); @Autowired private SpSenderService spSenderService; @@ -198,18 +197,14 @@ public class DeviceSimulatorUpdater { private static void handleArtifacts(final String targetToken, final List status, final Artifact artifact) { - artifact.getUrls().entrySet().forEach(entry -> { - switch (entry.getKey()) { - case HTTP: - case HTTPS: - status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1(), - artifact.getSize())); - break; - default: - // not supported yet - break; - } - }); + + if (artifact.getUrls().containsKey(UrlProtocol.HTTPS)) { + status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTPS), targetToken, + artifact.getHashes().getSha1(), artifact.getSize())); + } else if (artifact.getUrls().containsKey(UrlProtocol.HTTP)) { + status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTP), targetToken, + artifact.getHashes().getSha1(), artifact.getSize())); + } } private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash, @@ -236,22 +231,15 @@ public class DeviceSimulatorUpdater { return new UpdateStatus(ResponseStatus.ERROR, message); } - final File tempFile = File.createTempFile("uploadFile", null); - // Exception squid:S2070 - not used for hashing sensitive // data @SuppressWarnings("squid:S2070") final MessageDigest md = MessageDigest.getInstance("SHA-1"); - try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) { - try (final BufferedOutputStream bdos = new BufferedOutputStream(dos)) { - try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { - overallread = ByteStreams.copy(bis, bdos); - } - } - } finally { - if (tempFile != null && !tempFile.delete()) { - LOGGER.error("Could not delete temporary file: {}", tempFile); + try (final BufferedOutputStream bdos = new BufferedOutputStream( + new DigestOutputStream(ByteStreams.nullOutputStream(), md))) { + try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { + overallread = ByteStreams.copy(bis, bdos); } } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java index 6696c44ca..a170dfcd3 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/CacheAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.cache.interceptor.CacheOperationInvocationContext; import org.springframework.cache.interceptor.SimpleCacheResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; /** * A configuration for configuring the spring {@link CacheManager} for specific @@ -33,9 +34,6 @@ import org.springframework.context.annotation.Configuration; * * This is done by providing a special {@link TenantCacheResolver} which * generates a cache name included the current tenant. - * - * - * */ @Configuration @EnableCaching @@ -51,18 +49,27 @@ public class CacheAutoConfiguration extends CachingConfigurerSupport { @Override @Bean @ConditionalOnMissingBean + @Primary public TenancyCacheManager cacheManager() { - return new TenantAwareCacheManager(new GuavaCacheManager(), tenantAware); + return new TenantAwareCacheManager(directCacheManager(), tenantAware); + } + + /** + * @return the direct cache manager to access without tenant aware check, + * cause in sometimes it's necessary to access the cache directly + * without having the current tenant, e.g. initial creation of + * tenant + */ + @Bean(name = "directCacheManager") + @ConditionalOnMissingBean(name = "directCacheManager") + public CacheManager directCacheManager() { + return new GuavaCacheManager(); } /** * A {@link SimpleCacheResolver} implementation which includes the * {@link TenantAware#getCurrentTenant()} into the cache name before * resolving it. - * - * - * - * */ public class TenantCacheResolver extends SimpleCacheResolver { diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/DownloadIdCacheAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/DownloadIdCacheAutoConfiguration.java index 495200dec..134b50d90 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/DownloadIdCacheAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/cache/DownloadIdCacheAutoConfiguration.java @@ -31,7 +31,7 @@ public class DownloadIdCacheAutoConfiguration { private CacheManager cacheManager; /** - * Bean for the downlod id cache. + * Bean for the download id cache. * * @return the cache */ diff --git a/hawkbit-cache-redis/pom.xml b/hawkbit-cache-redis/pom.xml index 09567291b..637a6b49f 100644 --- a/hawkbit-cache-redis/pom.xml +++ b/hawkbit-cache-redis/pom.xml @@ -45,6 +45,12 @@ + + org.eclipse.hawkbit + hawkbit-repository-api + ${project.version} + test + org.springframework.boot spring-boot-starter-test diff --git a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java index edc183b17..fc3364d81 100644 --- a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java +++ b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java @@ -15,6 +15,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @@ -50,8 +51,14 @@ public class RedisConfiguration { * @return the spring redis cache manager. */ @Bean + @Primary public CacheManager cacheManager() { - return new TenantAwareCacheManager(new RedisCacheManager(redisTemplate()), tenantAware); + return new TenantAwareCacheManager(directCacheManager(), tenantAware); + } + + @Bean(name = "directCacheManager") + public CacheManager directCacheManager() { + return new RedisCacheManager(redisTemplate()); } /** diff --git a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java index c1ff54961..bebd82484 100644 --- a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java +++ b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java @@ -16,8 +16,8 @@ import static org.mockito.Mockito.verify; import java.util.Collection; -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; import org.eclipse.hawkbit.eventbus.event.EntityEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DownloadProgressEvent; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,7 +56,7 @@ public class EventDistributorTest { @Test public void distributeDistributedEventSendsToRedis() { - final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 10); + final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 500L, 100L, 200L); underTest.distribute(event); // origin node ID should be set by distributing the event @@ -67,7 +67,7 @@ public class EventDistributorTest { @Test public void dontDistributeDistributedEventIfSameNode() { final String knownNodeId = EventDistributor.getNodeId(); - final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 10); + final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 500L, 100L, 200L); event.setNodeId(knownNodeId); // test @@ -79,7 +79,7 @@ public class EventDistributorTest { @Test public void handleDistributedMessageFromRedis() { - final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 10); + final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 500L, 100L, 200L); final String knownChannel = "someChannel"; underTest.handleMessage(event, knownChannel); @@ -90,7 +90,7 @@ public class EventDistributorTest { @Test public void handleDistributedMessageFilteredIfSameNodeId() { - final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 10); + final DownloadProgressEvent event = new DownloadProgressEvent("tenant", 123L, 500L, 100L, 200L); final String knownChannel = "someChannel"; event.setOriginNodeId(EventDistributor.getNodeId()); diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/DownloadProgressEvent.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/DownloadProgressEvent.java deleted file mode 100644 index 74679f02e..000000000 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/DownloadProgressEvent.java +++ /dev/null @@ -1,57 +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.eventbus.event; - -/** - * Event that contains an updated download progress for a given Action. - * - * - * - * - */ -public class DownloadProgressEvent extends AbstractDistributedEvent { - - private static final long serialVersionUID = 1L; - - private final Long statusId; - private final int progressPercent; - - /** - * Constructor. - * - * @param tenant - * the tenant for this event - * @param statusId - * of {@link UpdateActionStatus} - * @param progressPercent - * number (1-100) - */ - public DownloadProgressEvent(final String tenant, final Long statusId, final int progressPercent) { - // the revision of the DownloadProgressEvent is just equal the - // progressPercentage due the - // percentage is going from 0 to 100. - super(statusId, tenant); - this.statusId = statusId; - this.progressPercent = progressPercent; - } - - /** - * @return the statusId - */ - public Long getStatusId() { - return statusId; - } - - /** - * @return the progressPercent - */ - public int getProgressPercent() { - return progressPercent; - } -} diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java index 75d502c47..e2af0a873 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java @@ -93,12 +93,12 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR // we set a download status only if we are aware of the // targetid, i.e. authenticated and not anonymous if (targetid != null && !"anonymous".equals(targetid)) { - final Action action = checkAndReportDownloadByTarget( + final ActionStatus actionStatus = checkAndReportDownloadByTarget( requestResponseContextHolder.getHttpServletRequest(), targetid, artifact); result = RestResourceConversionHelper.writeFileResponse(artifact, requestResponseContextHolder.getHttpServletResponse(), requestResponseContextHolder.getHttpServletRequest(), file, controllerManagement, - action.getId()); + actionStatus.getId()); } else { result = RestResourceConversionHelper.writeFileResponse(artifact, requestResponseContextHolder.getHttpServletResponse(), @@ -131,7 +131,7 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR return new ResponseEntity<>(HttpStatus.OK); } - private Action checkAndReportDownloadByTarget(final HttpServletRequest request, final String targetid, + private ActionStatus checkAndReportDownloadByTarget(final HttpServletRequest request, final String targetid, final LocalArtifact artifact) { final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest(request, securityProperties)); @@ -152,8 +152,8 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR actionStatus.addMessage( RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads: " + request.getRequestURI()); } - controllerManagement.addInformationalActionStatus(actionStatus); - return action; + + return controllerManagement.addInformationalActionStatus(actionStatus); } } diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 120e6c246..9dd65d5fc 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -156,8 +156,8 @@ public class DdiRootController implements DdiRootControllerRestApi { if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); } else { - final Action action = checkAndLogDownload(requestResponseContextHolder.getHttpServletRequest(), target, - module); + final ActionStatus action = checkAndLogDownload(requestResponseContextHolder.getHttpServletRequest(), + target, module); result = RestResourceConversionHelper.writeFileResponse(artifact, requestResponseContextHolder.getHttpServletResponse(), requestResponseContextHolder.getHttpServletRequest(), file, controllerManagement, @@ -167,7 +167,7 @@ public class DdiRootController implements DdiRootControllerRestApi { return result; } - private Action checkAndLogDownload(final HttpServletRequest request, final Target target, + private ActionStatus checkAndLogDownload(final HttpServletRequest request, final Target target, final SoftwareModule module) { final Action action = controllerManagement .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module); @@ -185,8 +185,8 @@ public class DdiRootController implements DdiRootControllerRestApi { statusMessage.addMessage( RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target downloads " + request.getRequestURI()); } - controllerManagement.addInformationalActionStatus(statusMessage); - return action; + + return controllerManagement.addInformationalActionStatus(statusMessage); } private static boolean checkModule(final String fileName, final SoftwareModule module) { diff --git a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java index 7f12b78ae..81be97b7c 100644 --- a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java +++ b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactDownloadTest.java @@ -26,14 +26,14 @@ import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.RandomUtils; -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DownloadProgressEvent; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTestWithMongoDB; import org.junit.Test; import org.slf4j.LoggerFactory; @@ -59,11 +59,15 @@ import ru.yandex.qatools.allure.annotations.Stories; @Stories("Artifact Download Resource") public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMongoDB { + private static final int ARTIFACT_SIZE = 5 * 1024 * 1024; + public DdiArtifactDownloadTest() { LOG = LoggerFactory.getLogger(DdiArtifactDownloadTest.class); } private volatile int downLoadProgress = 0; + private volatile long shippedBytes = 0; + private volatile long shippedBytesTotal = 0; @Autowired private EventBus eventBus; @@ -236,6 +240,8 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong @Description("Tests valid downloads through the artifact resource by identifying the artifact not by ID but file name.") public void downloadArtifactThroughFileName() throws Exception { downLoadProgress = 1; + shippedBytes = 0; + shippedBytesTotal = 0; eventBus.register(this); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); @@ -249,7 +255,7 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong final DistributionSet ds = testdataFactory.createDistributionSet(""); // create artifact - final byte random[] = RandomUtils.nextBytes(5 * 1024 * 1024); + final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE); final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), ds.findFirstModuleByType(osType).getId(), "file1", false); @@ -276,6 +282,7 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong // download complete assertThat(downLoadProgress).isEqualTo(10); + assertThat(shippedBytes).isEqualTo(shippedBytesTotal).isEqualTo(ARTIFACT_SIZE); } @Test @@ -313,35 +320,8 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong + "anonymous as authorization is notpossible, e.g. chekc if the controller has the artifact assigned.") public void downloadArtifactByNameFailsIfNotAuthenticated() throws Exception { downLoadProgress = 1; - eventBus.register(this); - - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - - // create target - Target target = entityFactory.generateTarget("4712"); - target = targetManagement.createTarget(target); - final List targets = new ArrayList(); - targets.add(target); - - // create ds - final DistributionSet ds = testdataFactory.createDistributionSet(""); - - // create artifact - final byte random[] = RandomUtils.nextBytes(5 * 1024); - final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), - ds.findFirstModuleByType(osType).getId(), "file1.tar.bz2", false); - - // download fails as artifact is not yet assigned to target - deploymentManagement.assignDistributionSet(ds, targets); - mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1.tar.bz2")) - .andExpect(status().isNotFound()); - } - - @Test - @WithUser(principal = "4712", authorities = "ROLE_CONTROLLER", allSpPermissions = true) - @Description("Ensures that an authenticated and named controller is permitted to download.") - public void downloadArtifactByNameByNamedController() throws Exception { - downLoadProgress = 1; + shippedBytes = 0; + shippedBytesTotal = 0; eventBus.register(this); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); @@ -356,7 +336,41 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong final DistributionSet ds = testdataFactory.createDistributionSet(""); // create artifact - final byte random[] = RandomUtils.nextBytes(5 * 1024 * 1024); + final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE); + artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), + ds.findFirstModuleByType(osType).getId(), "file1.tar.bz2", false); + + // download fails as artifact is not yet assigned to target + deploymentManagement.assignDistributionSet(ds, targets); + mvc.perform(get("/controller/artifacts/v1/filename/{filename}", "file1.tar.bz2")) + .andExpect(status().isNotFound()); + + assertThat(downLoadProgress).isEqualTo(1); + assertThat(shippedBytes).isEqualTo(shippedBytesTotal).isEqualTo(0L); + } + + @Test + @WithUser(principal = "4712", authorities = "ROLE_CONTROLLER", allSpPermissions = true) + @Description("Ensures that an authenticated and named controller is permitted to download.") + public void downloadArtifactByNameByNamedController() throws Exception { + downLoadProgress = 1; + shippedBytes = 0; + shippedBytesTotal = 0; + eventBus.register(this); + + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); + + // create target + Target target = entityFactory.generateTarget("4712"); + target = targetManagement.createTarget(target); + final List targets = new ArrayList<>(); + targets.add(target); + + // create ds + final DistributionSet ds = testdataFactory.createDistributionSet(""); + + // create artifact + final byte random[] = RandomUtils.nextBytes(ARTIFACT_SIZE); final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), ds.findFirstModuleByType(osType).getId(), "file1", false); @@ -389,6 +403,7 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong // download complete assertThat(downLoadProgress).isEqualTo(10); + assertThat(shippedBytes).isEqualTo(shippedBytesTotal).isEqualTo(ARTIFACT_SIZE); } @Test @@ -550,5 +565,8 @@ public class DdiArtifactDownloadTest extends AbstractRestIntegrationTestWithMong @Subscribe public void listen(final DownloadProgressEvent event) { downLoadProgress++; + shippedBytes += event.getShippedBytesSinceLast(); + shippedBytesTotal = event.getShippedBytesOverall(); + } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java index 538f82213..11f892654 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java @@ -22,11 +22,11 @@ import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.QueueBuilder; -import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; @@ -268,15 +268,8 @@ public class AmqpConfiguration { * AMQP messages */ @Bean(name = { "listenerContainerFactory" }) - public SimpleRabbitListenerContainerFactory listenerContainerFactory() { - final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory(); - containerFactory.setDefaultRequeueRejected(true); - containerFactory.setConnectionFactory(rabbitConnectionFactory); - containerFactory.setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal()); - containerFactory.setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers()); - containerFactory.setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers()); - containerFactory.setPrefetchCount(amqpProperties.getPrefetchCount()); - return containerFactory; + public RabbitListenerContainerFactory listenerContainerFactory() { + return new ConfigurableRabbitListenerContainerFactory(amqpProperties, rabbitConnectionFactory); } private static Map getTTLMaxArgsAuthenticationQueue() { 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 b003d316a..d1bf85103 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 @@ -138,9 +138,18 @@ public class AmqpMessageHandlerService extends BaseAmqpService { } @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory") - public Message onAuthenticationRequest(final Message message, - @Header(MessageHeaderKey.TENANT) final String tenant) { - return onAuthenticationRequest(message); + public Message onAuthenticationRequest(final Message message) { + checkContentTypeJson(message); + final SecurityContext oldContext = SecurityContextHolder.getContext(); + try { + return handleAuthentifiactionMessage(message); + } catch (final IllegalArgumentException ex) { + throw new AmqpRejectAndDontRequeueException("Invalid message!", ex); + } catch (final TenantNotExistException teex) { + throw new AmqpRejectAndDontRequeueException(teex); + } finally { + SecurityContextHolder.setContext(oldContext); + } } public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) { @@ -159,7 +168,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final EventTopic eventTopic = EventTopic.valueOf(topicValue); handleIncomingEvent(message, eventTopic); break; - default: logAndThrowMessageError(message, "No handle method was found for the given message type."); } @@ -173,20 +181,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService { return null; } - public Message onAuthenticationRequest(final Message message) { - checkContentTypeJson(message); - final SecurityContext oldContext = SecurityContextHolder.getContext(); - try { - return handleAuthentifiactionMessage(message); - } catch (final IllegalArgumentException ex) { - throw new AmqpRejectAndDontRequeueException("Invalid message!", ex); - } catch (final TenantNotExistException teex) { - throw new AmqpRejectAndDontRequeueException(teex); - } finally { - SecurityContextHolder.setContext(oldContext); - } - } - private Message handleAuthentifiactionMessage(final Message message) { final DownloadResponse authentificationResponse = new DownloadResponse(); final MessageProperties messageProperties = message.getMessageProperties(); @@ -414,7 +408,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { if (ArrayUtils.isNotEmpty(message.getMessageProperties().getCorrelationId())) { actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "DMF message correlation-id " - + message.getMessageProperties().getCorrelationId()); + + convertCorrelationId(message)); } actionStatus.setAction(action); @@ -422,6 +416,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService { return actionStatus; } + private static String convertCorrelationId(final Message message) { + return new String(message.getMessageProperties().getCorrelationId()); + } + private Action getUpdateActionStatus(final ActionStatus actionStatus) { if (actionStatus.getStatus().equals(Status.CANCELED)) { return controllerManagement.addCancelActionStatus(actionStatus); @@ -466,7 +464,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) { return; } - throw new IllegalArgumentException("Content-Type is not JSON compatible"); + throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible"); } void setControllerManagement(final ControllerManagement controllerManagement) { diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java index 888b204e7..56aa37772 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java @@ -68,6 +68,29 @@ public class AmqpProperties { */ private int initialConcurrentConsumers = 3; + /** + * The number of retry attempts when passive queue declaration fails. + * Passive queue declaration occurs when the consumer starts or, when + * consuming from multiple queues, when not all queues were available during + * initialization. + */ + private int declarationRetries = 50; + + /** + * @return the declarationRetries + */ + public int getDeclarationRetries() { + return declarationRetries; + } + + /** + * @param declarationRetries + * the declarationRetries to set + */ + public void setDeclarationRetries(final int declarationRetries) { + this.declarationRetries = declarationRetries; + } + public String getAuthenticationReceiverQueue() { return authenticationReceiverQueue; } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/ConfigurableRabbitListenerContainerFactory.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/ConfigurableRabbitListenerContainerFactory.java new file mode 100644 index 000000000..14e6f8fcb --- /dev/null +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/ConfigurableRabbitListenerContainerFactory.java @@ -0,0 +1,52 @@ +/** + * 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.amqp; + +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; + +/** + * {@link RabbitListenerContainerFactory} that can be configured through + * hawkBit's {@link AmqpProperties}. + * + */ +public class ConfigurableRabbitListenerContainerFactory extends SimpleRabbitListenerContainerFactory { + private final AmqpProperties amqpProperties; + + /** + * Constructor. + * + * @param rabbitConnectionFactory + * for the container factory + * @param amqpProperties + * to configure the container factory + */ + public ConfigurableRabbitListenerContainerFactory(final AmqpProperties amqpProperties, + final ConnectionFactory rabbitConnectionFactory) { + this.amqpProperties = amqpProperties; + setDefaultRequeueRejected(true); + setConnectionFactory(rabbitConnectionFactory); + setMissingQueuesFatal(amqpProperties.isMissingQueuesFatal()); + setConcurrentConsumers(amqpProperties.getInitialConcurrentConsumers()); + setMaxConcurrentConsumers(amqpProperties.getMaxConcurrentConsumers()); + setPrefetchCount(amqpProperties.getPrefetchCount()); + + } + + @Override + // Exception squid:UnusedProtectedMethod - called by + // AbstractRabbitListenerContainerFactory + @SuppressWarnings("squid:UnusedProtectedMethod") + protected void initializeContainer(final SimpleMessageListenerContainer instance) { + super.initializeContainer(instance); + instance.setDeclarationRetries(amqpProperties.getDeclarationRetries()); + } +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 6e61d1642..2627731c6 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -140,8 +140,8 @@ public class AmqpMessageHandlerServiceTest { final Message message = new Message(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to worng content type"); - } catch (final IllegalArgumentException e) { + fail("AmqpRejectAndDontRequeueException was excepeted due to worng content type"); + } catch (final AmqpRejectAndDontRequeueException e) { } } @@ -175,7 +175,7 @@ public class AmqpMessageHandlerServiceTest { try { amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted since no replyTo header was set"); + fail("AmqpRejectAndDontRequeueException was excepeted since no replyTo header was set"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -189,7 +189,7 @@ public class AmqpMessageHandlerServiceTest { final Message message = messageConverter.toMessage(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted since no thingID was set"); + fail("AmqpRejectAndDontRequeueException was excepeted since no thingID was set"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -205,7 +205,7 @@ public class AmqpMessageHandlerServiceTest { try { amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown message type"); + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -218,21 +218,21 @@ public class AmqpMessageHandlerServiceTest { final Message message = new Message(new byte[0], messageProperties); try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown message type"); + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type"); } catch (final AmqpRejectAndDontRequeueException e) { } try { messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic"); amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted due to unknown topic"); + fail("AmqpRejectAndDontRequeueException was excepeted due to unknown topic"); } catch (final AmqpRejectAndDontRequeueException e) { } messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name()); try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted because there was no event topic"); + fail("AmqpRejectAndDontRequeueException was excepeted because there was no event topic"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -251,7 +251,7 @@ public class AmqpMessageHandlerServiceTest { try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted since no action id was set"); + fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } @@ -268,7 +268,7 @@ public class AmqpMessageHandlerServiceTest { try { amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); - fail("IllegalArgumentException was excepeted since no action id was set"); + fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set"); } catch (final AmqpRejectAndDontRequeueException exception) { // test ok - exception was excepted } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/systemmanagement/MgmtSystemTenantServiceUsage.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/systemmanagement/MgmtSystemTenantServiceUsage.java index 8cce4314b..c3e37421d 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/systemmanagement/MgmtSystemTenantServiceUsage.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/systemmanagement/MgmtSystemTenantServiceUsage.java @@ -32,7 +32,6 @@ public class MgmtSystemTenantServiceUsage { * @param tenantName */ public MgmtSystemTenantServiceUsage(final String tenantName) { - super(); this.tenantName = tenantName; } diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java index b9c0b001a..f7a297be1 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java @@ -21,6 +21,17 @@ public class MgmtTargetRequestBody { @JsonProperty private String address; + @JsonProperty + private String securityToken; + + public String getSecurityToken() { + return securityToken; + } + + public void setSecurityToken(final String securityToken) { + this.securityToken = securityToken; + } + /** * @return the name */ diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java index f6cf9f54b..a889edc35 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java @@ -182,10 +182,11 @@ public final class MgmtTargetMapper { } static Target fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) { - final Target target = entityFactory.generateTarget(targetRest.getControllerId()); + final Target target = entityFactory.generateTarget(targetRest.getControllerId(), targetRest.getSecurityToken()); target.setDescription(targetRest.getDescription()); target.setName(targetRest.getName()); target.getTargetInfo().setAddress(targetRest.getAddress()); + return target; } diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index c41dadd27..5ddb74314 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -125,6 +125,13 @@ public class MgmtTargetResource implements MgmtTargetRestApi { if (targetRest.getName() != null) { existingTarget.setName(targetRest.getName()); } + if (targetRest.getAddress() != null) { + existingTarget.getTargetInfo().setAddress(targetRest.getAddress()); + } + if (targetRest.getSecurityToken() != null) { + existingTarget.setSecurityToken(targetRest.getSecurityToken()); + } + final Target updateTarget = this.targetManagement.updateTarget(existingTarget); return new ResponseEntity<>(MgmtTargetMapper.toResponse(updateTarget), HttpStatus.OK); 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 ed6ee4a93..a528438bb 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 @@ -355,6 +355,54 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); } + @Test + @Description("Ensures that target update request is reflected by repository.") + public void updateTargetSecurityToken() throws Exception { + final String knownControllerId = "123"; + final String knownNewToken = "6567576565"; + final String knownNameNotModiy = "nameNotModiy"; + final String body = new JSONObject().put("securityToken", knownNewToken).toString(); + + // prepare + final Target t = entityFactory.generateTarget(knownControllerId); + t.setName(knownNameNotModiy); + targetManagement.createTarget(t); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.controllerId", equalTo(knownControllerId))) + .andExpect(jsonPath("$.securityToken", equalTo(knownNewToken))) + .andExpect(jsonPath("$.name", equalTo(knownNameNotModiy))); + + final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownControllerId); + assertThat(findTargetByControllerID.getSecurityToken()).isEqualTo(knownNewToken); + assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); + } + + @Test + @Description("Ensures that target update request is reflected by repository.") + public void updateTargetAddress() throws Exception { + final String knownControllerId = "123"; + final String knownNewAddress = "amqp://test123/foobar"; + final String knownNameNotModiy = "nameNotModiy"; + final String body = new JSONObject().put("address", knownNewAddress).toString(); + + // prepare + final Target t = entityFactory.generateTarget(knownControllerId); + t.setName(knownNameNotModiy); + targetManagement.createTarget(t); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.controllerId", equalTo(knownControllerId))) + .andExpect(jsonPath("$.address", equalTo(knownNewAddress))) + .andExpect(jsonPath("$.name", equalTo(knownNameNotModiy))); + + final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownControllerId); + assertThat(findTargetByControllerID.getTargetInfo().getAddress().toString()).isEqualTo(knownNewAddress); + assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); + } + @Test @Description("Ensures that target query returns list of targets in defined format.") public void getTargetWithoutAddtionalRequestParameters() throws Exception { @@ -679,7 +727,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { @Test public void createTargetsListReturnsSuccessful() throws Exception { - final Target test1 = entityFactory.generateTarget("id1"); + final Target test1 = entityFactory.generateTarget("id1", "token"); test1.setDescription("testid1"); test1.setName("testname1"); test1.getTargetInfo().setAddress("amqp://test123/foobar"); @@ -696,7 +744,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { targets.add(test3); final MvcResult mvcResult = mvc - .perform(post("/rest/v1/targets/").content(JsonBuilder.targets(targets)) + .perform(post("/rest/v1/targets/").content(JsonBuilder.targets(targets, true)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -705,6 +753,7 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { .andExpect(jsonPath("[0].description", equalTo("testid1"))) .andExpect(jsonPath("[0].createdAt", not(equalTo(0)))) .andExpect(jsonPath("[0].createdBy", equalTo("bumlux"))) + .andExpect(jsonPath("[0].securityToken", equalTo("token"))) .andExpect(jsonPath("[0].address", equalTo("amqp://test123/foobar"))) .andExpect(jsonPath("[1].name", equalTo("testname2"))) .andExpect(jsonPath("[1].createdBy", equalTo("bumlux"))) @@ -731,6 +780,9 @@ public class MgmtTargetResourceTest extends AbstractRestIntegrationTest { assertThat(targetManagement.findTargetByControllerID("id1")).isNotNull(); assertThat(targetManagement.findTargetByControllerID("id1").getName()).isEqualTo("testname1"); assertThat(targetManagement.findTargetByControllerID("id1").getDescription()).isEqualTo("testid1"); + assertThat(targetManagement.findTargetByControllerID("id1").getSecurityToken()).isEqualTo("token"); + assertThat(targetManagement.findTargetByControllerID("id1").getTargetInfo().getAddress().toString()) + .isEqualTo("amqp://test123/foobar"); assertThat(targetManagement.findTargetByControllerID("id2")).isNotNull(); assertThat(targetManagement.findTargetByControllerID("id2").getName()).isEqualTo("testname2"); assertThat(targetManagement.findTargetByControllerID("id2").getDescription()).isEqualTo("testid2"); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 531da8819..e5dcb8f74 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -14,8 +14,8 @@ import java.util.Map; import javax.validation.constraints.NotNull; -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.eventbus.event.DownloadProgressEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.ToManyAttributeEntriesException; @@ -58,16 +58,20 @@ public interface ControllerManagement { Action addCancelActionStatus(@NotNull ActionStatus actionStatus); /** - * Sends the download progress in percentage and notifies the - * {@link EventBus} with a {@link DownloadProgressEvent}. + * Sends the download progress and notifies the {@link EventBus} with a + * {@link DownloadProgressEvent}. * * @param statusId * the ID of the {@link ActionStatus} - * @param progressPercent - * the progress in percentage which must be between 0-100 + * @param requestedBytes + * requested bytes of the request + * @param shippedBytesSinceLast + * since the last report + * @param shippedBytesOverall + * for the {@link ActionStatus} */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - void downloadProgressPercent(long statusId, int progressPercent); + void downloadProgress(Long statusId, Long requestedBytes, Long shippedBytesSinceLast, Long shippedBytesOverall); /** * Simple addition of a new {@link ActionStatus} entry to the {@link Action} @@ -75,9 +79,11 @@ public interface ControllerManagement { * * @param statusMessage * to add to the action + * + * @return create {@link ActionStatus} entity */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - void addInformationalActionStatus(@NotNull ActionStatus statusMessage); + ActionStatus addInformationalActionStatus(@NotNull ActionStatus statusMessage); /** * Adds an {@link ActionStatus} entry for an update {@link Action} including diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 9dc5f74c8..270de3e74 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -269,8 +269,7 @@ public interface DeploymentManagement { * @return the actions referring a specific rollout and a specific parent * rollout group in a specific status */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) List findActionsByRolloutGroupParentAndStatus(@NotNull Rollout rollout, @NotNull RolloutGroup rolloutGroupParent, @NotNull Action.Status actionStatus); @@ -496,8 +495,7 @@ public interface DeploymentManagement { * the action to start now. * @return the action which has been started */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Action startScheduledAction(@NotNull Action action); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java index 7542cd2ce..87919729e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java @@ -287,6 +287,7 @@ public interface EntityFactory { /** * Generates an empty {@link Target} without persisting it. + * {@link Target#getSecurityToken()} is generated. * * @param controllerID * of the {@link Target} @@ -295,6 +296,19 @@ public interface EntityFactory { */ Target generateTarget(@NotEmpty String controllerID); + /** + * Generates an empty {@link Target} without persisting it. + * + * @param controllerID + * of the {@link Target} + * @param securityToken + * of the {@link Target} for authentication if enabled on tenant. + * Generates one if empty or null. + * + * @return {@link Target} object + */ + Target generateTarget(@NotEmpty String controllerID, @NotEmpty String securityToken); + /** * Generates an empty {@link TargetFilterQuery} without persisting it. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 2c6e85dda..337475318 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -61,8 +61,7 @@ public interface RolloutManagement { * this check. This check is only applied if the last check is * less than (lastcheck-delay). */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void checkRunningRollouts(long delayBetweenChecks); /** @@ -266,8 +265,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#RUNNING}. * Only running rollouts can be paused. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void pauseRollout(@NotNull Rollout rollout); /** @@ -281,8 +279,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#PAUSED}. Only * paused rollouts can be resumed. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) void resumeRollout(@NotNull Rollout rollout); /** @@ -303,8 +300,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#READY}. Only * ready rollouts can be started. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) Rollout startRollout(@NotNull Rollout rollout); /** @@ -326,8 +322,7 @@ public interface RolloutManagement { * if given rollout is not in {@link RolloutStatus#READY}. Only * ready rollouts can be started. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) Rollout startRolloutAsync(@NotNull Rollout rollout); /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 039e4ae13..a44b13d92 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -39,16 +39,14 @@ public interface SystemManagement { * @param tenant * to delete */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) void deleteTenant(@NotNull String tenant); /** * * @return list of all tenant names in the system. */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) List findTenants(); /** @@ -68,8 +66,8 @@ public interface SystemManagement { /** * Returns {@link TenantMetaData} of given and current tenant. Creates for * new tenants also two {@link SoftwareModuleType} (os and app) and - * {@link RepositoryConstants#DEFAULT_DS_TYPES_IN_TENANT} {@link DistributionSetType}s - * (os and os_app). + * {@link RepositoryConstants#DEFAULT_DS_TYPES_IN_TENANT} + * {@link DistributionSetType}s (os and os_app). * * DISCLAIMER: this variant is used during initial login (where the tenant * is not yet in the session). Please user {@link #getTenantMetadata()} for diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java index 734704f05..9c467f9dd 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java @@ -54,8 +54,7 @@ public interface TenantConfigurationManagement { * @return if no default value is set and no database value available * or returns the tenant configuration value */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue buildTenantConfigurationValueByKey(TenantConfigurationKey configurationKey, Class propertyType, TenantConfiguration tenantConfiguration); @@ -87,8 +86,7 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue getConfigurationValue(TenantConfigurationKey configurationKey); /** @@ -114,8 +112,7 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) TenantConfigurationValue getConfigurationValue(TenantConfigurationKey configurationKey, Class propertyType); @@ -139,7 +136,6 @@ public interface TenantConfigurationManagement { * if the property cannot be converted to the given * {@code propertyType} */ - @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) T getGlobalConfigurationValue(TenantConfigurationKey configurationKey, Class propertyType); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java index 6d390c03c..f041bc45f 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TenantStatsManagement.java @@ -20,15 +20,14 @@ import org.springframework.security.access.prepost.PreAuthorize; public interface TenantStatsManagement { /** - * Service for stats of a single tenant. Opens a new transaction and as a - * result can an be used for multiple tenants, i.e. to allow in one session - * to collect data of all tenants in the system. + * Service for stats of the current tenant. * - * @param tenant - * to collect for * @return collected statistics */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) - TenantUsage getStatsOfTenant(String tenant); + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + TenantUsage getStatsOfTenant(); } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DownloadProgressEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DownloadProgressEvent.java new file mode 100644 index 000000000..6d145e0ae --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/eventbus/event/DownloadProgressEvent.java @@ -0,0 +1,67 @@ +/** + * 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; + +/** + * Event that contains an updated download progress for a given ActionStatus + * that was written for a download request. + * + */ +public class DownloadProgressEvent extends AbstractDistributedEvent { + + private static final long serialVersionUID = 1L; + + private final Long statusId; + private final long requestedBytes; + private final long shippedBytesSinceLast; + private final long shippedBytesOverall; + + /** + * Constructor. + * + * @param tenant + * the tenant for this event + * @param statusId + * of ActionStatus that was written for the download request + * @param requestedBytes + * bytes requested + * @param shippedBytesSinceLast + * bytes since last event + * @param shippedBytesOverall + * on the download request + */ + public DownloadProgressEvent(final String tenant, final Long statusId, final Long requestedBytes, + final Long shippedBytesSinceLast, final Long shippedBytesOverall) { + // the revision of the DownloadProgressEvent is just equal the + // shippedBytesOverall as this is a growing number. + super(shippedBytesOverall, tenant); + this.statusId = statusId; + this.requestedBytes = requestedBytes; + this.shippedBytesSinceLast = shippedBytesSinceLast; + this.shippedBytesOverall = shippedBytesOverall; + } + + public Long getStatusId() { + return statusId; + } + + public long getRequestedBytes() { + return requestedBytes; + } + + public long getShippedBytesSinceLast() { + return shippedBytesSinceLast; + } + + public long getShippedBytesOverall() { + return shippedBytesOverall; + } +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index c2b96905e..98d101471 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -39,12 +39,6 @@ public interface Action extends TenantAwareBaseEntity { return Status.CANCELING.equals(getStatus()) || Status.CANCELED.equals(getStatus()); } - /** - * @return current {@link Status#DOWNLOAD} progress if known by the update - * server. - */ - int getDownloadProgressPercent(); - /** * @return current {@link Status} of the {@link Action}. */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java index f3ca66887..e83108fe8 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java @@ -43,6 +43,12 @@ public interface ActionStatus extends TenantAwareBaseEntity { */ void addMessage(String message); + /** + * @return current {@link Status#DOWNLOAD} progress if known by the update + * server. + */ + int getDownloadProgressPercent(); + /** * @return list of message entries that can be added to the * {@link ActionStatus}. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 56ab89ad3..c09b28a2f 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -58,4 +58,10 @@ public interface Target extends NamedEntity { */ String getSecurityToken(); + /** + * @param token + * new securityToken + */ + void setSecurityToken(String token); + } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/TenantUsage.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/TenantUsage.java index 5467099c4..933ca564b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/TenantUsage.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/report/model/TenantUsage.java @@ -106,7 +106,7 @@ public class TenantUsage { } @Override - public int hashCode() { // NOSONAR - as this is generated code + public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (actions ^ (actions >>> 32)); @@ -118,15 +118,14 @@ public class TenantUsage { } @Override - public boolean equals(final Object obj) { // NOSONAR - as this is generated - // code + public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } - if (getClass() != obj.getClass()) { + if (!(obj instanceof TenantUsage)) { return false; } final TenantUsage other = (TenantUsage) obj; @@ -154,7 +153,7 @@ public class TenantUsage { @Override public String toString() { - return "SystemUsage [tenantName=" + tenantName + ", targets=" + targets + ", artifacts=" + artifacts + return "TenantUsage [tenantName=" + tenantName + ", targets=" + targets + ", artifacts=" + artifacts + ", actions=" + actions + ", overallArtifactVolumeInBytes=" + overallArtifactVolumeInBytes + "]"; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index 217fe8801..7236cbe79 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -54,6 +54,7 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManage import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; @@ -71,6 +72,8 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; +import com.google.common.eventbus.EventBus; + /** * General configuration for hawkBit's Repository. * @@ -85,6 +88,9 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess @EnableConfigurationProperties(RepositoryProperties.class) @EnableScheduling public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { + @Autowired + private EventBus eventBus; + /** * @return the {@link SystemSecurityContext} singleton bean which make it * accessible in beans which cannot access the service directly, @@ -249,7 +255,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean public TenantStatsManagement tenantStatsManagement() { - return new JpaTenantStatsManagement(); + final TenantStatsManagement mgmt = new JpaTenantStatsManagement(); + eventBus.register(mgmt); + return mgmt; } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index b9dfce105..b574d1452 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -451,8 +451,8 @@ public class JpaControllerManagement implements ControllerManagement { @Override @Modifying @Transactional(isolation = Isolation.READ_UNCOMMITTED) - public void addInformationalActionStatus(final ActionStatus statusMessage) { - actionStatusRepository.save((JpaActionStatus) statusMessage); + public ActionStatus addInformationalActionStatus(final ActionStatus statusMessage) { + return actionStatusRepository.save((JpaActionStatus) statusMessage); } @Override @@ -469,8 +469,9 @@ public class JpaControllerManagement implements ControllerManagement { } @Override - public void downloadProgressPercent(final long statusId, final int progressPercent) { - cacheWriteNotify.downloadProgressPercent(statusId, progressPercent); + public void downloadProgress(final Long statusId, final Long requestedBytes, final Long shippedBytesSinceLast, + final Long shippedBytesOverall) { + cacheWriteNotify.downloadProgress(statusId, requestedBytes, shippedBytesSinceLast, shippedBytesOverall); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java index a3792de54..51b59f08f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.Collection; +import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; @@ -87,6 +88,14 @@ public class JpaEntityFactory implements EntityFactory { return new JpaTarget(controllerId); } + @Override + public Target generateTarget(final String controllerId, final String securityToken) { + if (StringUtils.isEmpty(securityToken)) { + return new JpaTarget(controllerId); + } + return new JpaTarget(controllerId, securityToken); + } + @Override public TargetTag generateTargetTag() { return new JpaTargetTag(); 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 50127e227..b561667a9 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 @@ -132,7 +132,7 @@ public class JpaSoftwareManagement implements SoftwareManagement { final JpaSoftwareModuleType type = softwareModuleTypeRepository.findOne(sm.getId()); boolean updated = false; - if (sm.getDescription() != null && !sm.getDescription().equals(type.getDescription())) { + if (sm.getDescription() == null || !sm.getDescription().equals(type.getDescription())) { type.setDescription(sm.getDescription()); updated = true; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java index ea6c99708..f913a6c95 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java @@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.Constants; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData; @@ -26,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.repository.report.model.SystemUsageReport; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.springframework.beans.factory.annotation.Autowired; @@ -33,11 +35,14 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.interceptor.KeyGenerator; -import org.springframework.context.ApplicationContext; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; import org.springframework.validation.annotation.Validated; /** @@ -108,7 +113,10 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator; @Autowired - private ApplicationContext applicationContext; + private SystemSecurityContext systemSecurityContext; + + @Autowired + private PlatformTransactionManager txManager; @Override public SystemUsageReport getSystemUsageStatistics() { @@ -147,7 +155,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst final List tenants = findTenants(); tenants.forEach(tenant -> tenantAware.runAsTenant(tenant, () -> { - report.addTenantData(systemStatsManagement.getStatsOfTenant(tenant)); + report.addTenantData(systemStatsManagement.getStatsOfTenant()); return null; })); } @@ -159,27 +167,48 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst } @Override - @Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()") + @Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()", cacheManager = "directCacheManager") @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Modifying public TenantMetaData getTenantMetadata(final String tenant) { final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant); - // Create if it does not exist if (result == null) { try { currentTenantCacheKeyGenerator.getCreateInitialTenant().set(tenant); - cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null)); - applicationContext.getBean("currentTenantKeyGenerator"); - return tenantMetaDataRepository.save(new JpaTenantMetaData(createStandardSoftwareDataSetup(), tenant)); + return createInitialTenantMetaData(tenant); + } finally { currentTenantCacheKeyGenerator.getCreateInitialTenant().remove(); } } - return result; } + /** + * Creating the initial tenant meta-data in a new transaction. Due the + * {@link MultiTenantJpaTransactionManager} is using the current tenant to + * set the necessary tenant discriminator to the query. This is not working + * if we don't have a current tenant set. Due the + * {@link #getTenantMetadata(String)} is maybe called without having a + * current tenant we need to re-open a new transaction so the + * {@link MultiTenantJpaTransactionManager} is called again and set the + * tenant for this transaction. + * + * @param tenant + * the tenant to be created + * @return the initial created {@link TenantMetaData} + */ + private TenantMetaData createInitialTenantMetaData(final String tenant) { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("initial-tenant-creation"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + return systemSecurityContext.runAsSystemAsTenant( + () -> new TransactionTemplate(txManager, def).execute(status -> tenantMetaDataRepository + .save(new JpaTenantMetaData(createStandardSoftwareDataSetup(), tenant))), + tenant); + } + @Override public List findTenants() { return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList()); @@ -191,7 +220,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst @Modifying public void deleteTenant(final String tenant) { cacheManager.evictCaches(tenant); - cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null)); tenantAware.runAsTenant(tenant, () -> { entityManager.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, tenant.toUpperCase()); tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); @@ -226,7 +254,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst } @Override - @Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator") + @Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager") // set transaction to not supported, due we call this in // BaseEntity#prePersist methods // and it seems that JPA committing the transaction when executing this diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java index 75f99d886..83f37304a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java @@ -12,8 +12,8 @@ import java.util.Optional; import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.report.model.TenantUsage; +import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -35,9 +35,14 @@ public class JpaTenantStatsManagement implements TenantStatsManagement { @Autowired private ActionRepository actionRepository; + @Autowired + private TenantAware tenantAware; + @Override @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) - public TenantUsage getStatsOfTenant(final String tenant) { + public TenantUsage getStatsOfTenant() { + final String tenant = tenantAware.getCurrentTenant(); + final TenantUsage result = new TenantUsage(tenant); result.setTargets(targetRepository.count()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/cache/CacheWriteNotify.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/cache/CacheWriteNotify.java index fdaf868e8..6dfdf157f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/cache/CacheWriteNotify.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/cache/CacheWriteNotify.java @@ -8,9 +8,10 @@ */ package org.eclipse.hawkbit.repository.jpa.cache; -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; +import java.math.RoundingMode; + +import org.eclipse.hawkbit.repository.eventbus.event.DownloadProgressEvent; import org.eclipse.hawkbit.repository.eventbus.event.RolloutGroupCreatedEvent; -import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -20,6 +21,7 @@ import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import com.google.common.eventbus.EventBus; +import com.google.common.math.DoubleMath; /** * An service which combines the functionality for functional use cases to write @@ -30,10 +32,6 @@ import com.google.common.eventbus.EventBus; */ @Service public class CacheWriteNotify { - - /** - * - */ private static final int DOWNLOAD_PROGRESS_MAX = 100; @Autowired @@ -46,20 +44,29 @@ public class CacheWriteNotify { private TenantAware tenantAware; /** - * writes the download progress in percentage into the cache + * writes the download progress into the cache * {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the * {@link EventBus} with a {@link DownloadProgressEvent}. * * @param statusId * the ID of the {@link ActionStatus} - * @param progressPercent - * the progress in percentage which must be between 0-100 + * @param requestedBytes + * requested bytes of the request + * @param shippedBytesSinceLast + * since last event + * @param shippedBytesOverall + * for the download request */ - public void downloadProgressPercent(final long statusId, final int progressPercent) { + public void downloadProgress(final Long statusId, final Long requestedBytes, final Long shippedBytesSinceLast, + final Long shippedBytesOverall) { - final Cache cache = cacheManager.getCache(Action.class.getName()); + final Cache cache = cacheManager.getCache(ActionStatus.class.getName()); final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId), CacheKeys.DOWNLOAD_PROGRESS_PERCENT); + + final int progressPercent = DoubleMath.roundToInt(shippedBytesOverall * 100.0 / requestedBytes, + RoundingMode.DOWN); + if (progressPercent < DOWNLOAD_PROGRESS_MAX) { cache.put(cacheKey, progressPercent); } else { @@ -69,7 +76,8 @@ public class CacheWriteNotify { cache.evict(cacheKey); } - eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent)); + eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, requestedBytes, + shippedBytesSinceLast, shippedBytesOverall)); } /** 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 2b8744618..5fa2c3bed 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 @@ -27,10 +27,7 @@ import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; import javax.persistence.Table; -import javax.persistence.Transient; -import org.eclipse.hawkbit.repository.jpa.cache.CacheField; -import org.eclipse.hawkbit.repository.jpa.cache.CacheKeys; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -89,13 +86,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout")) private JpaRollout rollout; - /** - * Note: filled only in {@link Status#DOWNLOAD}. - */ - @Transient - @CacheField(key = CacheKeys.DOWNLOAD_PROGRESS_PERCENT) - private int downloadProgressPercent; - @Override public DistributionSet getDistributionSet() { return distributionSet; @@ -120,15 +110,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio this.status = status; } - @Override - public int getDownloadProgressPercent() { - return downloadProgressPercent; - } - - public void setDownloadProgressPercent(final int downloadProgressPercent) { - this.downloadProgressPercent = downloadProgressPercent; - } - @Override public boolean isActive() { return active; 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 ef1ad3d1e..3a8cb8683 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 @@ -24,7 +24,10 @@ import javax.persistence.ManyToOne; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; import javax.persistence.Table; +import javax.persistence.Transient; +import org.eclipse.hawkbit.repository.jpa.cache.CacheField; +import org.eclipse.hawkbit.repository.jpa.cache.CacheKeys; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; @@ -63,6 +66,13 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements @Column(name = "detail_message", length = 512) private final List messages = new ArrayList<>(); + /** + * Note: filled only in {@link Status#DOWNLOAD}. + */ + @Transient + @CacheField(key = CacheKeys.DOWNLOAD_PROGRESS_PERCENT) + private int downloadProgressPercent; + /** * Creates a new {@link ActionStatus} object. * @@ -105,6 +115,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements // JPA default constructor. } + @Override + public int getDownloadProgressPercent() { + return downloadProgressPercent; + } + @Override public Long getOccurredAt() { return occurredAt; 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 94f50ff0d..183405470 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 @@ -115,9 +115,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable throwables = ExceptionUtils.getThrowableList(ex); + final Throwable responseCause = Iterables.getLast(throwables); final ExceptionInfo response = createExceptionInfo(new MultiPartFileUploadException(responseCause)); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @@ -150,19 +148,6 @@ public class ResponseExceptionHandler { LOG.debug("Handling exception {} of request {}", ex.getClass().getName(), request.getRequestURL()); } - private static Throwable searchForCause(final Throwable t, final Class lookFor) { - if (t == null || t.getCause() == null) { - return null; - } - - final Throwable cause = t.getCause(); - - if (cause.getClass().equals(lookFor)) { - return cause; - } - return searchForCause(cause, lookFor); - } - private ExceptionInfo createExceptionInfo(final Exception ex) { final ExceptionInfo response = new ExceptionInfo(); response.setMessage(ex.getMessage()); 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 875152e0e..0c9dd39c5 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 @@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.repository.ControllerManagement; +import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,7 +88,7 @@ public final class RestResourceConversionHelper { * @param controllerManagement * to write progress updates to * @param statusId - * of the UpdateActionStatus + * of the {@link ActionStatus} * * @return http code * @@ -293,6 +294,7 @@ public final class RestResourceConversionHelper { long toRead = length; boolean toContinue = true; + long shippedSinceLastEvent = 0; while (toContinue) { final int r = from.read(buf); @@ -304,9 +306,11 @@ public final class RestResourceConversionHelper { if (toRead > 0) { to.write(buf, 0, r); total += r; + shippedSinceLastEvent += r; } else { to.write(buf, 0, (int) toRead + r); total += toRead + r; + shippedSinceLastEvent += toRead + r; toContinue = false; } @@ -316,7 +320,8 @@ public final class RestResourceConversionHelper { // every 10 percent an event if (newPercent == 100 || newPercent > progressPercent + 10) { progressPercent = newPercent; - controllerManagement.downloadProgressPercent(statusId, progressPercent); + controllerManagement.downloadProgress(statusId, length, shippedSinceLastEvent, total); + shippedSinceLastEvent = 0; } } } diff --git a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java index 51cbd7a99..0ee2c846d 100644 --- a/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java +++ b/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java @@ -367,11 +367,7 @@ public abstract class JsonBuilder { } - /** - * @param targets - * @return - */ - public static String targets(final List targets) { + public static String targets(final List targets, final boolean withToken) { final StringBuilder builder = new StringBuilder(); builder.append("["); @@ -381,10 +377,12 @@ public abstract class JsonBuilder { final String address = target.getTargetInfo().getAddress() != null ? target.getTargetInfo().getAddress().toString() : null; + final String token = withToken ? target.getSecurityToken() : null; + builder.append(new JSONObject().put("controllerId", target.getControllerId()) .put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0") .put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh") - .put("address", address).toString()); + .put("address", address).put("securityToken", token).toString()); } catch (final Exception e) { e.printStackTrace(); } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index 139954538..daf5f0dd9 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -224,8 +224,10 @@ public final class SpPermission { /* * Spring security eval expressions. */ - private static final String HAS_AUTH_PREFIX = "hasAuthority('"; - private static final String HAS_AUTH_SUFFIX = "')"; + private static final String BRACKET_OPEN = "("; + private static final String BRACKET_CLOSE = ")"; + private static final String HAS_AUTH_PREFIX = "hasAuthority" + BRACKET_OPEN + "'"; + private static final String HAS_AUTH_SUFFIX = "'" + BRACKET_CLOSE; private static final String HAS_AUTH_AND = " and "; /** @@ -257,99 +259,6 @@ public final class SpPermission { */ public static final String HAS_AUTH_OR = " or "; - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#UPDATE_TARGET}. - */ - public static final String HAS_AUTH_UPDATE_TARGET = HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#SYSTEM_ADMIN}. - */ - public static final String HAS_AUTH_SYSTEM_ADMIN = HAS_AUTH_PREFIX + SYSTEM_ADMIN + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_TARGET}. - */ - public static final String HAS_AUTH_READ_TARGET = HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_TARGET}. - */ - public static final String HAS_AUTH_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DELETE_TARGET}. - */ - public static final String HAS_AUTH_DELETE_TARGET = HAS_AUTH_PREFIX + DELETE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY} and - * {@link SpPermission#UPDATE_TARGET}. - */ - public static final String HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET = HAS_AUTH_PREFIX + READ_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_REPOSITORY}. - */ - public static final String HAS_AUTH_CREATE_REPOSITORY = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DELETE_REPOSITORY}. - */ - public static final String HAS_AUTH_DELETE_REPOSITORY = HAS_AUTH_PREFIX + DELETE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY}. - */ - public static final String HAS_AUTH_READ_REPOSITORY = HAS_AUTH_PREFIX + READ_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#UPDATE_REPOSITORY}. - */ - public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#READ_REPOSITORY} and - * {@link SpPermission#READ_TARGET}. - */ - public static final String HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET = HAS_AUTH_PREFIX + READ_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#DOWNLOAD_REPOSITORY_ARTIFACT}. - */ - public static final String HAS_AUTH_DOWNLOAD_ARTIFACT = HAS_AUTH_PREFIX + DOWNLOAD_REPOSITORY_ARTIFACT - + HAS_AUTH_SUFFIX; - - /** - * Spring security eval hasAnyRole expression to check if the spring - * context contains the anoynmous role or the controller specific role - * {@link SpPermission#CONTROLLER_ROLE}. - */ - public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE - + "')"; - - /** - * Spring security eval hasAuthority expression to check if the spring - * context contains the role to allow controllers to download specific - * role {@link SpPermission#CONTROLLER_DOWNLOAD_ROLE}. - */ - public static final String HAS_CONTROLLER_DOWNLOAD = HAS_AUTH_PREFIX + CONTROLLER_DOWNLOAD_ROLE - + HAS_AUTH_SUFFIX; - /** * Spring security eval hasAnyRole expression to check if the spring * context contains system code role @@ -359,48 +268,168 @@ public final class SpPermission { /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#CREATE_REPOSITORY} and - * {@link SpPermission#CREATE_TARGET}. + * context contains {@link SpPermission#UPDATE_TARGET} or + * {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_REPOSITORY - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; + public static final String HAS_AUTH_UPDATE_TARGET = HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} + * context contains {@link SpPermission#SYSTEM_ADMIN} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_SYSTEM_ADMIN = HAS_AUTH_PREFIX + SYSTEM_ADMIN + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_TARGET = HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + HAS_AUTH_OR + + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DELETE_TARGET} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DELETE_TARGET = HAS_AUTH_PREFIX + DELETE_TARGET + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} and + * {@link SpPermission#UPDATE_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + READ_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_REPOSITORY = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DELETE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DELETE_REPOSITORY = HAS_AUTH_PREFIX + DELETE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY = HAS_AUTH_PREFIX + READ_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#UPDATE_REPOSITORY} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#READ_REPOSITORY} and + * {@link SpPermission#READ_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + READ_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#DOWNLOAD_REPOSITORY_ARTIFACT} or + * {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_DOWNLOAD_ARTIFACT = HAS_AUTH_PREFIX + DOWNLOAD_REPOSITORY_ARTIFACT + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAnyRole expression to check if the spring + * context contains the anoynmous role or the controller specific role + * {@link SpringEvalExpressions#CONTROLLER_ROLE}. + */ + public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE + + "')"; + + /** + * Spring security eval hasAuthority expression to check if the spring + * context contains the role to allow controllers to download specific + * role {@link SpringEvalExpressions#CONTROLLER_DOWNLOAD_ROLE} + */ + public static final String HAS_CONTROLLER_DOWNLOAD = HAS_AUTH_PREFIX + CONTROLLER_DOWNLOAD_ROLE + + HAS_AUTH_SUFFIX; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#CREATE_REPOSITORY} and + * {@link SpPermission#CREATE_TARGET} or {@link #IS_SYSTEM_CODE}. + */ + public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = BRACKET_OPEN + HAS_AUTH_PREFIX + + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} or + * {@link #IS_SYSTEM_CODE}. */ public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT - + HAS_AUTH_SUFFIX; + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and - * {@link SpPermission#READ_TARGET} + * {@link SpPermission#READ_TARGET} or {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ = HAS_AUTH_PREFIX - + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET - + HAS_AUTH_SUFFIX;; + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ = BRACKET_OPEN + HAS_AUTH_PREFIX + + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + READ_TARGET + HAS_AUTH_SUFFIX + + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and - * {@link SpPermission#UPDATE_TARGET}. + * {@link SpPermission#UPDATE_TARGET} or {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT - + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = BRACKET_OPEN + HAS_AUTH_PREFIX + + ROLLOUT_MANAGEMENT + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + + HAS_AUTH_SUFFIX + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#TENANT_CONFIGURATION} + * context contains {@link SpPermission#TENANT_CONFIGURATION} or + * {@link #IS_SYSTEM_CODE}. */ public static final String HAS_AUTH_TENANT_CONFIGURATION = HAS_AUTH_PREFIX + TENANT_CONFIGURATION - + HAS_AUTH_SUFFIX; + + HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE; /** * Spring security eval hasAuthority expression to check if spring - * context contains {@link SpPermission#SYSTEM_MONITOR} + * context contains {@link SpPermission#SYSTEM_MONITOR} or + * {@link #IS_SYSTEM_CODE}. */ - public static final String HAS_AUTH_SYSTEM_MONITOR = HAS_AUTH_PREFIX + SYSTEM_MONITOR + HAS_AUTH_SUFFIX; + public static final String HAS_AUTH_SYSTEM_MONITOR = HAS_AUTH_PREFIX + SYSTEM_MONITOR + HAS_AUTH_SUFFIX + + HAS_AUTH_OR + IS_SYSTEM_CODE; private SpringEvalExpressions() { // utility class diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index 4b5fe7224..e67faa739 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.security; import java.util.Collection; +import java.util.Collections; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -80,32 +81,37 @@ public class SecurityContextTenantAware implements TenantAware { @Override public boolean equals(final Object another) { - return delegate.equals(another); + if (delegate != null) { + return delegate.equals(another); + } else if (another == null) { + return true; + } + return false; } @Override public String toString() { - return delegate.toString(); + return (delegate != null) ? delegate.toString() : null; } @Override public int hashCode() { - return delegate.hashCode(); + return (delegate != null) ? delegate.hashCode() : null; } @Override public String getName() { - return delegate.getName(); + return (delegate != null) ? delegate.getName() : null; } @Override public Collection getAuthorities() { - return delegate.getAuthorities(); + return (delegate != null) ? delegate.getAuthorities() : Collections.emptyList(); } @Override public Object getCredentials() { - return delegate.getCredentials(); + return (delegate != null) ? delegate.getCredentials() : null; } @Override @@ -115,16 +121,19 @@ public class SecurityContextTenantAware implements TenantAware { @Override public Object getPrincipal() { - return delegate.getPrincipal(); + return (delegate != null) ? delegate.getPrincipal() : null; } @Override public boolean isAuthenticated() { - return delegate.isAuthenticated(); + return (delegate != null) ? delegate.isAuthenticated() : null; } @Override - public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException { + public void setAuthenticated(final boolean isAuthenticated) { + if (delegate == null) { + return; + } delegate.setAuthenticated(isAuthenticated); } } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java index c0ecb8ceb..da998c87c 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -60,6 +60,9 @@ public class SystemSecurityContext { * The security context will be switched to the system code and back after * the callable is called. * + * The system code is executed for a current tenant by using the + * {@link TenantAware#getCurrentTenant()}. + * * @param callable * the callable to call within the system security context * @return the return value of the {@link Callable#call()} method. @@ -67,12 +70,36 @@ public class SystemSecurityContext { // Exception squid:S2221 - Callable declares Exception @SuppressWarnings("squid:S2221") public T runAsSystem(final Callable callable) { + return runAsSystemAsTenant(callable, tenantAware.getCurrentTenant()); + } + + /** + * Runs a given {@link Callable} within a system security context, which is + * permitted to call secured system code. Often the system needs to call + * secured methods by it's own without relying on the current security + * context e.g. if the current security context does not contain the + * necessary permission it's necessary to execute code as system code to + * execute necessary methods and functionality. + * + * The security context will be switched to the system code and back after + * the callable is called. + * + * The system code is executed for a specific given tenant by using the + * {@link TenantAware}. + * + * @param callable + * the callable to call within the system security context + * @param tenant + * the tenant to act as system code + * @return the return value of the {@link Callable#call()} method. + */ + public T runAsSystemAsTenant(final Callable callable, final String tenant) { final SecurityContext oldContext = SecurityContextHolder.getContext(); try { logger.debug("entering system code execution"); - return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), () -> { + return tenantAware.runAsTenant(tenant, () -> { try { - setSystemContext(oldContext); + setSystemContext(SecurityContextHolder.getContext()); return callable.call(); } catch (final Exception e) { throw Throwables.propagate(e); @@ -100,6 +127,13 @@ public class SystemSecurityContext { SecurityContextHolder.setContext(securityContextImpl); } + /** + * An implementation of the Spring's {@link Authentication} object which is + * used within a system security code block and wraps the original + * authentication object. The wrapped object contains the necessary + * {@link SpringEvalExpressions#SYSTEM_ROLE} which is allowed to execute all + * secured methods. + */ public static class SystemCodeAuthentication implements Authentication { private static final long serialVersionUID = 1L; diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index 08d225c62..b90e36e88 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -200,6 +200,10 @@ org.springframework.security spring-security-web + + org.apache.commons + commons-collections4 + com.vaadin 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 6a5a07a40..c8eb74313 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 @@ -10,6 +10,8 @@ package org.eclipse.hawkbit.ui.artifacts.smtable; import java.io.Serializable; +import javax.annotation.PostConstruct; + import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -18,18 +20,17 @@ import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.SoftwareModuleTypeBeanQuery; 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; 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; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; import org.vaadin.spring.events.EventBus; -import com.vaadin.event.FieldEvents.TextChangeEvent; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.ComboBox; @@ -38,8 +39,6 @@ import com.vaadin.ui.FormLayout; import com.vaadin.ui.Label; import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; -import com.vaadin.ui.UI; -import com.vaadin.ui.Window; import com.vaadin.ui.themes.ValoTheme; /** @@ -66,8 +65,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se @Autowired private transient EntityFactory entityFactory; - private Label mandatoryLabel; - private TextField nameTextField; private TextField versionTextField; @@ -80,14 +77,20 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se private CommonDialogWindow window; - private String oldDescriptionValue; - - private String oldVendorValue; - private Boolean editSwModule = Boolean.FALSE; private Long baseSwModuleId; + private FormLayout formLayout; + + /** + * Initialize Distribution Add and Edit Window. + */ + @PostConstruct + void init() { + createRequiredComponents(); + } + /** * Create window for new software module. * @@ -95,11 +98,7 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se * module. */ public CommonDialogWindow createAddSoftwareModuleWindow() { - - editSwModule = Boolean.FALSE; - createRequiredComponents(); - createWindow(); - return window; + return createUpdateSoftwareModuleWindow(null); } /** @@ -110,17 +109,11 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se * @return reference of {@link com.vaadin.ui.Window} to update software * module. */ - public Window createUpdateSoftwareModuleWindow(final Long baseSwModuleId) { - - editSwModule = Boolean.TRUE; + public CommonDialogWindow createUpdateSoftwareModuleWindow(final Long baseSwModuleId) { this.baseSwModuleId = baseSwModuleId; - createRequiredComponents(); - createWindow(); - /* populate selected target values to edit. */ + resetComponents(); populateValuesOfSwModule(); - nameTextField.setEnabled(false); - versionTextField.setEnabled(false); - typeComboBox.setEnabled(false); + createWindow(); return window; } @@ -145,13 +138,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se ValoTheme.TEXTAREA_TINY, false, null, i18n.get("textfield.description"), SPUILabelDefinitions.TEXT_AREA_MAX_LENGTH); descTextArea.setId(SPUIComponentIdProvider.ADD_SW_MODULE_DESCRIPTION); - addDescriptionTextChangeListener(); - addVendorTextChangeListener(); - - /* Label for mandatory symbol */ - mandatoryLabel = new Label(i18n.get("label.mandatory.field")); - mandatoryLabel.setStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - mandatoryLabel.addStyleName(ValoTheme.LABEL_SMALL); typeComboBox = SPUIComponentProvider.getComboBox(i18n.get("upload.swmodule.type"), "", "", null, null, true, null, i18n.get("upload.swmodule.type")); @@ -159,46 +145,34 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se typeComboBox.setStyleName(SPUIDefinitions.COMBO_BOX_SPECIFIC_STYLE + " " + ValoTheme.COMBOBOX_TINY); typeComboBox.setNewItemsAllowed(Boolean.FALSE); typeComboBox.setImmediate(Boolean.TRUE); - populateTypeNameCombo(); - - resetOldValues(); } - /** - * - */ private void populateTypeNameCombo() { typeComboBox.setContainerDataSource(HawkbitCommonUtil.createLazyQueryContainer( new BeanQueryFactory(SoftwareModuleTypeBeanQuery.class))); typeComboBox.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME); - } - private void resetOldValues() { - oldDescriptionValue = null; - oldVendorValue = null; + private void resetComponents() { + + vendorTextField.clear(); + nameTextField.clear(); + versionTextField.clear(); + descTextArea.clear(); + typeComboBox.clear(); + editSwModule = Boolean.FALSE; } - /** - * Build the window content and get an instance of customDialogWindow - * - */ private void createWindow() { - final Label madatoryStarLabel = new Label("*"); madatoryStarLabel.setStyleName("v-caption v-required-field-indicator"); madatoryStarLabel.setWidth(null); - - /* - * The main layout of the window contains mandatory info, textboxes - * (controller Id, name & description) and action buttons layout - */ addStyleName("lay-color"); setSizeUndefined(); - final FormLayout formLayout = new FormLayout(); - formLayout.addComponent(mandatoryLabel); + formLayout = new FormLayout(); + formLayout.setCaption(null); formLayout.addComponent(typeComboBox); formLayout.addComponent(nameTextField); formLayout.addComponent(versionTextField); @@ -207,24 +181,17 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se setCompositionRoot(formLayout); - /* add main layout to the window */ - window = SPUIComponentProvider.getWindow(i18n.get("upload.caption.add.new.swmodule"), null, - SPUIDefinitions.CREATE_UPDATE_WINDOW, this, event -> saveOrUpdate(), event -> closeThisWindow(), null); + window = SPUIWindowDecorator.getWindow(i18n.get("upload.caption.add.new.swmodule"), null, + SPUIDefinitions.CREATE_UPDATE_WINDOW, this, event -> saveOrUpdate(), null, null, formLayout, i18n); window.getButtonsLayout().removeStyleName("actionButtonsMargin"); - nameTextField.focus(); + + nameTextField.setEnabled(!editSwModule); + versionTextField.setEnabled(!editSwModule); + typeComboBox.setEnabled(!editSwModule); + + typeComboBox.focus(); } - private void addDescriptionTextChangeListener() { - descTextArea.addTextChangeListener(event -> window.setSaveButtonEnabled(hasDescriptionChanged(event))); - } - - private void addVendorTextChangeListener() { - vendorTextField.addTextChangeListener(event -> window.setSaveButtonEnabled(hasVendorChanged(event))); - } - - /** - * Add new SW module. - */ private void addNewBaseSoftware() { final String name = HawkbitCommonUtil.trimAndNullIfEmpty(nameTextField.getValue()); final String version = HawkbitCommonUtil.trimAndNullIfEmpty(versionTextField.getValue()); @@ -232,10 +199,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se final String description = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; - if (!mandatoryCheck(name, version, type)) { - return; - } - if (HawkbitCommonUtil.isDuplicate(name, version, type)) { uiNotifcation.displayValidationError( i18n.get("message.duplicate.softwaremodule", new Object[] { name, version })); @@ -248,8 +211,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se new Object[] { newBaseSoftwareModule.getName() + ":" + newBaseSoftwareModule.getVersion() })); eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.NEW_ENTITY, newBaseSoftwareModule)); } - // close the window - closeThisWindow(); } } @@ -269,13 +230,16 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.UPDATED_ENTITY, newSWModule)); } - closeThisWindow(); } /** * fill the data of a softwareModule in the content of the window */ private void populateValuesOfSwModule() { + if (baseSwModuleId == null) { + return; + } + editSwModule = Boolean.TRUE; final SoftwareModule swModle = softwareManagement.findSoftwareModuleById(baseSwModuleId); nameTextField.setValue(swModle.getName()); versionTextField.setValue(swModle.getVersion()); @@ -283,49 +247,10 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se : HawkbitCommonUtil.trimAndNullIfEmpty(swModle.getVendor())); descTextArea.setValue(swModle.getDescription() == null ? HawkbitCommonUtil.SP_STRING_EMPTY : HawkbitCommonUtil.trimAndNullIfEmpty(swModle.getDescription())); - oldDescriptionValue = descTextArea.getValue(); - oldVendorValue = vendorTextField.getValue(); if (swModle.getType().isDeleted()) { typeComboBox.addItem(swModle.getType().getName()); } typeComboBox.setValue(swModle.getType().getName()); - window.setSaveButtonEnabled(Boolean.FALSE); - } - - /** - * Method to close window. - */ - private void closeThisWindow() { - window.close(); - UI.getCurrent().removeWindow(window); - } - - /** - * Validation check - Mandatory. - * - * @param name - * as String - * @param version - * as version - * @return boolena as flag - */ - private boolean mandatoryCheck(final String name, final String version, final String type) { - boolean isValid = true; - if (name == null || version == null || type == null) { - if (name == null) { - nameTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - } - if (version == null) { - versionTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); - } - if (type == null) { - typeComboBox.addStyleName(SPUIStyleDefinitions.SP_COMBOFIELD_ERROR); - } - - uiNotifcation.displayValidationError(i18n.get("message.mandatory.check")); - isValid = false; - } - return isValid; } private void saveOrUpdate() { @@ -336,12 +261,8 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent implements Se } } - private boolean hasDescriptionChanged(final TextChangeEvent event) { - return !(event.getText().equals(oldDescriptionValue) && vendorTextField.getValue().equals(oldVendorValue)); - } - - private boolean hasVendorChanged(final TextChangeEvent event) { - return !(event.getText().equals(oldVendorValue) && descTextArea.getValue().equals(oldDescriptionValue)); + public FormLayout getFormLayout() { + return formLayout; } } 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 99fe185e5..56367f230 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 @@ -44,7 +44,6 @@ import com.vaadin.ui.UI; /** * Header of Software module table. - * */ @SpringComponent @ViewScope diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java index d6ecd70dd..6c5ef6159 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java @@ -11,13 +11,11 @@ package org.eclipse.hawkbit.ui.artifacts.smtype; import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleTypeEvent; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleTypeEvent.SoftwareModuleTypeEnum; -import org.eclipse.hawkbit.ui.colorpicker.ColorPickerConstants; import org.eclipse.hawkbit.ui.colorpicker.ColorPickerHelper; import org.eclipse.hawkbit.ui.common.SoftwareModuleTypeBeanQuery; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; @@ -34,12 +32,10 @@ import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.shared.ui.colorpicker.Color; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; -import com.vaadin.ui.Alignment; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Label; import com.vaadin.ui.OptionGroup; import com.vaadin.ui.components.colorpicker.ColorChangeListener; -import com.vaadin.ui.components.colorpicker.ColorSelector; import com.vaadin.ui.themes.ValoTheme; /** @@ -48,8 +44,7 @@ import com.vaadin.ui.themes.ValoTheme; */ @SpringComponent @ViewScope -public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout - implements ColorChangeListener, ColorSelector { +public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout { private static final long serialVersionUID = -5169398523815919367L; private static final Logger LOG = LoggerFactory.getLogger(CreateUpdateSoftwareTypeLayout.class); @@ -69,7 +64,7 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout @Override protected void addListeners() { super.addListeners(); - optiongroup.addValueChangeListener(this::createOptionValueChanged); + optiongroup.addValueChangeListener(this::optionValueChanged); } @Override @@ -95,7 +90,6 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout tagDesc = SPUIComponentProvider.getTextArea(i18n.get("textfield.description"), "", ValoTheme.TEXTFIELD_TINY + " " + SPUIDefinitions.TYPE_DESC, false, "", i18n.get("textfield.description"), SPUILabelDefinitions.TEXT_AREA_MAX_LENGTH); - tagDesc.setId(SPUIDefinitions.NEW_SOFTWARE_TYPE_DESC); tagDesc.setImmediate(true); tagDesc.setNullRepresentation(""); @@ -113,10 +107,8 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout } @Override - public void createWindow() { - reset(); - window = SPUIComponentProvider.getWindow(i18n.get("caption.add.type"), null, - SPUIDefinitions.CREATE_UPDATE_WINDOW, this, this::save, this::discard, null); + protected String getWindowCaption() { + return i18n.get("caption.add.type"); } /** @@ -126,15 +118,16 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout * ValueChangeEvent */ @Override - protected void createOptionValueChanged(final ValueChangeEvent event) { + protected void optionValueChanged(final ValueChangeEvent event) { - super.createOptionValueChanged(event); + super.optionValueChanged(event); if (updateTypeStr.equals(event.getProperty().getValue())) { assignOptiongroup.setEnabled(false); } else { assignOptiongroup.setEnabled(true); } + assignOptiongroup.select(singleAssignStr); } /** @@ -171,12 +164,11 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout if (null != selectedTypeTag) { tagDesc.setValue(selectedTypeTag.getDescription()); typeKey.setValue(selectedTypeTag.getKey()); - if (selectedTypeTag.getMaxAssignments() == Integer.MAX_VALUE) { - assignOptiongroup.setValue(multiAssignStr); - } else { + if (selectedTypeTag.getMaxAssignments() == 1) { assignOptiongroup.setValue(singleAssignStr); + } else { + assignOptiongroup.setValue(multiAssignStr); } - setColorPickerComponentsColor(selectedTypeTag.getColour()); } } @@ -198,10 +190,6 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout @Override protected void save(final ClickEvent event) { - if (!mandatoryValuesPresent()) { - return; - } - final SoftwareModuleType existingSMTypeByKey = swTypeManagementService .findSoftwareModuleTypeByKey(typeKey.getValue()); final SoftwareModuleType existingSMTypeByName = swTypeManagementService @@ -211,7 +199,6 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout createNewSWModuleType(); } } else { - updateSWModuleType(existingSMTypeByName); } } @@ -233,22 +220,14 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout SoftwareModuleType newSWType = entityFactory.generateSoftwareModuleType(typeKeyValue, typeNameValue, typeDescValue, assignNumber); newSWType.setColour(colorPicked); - - if (null != typeDescValue) { - newSWType.setDescription(typeDescValue); - } - + newSWType.setDescription(typeDescValue); newSWType.setColour(colorPicked); - newSWType = swTypeManagementService.createSoftwareModuleType(newSWType); uiNotification.displaySuccess(i18n.get("message.save.success", new Object[] { newSWType.getName() })); - closeWindow(); eventBus.publish(this, new SoftwareModuleTypeEvent(SoftwareModuleTypeEnum.ADD_SOFTWARE_MODULE_TYPE, newSWType)); - } else { uiNotification.displayValidationError(i18n.get("message.error.missing.typenameorkey")); - } } @@ -258,51 +237,15 @@ public class CreateUpdateSoftwareTypeLayout extends CreateUpdateTypeLayout final String typeDescValue = HawkbitCommonUtil.trimAndNullIfEmpty(tagDesc.getValue()); if (null != typeNameValue) { existingType.setName(typeNameValue); - - existingType.setDescription(null != typeDescValue ? typeDescValue : null); - + existingType.setDescription(typeDescValue); existingType.setColour(ColorPickerHelper.getColorPickedString(getColorPickerLayout().getSelPreview())); swTypeManagementService.updateSoftwareModuleType(existingType); uiNotification.displaySuccess(i18n.get("message.update.success", new Object[] { existingType.getName() })); - closeWindow(); eventBus.publish(this, new SoftwareModuleTypeEvent(SoftwareModuleTypeEnum.UPDATE_SOFTWARE_MODULE_TYPE, existingType)); - } else { uiNotification.displayValidationError(i18n.get("message.tag.update.mandatory")); } - - } - - /** - * Open color picker on click of preview button. Auto select the color based - * on target tag if already selected. - */ - @Override - protected void previewButtonClicked() { - if (!tagPreviewBtnClicked) { - final String selectedOption = (String) optiongroup.getValue(); - if (StringUtils.isNotEmpty(selectedOption) && selectedOption.equalsIgnoreCase(updateTypeStr)) { - if (null != tagNameComboBox.getValue()) { - final SoftwareModuleType typeSelected = swTypeManagementService - .findSoftwareModuleTypeByName(tagNameComboBox.getValue().toString()); - if (null != typeSelected) { - getColorPickerLayout().setSelectedColor(typeSelected.getColour() != null - ? ColorPickerHelper.rgbToColorConverter(typeSelected.getColour()) - : ColorPickerHelper.rgbToColorConverter(ColorPickerConstants.DEFAULT_COLOR)); - } - } else { - getColorPickerLayout().setSelectedColor( - ColorPickerHelper.rgbToColorConverter(ColorPickerConstants.DEFAULT_COLOR)); - } - } - getColorPickerLayout().getSelPreview().setColor(getColorPickerLayout().getSelectedColor()); - mainLayout.addComponent(colorPickerLayout, 1, 0); - mainLayout.setComponentAlignment(colorPickerLayout, Alignment.MIDDLE_CENTER); - } else { - mainLayout.removeComponent(colorPickerLayout); - } - tagPreviewBtnClicked = !tagPreviewBtnClicked; } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/colorpicker/ColorPickerLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/colorpicker/ColorPickerLayout.java index 7d2046bd9..2c6191bc8 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/colorpicker/ColorPickerLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/colorpicker/ColorPickerLayout.java @@ -13,6 +13,7 @@ import java.util.Set; import org.eclipse.hawkbit.ui.common.CoordinatesToColor; import org.eclipse.hawkbit.ui.management.tag.SpColorPickerPreview; +import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; import com.vaadin.shared.ui.colorpicker.Color; import com.vaadin.ui.AbstractColorPicker.Coordinates2Color; @@ -47,6 +48,7 @@ public class ColorPickerLayout extends GridLayout { setColumns(2); setRows(4); + setId(SPUIComponentIdProvider.COLOR_PICKER_LAYOUT); init(); @@ -71,6 +73,7 @@ public class ColorPickerLayout extends GridLayout { colorSelect.setWidth("220px"); redSlider = createRGBSlider("", "red"); + redSlider.setId(SPUIComponentIdProvider.COLOR_PICKER_RED_SLIDER); greenSlider = createRGBSlider("", "green"); blueSlider = createRGBSlider("", "blue"); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java index 29519f162..3108455dd 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java @@ -10,32 +10,69 @@ package org.eclipse.hawkbit.ui.common; import static com.google.common.base.Preconditions.checkNotNull; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; -import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleBorderWithIcon; -import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.hawkbit.ui.artifacts.smtable.SoftwareModuleAddUpdateWindow; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon; +import org.eclipse.hawkbit.ui.layouts.AbstractCreateUpdateTagLayout; +import org.eclipse.hawkbit.ui.management.targettable.TargetAddUpdateWindowLayout; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import com.vaadin.data.Container.ItemSetChangeEvent; +import com.vaadin.data.Container.ItemSetChangeListener; +import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.Validator; +import com.vaadin.data.validator.NullValidator; +import com.vaadin.event.FieldEvents.TextChangeEvent; +import com.vaadin.event.FieldEvents.TextChangeListener; +import com.vaadin.event.FieldEvents.TextChangeNotifier; import com.vaadin.server.FontAwesome; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.AbstractLayout; import com.vaadin.ui.AbstractOrderedLayout; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.CheckBox; import com.vaadin.ui.Component; +import com.vaadin.ui.Field; +import com.vaadin.ui.GridLayout; import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; import com.vaadin.ui.Link; +import com.vaadin.ui.Table; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; +import com.vaadin.ui.themes.ValoTheme; /** * - * Superclass for pop-up-windows including a minimize and close icon in the - * upper right corner and a save and cancel button at the bottom. - * + * Table pop-up-windows including a minimize and close icon in the upper right + * corner and a save and cancel button at the bottom. Is not intended to reuse. + * */ -public class CommonDialogWindow extends Window { +public class CommonDialogWindow extends Window implements Serializable { - private static final long serialVersionUID = -1321949234316858703L; + private static final long serialVersionUID = 1L; private final VerticalLayout mainLayout = new VerticalLayout(); @@ -57,6 +94,14 @@ public class CommonDialogWindow extends Window { private final ClickListener cancelButtonClickListener; + private final ClickListener close = event -> close(); + + private final transient Map orginalValues; + + private final List> allComponents; + + private final I18N i18n; + /** * Constructor. * @@ -72,28 +117,83 @@ public class CommonDialogWindow extends Window { * the cancelButtonClickListener */ public CommonDialogWindow(final String caption, final Component content, final String helpLink, - final ClickListener saveButtonClickListener, final ClickListener cancelButtonClickListener) { + final ClickListener saveButtonClickListener, final ClickListener cancelButtonClickListener, + final AbstractLayout layout, final I18N i18n) { checkNotNull(saveButtonClickListener); - checkNotNull(cancelButtonClickListener); this.caption = caption; this.content = content; this.helpLink = helpLink; this.saveButtonClickListener = saveButtonClickListener; this.cancelButtonClickListener = cancelButtonClickListener; - + this.orginalValues = new HashMap<>(); + this.allComponents = getAllComponents(layout); + this.i18n = i18n; init(); } + @Override + public void close() { + super.close(); + orginalValues.clear(); + removeListeners(); + allComponents.clear(); + this.saveButton.setEnabled(false); + } + + private void removeListeners() { + for (final AbstractField field : allComponents) { + removeTextListener(field); + removeValueChangeListener(field); + removeItemSetChangeistener(field); + } + } + + private void removeItemSetChangeistener(final AbstractField field) { + if (!(field instanceof Table)) { + return; + } + for (final Object listener : field.getListeners(ItemSetChangeEvent.class)) { + if (listener instanceof ChangeListener) { + ((Table) field).removeItemSetChangeListener((ChangeListener) listener); + } + } + } + + private void removeTextListener(final AbstractField field) { + if (!(field instanceof TextChangeNotifier)) { + return; + } + for (final Object listener : field.getListeners(TextChangeEvent.class)) { + if (listener instanceof ChangeListener) { + ((TextChangeNotifier) field).removeTextChangeListener((ChangeListener) listener); + } + } + } + + private void removeValueChangeListener(final AbstractField field) { + for (final Object listener : field.getListeners(ValueChangeEvent.class)) { + if (listener instanceof ChangeListener) { + field.removeValueChangeListener((ChangeListener) listener); + } + } + } + private final void init() { if (content instanceof AbstractOrderedLayout) { ((AbstractOrderedLayout) content).setSpacing(true); ((AbstractOrderedLayout) content).setMargin(true); } + if (content instanceof GridLayout) { + addStyleName("marginTop"); + } if (null != content) { mainLayout.addComponent(content); } + + createMandatoryLabel(); + final HorizontalLayout buttonLayout = createActionButtonsLayout(); mainLayout.addComponent(buttonLayout); mainLayout.setComponentAlignment(buttonLayout, Alignment.TOP_CENTER); @@ -104,6 +204,173 @@ public class CommonDialogWindow extends Window { center(); setModal(true); addStyleName("fontsize"); + setOrginaleValues(); + addListeners(); + } + + /** + * saves the original values in a Map so we can use them for detecting + * changes + */ + public final void setOrginaleValues() { + for (final AbstractField field : allComponents) { + Object value = field.getValue(); + + if (field instanceof Table) { + value = Sets.newHashSet(((Table) field).getContainerDataSource().getItemIds()); + } + orginalValues.put(field, value); + } + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(null, null)); + } + + private final void addListeners() { + for (final AbstractField field : allComponents) { + if (field instanceof TextChangeNotifier) { + ((TextChangeNotifier) field).addTextChangeListener(new ChangeListener(field)); + } + + if (field instanceof Table) { + ((Table) field).addItemSetChangeListener(new ChangeListener(field)); + } else { + field.addValueChangeListener(new ChangeListener(field)); + } + } + + saveButton.addClickListener(close); + cancelButton.addClickListener(close); + } + + private boolean isSaveButtonEnabledAfterValueChange(final Component currentChangedComponent, + final Object newValue) { + return isMandatoryFieldNotEmptyAndValid(currentChangedComponent, newValue) + && isValuesChanged(currentChangedComponent, newValue); + } + + private boolean isValuesChanged(final Component currentChangedComponent, final Object newValue) { + for (final AbstractField field : allComponents) { + Object originalValue = orginalValues.get(field); + if (field instanceof CheckBox && originalValue == null) { + originalValue = Boolean.FALSE; + } + final Object currentValue = getCurrentVaue(currentChangedComponent, newValue, field); + + if (!isValueEquals(field, originalValue, currentValue)) { + return true; + } + } + return false; + } + + private boolean isValueEquals(final AbstractField field, final Object orginalValue, final Object currentValue) { + if (Set.class.equals(field.getType())) { + return CollectionUtils.isEqualCollection(CollectionUtils.emptyIfNull((Collection) orginalValue), + CollectionUtils.emptyIfNull((Collection) currentValue)); + } + + if (String.class.equals(field.getType())) { + return Objects.equals(Strings.emptyToNull((String) orginalValue), + Strings.emptyToNull((String) currentValue)); + } + + return Objects.equals(orginalValue, currentValue); + } + + private Object getCurrentVaue(final Component currentChangedComponent, final Object newValue, + final AbstractField field) { + Object currentValue = field.getValue(); + if (field instanceof Table) { + currentValue = ((Table) field).getContainerDataSource().getItemIds(); + } + + if (field.equals(currentChangedComponent)) { + currentValue = newValue; + } + return currentValue; + } + + private boolean shouldMandatoryLabelShown() { + for (final AbstractField field : allComponents) { + if (field.isRequired()) { + return true; + } + } + + return false; + } + + private boolean isMandatoryFieldNotEmptyAndValid(final Component currentChangedComponent, final Object newValue) { + + boolean valid = true; + final List> requiredComponents = allComponents.stream().filter(field -> field.isRequired()) + .collect(Collectors.toList()); + + requiredComponents.addAll(allComponents.stream().filter(this::hasNullValidator).collect(Collectors.toList())); + + for (final AbstractField field : requiredComponents) { + Object value = getCurrentVaue(currentChangedComponent, newValue, field); + + if (String.class.equals(field.getType())) { + value = Strings.emptyToNull((String) value); + } + + if (Set.class.equals(field.getType())) { + value = emptyToNull((Collection) value); + } + + if (value == null) { + return false; + } + + // We need to loop through the entire loop for validity testing. + // Otherwise the UI will only mark the + // first field with errors and then stop. If there are several + // fields with errors, this is bad. + field.setValue(value); + if (!field.isValid()) { + valid = false; + } + } + + return valid; + } + + private static Object emptyToNull(final Collection c) { + return (c == null || c.isEmpty()) ? null : c; + } + + private boolean hasNullValidator(final Component component) { + + if (component instanceof AbstractField) { + final AbstractField fieldComponent = (AbstractField) component; + for (final Validator validator : fieldComponent.getValidators()) { + if (validator instanceof NullValidator) { + return true; + } + } + } + return false; + } + + private List> getAllComponents(final AbstractLayout abstractLayout) { + final List> components = new ArrayList<>(); + + final Iterator iterate = abstractLayout.iterator(); + while (iterate.hasNext()) { + final Component c = iterate.next(); + if (c instanceof AbstractLayout) { + components.addAll(getAllComponents((AbstractLayout) c)); + } + + if (c instanceof AbstractField) { + components.add((AbstractField) c); + } + + if (c instanceof FlexibleOptionGroupItemComponent) { + components.add(((FlexibleOptionGroupItemComponent) c).getOwner()); + } + } + return components; } private HorizontalLayout createActionButtonsLayout() { @@ -111,23 +378,43 @@ public class CommonDialogWindow extends Window { buttonsLayout = new HorizontalLayout(); buttonsLayout.setSizeFull(); buttonsLayout.setSpacing(true); + buttonsLayout.addStyleName("actionButtonsMargin"); createSaveButton(); - createCancelButton(); - buttonsLayout.addStyleName("actionButtonsMargin"); addHelpLink(); return buttonsLayout; } + private void createMandatoryLabel() { + + if (!shouldMandatoryLabelShown()) { + return; + } + + final Label mandatoryLabel = new Label(i18n.get("label.mandatory.field")); + mandatoryLabel.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR + " " + ValoTheme.LABEL_TINY); + + if (content instanceof TargetAddUpdateWindowLayout) { + ((TargetAddUpdateWindowLayout) content).getFormLayout().addComponent(mandatoryLabel); + } else if (content instanceof SoftwareModuleAddUpdateWindow) { + ((SoftwareModuleAddUpdateWindow) content).getFormLayout().addComponent(mandatoryLabel); + } else if (content instanceof AbstractCreateUpdateTagLayout) { + ((AbstractCreateUpdateTagLayout) content).getMainLayout().addComponent(mandatoryLabel); + } + + mainLayout.addComponent(mandatoryLabel); + } + private void createCancelButton() { cancelButton = SPUIComponentProvider.getButton(SPUIComponentIdProvider.CANCEL_BUTTON, "Cancel", "", "", true, - FontAwesome.TIMES, SPUIButtonStyleBorderWithIcon.class); + FontAwesome.TIMES, SPUIButtonStyleNoBorderWithIcon.class); cancelButton.setSizeUndefined(); - cancelButton.addStyleName("default-color"); - cancelButton.addClickListener(cancelButtonClickListener); + if (cancelButtonClickListener != null) { + cancelButton.addClickListener(cancelButtonClickListener); + } buttonsLayout.addComponent(cancelButton); buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); @@ -136,10 +423,10 @@ public class CommonDialogWindow extends Window { private void createSaveButton() { saveButton = SPUIComponentProvider.getButton(SPUIComponentIdProvider.SAVE_BUTTON, "Save", "", "", true, - FontAwesome.SAVE, SPUIButtonStyleBorderWithIcon.class); + FontAwesome.SAVE, SPUIButtonStyleNoBorderWithIcon.class); saveButton.setSizeUndefined(); - saveButton.addStyleName("default-color"); saveButton.addClickListener(saveButtonClickListener); + saveButton.setEnabled(false); buttonsLayout.addComponent(saveButton); buttonsLayout.setComponentAlignment(saveButton, Alignment.MIDDLE_RIGHT); buttonsLayout.setExpandRatio(saveButton, 1.0F); @@ -155,16 +442,52 @@ public class CommonDialogWindow extends Window { buttonsLayout.setComponentAlignment(helpLinkComponent, Alignment.MIDDLE_RIGHT); } - public void setSaveButtonEnabled(final boolean enabled) { - saveButton.setEnabled(enabled); + public AbstractComponent getButtonsLayout() { + return this.buttonsLayout; } - public void setCancelButtonEnabled(final boolean enabled) { - cancelButton.setEnabled(enabled); + private class ChangeListener implements ValueChangeListener, TextChangeListener, ItemSetChangeListener { + + private final Field field; + + public ChangeListener(final Field field) { + super(); + this.field = field; + } + + @Override + public void textChange(final TextChangeEvent event) { + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, event.getText())); + } + + @Override + public void valueChange(final ValueChangeEvent event) { + saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(field, field.getValue())); + } + + @Override + public void containerItemSetChange(final ItemSetChangeEvent event) { + if (!(field instanceof Table)) { + return; + } + final Table table = (Table) field; + saveButton.setEnabled( + isSaveButtonEnabledAfterValueChange(table, table.getContainerDataSource().getItemIds())); + } } - public HorizontalLayout getButtonsLayout() { - return buttonsLayout; + /** + * Adds the component manually to the allComponents-List and adds a + * ValueChangeListener to it. Necessary in Update Distribution Type as the + * CheckBox concerned is an ItemProperty... + * + * @param component + * AbstractField + */ + public void updateAllComponents(final AbstractField component) { + + allComponents.add(component); + component.addValueChangeListener(new ChangeListener(component)); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java index 8b3ac4cde..f7d3656b1 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterMultiButtonClick.java @@ -22,7 +22,8 @@ import com.vaadin.ui.Button.ClickEvent; */ public abstract class AbstractFilterMultiButtonClick extends AbstractFilterButtonClickBehaviour { - protected final Set