Merge branch 'master' into feature_auto_assignment_squashed

This commit is contained in:
Dominik Herbst
2016-10-07 14:57:11 +02:00
211 changed files with 5164 additions and 2794 deletions

View File

@@ -1,11 +1,12 @@
Build: [![Circle CI](https://circleci.com/gh/eclipse/hawkbit.svg?style=svg)](https://circleci.com/gh/eclipse/hawkbit)
<img src=hawkbit_logo.png width=533 height=246 /> <img src=hawkbit_logo.png width=533 height=246 />
# Eclipse.IoT hawkBit - Update Server # Eclipse.IoT hawkBit - Update Server
[hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) is an domain independent back end solution for rolling out software updates to constrained edge devices as well as more powerful controllers and gateways connected to IP based networking infrastructure. [hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) is an domain independent back end solution for rolling out software updates to constrained edge devices as well as more powerful controllers and gateways connected to IP based networking infrastructure.
Build: [![Circle CI](https://circleci.com/gh/eclipse/hawkbit.svg?style=shield)](https://circleci.com/gh/eclipse/hawkbit)
[![SonarQuality](https://sonar.eu-gb.mybluemix.net/api/badges/gate?key=org.eclipse.hawkbit:hawkbit-parent)](https://sonar.eu-gb.mybluemix.net)
# Documentation # Documentation
see [hawkBit Wiki](https://github.com/eclipse/hawkbit/wiki) see [hawkBit Wiki](https://github.com/eclipse/hawkbit/wiki)

View File

@@ -8,6 +8,8 @@ package org.eclipse.hawkbit.app;
* http://www.eclipse.org/legal/epl-v10.html * http://www.eclipse.org/legal/epl-v10.html
*/ */
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.ui.HawkbitUI; import org.eclipse.hawkbit.ui.HawkbitUI;
import org.eclipse.hawkbit.ui.UIEventProvider; import org.eclipse.hawkbit.ui.UIEventProvider;
import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy; import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy;
@@ -38,8 +40,8 @@ public class MyUI extends HawkbitUI {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Autowired @Autowired
public MyUI(final EventBus systemEventBus, final org.vaadin.spring.events.EventBus.SessionEventBus eventBus, public MyUI(final ScheduledExecutorService executorService, final EventBus systemEventBus,
final UIEventProvider provider) { final org.vaadin.spring.events.EventBus.SessionEventBus eventBus, final UIEventProvider provider) {
super(new DelayedEventBusPushStrategy(eventBus, systemEventBus, provider)); super(new DelayedEventBusPushStrategy(executorService, eventBus, systemEventBus, provider));
} }
} }

View File

@@ -34,7 +34,6 @@ import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.eclipse.hawkbit.dmf.json.model.Artifact; 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.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus; import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
@@ -206,12 +205,12 @@ public class DeviceSimulatorUpdater {
private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status, private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status,
final Artifact artifact) { final Artifact artifact) {
if (artifact.getUrls().containsKey(UrlProtocol.HTTPS)) { if (artifact.getUrls().containsKey("HTTPS")) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTPS), targetToken, status.add(downloadUrl(artifact.getUrls().get("HTTPS"), targetToken, artifact.getHashes().getSha1(),
artifact.getHashes().getSha1(), artifact.getSize())); artifact.getSize()));
} else if (artifact.getUrls().containsKey(UrlProtocol.HTTP)) { } else if (artifact.getUrls().containsKey("HTTP")) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTP), targetToken, status.add(downloadUrl(artifact.getUrls().get("HTTP"), targetToken, artifact.getHashes().getSha1(),
artifact.getHashes().getSha1(), artifact.getSize())); artifact.getSize()));
} }
} }

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.simulator.amqp;
import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX; import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -36,6 +35,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import com.google.common.collect.Maps;
/** /**
* The spring AMQP configuration to use a AMQP for communication with SP update * The spring AMQP configuration to use a AMQP for communication with SP update
* server. * server.
@@ -200,13 +201,13 @@ public class AmqpConfiguration {
} }
private Map<String, Object> getDeadLetterExchangeArgs() { private Map<String, Object> getDeadLetterExchangeArgs() {
final Map<String, Object> args = new HashMap<>(); final Map<String, Object> args = Maps.newHashMapWithExpectedSize(1);
args.put("x-dead-letter-exchange", amqpProperties.getDeadLetterExchange()); args.put("x-dead-letter-exchange", amqpProperties.getDeadLetterExchange());
return args; return args;
} }
private static Map<String, Object> getTTLMaxArgs() { private static Map<String, Object> getTTLMaxArgs() {
final Map<String, Object> args = new HashMap<>(); final Map<String, Object> args = Maps.newHashMapWithExpectedSize(2);
args.put("x-message-ttl", Duration.ofDays(1).toMillis()); args.put("x-message-ttl", Duration.ofDays(1).toMillis());
args.put("x-max-length", 100_000); args.put("x-max-length", 100_000);
return args; return args;

View File

@@ -206,7 +206,7 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant()); headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant());
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
actionUpdateStatus.getMessage().addAll(updateResultMessages); actionUpdateStatus.addMessage(updateResultMessages);
actionUpdateStatus.setActionId(cacheValue.getActionId()); actionUpdateStatus.setActionId(cacheValue.getActionId());
return convertMessage(actionUpdateStatus, messageProperties); return convertMessage(actionUpdateStatus, messageProperties);
} }

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.app; package org.eclipse.hawkbit.app;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.ui.HawkbitUI; import org.eclipse.hawkbit.ui.HawkbitUI;
import org.eclipse.hawkbit.ui.UIEventProvider; import org.eclipse.hawkbit.ui.UIEventProvider;
import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy; import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy;
@@ -37,8 +39,8 @@ public class MyUI extends HawkbitUI {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Autowired @Autowired
public MyUI(final EventBus systemEventBus, final org.vaadin.spring.events.EventBus.SessionEventBus eventBus, public MyUI(final ScheduledExecutorService scheduledExecutorService, final EventBus systemEventBus,
final UIEventProvider provider) { final org.vaadin.spring.events.EventBus.SessionEventBus eventBus, final UIEventProvider provider) {
super(new DelayedEventBusPushStrategy(eventBus, systemEventBus, provider)); super(new DelayedEventBusPushStrategy(scheduledExecutorService, eventBus, systemEventBus, provider));
} }
} }

View File

@@ -9,8 +9,15 @@
vaadin.servlet.productionMode=true vaadin.servlet.productionMode=true
hawkbit.artifact.url.coap.enabled=false ## Configuration for building download URLs - START
hawkbit.artifact.url.http.enabled=false hawkbit.artifact.url.protocols.download-http.rel=download-http
hawkbit.artifact.url.https.enabled=true hawkbit.artifact.url.protocols.download-http.protocol=https
hawkbit.artifact.url.https.pattern={protocol}://{hostname}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName} hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.https.hostname=hawkbit.eu-gb.mybluemix.net hawkbit.artifact.url.protocols.download-http.hostname=hawkbit.eu-gb.mybluemix.net
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM
## Configuration for building download URLs - END

View File

@@ -16,12 +16,6 @@ hawkbit.server.ddi.security.authentication.anonymous.enabled=true
hawkbit.server.ddi.security.authentication.targettoken.enabled=true hawkbit.server.ddi.security.authentication.targettoken.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=true hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=true
# Download URL generation config
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=true
hawkbit.artifact.url.http.port=8080
hawkbit.artifact.url.https.enabled=false
## Vaadin configuration ## Vaadin configuration
vaadin.servlet.productionMode=false vaadin.servlet.productionMode=false

View File

@@ -50,11 +50,21 @@ public class SoftwareModuleTypeBuilder {
return this; return this;
} }
/**
* @param description
* of the module
* @return the builder itself
*/
public SoftwareModuleTypeBuilder description(final String description) { public SoftwareModuleTypeBuilder description(final String description) {
this.description = description; this.description = description;
return this; return this;
} }
/**
* @param maxAssignments
* of a module of that type to the same distribution set
* @return the builder itself
*/
public SoftwareModuleTypeBuilder maxAssignments(final int maxAssignments) { public SoftwareModuleTypeBuilder maxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments; this.maxAssignments = maxAssignments;
return this; return this;
@@ -99,4 +109,4 @@ public class SoftwareModuleTypeBuilder {
return body; return body;
} }
} }

View File

@@ -8,8 +8,7 @@
http://www.eclipse.org/legal/epl-v10.html http://www.eclipse.org/legal/epl-v10.html
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
@@ -22,14 +21,31 @@
<name>hawkBit-example :: Parent</name> <name>hawkBit-example :: Parent</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <profiles>
<module>hawkbit-device-simulator</module> <profile>
<module>hawkbit-example-app</module> <id>noExampleApp</id>
<module>hawkbit-example-core-feign-client</module> <modules>
<module>hawkbit-example-ddi-feign-client</module> <module>hawkbit-example-core-feign-client</module>
<module>hawkbit-example-mgmt-feign-client</module> <module>hawkbit-example-ddi-feign-client</module>
<module>hawkbit-example-mgmt-simulator</module> <module>hawkbit-example-mgmt-feign-client</module>
</modules> <module>hawkbit-example-mgmt-simulator</module>
</modules>
</profile>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>hawkbit-device-simulator</module>
<module>hawkbit-example-app</module>
<module>hawkbit-example-core-feign-client</module>
<module>hawkbit-example-ddi-feign-client</module>
<module>hawkbit-example-mgmt-feign-client</module>
<module>hawkbit-example-mgmt-simulator</module>
</modules>
</profile>
</profiles>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>

View File

@@ -227,7 +227,7 @@ public class ArtifactStore implements ArtifactRepository {
* @return a paged list of artifacts mapped from the given dbFiles * @return a paged list of artifacts mapped from the given dbFiles
*/ */
private List<DbArtifact> map(final List<GridFSDBFile> dbFiles) { private List<DbArtifact> map(final List<GridFSDBFile> dbFiles) {
return dbFiles.stream().map(this::map).collect(Collectors.toList()); return dbFiles.stream().map(ArtifactStore::map).collect(Collectors.toList());
} }
/** /**
@@ -263,7 +263,7 @@ public class ArtifactStore implements ArtifactRepository {
* the mongoDB gridFs file. * the mongoDB gridFs file.
* @return a mapped artifact from the given dbFile * @return a mapped artifact from the given dbFile
*/ */
private GridFsArtifact map(final GridFSFile fsFile) { private static GridFsArtifact map(final GridFSFile fsFile) {
if (fsFile == null) { if (fsFile == null) {
return null; return null;
} }

View File

@@ -82,8 +82,7 @@ public class CacheAutoConfiguration extends CachingConfigurerSupport {
*/ */
@Override @Override
public Collection<Cache> resolveCaches(final CacheOperationInvocationContext<?> context) { public Collection<Cache> resolveCaches(final CacheOperationInvocationContext<?> context) {
return super.resolveCaches(context).stream().map(cache -> new TenantCacheWrapper(cache)) return super.resolveCaches(context).stream().map(TenantCacheWrapper::new).collect(Collectors.toList());
.collect(Collectors.toList());
} }
/* /*

View File

@@ -148,7 +148,7 @@ public class SecurityManagedConfiguration {
private DdiSecurityProperties ddiSecurityConfiguration; private DdiSecurityProperties ddiSecurityConfiguration;
@Autowired @Autowired
private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties; private SecurityProperties springSecurityProperties;
@Autowired @Autowired
private SystemSecurityContext systemSecurityContext; private SystemSecurityContext systemSecurityContext;
@@ -478,7 +478,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
public void onAuthenticationSuccess(final Authentication authentication) throws Exception { public void onAuthenticationSuccess(final Authentication authentication) throws Exception {
if (authentication.getClass().equals(TenantUserPasswordAuthenticationToken.class)) { if (authentication.getClass().equals(TenantUserPasswordAuthenticationToken.class)) {
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(), systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata,
((TenantUserPasswordAuthenticationToken) authentication).getTenant().toString()); ((TenantUserPasswordAuthenticationToken) authentication).getTenant().toString());
} else if (authentication.getClass().equals(UsernamePasswordAuthenticationToken.class)) { } else if (authentication.getClass().equals(UsernamePasswordAuthenticationToken.class)) {
// TODO: vaadin4spring-ext-security does not give us the // TODO: vaadin4spring-ext-security does not give us the
@@ -489,7 +489,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
// vaadin4spring 0.0.7 because it // vaadin4spring 0.0.7 because it
// has been fixed. // has been fixed.
final String defaultTenant = "DEFAULT"; final String defaultTenant = "DEFAULT";
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(), defaultTenant); systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant);
} }
super.onAuthenticationSuccess(authentication); super.onAuthenticationSuccess(authentication);
@@ -526,7 +526,7 @@ class AuthenticationSuccessTenantMetadataCreationFilter implements Filter {
private void lazyCreateTenantMetadata() { private void lazyCreateTenantMetadata() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
systemSecurityContext.runAsSystem(() -> systemManagement.getTenantMetadata()); systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata);
} }
} }

View File

@@ -12,8 +12,10 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import org.eclipse.hawkbit.HawkbitServerProperties; import org.eclipse.hawkbit.HawkbitServerProperties;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties;
import org.eclipse.hawkbit.api.HostnameResolver; import org.eclipse.hawkbit.api.HostnameResolver;
import org.springframework.beans.factory.annotation.Autowired; import org.eclipse.hawkbit.api.PropertyBasedArtifactUrlHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -22,35 +24,41 @@ import org.springframework.context.annotation.Configuration;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
/** /**
* Autoconfiguration of the {@link HostnameResolver} based on a property. * Auto configuration for {@link HostnameResolver} and
* * {@link ArtifactUrlHandler} based on a properties.
*
*
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(HawkbitServerProperties.class) @EnableConfigurationProperties({ HawkbitServerProperties.class, ArtifactUrlHandlerProperties.class })
public class PropertyHostnameResolverAutoConfiguration { public class PropertyHostnameResolverAutoConfiguration {
@Autowired
private HawkbitServerProperties serverProperties;
/** /**
* @param serverProperties
* to get the servers URL
* @return the default autoconfigure hostname resolver implementation which * @return the default autoconfigure hostname resolver implementation which
* is property based specified by the property {@link #url} * is property based specified by the property {@link #url}
*/ */
@Bean @Bean
@ConditionalOnMissingBean(value = HostnameResolver.class) @ConditionalOnMissingBean(value = HostnameResolver.class)
public HostnameResolver hostnameResolver() { public HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) {
return new HostnameResolver() { return () -> {
@Override try {
public URL resolveHostname() { return new URL(serverProperties.getUrl());
try { } catch (final MalformedURLException e) {
return new URL(serverProperties.getUrl()); throw Throwables.propagate(e);
} catch (final MalformedURLException e) {
throw Throwables.propagate(e);
}
} }
}; };
} }
/**
* @param urlHandlerProperties
* for bean configuration
* @return PropertyBasedArtifactUrlHandler bean
*/
@Bean
@ConditionalOnMissingBean(ArtifactUrlHandler.class)
public PropertyBasedArtifactUrlHandler propertyBasedArtifactUrlHandler(
final ArtifactUrlHandlerProperties urlHandlerProperties) {
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties);
}
} }

View File

@@ -41,9 +41,25 @@ hawkbit.controller.maxPollingTime=23:59:59
hawkbit.controller.minPollingTime=00:00:30 hawkbit.controller.minPollingTime=00:00:30
# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window # Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window
# Configuration for RabbitMQ integration # Configuration for RabbitMQ integration
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl
hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter
hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver
hawkbit.dmf.rabbitmq.authenticationReceiverQueue=authentication_receiver hawkbit.dmf.rabbitmq.authenticationReceiverQueue=authentication_receiver
# Download URL generation configuration
hawkbit.artifact.url.protocols.download-http.rel=download-http
hawkbit.artifact.url.protocols.download-http.hostname=localhost
hawkbit.artifact.url.protocols.download-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-http.protocol=http
hawkbit.artifact.url.protocols.download-http.port=8080
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
hawkbit.artifact.url.protocols.md5sum-http.ip=${hawkbit.artifact.url.protocols.download-http.ip}
hawkbit.artifact.url.protocols.md5sum-http.port=${hawkbit.artifact.url.protocols.download-http.port}
hawkbit.artifact.url.protocols.md5sum-http.supports=DDI
hawkbit.artifact.url.protocols.md5sum-http.ref=${hawkbit.artifact.url.protocols.download-http.ref}.MD5SUM

View File

@@ -33,6 +33,16 @@
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@@ -9,8 +9,18 @@
package org.eclipse.hawkbit.api; package org.eclipse.hawkbit.api;
/** /**
* Represented the supported protocols for artifact url's. * hawkBit API type.
*
*/ */
public enum UrlProtocol { public enum ApiType {
COAP, HTTP, HTTPS
/**
* Support for Device Management Federation API.
*/
DMF,
/**
* Support for Direct Device Integration API.
*/
DDI;
} }

View File

@@ -0,0 +1,109 @@
/**
* 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.api;
/**
* Container for a generated Artifact URL.
*
*/
public class ArtifactUrl {
private final String protocol;
private final String rel;
private final String ref;
/**
* Constructor.
*
* @param protocol
* string, e.g. ftp, http, https
* @param rel
* hypermedia value
* @param ref
* hypermedia value
*/
public ArtifactUrl(final String protocol, final String rel, final String ref) {
this.protocol = protocol;
this.rel = rel;
this.ref = ref;
}
/**
* @return protocol name used in DMF API messages.
*/
public String getProtocol() {
return protocol;
}
/**
* @return rel links value useful in hypermedia.
*/
public String getRel() {
return rel;
}
/**
* @return generated artifact download URL
*/
public String getRef() {
return ref;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
result = prime * result + ((ref == null) ? 0 : ref.hashCode());
result = prime * result + ((rel == null) ? 0 : rel.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArtifactUrl other = (ArtifactUrl) obj;
if (protocol == null) {
if (other.protocol != null) {
return false;
}
} else if (!protocol.equals(other.protocol)) {
return false;
}
if (ref == null) {
if (other.ref != null) {
return false;
}
} else if (!ref.equals(other.ref)) {
return false;
}
if (rel == null) {
if (other.rel != null) {
return false;
}
} else if (!rel.equals(other.rel)) {
return false;
}
return true;
}
@Override
public String toString() {
return "ArtifactUrl [protocol=" + protocol + ", rel=" + rel + ", ref=" + ref + "]";
}
}

View File

@@ -8,36 +8,26 @@
*/ */
package org.eclipse.hawkbit.api; package org.eclipse.hawkbit.api;
import java.util.List;
/** /**
* Interface declaration of the {@link ArtifactUrlHandler} which generates the * Interface declaration of the {@link ArtifactUrlHandler} which generates the
* URLs to specific artifacts. * URLs to specific artifacts.
* *
*/ */
@FunctionalInterface
public interface ArtifactUrlHandler { public interface ArtifactUrlHandler {
/** /**
* Returns a generated download URL for a given artifact parameters for a * Returns a generated download URL for a given artifact parameters for a
* specific protocol. * specific protocol.
* *
* @param controllerId * @param placeholder
* the authenticated controller id * data for URL generation
* @param softwareModuleId * @param api
* the softwareModuleId belonging to the artifact * given protocol that URL needs to support
* @param filename *
* the filename of the artifact
* @param sha1Hash
* the sha1Hash of the artifact
* @param protocol
* the protocol the URL should be generated
* @return an URL for the given artifact parameters in a given protocol * @return an URL for the given artifact parameters in a given protocol
*/ */
String getUrl(String controllerId, final Long softwareModuleId, final String filename, final String sha1Hash, List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api);
final UrlProtocol protocol);
/**
* @param protocol
* to check support for
* @return <code>true</code> of the handler supports given protocol.
*/
boolean protocolSupported(UrlProtocol protocol);
} }

View File

@@ -8,90 +8,153 @@
*/ */
package org.eclipse.hawkbit.api; package org.eclipse.hawkbit.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import com.google.common.collect.Lists;
/** /**
* Artifact handler properties class for holding all supported protocols with * Artifact handler properties class for holding all supported protocols with
* host, ip, port and download pattern. * host, ip, port and download pattern.
*
* @see PropertyBasedArtifactUrlHandler
*/ */
@ConfigurationProperties("hawkbit.artifact.url") @ConfigurationProperties("hawkbit.artifact.url")
public class ArtifactUrlHandlerProperties { public class ArtifactUrlHandlerProperties {
private final Http http = new Http(); /**
private final Https https = new Https(); * Rel as key and complete protocol as value.
private final Coap coap = new Coap(); */
private final Map<String, UrlProtocol> protocols = new HashMap<>();
public Http getHttp() {
return http;
}
public Https getHttps() {
return https;
}
public Coap getCoap() {
return coap;
}
/** /**
* @param protocol * Protocol specific properties to generate URLs accordingly.
* the protocol schema to retrieve the properties. *
* @return the properties to a protocol or {@code null} if protocol does not
* have properties or protocol not supported
*/ */
public ProtocolProperties getProperties(final String protocol) { public static class UrlProtocol {
switch (protocol) {
case "http":
return getHttp();
case "https":
return getHttps();
case "coap":
return getCoap();
default:
return null;
}
}
/** private static final int DEFAULT_HTTP_PORT = 8080;
* Object to hold the properties for the HTTP protocol.
*/
public static class Http extends DefaultProtocolProperties {
/** /**
* Constructor. * Set to true if enabled.
*/ */
public Http() { private boolean enabled = true;
setPattern(
"{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
}
}
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Https extends DefaultProtocolProperties {
/** /**
* Constructor. * Hypermedia rel value for this protocol.
*/ */
public Https() { private String rel = "download-http";
setPattern(
"{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
}
}
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Coap extends DefaultProtocolProperties {
/** /**
* Constructor. * Hypermedia ref pattern for this protocol. Supported place holders are
* protocol,controllerId,targetId,targetIdBase62,ip,port,hostname,
* artifactFileName,artifactSHA1,
* artifactIdBase62,artifactId,tenant,softwareModuleId,
* softwareModuleIdBase62.
*
* The update server itself supports
*/ */
public Coap() { private String ref = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
setPattern("{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}");
setPort("5683"); /**
* Protocol name placeholder that can be used in ref pattern.
*/
private String protocol = "http";
/**
* Hostname placeholder that can be used in ref pattern.
*/
private String hostname = "localhost";
/**
* IP address placeholder that can be used in ref pattern.
*/
// Exception squid:S1313 - default only, can be configured
@SuppressWarnings("squid:S1313")
private String ip = "127.0.0.1";
/**
* Port placeholder that can be used in ref pattern.
*/
private Integer port = DEFAULT_HTTP_PORT;
/**
* Support for the following hawkBit API.
*/
private List<ApiType> supports = Lists.newArrayList(ApiType.DDI, ApiType.DMF);
public boolean isEnabled() {
return enabled;
} }
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public String getRel() {
return rel;
}
public void setRel(final String rel) {
this.rel = rel;
}
public String getRef() {
return ref;
}
public void setRef(final String ref) {
this.ref = ref;
}
public String getHostname() {
return hostname;
}
public void setHostname(final String hostname) {
this.hostname = hostname;
}
public String getIp() {
return ip;
}
public void setIp(final String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(final Integer port) {
this.port = port;
}
public List<ApiType> getSupports() {
return Collections.unmodifiableList(supports);
}
public void setSupports(final List<ApiType> supports) {
this.supports = Collections.unmodifiableList(supports);
}
public String getProtocol() {
return protocol;
}
public void setProtocol(final String protocol) {
this.protocol = protocol;
}
}
public Map<String, UrlProtocol> getProtocols() {
return protocols;
} }
} }

View File

@@ -0,0 +1,68 @@
/**
* 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.api;
/**
* Utility class for Base10 to Base62 conversion and vice versa. Base62 has the
* benefit of being shorter in ASCII representation than Base10.
*/
public final class Base62Util {
private static final String BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int BASE62_BASE = BASE62_ALPHABET.length();
private Base62Util() {
// Utility class
}
/**
* @param base10
* number
* @return converted number into Base62 ASCII string
*/
public static String fromBase10(final long base10) {
if (base10 == 0) {
return "0";
}
long temp = base10;
final StringBuilder sb = new StringBuilder();
while (temp > 0) {
temp = fromBase10(temp, sb);
}
return sb.reverse().toString();
}
/**
* @param base62
* number
* @return converted number into Base10
*/
public static Long toBase10(final String base62) {
return toBase10(new StringBuilder(base62).reverse().toString().toCharArray());
}
private static Long fromBase10(final long base10, final StringBuilder sb) {
final int rem = (int) (base10 % BASE62_BASE);
sb.append(BASE62_ALPHABET.charAt(rem));
return base10 / BASE62_BASE;
}
private static Long toBase10(final char[] chars) {
long base10 = 0L;
for (int i = chars.length - 1; i >= 0; i--) {
base10 += toBase10(BASE62_ALPHABET.indexOf(chars[i]), i);
}
return base10;
}
private static int toBase10(final int n, final int pow) {
return n * (int) Math.pow(BASE62_BASE, pow);
}
}

View File

@@ -1,79 +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.api;
/**
* Object to hold the properties for the base protocols.
*/
public class DefaultProtocolProperties implements ProtocolProperties {
// The IP address is not hardcoded. It's the default value, if the IP
// address is not configured.
@SuppressWarnings("squid:S1313")
private static final String DEFAULT_IP_LOCALHOST = "127.0.0.1";
private static final String LOCALHOST = "localhost";
private String hostname = LOCALHOST;
private String ip = DEFAULT_IP_LOCALHOST;
private String port = "";
/**
* An ant-URL pattern with placeholder to build the URL on. The URL can have
* specific artifact placeholder.
*/
private String pattern;
/**
* Enables protocol.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
public String getHostname() {
return hostname;
}
public void setHostname(final String hostname) {
this.hostname = hostname;
}
@Override
public String getIp() {
return ip;
}
public void setIp(final String ip) {
this.ip = ip;
}
@Override
public String getPattern() {
return pattern;
}
public void setPattern(final String urlPattern) {
this.pattern = urlPattern;
}
@Override
public String getPort() {
return port;
}
public void setPort(final String port) {
this.port = port;
}
}

View File

@@ -9,55 +9,80 @@
package org.eclipse.hawkbit.api; package org.eclipse.hawkbit.api;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.net.UrlEscapers; import com.google.common.net.UrlEscapers;
/** /**
* Implementation for ArtifactUrlHandler for creating urls to download resource * Implementation for ArtifactUrlHandler for creating urls to download resource
* based on pattern. * based on patterns configured by {@link ArtifactUrlHandlerProperties}.
*
* This mechanism can be used to generate links to arbitrary file hosting
* infrastructure. However, the hawkBit update server supports hosting files as
* well in the following {@link UrlProtocol#getRef()} patterns:
*
* Default:
* {protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
*
* Default (MD5SUM files):
* {protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/
* softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}.MD5SUM
*
*/ */
@Component
@EnableConfigurationProperties(ArtifactUrlHandlerProperties.class)
public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler { public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
private static final String PROTOCOL_PLACEHOLDER = "protocol"; private static final String PROTOCOL_PLACEHOLDER = "protocol";
private static final String TARGET_ID_PLACEHOLDER = "targetId"; private static final String CONTROLLER_ID_PLACEHOLDER = "controllerId";
private static final String TARGET_ID_BASE10_PLACEHOLDER = "targetId";
private static final String TARGET_ID_BASE62_PLACEHOLDER = "targetIdBase62";
private static final String IP_PLACEHOLDER = "ip"; private static final String IP_PLACEHOLDER = "ip";
private static final String PORT_PLACEHOLDER = "port"; private static final String PORT_PLACEHOLDER = "port";
private static final String HOSTNAME_PLACEHOLDER = "hostname"; private static final String HOSTNAME_PLACEHOLDER = "hostname";
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName"; private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1"; private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
private static final String ARTIFACT_ID_BASE10_PLACEHOLDER = "artifactId";
private static final String ARTIFACT_ID_BASE62_PLACEHOLDER = "artifactIdBase62";
private static final String TENANT_PLACEHOLDER = "tenant"; private static final String TENANT_PLACEHOLDER = "tenant";
private static final String SOFTWARE_MODULE_ID_PLACDEHOLDER = "softwareModuleId"; private static final String TENANT_ID_BASE10_PLACEHOLDER = "tenantId";
private static final String TENANT_ID_BASE62_PLACEHOLDER = "tenantIdBase62";
private static final String SOFTWARE_MODULE_ID_BASE10_PLACDEHOLDER = "softwareModuleId";
private static final String SOFTWARE_MODULE_ID_BASE62_PLACDEHOLDER = "softwareModuleIdBase62";
@Autowired private final ArtifactUrlHandlerProperties urlHandlerProperties;
private ArtifactUrlHandlerProperties urlHandlerProperties;
@Autowired /**
private TenantAware tenantAware; * @param urlHandlerProperties
* for URL generation configuration
*/
public PropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties) {
this.urlHandlerProperties = urlHandlerProperties;
}
@Override @Override
public String getUrl(final String targetId, final Long softwareModuleId, final String filename, public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api) {
final String sha1Hash, final UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase(); return urlHandlerProperties.getProtocols().entrySet().stream()
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString); .filter(entry -> entry.getValue().getSupports().contains(api))
if (properties == null || properties.getPattern() == null) { .filter(entry -> entry.getValue().isEnabled())
return null; .map(entry -> new ArtifactUrl(entry.getValue().getProtocol().toUpperCase(), entry.getValue().getRel(),
} generateUrl(entry.getValue(), placeholder)))
.collect(Collectors.toList());
}
private static String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder) {
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder).entrySet();
String urlPattern = protocol.getRef();
String urlPattern = properties.getPattern();
final Set<Entry<String, String>> entrySet = getReplaceMap(targetId, softwareModuleId,
UrlEscapers.urlFragmentEscaper().escape(filename), sha1Hash, protocolString, properties).entrySet();
for (final Entry<String, String> entry : entrySet) { for (final Entry<String, String> entry : entrySet) {
if (entry.getKey().equals(PORT_PLACEHOLDER)) { if (entry.getKey().equals(PORT_PLACEHOLDER)) {
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}", urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
@@ -69,30 +94,29 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
return urlPattern; return urlPattern;
} }
private Map<String, String> getReplaceMap(final String targetId, final Long softwareModuleId, final String filename, private static Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder) {
final String sha1Hash, final String protocol, final ProtocolProperties properties) {
final Map<String, String> replaceMap = new HashMap<>(); final Map<String, String> replaceMap = new HashMap<>();
replaceMap.put(IP_PLACEHOLDER, properties.getIp()); replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, properties.getHostname()); replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, filename); replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, sha1Hash); UrlEscapers.urlFragmentEscaper().escape(placeholder.getSoftwareData().getFilename()));
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol); replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
replaceMap.put(PORT_PLACEHOLDER, properties.getPort()); replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
replaceMap.put(TENANT_PLACEHOLDER, tenantAware.getCurrentTenant()); replaceMap.put(PORT_PLACEHOLDER, protocol.getPort() == null ? null : String.valueOf(protocol.getPort()));
replaceMap.put(TARGET_ID_PLACEHOLDER, targetId); replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
replaceMap.put(SOFTWARE_MODULE_ID_PLACDEHOLDER, String.valueOf(softwareModuleId)); replaceMap.put(TENANT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTenantId()));
replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
replaceMap.put(CONTROLLER_ID_PLACEHOLDER, placeholder.getControllerId());
replaceMap.put(TARGET_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTargetId()));
replaceMap.put(TARGET_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTargetId()));
replaceMap.put(ARTIFACT_ID_BASE62_PLACEHOLDER,
Base62Util.fromBase10(placeholder.getSoftwareData().getArtifactId()));
replaceMap.put(ARTIFACT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getSoftwareData().getArtifactId()));
replaceMap.put(SOFTWARE_MODULE_ID_BASE10_PLACDEHOLDER,
String.valueOf(placeholder.getSoftwareData().getSoftwareModuleId()));
replaceMap.put(SOFTWARE_MODULE_ID_BASE62_PLACDEHOLDER,
Base62Util.fromBase10(placeholder.getSoftwareData().getSoftwareModuleId()));
return replaceMap; return replaceMap;
} }
@Override
public boolean protocolSupported(final UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return false;
}
return properties.isEnabled();
}
} }

View File

@@ -0,0 +1,247 @@
/**
* 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.api;
/**
* Container for variables available to the {@link ArtifactUrlHandler}.
*
*/
public class URLPlaceholder {
private final String tenant;
private final Long tenantId;
private final String controllerId;
private final Long targetId;
private final SoftwareData softwareData;
/**
* Constructor.
*
* @param tenant
* of the client
* @param tenantId
* of teh tenant
* @param controllerId
* of the target
* @param targetId
* of the target
* @param softwareData
* information about the artifact and software module that can be
* accessed by the URL.
*/
public URLPlaceholder(final String tenant, final Long tenantId, final String controllerId, final Long targetId,
final SoftwareData softwareData) {
this.tenant = tenant;
this.tenantId = tenantId;
this.controllerId = controllerId;
this.targetId = targetId;
this.softwareData = softwareData;
}
/**
* Information about the artifact and software module that can be accessed
* by the URL.
*
*/
public static class SoftwareData {
private Long softwareModuleId;
private String filename;
private Long artifactId;
private String sha1Hash;
/**
* Constructor.
*
* @param softwareModuleId
* of the module the artifact belongs to
* @param filename
* of the artifact
* @param artifactId
* of the artifact
* @param sha1Hash
* of the artifact
*/
public SoftwareData(final Long softwareModuleId, final String filename, final Long artifactId,
final String sha1Hash) {
this.softwareModuleId = softwareModuleId;
this.filename = filename;
this.artifactId = artifactId;
this.sha1Hash = sha1Hash;
}
public Long getSoftwareModuleId() {
return softwareModuleId;
}
public void setSoftwareModuleId(final Long softwareModuleId) {
this.softwareModuleId = softwareModuleId;
}
public String getFilename() {
return filename;
}
public void setFilename(final String filename) {
this.filename = filename;
}
public Long getArtifactId() {
return artifactId;
}
public void setArtifactId(final Long artifactId) {
this.artifactId = artifactId;
}
public String getSha1Hash() {
return sha1Hash;
}
public void setSha1Hash(final String sha1Hash) {
this.sha1Hash = sha1Hash;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
result = prime * result + ((sha1Hash == null) ? 0 : sha1Hash.hashCode());
result = prime * result + ((softwareModuleId == null) ? 0 : softwareModuleId.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SoftwareData other = (SoftwareData) obj;
if (artifactId == null) {
if (other.artifactId != null) {
return false;
}
} else if (!artifactId.equals(other.artifactId)) {
return false;
}
if (filename == null) {
if (other.filename != null) {
return false;
}
} else if (!filename.equals(other.filename)) {
return false;
}
if (sha1Hash == null) {
if (other.sha1Hash != null) {
return false;
}
} else if (!sha1Hash.equals(other.sha1Hash)) {
return false;
}
if (softwareModuleId == null) {
if (other.softwareModuleId != null) {
return false;
}
} else if (!softwareModuleId.equals(other.softwareModuleId)) {
return false;
}
return true;
}
}
public String getTenant() {
return tenant;
}
public Long getTenantId() {
return tenantId;
}
public String getControllerId() {
return controllerId;
}
public Long getTargetId() {
return targetId;
}
public SoftwareData getSoftwareData() {
return softwareData;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((controllerId == null) ? 0 : controllerId.hashCode());
result = prime * result + ((softwareData == null) ? 0 : softwareData.hashCode());
result = prime * result + ((targetId == null) ? 0 : targetId.hashCode());
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final URLPlaceholder other = (URLPlaceholder) obj;
if (controllerId == null) {
if (other.controllerId != null) {
return false;
}
} else if (!controllerId.equals(other.controllerId)) {
return false;
}
if (softwareData == null) {
if (other.softwareData != null) {
return false;
}
} else if (!softwareData.equals(other.softwareData)) {
return false;
}
if (targetId == null) {
if (other.targetId != null) {
return false;
}
} else if (!targetId.equals(other.targetId)) {
return false;
}
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
if (tenantId == null) {
if (other.tenantId != null) {
return false;
}
} else if (!tenantId.equals(other.tenantId)) {
return false;
}
return true;
}
}

View File

@@ -90,7 +90,7 @@ public enum TenantConfigurationKey {
* @param validator * @param validator
* Validator which validates, that property is of correct format * Validator which validates, that property is of correct format
*/ */
private TenantConfigurationKey(final String key, final String defaultKeyName, final Class<?> dataType, TenantConfigurationKey(final String key, final String defaultKeyName, final Class<?> dataType,
final String defaultValue, final Class<? extends TenantConfigurationValidator> validator) { final String defaultValue, final Class<? extends TenantConfigurationValidator> validator) {
this.keyName = key; this.keyName = key;
this.dataType = dataType; this.dataType = dataType;

View File

@@ -0,0 +1,40 @@
/**
* 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.api;
import static org.fest.assertions.api.Assertions.assertThat;
import org.junit.Test;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features("Unit Tests - Artifact URL Handler")
@Stories("Base62 Utility tests")
public class Base62UtilTest {
@Test
@Description("Convert Base10 numbres to Base62 ASCII strings.")
public void fromBase10() {
assertThat(Base62Util.fromBase10(0L)).isEqualTo("0");
assertThat(Base62Util.fromBase10(11L)).isEqualTo("B");
assertThat(Base62Util.fromBase10(36L)).isEqualTo("a");
assertThat(Base62Util.fromBase10(999L)).isEqualTo("G7");
}
@Test
@Description("Convert Base62 ASCII strings to Base10 numbers.")
public void toBase10() {
assertThat(Base62Util.toBase10("0")).isEqualTo(0L);
assertThat(Base62Util.toBase10("B")).isEqualTo(11);
assertThat(Base62Util.toBase10("a")).isEqualTo(36L);
assertThat(Base62Util.toBase10("G7")).isEqualTo(999L);
}
}

View File

@@ -0,0 +1,124 @@
/**
* 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.api;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Tests for creating urls to download artifacts.
*/
@Features("Unit Tests - Artifact URL Handler")
@Stories("Test to generate the artifact download URL")
@RunWith(MockitoJUnitRunner.class)
public class PropertyBasedArtifactUrlHandlerTest {
private static final String TEST_PROTO = "coap";
private static final String TEST_REL = "download-udp";
private static final long TENANT_ID = 456789L;
private static final String CONTROLLER_ID = "Test";
private static final String FILENAME = "Afile1234";
private static final long SOFTWAREMODULEID = 87654L;
private static final long TARGETID = 3474366L;
private static final String TARGETID_BASE62 = "EZqA";
private static final String SHA1HASH = "test12345";
private static final long ARTIFACTID = 1345678L;
private static final String ARTIFACTID_BASE62 = "5e4U";
private static final String TENANT = "TEST_TENANT";
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
private ArtifactUrlHandler urlHandlerUnderTest;
private ArtifactUrlHandlerProperties properties;
private static URLPlaceholder placeholder = new URLPlaceholder(TENANT, TENANT_ID, CONTROLLER_ID, TARGETID,
new SoftwareData(SOFTWAREMODULEID, FILENAME, ARTIFACTID, SHA1HASH));
@Before
public void setup() {
properties = new ArtifactUrlHandlerProperties();
urlHandlerUnderTest = new PropertyBasedArtifactUrlHandler(properties);
}
@Test
@Description("Tests the generation of http download url.")
public void urlGenerationWithDefaultConfiguration() {
properties.getProtocols().put("download-http", new UrlProtocol());
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertEquals(Lists.newArrayList(
new ArtifactUrl("http".toUpperCase(), "download-http", HTTP_LOCALHOST + TENANT + "/controller/v1/"
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
ddiUrls);
final List<ArtifactUrl> dmfUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(ddiUrls, dmfUrls);
}
@Test
@Description("Tests the generation of custom download url with a CoAP example that supports DMF only.")
public void urlGenerationWithCustomConfiguration() {
final UrlProtocol proto = new UrlProtocol();
proto.setIp("127.0.0.1");
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
proto.setSupports(Lists.newArrayList(ApiType.DMF));
proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactSHA1}");
properties.getProtocols().put(TEST_PROTO, proto);
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertThat(urls).isEmpty();
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO.toUpperCase(), TEST_REL,
"coap://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + SHA1HASH)), urls);
}
@Test
@Description("Tests the generation of custom download url using Base62 references with a CoAP example that supports DMF only.")
public void urlGenerationWithCustomShortConfiguration() {
final UrlProtocol proto = new UrlProtocol();
proto.setIp("127.0.0.1");
proto.setPort(5683);
proto.setProtocol(TEST_PROTO);
proto.setRel(TEST_REL);
proto.setSupports(Lists.newArrayList(ApiType.DMF));
proto.setRef("{protocol}://{ip}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
properties.getProtocols().put("ftp", proto);
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
assertThat(urls).isEmpty();
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO.toUpperCase(), TEST_REL,
TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGETID_BASE62 + "/" + ARTIFACTID_BASE62)),
urls);
}
}

View File

@@ -25,7 +25,6 @@ public class DdiCancelActionToStop {
* ID of the action to be stoppedW * ID of the action to be stoppedW
*/ */
public DdiCancelActionToStop(final String stopId) { public DdiCancelActionToStop(final String stopId) {
super();
this.stopId = stopId; this.stopId = stopId;
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.ddi.json.model; package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -65,7 +66,11 @@ public class DdiChunk {
} }
public List<DdiArtifact> getArtifacts() { public List<DdiArtifact> getArtifacts() {
return artifacts; if (artifacts == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(artifacts);
} }
} }

View File

@@ -30,7 +30,6 @@ public class DdiConfig {
* configuration of the SP target * configuration of the SP target
*/ */
public DdiConfig(final DdiPolling polling) { public DdiConfig(final DdiPolling polling) {
super();
this.polling = polling; this.polling = polling;
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.ddi.json.model; package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
@@ -41,7 +42,6 @@ public class DdiDeployment {
* to handle. * to handle.
*/ */
public DdiDeployment(final HandlingType download, final HandlingType update, final List<DdiChunk> chunks) { public DdiDeployment(final HandlingType download, final HandlingType update, final List<DdiChunk> chunks) {
super();
this.download = download; this.download = download;
this.update = update; this.update = update;
this.chunks = chunks; this.chunks = chunks;
@@ -56,7 +56,11 @@ public class DdiDeployment {
} }
public List<DdiChunk> getChunks() { public List<DdiChunk> getChunks() {
return chunks; if (chunks == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(chunks);
} }
/** /**
@@ -81,7 +85,7 @@ public class DdiDeployment {
private String name; private String name;
private HandlingType(final String name) { HandlingType(final String name) {
this.name = name; this.name = name;
} }

View File

@@ -71,7 +71,7 @@ public class DdiResult {
private String name; private String name;
private FinalResult(final String name) { FinalResult(final String name) {
this.name = name; this.name = name;
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.ddi.json.model; package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -57,7 +58,11 @@ public class DdiStatus {
} }
public List<String> getDetails() { public List<String> getDetails() {
return details; if (details == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(details);
} }
/** /**
@@ -98,7 +103,7 @@ public class DdiStatus {
private String name; private String name;
private ExecutionStatus(final String name) { ExecutionStatus(final String name) {
this.name = name; this.name = name;
} }

View File

@@ -37,39 +37,45 @@ public interface DdiRootControllerRestApi {
/** /**
* Returns all artifacts of a given software module and target. * Returns all artifacts of a given software module and target.
* *
* @param targetid * @param tenant
* of the client
* @param controllerId
* of the target that matches to controller id * of the target that matches to controller id
* @param softwareModuleId * @param softwareModuleId
* of the software module * of the software module
* @return the response * @return the response
*/ */
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts", produces = { @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts", produces = {
"application/hal+json", MediaType.APPLICATION_JSON_VALUE }) "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts( ResponseEntity<List<DdiArtifact>> getSoftwareModulesArtifacts(@PathVariable("tenant") final String tenant,
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId); @PathVariable("softwareModuleId") final Long softwareModuleId);
/** /**
* Root resource for an individual {@link Target}. * Root resource for an individual {@link Target}.
* *
* @param targetid * @param tenant
* of the request
* @param controllerId
* of the target that matches to controller id * of the target that matches to controller id
* @param request * @param request
* the HTTP request injected by spring * the HTTP request injected by spring
* @return the response * @return the response
*/ */
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}", produces = { "application/hal+json", @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }) MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant, ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid); @PathVariable("controllerId") final String controllerId);
/** /**
* Handles GET {@link DdiArtifact} download request. This could be full or * Handles GET {@link DdiArtifact} download request. This could be full or
* partial (as specified by RFC7233 (Range Requests)) download request. * partial (as specified by RFC7233 (Range Requests)) download request.
* *
* @param targetid * @param tenant
* of the related target * of the request
* @param controllerId
* of the target
* @param softwareModuleId * @param softwareModuleId
* of the parent software module * of the parent software module
* @param fileName * @param fileName
@@ -83,17 +89,19 @@ public interface DdiRootControllerRestApi {
* {@link HttpStatus#OK} or in case of partial download * {@link HttpStatus#OK} or in case of partial download
* {@link HttpStatus#PARTIAL_CONTENT}. * {@link HttpStatus#PARTIAL_CONTENT}.
*/ */
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}") @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{fileName}")
ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant, ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName); @PathVariable("fileName") final String fileName);
/** /**
* Handles GET {@link DdiArtifact} MD5 checksum file download request. * Handles GET {@link DdiArtifact} MD5 checksum file download request.
* *
* @param targetid * @param tenant
* of the related target * of the request
* @param controllerId
* of the target
* @param softwareModuleId * @param softwareModuleId
* of the parent software module * of the parent software module
* @param fileName * @param fileName
@@ -106,18 +114,20 @@ public interface DdiRootControllerRestApi {
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if * @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* successful * successful
*/ */
@RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}" @RequestMapping(method = RequestMethod.GET, value = "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{fileName}"
+ DdiRestConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = MediaType.TEXT_PLAIN_VALUE) + DdiRestConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = MediaType.TEXT_PLAIN_VALUE)
ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant, ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName); @PathVariable("fileName") final String fileName);
/** /**
* Resource for software module. * Resource for software module.
* *
* @param targetid * @param tenant
* of the target that matches to controller id * of the request
* @param controllerId
* of the target
* @param actionId * @param actionId
* of the {@link DdiDeploymentBase} that matches to active * of the {@link DdiDeploymentBase} that matches to active
* actions. * actions.
@@ -129,19 +139,21 @@ public interface DdiRootControllerRestApi {
* the HTTP request injected by spring * the HTTP request injected by spring
* @return the response * @return the response
*/ */
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION @RequestMapping(value = "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION
+ "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction(@PathVariable("tenant") final String tenant, ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid, @PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId, @PathVariable("actionId") @NotEmpty final Long actionId,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource); @RequestParam(value = "c", required = false, defaultValue = "-1") final int resource);
/** /**
* This is the feedback channel for the {@link DdiDeploymentBase} action. * This is the feedback channel for the {@link DdiDeploymentBase} action.
* *
* @param tenant
* of the client
* @param feedback * @param feedback
* to provide * to provide
* @param targetid * @param controllerId
* of the target that matches to controller id * of the target that matches to controller id
* @param actionId * @param actionId
* of the action we have feedback for * of the action we have feedback for
@@ -150,33 +162,37 @@ public interface DdiRootControllerRestApi {
* *
* @return the response * @return the response
*/ */
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/" @RequestMapping(value = "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/"
+ DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) + DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant, ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid final DdiActionFeedback feedback,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId); @PathVariable("actionId") @NotEmpty final Long actionId);
/** /**
* This is the feedback channel for the config data action. * This is the feedback channel for the config data action.
* *
* @param tenant
* of the client
* @param configData * @param configData
* as body * as body
* @param targetid * @param controllerId
* to provide data for * to provide data for
* @param request * @param request
* the HTTP request injected by spring * the HTTP request injected by spring
* *
* @return status of the request * @return status of the request
*/ */
@RequestMapping(value = "/{targetid}/" @RequestMapping(value = "/{controllerId}/"
+ DdiRestConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + DdiRestConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant, ResponseEntity<Void> putConfigData(@Valid final DdiConfigData configData,
@Valid final DdiConfigData configData, @PathVariable("targetid") final String targetid); @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId);
/** /**
* RequestMethod.GET method for the {@link DdiCancel} action. * RequestMethod.GET method for the {@link DdiCancel} action.
* *
* @param targetid * @param tenant
* of the request
* @param controllerId
* ID of the calling target * ID of the calling target
* @param actionId * @param actionId
* of the action * of the action
@@ -185,19 +201,21 @@ public interface DdiRootControllerRestApi {
* *
* @return the {@link DdiCancel} response * @return the {@link DdiCancel} response
*/ */
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.CANCEL_ACTION @RequestMapping(value = "/{controllerId}/" + DdiRestConstants.CANCEL_ACTION
+ "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + "/{actionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant, ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid, @PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId); @PathVariable("actionId") @NotEmpty final Long actionId);
/** /**
* RequestMethod.POST method receiving the {@link DdiActionFeedback} from * RequestMethod.POST method receiving the {@link DdiActionFeedback} from
* the target. * the target.
* *
* @param tenant
* of the client
* @param feedback * @param feedback
* the {@link DdiActionFeedback} from the target. * the {@link DdiActionFeedback} from the target.
* @param targetid * @param controllerId
* the ID of the calling target * the ID of the calling target
* @param actionId * @param actionId
* of the action we have feedback for * of the action we have feedback for
@@ -207,10 +225,11 @@ public interface DdiRootControllerRestApi {
* @return the {@link DdiActionFeedback} response * @return the {@link DdiActionFeedback} response
*/ */
@RequestMapping(value = "/{targetid}/" + DdiRestConstants.CANCEL_ACTION + "/{actionId}/" @RequestMapping(value = "/{controllerId}/" + DdiRestConstants.CANCEL_ACTION + "/{actionId}/"
+ DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) + DdiRestConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant, ResponseEntity<Void> postCancelActionFeedback(@Valid final DdiActionFeedback feedback,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") @NotEmpty final String targetid, @PathVariable("tenant") final String tenant,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId); @PathVariable("actionId") @NotEmpty final Long actionId);
} }

View File

@@ -12,7 +12,7 @@ import java.io.InputStream;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@@ -27,7 +27,9 @@ public interface DdiDlArtifactStoreControllerRestApi {
/** /**
* Handles GET download request. This could be full or partial download * Handles GET download request. This could be full or partial download
* request. * request.
* *
* @param tenant
* name of the client
* @param fileName * @param fileName
* to search for * to search for
* @param targetid * @param targetid
@@ -40,12 +42,14 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME @RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME
+ "/{fileName}") + "/{fileName}")
@ResponseBody @ResponseBody
public ResponseEntity<InputStream> downloadArtifactByFilename(@PathVariable("tenant") final String tenant, ResponseEntity<InputStream> downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName, @AuthenticationPrincipal final String targetid); @PathVariable("fileName") final String fileName, @AuthenticationPrincipal final String targetid);
/** /**
* Handles GET MD5 checksum file download request. * Handles GET MD5 checksum file download request.
* *
* @param tenant
* name of the client
* @param fileName * @param fileName
* to search for * to search for
* *
@@ -54,7 +58,7 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME + "/{fileName}" @RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME + "/{fileName}"
+ DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX) + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX)
@ResponseBody @ResponseBody
public ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant, ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
@PathVariable("fileName") final String fileName); @PathVariable("fileName") final String fileName);
} }

View File

@@ -12,15 +12,16 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol; import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlRestConstants; import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlRestConstants;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifact; import org.eclipse.hawkbit.ddi.json.model.DdiArtifact;
import org.eclipse.hawkbit.ddi.json.model.DdiArtifactHash; import org.eclipse.hawkbit.ddi.json.model.DdiArtifactHash;
@@ -29,6 +30,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiConfig;
import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase; import org.eclipse.hawkbit.ddi.json.model.DdiControllerBase;
import org.eclipse.hawkbit.ddi.json.model.DdiPolling; import org.eclipse.hawkbit.ddi.json.model.DdiPolling;
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -46,11 +48,11 @@ public final class DataConversionHelper {
} }
static List<DdiChunk> createChunks(final String targetid, final Action uAction, static List<DdiChunk> createChunks(final Target target, final Action uAction,
final ArtifactUrlHandler artifactUrlHandler) { final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
return uAction.getDistributionSet().getModules().stream() return uAction.getDistributionSet().getModules().stream()
.map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(), .map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
module.getName(), createArtifacts(targetid, module, artifactUrlHandler))) module.getName(), createArtifacts(target, module, artifactUrlHandler, systemManagement)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -69,43 +71,42 @@ public final class DataConversionHelper {
/** /**
* Creates all (rest) artifacts for a given software module. * Creates all (rest) artifacts for a given software module.
* *
* @param targetid * @param target
* of the target * for create URLs for
* @param module * @param module
* the software module * the software module
* @param artifactUrlHandler
* for creating download URLs
* @param systemManagement
* for access to tenant meta data
* @return a list of artifacts or a empty list. Cannot be <null>. * @return a list of artifacts or a empty list. Cannot be <null>.
*/ */
public static List<DdiArtifact> createArtifacts(final String targetid, public static List<DdiArtifact> createArtifacts(final Target target,
final org.eclipse.hawkbit.repository.model.SoftwareModule module, final org.eclipse.hawkbit.repository.model.SoftwareModule module,
final ArtifactUrlHandler artifactUrlHandler) { final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
final List<DdiArtifact> files = new ArrayList<>();
module.getLocalArtifacts() return module.getLocalArtifacts().stream()
.forEach(artifact -> files.add(createArtifact(targetid, artifactUrlHandler, artifact))); .map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement))
return files; .collect(Collectors.toList());
} }
private static DdiArtifact createArtifact(final String targetid, final ArtifactUrlHandler artifactUrlHandler, private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact) { final LocalArtifact artifact, final SystemManagement systemManagement) {
final DdiArtifact file = new DdiArtifact(); final DdiArtifact file = new DdiArtifact();
file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash())); file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
file.setFilename(artifact.getFilename()); file.setFilename(artifact.getFilename());
file.setSize(artifact.getSize()); file.setSize(artifact.getSize());
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) { artifactUrlHandler
final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(), .getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP); systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
file.add(new Link(linkHttp).withRel("download-http")); new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(),
file.add(new Link(linkHttp + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http")); artifact.getSha1Hash())),
} ApiType.DDI)
.forEach(entry -> file.add(new Link(entry.getRef()).withRel(entry.getRel())));
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
final String linkHttps = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTPS);
file.add(new Link(linkHttps).withRel("download"));
file.add(new Link(linkHttps + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum"));
}
return file; return file;
} }
static DdiControllerBase fromTarget(final Target target, final Optional<Action> action, static DdiControllerBase fromTarget(final Target target, final Optional<Action> action,
@@ -133,8 +134,8 @@ public final class DataConversionHelper {
} }
if (target.getTargetInfo().isRequestControllerAttributes()) { if (target.getTargetInfo().isRequestControllerAttributes()) {
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()).putConfigData(null,
.putConfigData(tenantAware.getCurrentTenant(), null, target.getControllerId())) tenantAware.getCurrentTenant(), target.getControllerId()))
.withRel(DdiRestConstants.CONFIG_DATA_ACTION)); .withRel(DdiRestConstants.CONFIG_DATA_ACTION));
} }
return result; return result;

View File

@@ -35,7 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;

View File

@@ -33,6 +33,7 @@ import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -88,6 +89,9 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Autowired @Autowired
private TenantAware tenantAware; private TenantAware tenantAware;
@Autowired
private SystemManagement systemManagement;
@Autowired @Autowired
private ArtifactUrlHandler artifactUrlHandler; private ArtifactUrlHandler artifactUrlHandler;
@@ -99,9 +103,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override @Override
public ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts( public ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId) { @PathVariable("softwareModuleId") final Long softwareModuleId) {
LOG.debug("getSoftwareModulesArtifacts({})", targetid); LOG.debug("getSoftwareModulesArtifacts({})", controllerId);
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(softwareModuleId); final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -111,20 +118,21 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
return new ResponseEntity<>(DataConversionHelper.createArtifacts(targetid, softwareModule, artifactUrlHandler), return new ResponseEntity<>(
DataConversionHelper.createArtifacts(target, softwareModule, artifactUrlHandler, systemManagement),
HttpStatus.OK); HttpStatus.OK);
} }
@Override @Override
public ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant, public ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid) { @PathVariable("controllerId") final String controllerId) {
LOG.debug("getControllerBase({})", targetid); LOG.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid, IpUtil final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (target.getTargetInfo().getUpdateStatus() == TargetUpdateStatus.UNKNOWN) { if (target.getTargetInfo().getUpdateStatus() == TargetUpdateStatus.UNKNOWN) {
LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", targetid); LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", controllerId);
controllerManagement.updateTargetStatus(target.getTargetInfo(), TargetUpdateStatus.REGISTERED, controllerManagement.updateTargetStatus(target.getTargetInfo(), TargetUpdateStatus.REGISTERED,
System.currentTimeMillis(), IpUtil.getClientIpFromRequest( System.currentTimeMillis(), IpUtil.getClientIpFromRequest(
requestResponseContextHolder.getHttpServletRequest(), securityProperties)); requestResponseContextHolder.getHttpServletRequest(), securityProperties));
@@ -138,12 +146,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override @Override
public ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant, public ResponseEntity<InputStream> downloadArtifact(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName) { @PathVariable("fileName") final String fileName) {
ResponseEntity<InputStream> result; ResponseEntity<InputStream> result;
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -151,7 +159,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
LOG.warn("Softare module with id {} could not be found.", softwareModuleId); LOG.warn("Softare module with id {} could not be found.", softwareModuleId);
result = new ResponseEntity<>(HttpStatus.NOT_FOUND); result = new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else { } else {
// Exception squid:S3655 - Optional access is checked in checkModule
// subroutine
@SuppressWarnings("squid:S3655")
final LocalArtifact artifact = module.getLocalArtifactByFilename(fileName).get(); final LocalArtifact artifact = module.getLocalArtifactByFilename(fileName).get();
final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact); final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact);
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match"); final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match");
@@ -196,11 +209,14 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
@Override @Override
// Exception squid:S3655 - Optional access is checked in checkModule
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant, public ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid, @PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName) { @PathVariable("fileName") final String fileName) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -223,12 +239,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override @Override
public ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction( public ResponseEntity<DdiDeploymentBase> getControllerBasedeploymentAction(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") final Long actionId, @PathVariable("actionId") final Long actionId,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource) { @RequestParam(value = "c", required = false, defaultValue = "-1") final int resource) {
LOG.debug("getControllerBasedeploymentAction({},{})", targetid, resource); LOG.debug("getControllerBasedeploymentAction({},{})", controllerId, resource);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action action = findActionWithExceptionIfNotFound(actionId); final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -239,14 +255,15 @@ public class DdiRootController implements DdiRootControllerRestApi {
if (!action.isCancelingOrCanceled()) { if (!action.isCancelingOrCanceled()) {
final List<DdiChunk> chunks = DataConversionHelper.createChunks(targetid, action, artifactUrlHandler); final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
systemManagement);
final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT; final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()), final DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()),
new DdiDeployment(handlingType, handlingType, chunks)); new DdiDeployment(handlingType, handlingType, chunks));
LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", targetid, base); LOG.debug("Found an active UpdateAction for target {}. returning deyploment: {}", controllerId, base);
controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX
+ "Target retrieved update action and should start now the download."); + "Target retrieved update action and should start now the download.");
@@ -258,12 +275,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
@Override @Override
public ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant, public ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid, @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) { @PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", targetid, actionId, feedback); LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", controllerId, actionId, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (!actionId.equals(feedback.getId())) { if (!actionId.equals(feedback.getId())) {
@@ -285,13 +302,14 @@ public class DdiRootController implements DdiRootControllerRestApi {
return new ResponseEntity<>(HttpStatus.GONE); return new ResponseEntity<>(HttpStatus.GONE);
} }
controllerManagement.addUpdateActionStatus(generateUpdateStatus(feedback, targetid, feedback.getId(), action)); controllerManagement
.addUpdateActionStatus(generateUpdateStatus(feedback, controllerId, feedback.getId(), action));
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
private ActionStatus generateUpdateStatus(final DdiActionFeedback feedback, final String targetid, private ActionStatus generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final Action action) { final Long actionid, final Action action) {
final ActionStatus actionStatus = entityFactory.generateActionStatus(); final ActionStatus actionStatus = entityFactory.generateActionStatus();
@@ -300,22 +318,22 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) { switch (feedback.getStatus().getExecution()) {
case CANCELED: case CANCELED:
LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.debug("Controller confirmed cancel (actionid: {}, controllerId: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution()); controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.CANCELED); actionStatus.setStatus(Status.CANCELED);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed cancelation."); actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed cancelation.");
break; break;
case REJECTED: case REJECTED:
LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.info("Controller reported internal error (actionid: {}, controllerId: {}) as we got {} report.",
targetid, feedback.getStatus().getExecution()); actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING); actionStatus.setStatus(Status.WARNING);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target REJECTED update."); actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target REJECTED update.");
break; break;
case CLOSED: case CLOSED:
handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus); handleClosedUpdateStatus(feedback, controllerId, actionid, actionStatus);
break; break;
default: default:
handleDefaultUpdateStatus(feedback, targetid, actionid, actionStatus); handleDefaultUpdateStatus(feedback, controllerId, actionid, actionStatus);
break; break;
} }
@@ -331,19 +349,19 @@ public class DdiRootController implements DdiRootControllerRestApi {
return actionStatus; return actionStatus;
} }
private static void handleDefaultUpdateStatus(final DdiActionFeedback feedback, final String targetid, private static void handleDefaultUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final ActionStatus actionStatus) { final Long actionid, final ActionStatus actionStatus) {
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid, LOG.debug("Controller reported intermediate status (actionid: {}, controllerId: {}) as we got {} report.",
targetid, feedback.getStatus().getExecution()); actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING); actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage( actionStatus.addMessage(
RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported " + feedback.getStatus().getExecution()); RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported " + feedback.getStatus().getExecution());
} }
private static void handleClosedUpdateStatus(final DdiActionFeedback feedback, final String targetid, private static void handleClosedUpdateStatus(final DdiActionFeedback feedback, final String controllerId,
final Long actionid, final ActionStatus actionStatus) { final Long actionid, final ActionStatus actionStatus) {
LOG.debug("Controller reported closed (actionid: {}, targetid: {}) as we got {} report.", actionid, targetid, LOG.debug("Controller reported closed (actionid: {}, controllerId: {}) as we got {} report.", actionid,
feedback.getStatus().getExecution()); controllerId, feedback.getStatus().getExecution());
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) { if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR); actionStatus.setStatus(Status.ERROR);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with ERROR!"); actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with ERROR!");
@@ -354,23 +372,23 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
@Override @Override
public ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant, public ResponseEntity<Void> putConfigData(@Valid @RequestBody final DdiConfigData configData,
@Valid @RequestBody final DdiConfigData configData, @PathVariable("targetid") final String targetid) { @PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
controllerManagement.updateControllerAttributes(targetid, configData.getData()); controllerManagement.updateControllerAttributes(controllerId, configData.getData());
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
@Override @Override
public ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant, public ResponseEntity<DdiCancel> getControllerCancelAction(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid, @PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) { @PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("getControllerCancelAction({})", targetid); LOG.debug("getControllerCancelAction({})", controllerId);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action action = findActionWithExceptionIfNotFound(actionId); final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -383,7 +401,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
final DdiCancel cancel = new DdiCancel(String.valueOf(action.getId()), final DdiCancel cancel = new DdiCancel(String.valueOf(action.getId()),
new DdiCancelActionToStop(String.valueOf(action.getId()))); new DdiCancelActionToStop(String.valueOf(action.getId())));
LOG.debug("Found an active CancelAction for target {}. returning cancel: {}", targetid, cancel); LOG.debug("Found an active CancelAction for target {}. returning cancel: {}", controllerId, cancel);
controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX controllerManagement.registerRetrieved(action, RepositoryConstants.SERVER_MESSAGE_PREFIX
+ "Target retrieved cancel action and should start now the cancelation."); + "Target retrieved cancel action and should start now the cancelation.");
@@ -395,13 +413,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
@Override @Override
public ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant, public ResponseEntity<Void> postCancelActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("tenant") final String tenant,
@PathVariable("targetid") @NotEmpty final String targetid, @PathVariable("controllerId") @NotEmpty final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId) { @PathVariable("actionId") @NotEmpty final Long actionId) {
LOG.debug("provideCancelActionFeedback for target [{}]: {}", targetid, feedback); LOG.debug("provideCancelActionFeedback for target [{}]: {}", controllerId, feedback);
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
if (!actionId.equals(feedback.getId())) { if (!actionId.equals(feedback.getId())) {
@@ -432,13 +450,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) { switch (feedback.getStatus().getExecution()) {
case CANCELED: case CANCELED:
LOG.error( LOG.error(
"Controller reported cancel for a cancel which is not supported by the server (actionid: {}, targetid: {}) as we got {} report.", "Controller reported cancel for a cancel which is not supported by the server (actionid: {}, controllerId: {}) as we got {} report.",
actionid, target.getControllerId(), feedback.getStatus().getExecution()); actionid, target.getControllerId(), feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING); actionStatus.setStatus(Status.WARNING);
break; break;
case REJECTED: case REJECTED:
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, targetid: {}).", actionid, LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, controllerId: {}).",
target.getControllerId()); actionid, target.getControllerId());
actionStatus.setStatus(Status.WARNING); actionStatus.setStatus(Status.WARNING);
break; break;
case CLOSED: case CLOSED:

View File

@@ -61,8 +61,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("Deployment Action Resource") @Stories("Deployment Action Resource")
public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoDB { public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoDB {
private static final String HTTP_LOCALHOST = "http://localhost/"; private static final String HTTP_LOCALHOST = "http://localhost:8080/";
private static final String HTTPS_LOCALHOST = "https://localhost/";
@Test() @Test()
@Description("Ensures that artifacts are not found, when softare module does not exists.") @Description("Ensures that artifacts are not found, when softare module does not exists.")
@@ -174,25 +173,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash()))) contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1"))) + "/artifacts/test1")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -203,27 +191,17 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
contains("test1.signature"))) contains("test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.md5", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.md5",
contains(artifactSignature.getMd5Hash()))) contains(artifactSignature.getMd5Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature"))) + "/artifacts/test1.signature")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -359,16 +337,18 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash()))) contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href", .andExpect(
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant() jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
+ "/controller/v1/4712/softwaremodules/" contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + "/controller/v1/4712/softwaremodules/"
+ "/artifacts/test1"))) + findDistributionSetByAction.findFirstModuleByType(osType).getId()
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href", + "/artifacts/test1")))
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant() .andExpect(
+ "/controller/v1/4712/softwaremodules/" jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/artifacts/test1.MD5SUM"))) + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024))) .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024)))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename",
contains("test1.signature"))) contains("test1.signature")))
@@ -377,24 +357,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash()))) contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature"))) + "/artifacts/test1.signature")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -486,31 +456,22 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].filename", contains("test1"))) .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].filename", contains("test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.md5", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.md5",
contains(artifact.getMd5Hash()))) contains(artifact.getMd5Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0].hashes.sha1",
contains(artifact.getSha1Hash())))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1"))) + "/artifacts/test1")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.MD5SUM"))) + "/artifacts/test1.MD5SUM")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024))) .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].size", contains(5 * 1024)))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename", .andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].filename",
contains("test1.signature"))) contains("test1.signature")))
@@ -519,25 +480,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash()))) contains(artifactSignature.getSha1Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTPS_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature.MD5SUM")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature"))) + "/artifacts/test1.signature")))
.andExpect( .andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum-http.href", jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.md5sum.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/" + "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId() + findDistributionSetByAction.findFirstModuleByType(osType).getId()

View File

@@ -0,0 +1,43 @@
#
# 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
#
spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value}
spring.data.mongodb.port=28017
hawkbit.server.ddi.security.authentication.header.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.name=TestToken
multipart.max-file-size=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
spring.jpa.database=H2
spring.datasource.url=jdbc:h2:mem:sp-db;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
flyway.enabled=true
flyway.sqlMigrationSuffix=${spring.jpa.database}.sql
#spring.jpa.show-sql=true
# DDI configuration
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
hawkbit.artifact.url.protocols[0].rel=download
hawkbit.artifact.url.protocols[0].protocol=http
hawkbit.artifact.url.protocols[0].supports=DMF,DDI
hawkbit.artifact.url.protocols[0].ref={protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.protocols[1].rel=md5sum
hawkbit.artifact.url.protocols[1].protocol=${hawkbit.artifact.url.protocols[0].protocol}
hawkbit.artifact.url.protocols[1].supports=${hawkbit.artifact.url.protocols[0].supports}
hawkbit.artifact.url.protocols[1].ref=${hawkbit.artifact.url.protocols[0].ref}.MD5SUM

View File

@@ -0,0 +1,236 @@
/**
* 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 java.net.URISyntaxException;
import java.util.UUID;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
* {@link AmqpMessageHandlerService} handles all incoming target authentication
* AMQP messages that can be used by 3rd party CDN services to check if a target
* is permitted to download certain artifact. This is handled by the queue that
* is configured for the property
* hawkbit.dmf.rabbitmq.authenticationReceiverQueue.
*
*/
public class AmqpAuthenticationMessageHandler extends BaseAmqpService {
private static final Logger LOG = LoggerFactory.getLogger(AmqpAuthenticationMessageHandler.class);
private final AmqpControllerAuthentication authenticationManager;
private final ArtifactManagement artifactManagement;
private final Cache cache;
private final HostnameResolver hostnameResolver;
private final ControllerManagement controllerManagement;
/**
* @param rabbitTemplate
* the configured amqp template.
* @param artifactManagement
* for artifact URI generation
* @param cache
* for download Ids
* @param hostnameResolver
* for resolving the host for downloads
* @param authenticationManager
* for target authentication
* @param controllerManagement
* for target repo access
*/
public AmqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
final Cache cache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement) {
super(rabbitTemplate);
this.authenticationManager = authenticationManager;
this.artifactManagement = artifactManagement;
this.cache = cache;
this.hostnameResolver = hostnameResolver;
this.controllerManagement = controllerManagement;
}
/**
* Executed on a authentication request.
*
* @param message
* the amqp message
* @return the rpc message back to supplier.
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory")
public Message onAuthenticationRequest(final Message message) {
checkContentTypeJson(message);
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
return handleAuthenticationMessage(message);
} catch (final RuntimeException ex) {
throw new AmqpRejectAndDontRequeueException(ex);
} finally {
SecurityContextHolder.setContext(oldContext);
}
}
/**
* check action for this download purposes, the method will throw an
* EntityNotFoundException in case the controller is not allowed to download
* this file because it's not assigned to an action and not assigned to this
* controller. Otherwise no controllerId is set = anonymous download
*
* @param secruityToken
* the security token which holds the target ID to check on
* @param localArtifact
* the local artifact to verify if the given target is allowed to
* download this artifact
*/
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken,
final LocalArtifact localArtifact) {
if (secruityToken.getControllerId() != null) {
checkByControllerId(localArtifact, secruityToken.getControllerId());
} else if (secruityToken.getTargetId() != null) {
checkByTargetId(localArtifact, secruityToken.getTargetId());
} else {
LOG.info("anonymous download no authentication check for artifact {}", localArtifact);
return;
}
}
private void checkByTargetId(final LocalArtifact localArtifact, final Long targetId) {
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}", targetId,
localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(targetId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", targetId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", targetId, localArtifact);
}
private void checkByControllerId(final LocalArtifact localArtifact, final String controllerId) {
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", controllerId, localArtifact);
}
private LocalArtifact findLocalArtifactByFileResource(final FileResource fileResource) {
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstLocalArtifactsBySHA1(fileResource.getSha1());
} else if (fileResource.getFilename() != null) {
return artifactManagement.findLocalArtifactByFilename(fileResource.getFilename()).stream().findFirst()
.orElse(null);
} else if (fileResource.getArtifactId() != null) {
return artifactManagement.findLocalArtifact(fileResource.getArtifactId());
} else if (fileResource.getSoftwareModuleFilenameResource() != null) {
return artifactManagement
.findByFilenameAndSoftwareModule(fileResource.getSoftwareModuleFilenameResource().getFilename(),
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId())
.stream().findFirst().orElse(null);
}
return null;
}
private static Artifact convertDbArtifact(final DbArtifact dbArtifact) {
final Artifact artifact = new Artifact();
artifact.setSize(dbArtifact.getSize());
final DbArtifactHash dbArtifactHash = dbArtifact.getHashes();
artifact.setHashes(new ArtifactHash(dbArtifactHash.getSha1(), dbArtifactHash.getMd5()));
return artifact;
}
private Message handleAuthenticationMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class);
final FileResource fileResource = secruityToken.getFileResource();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = findLocalArtifactByFileResource(fileResource);
if (localArtifact == null) {
LOG.info("target {} requested file resource {} which does not exists to download",
secruityToken.getControllerId(), fileResource);
throw new EntityNotFoundException();
}
checkIfArtifactIsAssignedToTarget(secruityToken, localArtifact);
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
throw new EntityNotFoundException();
}
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1,
localArtifact.getSha1Hash());
cache.put(downloadId, downloadCache);
authentificationResponse
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
authentificationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
LOG.error("Login failed", e);
authentificationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
authentificationResponse.setMessage("Login failed");
} catch (final URISyntaxException e) {
LOG.error("URI build exception", e);
authentificationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
authentificationResponse.setMessage("Building download URI failed");
} catch (final EntityNotFoundException e) {
final String errorMessage = "Artifact for resource " + fileResource + "not found ";
LOG.warn(errorMessage, e);
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authentificationResponse.setMessage(errorMessage);
}
return getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
}

View File

@@ -9,14 +9,18 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -41,14 +45,17 @@ import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.ErrorHandler; import org.springframework.util.ErrorHandler;
import com.google.common.collect.Maps;
/** /**
* Spring configuration for AMQP 0.9 based DMF communication for indirect device * Spring configuration for AMQP based DMF communication for indirect device
* integration. * integration.
* *
*/ */
@@ -251,17 +258,51 @@ public class AmqpConfiguration {
} }
/** /**
* Create amqp handler service bean. * Create AMQP handler service bean.
* *
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService * @param amqpMessageDispatcherService
* to sending events to DMF client * to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
* *
* @return handler service bean * @return handler service bean
*/ */
@Bean @Bean
public AmqpMessageHandlerService amqpMessageHandlerService( public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService) { final AmqpMessageDispatcherService amqpMessageDispatcherService,
return new AmqpMessageHandlerService(rabbitTemplate(), amqpMessageDispatcherService); final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
entityFactory);
}
/**
* Create AMQP handler service bean for authentication messages.
*
* @param rabbitTemplate
* for converting messages
* @param authenticationManager
* for target authentication
* @param artifactManagement
* for artifact URI generation
* @param cache
* for download IDs
* @param hostnameResolver
* for resolving the host for downloads
* @param controllerManagement
* for target repo access
* @return handler service bean
*/
@Bean
public AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandler(final RabbitTemplate rabbitTemplate,
final AmqpControllerAuthentication authenticationManager, final ArtifactManagement artifactManagement,
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE) final Cache cache, final HostnameResolver hostnameResolver,
final ControllerManagement controllerManagement) {
return new AmqpAuthenticationMessageHandler(rabbitTemplate, authenticationManager, artifactManagement, cache,
hostnameResolver, controllerManagement);
} }
/** /**
@@ -291,22 +332,25 @@ public class AmqpConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(AmqpControllerAuthentication.class) @ConditionalOnMissingBean(AmqpControllerAuthentication.class)
public AmqpControllerAuthentication amqpControllerAuthentication(final ControllerManagement controllerManagement, public AmqpControllerAuthentication amqpControllerAuthentication(final SystemManagement systemManagement,
final ControllerManagement controllerManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) { final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
return new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement, tenantAware, return new AmqpControllerAuthentication(systemManagement, controllerManagement, tenantConfigurationManagement,
ddiSecruityProperties, systemSecurityContext); tenantAware, ddiSecruityProperties, systemSecurityContext);
} }
@Bean @Bean
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class) @ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
public AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, public AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler) { final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler); final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemSecurityContext, systemManagement);
} }
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() { private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
final Map<String, Object> args = new HashMap<>(); final Map<String, Object> args = Maps.newHashMapWithExpectedSize(2);
args.put("x-message-ttl", Duration.ofSeconds(30).toMillis()); args.put("x-message-ttl", Duration.ofSeconds(30).toMillis());
args.put("x-max-length", 1_000); args.put("x-max-length", 1_000);
return args; return args;

View File

@@ -8,7 +8,6 @@
*/ */
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@@ -16,6 +15,7 @@ import javax.annotation.PostConstruct;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken; import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter;
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousDownload; import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousDownload;
@@ -32,6 +32,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import com.google.common.collect.Lists;
/** /**
* *
* A controller which handles the DMF AMQP authentication. * A controller which handles the DMF AMQP authentication.
@@ -42,10 +44,12 @@ public class AmqpControllerAuthentication {
private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider(); private final PreAuthTokenSourceTrustAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthTokenSourceTrustAuthenticationProvider();
private final List<PreAuthentificationFilter> filterChain = new ArrayList<>(); private List<PreAuthentificationFilter> filterChain;
private final ControllerManagement controllerManagement; private final ControllerManagement controllerManagement;
private final SystemManagement systemManagement;
private final TenantConfigurationManagement tenantConfigurationManagement; private final TenantConfigurationManagement tenantConfigurationManagement;
private final TenantAware tenantAware; private final TenantAware tenantAware;
@@ -57,6 +61,7 @@ public class AmqpControllerAuthentication {
/** /**
* Constructor. * Constructor.
* *
* @param systemManagement
* @param controllerManagement * @param controllerManagement
* @param tenantConfigurationManagement * @param tenantConfigurationManagement
* @param tenantAware * @param tenantAware
@@ -66,10 +71,12 @@ public class AmqpControllerAuthentication {
* @param systemSecurityContext * @param systemSecurityContext
* security context * security context
*/ */
public AmqpControllerAuthentication(final ControllerManagement controllerManagement, public AmqpControllerAuthentication(final SystemManagement systemManagement,
final ControllerManagement controllerManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) { final DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
this.controllerManagement = controllerManagement; this.controllerManagement = controllerManagement;
this.systemManagement = systemManagement;
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
this.ddiSecruityProperties = ddiSecruityProperties; this.ddiSecruityProperties = ddiSecruityProperties;
@@ -85,6 +92,8 @@ public class AmqpControllerAuthentication {
} }
private void addFilter() { private void addFilter() {
filterChain = Lists.newArrayListWithExpectedSize(5);
final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter( final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter(
tenantConfigurationManagement, tenantAware, systemSecurityContext); tenantConfigurationManagement, tenantAware, systemSecurityContext);
filterChain.add(gatewaySecurityTokenFilter); filterChain.add(gatewaySecurityTokenFilter);
@@ -106,13 +115,15 @@ public class AmqpControllerAuthentication {
} }
/** /**
* Performs authentication with the secruity token. * Performs authentication with the security token.
* *
* @param secruityToken * @param secruityToken
* the authentication request object * the authentication request object
* @return the authentfication object * @return the authentication object
*/ */
public Authentication doAuthenticate(final TenantSecurityToken secruityToken) { public Authentication doAuthenticate(final TenantSecurityToken secruityToken) {
resolveTenant(secruityToken);
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null); PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null);
for (final PreAuthentificationFilter filter : filterChain) { for (final PreAuthentificationFilter filter : filterChain) {
final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, secruityToken); final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, secruityToken);
@@ -126,6 +137,14 @@ public class AmqpControllerAuthentication {
} }
private void resolveTenant(final TenantSecurityToken securityToken) {
if (securityToken.getTenant() == null) {
securityToken.setTenant(systemSecurityContext
.runAsSystem(() -> systemManagement.getTenantMetadata(securityToken.getTenantId()).getTenant()));
}
}
private static PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthentificationFilter filter, private static PreAuthenticatedAuthenticationToken createAuthentication(final PreAuthentificationFilter filter,
final TenantSecurityToken secruityToken) { final TenantSecurityToken secruityToken) {

View File

@@ -9,24 +9,26 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.Queue;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import com.google.common.collect.Maps;
/** /**
* Bean which holds the necessary properties for configuring the AMQP deadletter * Bean which holds the necessary properties for configuring the AMQP deadletter
* queue. * queue.
*/ */
@ConfigurationProperties("hawkbit.dmf.rabbitmq.deadLetter") @ConfigurationProperties("hawkbit.dmf.rabbitmq.deadLetter")
public class AmqpDeadletterProperties { public class AmqpDeadletterProperties {
private static final int THREE_WEEKS = 21;
/** /**
* Message time to live (ttl) for the deadletter queue. Default ttl is 3 * Message time to live (ttl) for the deadletter queue. Default ttl is 3
* weeks. * weeks.
*/ */
private long ttl = Duration.ofDays(21).toMillis(); private long ttl = Duration.ofDays(THREE_WEEKS).toMillis();
/** /**
* Return the deadletter arguments. * Return the deadletter arguments.
@@ -36,7 +38,7 @@ public class AmqpDeadletterProperties {
* @return map which holds the properties * @return map which holds the properties
*/ */
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) { public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
final Map<String, Object> args = new HashMap<>(); final Map<String, Object> args = Maps.newHashMapWithExpectedSize(1);
args.put("x-dead-letter-exchange", exchange); args.put("x-dead-letter-exchange", exchange);
return args; return args;
} }
@@ -53,7 +55,7 @@ public class AmqpDeadletterProperties {
} }
private Map<String, Object> getTTLArgs() { private Map<String, Object> getTTLArgs() {
final Map<String, Object> args = new HashMap<>(); final Map<String, Object> args = Maps.newHashMapWithExpectedSize(1);
args.put("x-message-ttl", getTtl()); args.put("x-message-ttl", getTtl());
return args; return args;
} }

View File

@@ -14,8 +14,10 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol; import org.eclipse.hawkbit.api.URLPlaceholder;
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
@@ -25,8 +27,11 @@ import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule; import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.eventbus.EventSubscriber; import org.eclipse.hawkbit.eventbus.EventSubscriber;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.core.MessageProperties;
@@ -47,6 +52,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private final ArtifactUrlHandler artifactUrlHandler; private final ArtifactUrlHandler artifactUrlHandler;
private final AmqpSenderService amqpSenderService; private final AmqpSenderService amqpSenderService;
private final SystemSecurityContext systemSecurityContext;
private final SystemManagement systemManagement;
/** /**
* Constructor. * Constructor.
@@ -57,12 +64,19 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* to send AMQP message * to send AMQP message
* @param artifactUrlHandler * @param artifactUrlHandler
* for generating download URLs * for generating download URLs
* @param systemSecurityContext
* for execution with system permissions
* @param systemManagement
* to access to tenant metadata
*/ */
public AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpSenderService amqpSenderService, public AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate, final AmqpSenderService amqpSenderService,
final ArtifactUrlHandler artifactUrlHandler) { final ArtifactUrlHandler artifactUrlHandler, final SystemSecurityContext systemSecurityContext,
final SystemManagement systemManagement) {
super(rabbitTemplate); super(rabbitTemplate);
this.artifactUrlHandler = artifactUrlHandler; this.artifactUrlHandler = artifactUrlHandler;
this.amqpSenderService = amqpSenderService; this.amqpSenderService = amqpSenderService;
this.systemSecurityContext = systemSecurityContext;
this.systemManagement = systemManagement;
} }
/** /**
@@ -74,20 +88,24 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
*/ */
@Subscribe @Subscribe
public void targetAssignDistributionSet(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) { public void targetAssignDistributionSet(final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent) {
final URI targetAdress = targetAssignDistributionSetEvent.getTargetAdress(); final URI targetAdress = targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress();
if (!IpUtil.isAmqpUri(targetAdress)) { if (!IpUtil.isAmqpUri(targetAdress)) {
return; return;
} }
final String controllerId = targetAssignDistributionSetEvent.getControllerId(); final String controllerId = targetAssignDistributionSetEvent.getTarget().getControllerId();
final Collection<org.eclipse.hawkbit.repository.model.SoftwareModule> modules = targetAssignDistributionSetEvent final Collection<org.eclipse.hawkbit.repository.model.SoftwareModule> modules = targetAssignDistributionSetEvent
.getSoftwareModules(); .getSoftwareModules();
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest(); final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId()); downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId());
downloadAndUpdateRequest.setTargetSecurityToken(targetAssignDistributionSetEvent.getTargetToken());
final String targetSecurityToken = systemSecurityContext
.runAsSystem(targetAssignDistributionSetEvent.getTarget()::getSecurityToken);
downloadAndUpdateRequest.setTargetSecurityToken(targetSecurityToken);
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) { for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule); final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(
targetAssignDistributionSetEvent.getTarget(), softwareModule);
downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule); downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule);
} }
@@ -133,51 +151,42 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return messageProperties; return messageProperties;
} }
private SoftwareModule convertToAmqpSoftwareModule(final String targetId, private SoftwareModule convertToAmqpSoftwareModule(final Target target,
final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule) { final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule) {
final SoftwareModule amqpSoftwareModule = new SoftwareModule(); final SoftwareModule amqpSoftwareModule = new SoftwareModule();
amqpSoftwareModule.setModuleId(softwareModule.getId()); amqpSoftwareModule.setModuleId(softwareModule.getId());
amqpSoftwareModule.setModuleType(softwareModule.getType().getKey()); amqpSoftwareModule.setModuleType(softwareModule.getType().getKey());
amqpSoftwareModule.setModuleVersion(softwareModule.getVersion()); amqpSoftwareModule.setModuleVersion(softwareModule.getVersion());
final List<Artifact> artifacts = convertArtifacts(targetId, softwareModule.getLocalArtifacts()); final List<Artifact> artifacts = convertArtifacts(target, softwareModule.getLocalArtifacts());
amqpSoftwareModule.setArtifacts(artifacts); amqpSoftwareModule.setArtifacts(artifacts);
return amqpSoftwareModule; return amqpSoftwareModule;
} }
private List<Artifact> convertArtifacts(final String targetId, final List<LocalArtifact> localArtifacts) { private List<Artifact> convertArtifacts(final Target target, final List<LocalArtifact> localArtifacts) {
if (localArtifacts.isEmpty()) { if (localArtifacts.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
return localArtifacts.stream().map(localArtifact -> convertArtifact(targetId, localArtifact)) return localArtifacts.stream().map(localArtifact -> convertArtifact(target, localArtifact))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) { private Artifact convertArtifact(final Target target, final LocalArtifact localArtifact) {
final Artifact artifact = new Artifact(); final Artifact artifact = new Artifact();
if (artifactUrlHandler.protocolSupported(UrlProtocol.COAP)) { artifact.setUrls(artifactUrlHandler
artifact.getUrls().put(Artifact.UrlProtocol.COAP, .getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(), systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP)); new SoftwareData(localArtifact.getSoftwareModule().getId(), localArtifact.getFilename(),
} localArtifact.getId(), localArtifact.getSha1Hash())),
ApiType.DMF)
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) { .stream().collect(Collectors.toMap(e -> e.getProtocol(), e -> e.getRef())));
artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS));
}
artifact.setFilename(localArtifact.getFilename()); artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash())); artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
artifact.setSize(localArtifact.getSize()); artifact.setSize(localArtifact.getSize());
return artifact; return artifact;
} }
} }

View File

@@ -9,7 +9,6 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -18,68 +17,45 @@ import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.DownloadArtifactCache;
import org.eclipse.hawkbit.cache.DownloadType;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus; import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.ArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.exception.TooManyStatusEntriesException; import org.eclipse.hawkbit.repository.exception.TooManyStatusEntriesException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.web.util.UriComponentsBuilder;
/** /**
* *
* {@link AmqpMessageHandlerService} handles all incoming AMQP messages for the * {@link AmqpMessageHandlerService} handles all incoming target interaction
* queue which is configure for the property hawkbit.dmf.rabbitmq.receiverQueue. * AMQP messages (e.g. create target, check for updates etc.) for the queue
* which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
* *
*/ */
public class AmqpMessageHandlerService extends BaseAmqpService { public class AmqpMessageHandlerService extends BaseAmqpService {
@@ -88,40 +64,29 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final AmqpMessageDispatcherService amqpMessageDispatcherService; private final AmqpMessageDispatcherService amqpMessageDispatcherService;
@Autowired private final ControllerManagement controllerManagement;
private ControllerManagement controllerManagement;
@Autowired private final EntityFactory entityFactory;
private AmqpControllerAuthentication authenticationManager;
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
@Qualifier(CacheConstants.DOWNLOAD_ID_CACHE)
private Cache cache;
@Autowired
private HostnameResolver hostnameResolver;
@Autowired
private EntityFactory entityFactory;
@Autowired
private SystemSecurityContext systemSecurityContext;
/** /**
* Constructor. * Constructor.
* *
* @param defaultTemplate * @param rabbitTemplate
* the configured amqp template. * for converting messages
* @param amqpMessageDispatcherService * @param amqpMessageDispatcherService
* to sending events to DMF client * to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
*/ */
public AmqpMessageHandlerService(final RabbitTemplate defaultTemplate, public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService) { final AmqpMessageDispatcherService amqpMessageDispatcherService,
super(defaultTemplate); final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
super(rabbitTemplate);
this.amqpMessageDispatcherService = amqpMessageDispatcherService; this.amqpMessageDispatcherService = amqpMessageDispatcherService;
this.controllerManagement = controllerManagement;
this.entityFactory = entityFactory;
} }
/** /**
@@ -142,28 +107,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost()); return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost());
} }
/**
* Executed on a authentication request.
*
* @param message
* the amqp message
* @return the rpc message back to supplier.
*/
@RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory")
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 | TooManyStatusEntriesException e) {
throw new AmqpRejectAndDontRequeueException(e);
} finally {
SecurityContextHolder.setContext(oldContext);
}
}
/** /**
* * Executed if a amqp message arrives. * * Executed if a amqp message arrives.
* *
@@ -206,108 +149,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return null; return null;
} }
private Message handleAuthentifiactionMessage(final Message message) {
final DownloadResponse authentificationResponse = new DownloadResponse();
final MessageProperties messageProperties = message.getMessageProperties();
final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class);
final FileResource fileResource = secruityToken.getFileResource();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = findLocalArtifactByFileResource(fileResource);
if (localArtifact == null) {
LOG.info("target {} requested file resource {} which does not exists to download",
secruityToken.getControllerId(), fileResource);
throw new EntityNotFoundException();
}
checkIfArtifactIsAssignedToTarget(secruityToken, localArtifact);
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
throw new EntityNotFoundException();
}
authentificationResponse.setArtifact(artifact);
final String downloadId = UUID.randomUUID().toString();
// SHA1 key is set, download by SHA1
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1,
localArtifact.getSha1Hash());
cache.put(downloadId, downloadCache);
authentificationResponse
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
authentificationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
LOG.error("Login failed", e);
authentificationResponse.setResponseCode(HttpStatus.FORBIDDEN.value());
authentificationResponse.setMessage("Login failed");
} catch (final URISyntaxException e) {
LOG.error("URI build exception", e);
authentificationResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
authentificationResponse.setMessage("Building download URI failed");
} catch (final EntityNotFoundException e) {
final String errorMessage = "Artifact for resource " + fileResource + "not found ";
LOG.warn(errorMessage, e);
authentificationResponse.setResponseCode(HttpStatus.NOT_FOUND.value());
authentificationResponse.setMessage(errorMessage);
}
return getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
/**
* check action for this download purposes, the method will throw an
* EntityNotFoundException in case the controller is not allowed to download
* this file because it's not assigned to an action and not assigned to this
* controller. Otherwise no controllerId is set = anonymous download
*
* @param secruityToken
* the security token which holds the target ID to check on
* @param localArtifact
* the local artifact to verify if the given target is allowed to
* download this artifact
*/
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken,
final LocalArtifact localArtifact) {
final String controllerId = secruityToken.getControllerId();
if (controllerId == null) {
LOG.info("anonymous download no authentication check for artifact {}", localArtifact);
return;
}
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", controllerId, localArtifact);
}
private LocalArtifact findLocalArtifactByFileResource(final FileResource fileResource) {
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstLocalArtifactsBySHA1(fileResource.getSha1());
} else if (fileResource.getFilename() != null) {
return artifactManagement.findLocalArtifactByFilename(fileResource.getFilename()).stream().findFirst()
.orElse(null);
} else if (fileResource.getSoftwareModuleFilenameResource() != null) {
return artifactManagement
.findByFilenameAndSoftwareModule(fileResource.getSoftwareModuleFilenameResource().getFilename(),
fileResource.getSoftwareModuleFilenameResource().getSoftwareModuleId())
.stream().findFirst().orElse(null);
}
return null;
}
private static Artifact convertDbArtifact(final DbArtifact dbArtifact) {
final Artifact artifact = new Artifact();
artifact.setSize(dbArtifact.getSize());
final DbArtifactHash dbArtifactHash = dbArtifact.getHashes();
artifact.setHashes(new ArtifactHash(dbArtifactHash.getSha1(), dbArtifactHash.getMd5()));
return artifact;
}
private static void setSecurityContext(final Authentication authentication) { private static void setSecurityContext(final Authentication authentication) {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(authentication); securityContextImpl.setAuthentication(authentication);
@@ -361,10 +202,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DistributionSet distributionSet = action.get().getDistributionSet(); final DistributionSet distributionSet = action.get().getDistributionSet();
final List<SoftwareModule> softwareModuleList = controllerManagement final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet); .findSoftwareModulesByDistributionSet(distributionSet);
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
amqpMessageDispatcherService.targetAssignDistributionSet(new TargetAssignDistributionSetEvent( amqpMessageDispatcherService.targetAssignDistributionSet(new TargetAssignDistributionSetEvent(
target.getOptLockRevision(), target.getTenant(), target.getControllerId(), action.get().getId(), target.getOptLockRevision(), target.getTenant(), target, action.get().getId(), softwareModuleList));
softwareModuleList, target.getTargetInfo().getAddress(), targetSecurityToken));
} }
@@ -481,7 +320,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return action; return action;
} }
private void handleCancelRejected(final Message message, final Action action, final ActionStatus actionStatus) { private static void handleCancelRejected(final Message message, final Action action,
final ActionStatus actionStatus) {
if (action.isCancelingOrCanceled()) { if (action.isCancelingOrCanceled()) {
actionStatus.setStatus(Status.WARNING); actionStatus.setStatus(Status.WARNING);
@@ -495,39 +335,4 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
} }
private static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
}
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
}
void setControllerManagement(final ControllerManagement controllerManagement) {
this.controllerManagement = controllerManagement;
}
void setHostnameResolver(final HostnameResolver hostnameResolver) {
this.hostnameResolver = hostnameResolver;
}
void setAuthenticationManager(final AmqpControllerAuthentication authenticationManager) {
this.authenticationManager = authenticationManager;
}
void setArtifactManagement(final ArtifactManagement artifactManagement) {
this.artifactManagement = artifactManagement;
}
void setCache(final Cache cache) {
this.cache = cache;
}
void setEntityFactory(final EntityFactory entityFactory) {
this.entityFactory = entityFactory;
}
void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) {
this.systemSecurityContext = systemSecurityContext;
}
} }

View File

@@ -20,6 +20,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("hawkbit.dmf.rabbitmq") @ConfigurationProperties("hawkbit.dmf.rabbitmq")
public class AmqpProperties { public class AmqpProperties {
private static final int ONE_MINUTE = 60;
private static final int DEFAULT_QUEUE_DECLARATION_RETRIES = 50;
private static final int DEFAULT_INITIAL_CONSUMERS = 3;
private static final int DEFAULT_PREFETCH_COUNT = 10;
private static final int DEFAULT_MAX_CONSUMERS = 10;
/** /**
* Enable DMF API based on AMQP 0.9 * Enable DMF API based on AMQP 0.9
*/ */
@@ -54,24 +64,24 @@ public class AmqpProperties {
/** /**
* Requested heartbeat interval from broker in {@link TimeUnit#SECONDS}. * Requested heartbeat interval from broker in {@link TimeUnit#SECONDS}.
*/ */
private int requestedHeartBeat = (int) TimeUnit.SECONDS.toSeconds(60); private int requestedHeartBeat = (int) TimeUnit.SECONDS.toSeconds(ONE_MINUTE);
/** /**
* Sets an upper limit to the number of consumers. * Sets an upper limit to the number of consumers.
*/ */
private int maxConcurrentConsumers = 10; private int maxConcurrentConsumers = DEFAULT_MAX_CONSUMERS;
/** /**
* Tells the broker how many messages to send to each consumer in a single * Tells the broker how many messages to send to each consumer in a single
* request. Often this can be set quite high to improve throughput. * request. Often this can be set quite high to improve throughput.
*/ */
private int prefetchCount = 10; private int prefetchCount = DEFAULT_PREFETCH_COUNT;
/** /**
* Initial number of consumers. Is scaled up if necessary up to * Initial number of consumers. Is scaled up if necessary up to
* {@link #maxConcurrentConsumers}. * {@link #maxConcurrentConsumers}.
*/ */
private int initialConcurrentConsumers = 3; private int initialConcurrentConsumers = DEFAULT_INITIAL_CONSUMERS;
/** /**
* The number of retry attempts when passive queue declaration fails. * The number of retry attempts when passive queue declaration fails.
@@ -79,7 +89,7 @@ public class AmqpProperties {
* consuming from multiple queues, when not all queues were available during * consuming from multiple queues, when not all queues were available during
* initialization. * initialization.
*/ */
private int declarationRetries = 50; private int declarationRetries = DEFAULT_QUEUE_DECLARATION_RETRIES;
public int getDeclarationRetries() { public int getDeclarationRetries() {
return declarationRetries; return declarationRetries;

View File

@@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.MessageConverter;
@@ -39,6 +40,14 @@ public class BaseAmqpService {
this.rabbitTemplate = rabbitTemplate; this.rabbitTemplate = rabbitTemplate;
} }
protected static void checkContentTypeJson(final Message message) {
final MessageProperties messageProperties = message.getMessageProperties();
if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) {
return;
}
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
}
/** /**
* Is needed to convert a incoming message to is originally object type. * Is needed to convert a incoming message to is originally object type.
* *
@@ -98,7 +107,7 @@ public class BaseAmqpService {
return value.toString(); return value.toString();
} }
protected final void logAndThrowMessageError(final Message message, final String error) { protected static final void logAndThrowMessageError(final Message message, final String error) {
LOGGER.warn("Warning! \"{}\" reported by message: {}", error, message); LOGGER.warn("Warning! \"{}\" reported by message: {}", error, message);
throw new AmqpRejectAndDontRequeueException(error); throw new AmqpRejectAndDontRequeueException(error);
} }

View File

@@ -16,6 +16,11 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.net.URL;
import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DownloadResponse; import org.eclipse.hawkbit.dmf.json.model.DownloadResponse;
@@ -23,8 +28,16 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource; import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.JpaEntityFactory;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymous; import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymous;
import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp; import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp;
@@ -33,11 +46,15 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -54,15 +71,44 @@ import ru.yandex.qatools.allure.annotations.Stories;
*/ */
@Features("Component Tests - Device Management Federation API") @Features("Component Tests - Device Management Federation API")
@Stories("AmqpController Authentication Test") @Stories("AmqpController Authentication Test")
@RunWith(MockitoJUnitRunner.class)
public class AmqpControllerAuthenticationTest { public class AmqpControllerAuthenticationTest {
private static final String SHA1 = "12345";
private static final Long ARTIFACT_ID = 1123L;
private static final Long ARTIFACT_SIZE = 6666L;
private static final String TENANT = "DEFAULT"; private static final String TENANT = "DEFAULT";
private static String CONTROLLLER_ID = "123"; private static final Long TENANT_ID = 123L;
private static final String CONTROLLER_ID = "123";
private static final Long TARGET_ID = 123L;
private AmqpMessageHandlerService amqpMessageHandlerService; private AmqpMessageHandlerService amqpMessageHandlerService;
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter; private MessageConverter messageConverter;
private TenantConfigurationManagement tenantConfigurationManagement;
private AmqpControllerAuthentication authenticationManager; private AmqpControllerAuthentication authenticationManager;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private SystemManagement systemManagement;
@Mock
private Cache cacheMock;
@Mock
private HostnameResolver hostnameResolverMock;
@Mock
private ArtifactManagement artifactManagementMock;
@Mock
private ControllerManagement controllerManagementMock;
@Mock
private Target targteMock;
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
.<Boolean> builder().value(Boolean.FALSE).build(); .<Boolean> builder().value(Boolean.FALSE).build();
@@ -74,8 +120,6 @@ public class AmqpControllerAuthenticationTest {
messageConverter = new Jackson2JsonMessageConverter(); messageConverter = new Jackson2JsonMessageConverter();
final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class); final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class));
final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class); final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class);
final Rp rp = mock(Rp.class); final Rp rp = mock(Rp.class);
@@ -88,30 +132,57 @@ public class AmqpControllerAuthenticationTest {
when(ddiAuthentication.getAnonymous()).thenReturn(anonymous); when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
when(anonymous.isEnabled()).thenReturn(false); when(anonymous.isEnabled()).thenReturn(false);
tenantConfigurationManagement = mock(TenantConfigurationManagement.class); when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
when(tenantConfigurationManagement.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE); .thenReturn(CONFIG_VALUE_FALSE);
final ControllerManagement controllerManagement = mock(ControllerManagement.class); final ControllerManagement controllerManagement = mock(ControllerManagement.class);
when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID); when(controllerManagement.findByControllerId(anyString())).thenReturn(targteMock);
amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class)); when(controllerManagement.findByTargetId(any(Long.class))).thenReturn(targteMock);
when(targteMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targteMock.getControllerId()).thenReturn(CONTROLLER_ID);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement, final TenantMetaData tenantMetaData = mock(TenantMetaData.class);
tenantAware, secruityProperties, systemSecurityContext); when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
tenantConfigurationManagementMock, tenantAware, secruityProperties, systemSecurityContext);
authenticationManager.postConstruct(); authenticationManager.postConstruct();
amqpMessageHandlerService.setAuthenticationManager(authenticationManager);
final LocalArtifact testArtifact = new JpaLocalArtifact("afilename", "afilename", new JpaSoftwareModule(
new JpaSoftwareModuleType("a key", "a name", null, 1), "a name", null, null, null));
when(artifactManagementMock.findLocalArtifact(ARTIFACT_ID)).thenReturn(testArtifact);
when(artifactManagementMock.findFirstLocalArtifactsBySHA1(SHA1)).thenReturn(testArtifact);
final DbArtifact artifact = new DbArtifact();
artifact.setSize(ARTIFACT_SIZE);
artifact.setHashes(new DbArtifactHash("sha1 test", "md5 test"));
when(artifactManagementMock.loadLocalArtifactBinary(testArtifact)).thenReturn(artifact);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory());
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock,
controllerManagementMock);
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
when(controllerManagementMock.hasTargetArtifactAssigned(TARGET_ID, testArtifact)).thenReturn(true);
when(controllerManagementMock.hasTargetArtifactAssigned(CONTROLLER_ID, testArtifact)).thenReturn(true);
} }
@Test @Test
@Description("Tests authentication manager without principal") @Description("Tests authentication manager without principal")
public void testAuthenticationeBadCredantialsWithoutPricipal() { public void testAuthenticationeBadCredantialsWithoutPricipal() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
try { try {
authenticationManager.doAuthenticate(securityToken); authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted since principal was missing"); fail("BadCredentialsException was excepeted since principal was missing");
@@ -124,12 +195,12 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication manager without wrong credential") @Description("Tests authentication manager without wrong credential")
public void testAuthenticationBadCredantialsWithWrongCredential() { public void testAuthenticationBadCredantialsWithWrongCredential() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagement.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
try { try {
authenticationManager.doAuthenticate(securityToken); authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted due to wrong credential"); fail("BadCredentialsException was excepeted due to wrong credential");
@@ -142,12 +213,12 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication successfull") @Description("Tests authentication successfull")
public void testSuccessfullAuthentication() { public void testSuccessfullAuthentication() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagement.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Authentication authentication = authenticationManager.doAuthenticate(securityToken); final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
assertThat(authentication).isNotNull(); assertThat(authentication).isNotNull();
} }
@@ -157,13 +228,13 @@ public class AmqpControllerAuthenticationTest {
public void testAuthenticationMessageBadCredantialsWithoutPricipal() { public void testAuthenticationMessageBadCredantialsWithoutPricipal() {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -175,17 +246,17 @@ public class AmqpControllerAuthenticationTest {
@Description("Tests authentication message without wrong credential") @Description("Tests authentication message without wrong credential")
public void testAuthenticationMessageBadCredantialsWithWrongCredential() { public void testAuthenticationMessageBadCredantialsWithWrongCredential() {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagement.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -195,24 +266,81 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication message successfull") @Description("Tests authentication message successfull")
public void testSuccessfullMessageAuthentication() { public void successfullMessageAuthentication() {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID, final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagement.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
securityToken.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull(); assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
}
@Test
@Description("Tests authentication message successfull with targetId intead of controllerId provided and artifactId instead of SHA1.")
public void successfullMessageAuthenticationWithTargetIdAndArtifactId() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, null, TARGET_ID,
FileResource.createFileResourceByArtifactId(ARTIFACT_ID));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())
.isEqualTo(PreAuthenticatedAuthenticationToken.class.getName());
}
@Test
@Description("Tests authentication message successfull")
public void successfullMessageAuthenticationWithTenantid() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(null, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
assertThat(downloadResponse).isNotNull();
assertThat(downloadResponse.getDownloadUrl()).isNotNull();
assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value());
assertThat(SecurityContextHolder.getContext()).isNotNull(); assertThat(SecurityContextHolder.getContext()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull();
assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName()) assertThat(SecurityContextHolder.getContext().getAuthentication().getClass().getName())

View File

@@ -13,9 +13,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -24,6 +22,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
@@ -31,11 +30,14 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest; import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.junit.Test; import org.junit.Test;
@@ -49,6 +51,8 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories; import ru.yandex.qatools.allure.annotations.Stories;
@@ -60,6 +64,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private static final String TENANT = "default"; private static final String TENANT = "default";
private static final Long TENANT_ID = 4711L;
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest"); private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
@@ -71,31 +76,47 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private DefaultAmqpSenderService senderService; private DefaultAmqpSenderService senderService;
private SystemManagement systemManagement;
private static final String CONTROLLER_ID = "1"; private static final String CONTROLLER_ID = "1";
private Target testTarget;
@Override @Override
public void before() throws Exception { public void before() throws Exception {
super.before(); super.before();
testTarget = entityFactory.generateTarget(CONTROLLER_ID, TEST_TOKEN);
testTarget.getTargetInfo().setAddress(AMQP_URI.toString());
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class); this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter()); when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter());
senderService = Mockito.mock(DefaultAmqpSenderService.class); senderService = Mockito.mock(DefaultAmqpSenderService.class);
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class); final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
when(artifactUrlHandlerMock.getUrl(anyString(), anyLong(), anyString(), anyString(), anyObject())) when(artifactUrlHandlerMock.getUrls(anyObject(), anyObject()))
.thenReturn("http://mockurl"); .thenReturn(Lists.newArrayList(new ArtifactUrl("http", "download", "http://mockurl")));
systemManagement = Mockito.mock(SystemManagement.class);
final TenantMetaData tenantMetaData = Mockito.mock(TenantMetaData.class);
when(tenantMetaData.getId()).thenReturn(TENANT_ID);
when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata()).thenReturn(tenantMetaData);
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService, amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
artifactUrlHandlerMock); artifactUrlHandlerMock, systemSecurityContext, systemManagement);
} }
@Test @Test
@Description("Verfies that download and install event with no software modul works") @Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() { public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, new ArrayList<SoftwareModule>(), AMQP_URI, TEST_TOKEN); 1L, TENANT, testTarget, 1L, new ArrayList<SoftwareModule>());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN); assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
assertTrue("No softwaremmodule should be contained in the request", assertTrue("No softwaremmodule should be contained in the request",
@@ -107,9 +128,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() { public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() {
final DistributionSet dsA = testdataFactory.createDistributionSet(""); final DistributionSet dsA = testdataFactory.createDistributionSet("");
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN); 1L, TENANT, testTarget, 1L, dsA.getModules());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("Expecting a size of 3 software modules in the reuqest", 3, assertEquals("Expecting a size of 3 software modules in the reuqest", 3,
downloadAndUpdateRequest.getSoftwareModules().size()); downloadAndUpdateRequest.getSoftwareModules().size());
@@ -146,9 +168,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList); Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN); 1L, TENANT, testTarget, 1L, dsA.getModules());
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3, assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3,
downloadAndUpdateRequest.getSoftwareModules().size()); downloadAndUpdateRequest.getSoftwareModules().size());

View File

@@ -54,7 +54,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -81,8 +80,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageHandlerServiceTest { public class AmqpMessageHandlerServiceTest {
private static final String TENANT = "DEFAULT"; private static final String TENANT = "DEFAULT";
private static final Long TENANT_ID = 123L;
private static String CONTROLLLER_ID = "123";
private static final Long TARGET_ID = 123L;
private AmqpMessageHandlerService amqpMessageHandlerService; private AmqpMessageHandlerService amqpMessageHandlerService;
private AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter; private MessageConverter messageConverter;
@@ -113,22 +116,16 @@ public class AmqpMessageHandlerServiceTest {
@Mock @Mock
private RabbitTemplate rabbitTemplate; private RabbitTemplate rabbitTemplate;
@Mock
private SystemSecurityContext systemSecurityContextMock;
@Before @Before
public void before() throws Exception { public void before() throws Exception {
messageConverter = new Jackson2JsonMessageConverter(); messageConverter = new Jackson2JsonMessageConverter();
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock);
amqpMessageHandlerService.setControllerManagement(controllerManagementMock);
amqpMessageHandlerService.setAuthenticationManager(authenticationManagerMock);
amqpMessageHandlerService.setArtifactManagement(artifactManagementMock);
amqpMessageHandlerService.setCache(cacheMock);
amqpMessageHandlerService.setHostnameResolver(hostnameResolverMock);
amqpMessageHandlerService.setEntityFactory(entityFactoryMock);
amqpMessageHandlerService.setSystemSecurityContext(systemSecurityContextMock);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManagerMock, artifactManagementMock, cacheMock, hostnameResolverMock,
controllerManagementMock);
} }
@Test @Test
@@ -279,13 +276,13 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which does not exists") @Description("Tests that an download request is denied for an artifact which does not exists")
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() { public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -298,7 +295,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which is not assigned to the requested target") @Description("Tests that an download request is denied for an artifact which is not assigned to the requested target")
public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() { public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
@@ -309,7 +306,7 @@ public class AmqpMessageHandlerServiceTest {
.thenThrow(EntityNotFoundException.class); .thenThrow(EntityNotFoundException.class);
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -322,7 +319,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target") @Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target")
public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException { public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, "123", final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1("12345")); FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties); messageProperties);
@@ -340,7 +337,7 @@ public class AmqpMessageHandlerServiceTest {
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost")); when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
// test // test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message); final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify // verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -364,15 +361,12 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.addUpdateActionStatus(Matchers.any())).thenReturn(action); when(controllerManagementMock.addUpdateActionStatus(Matchers.any())).thenReturn(action);
when(entityFactoryMock.generateActionStatus()).thenReturn(new JpaActionStatus()); when(entityFactoryMock.generateActionStatus()).thenReturn(new JpaActionStatus());
// for the test the same action can be used // for the test the same action can be used
when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any())) when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any())).thenReturn(Optional.of(action));
.thenReturn(Optional.of(action));
final List<SoftwareModule> softwareModuleList = createSoftwareModuleList(); final List<SoftwareModule> softwareModuleList = createSoftwareModuleList();
when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any())) when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any()))
.thenReturn(softwareModuleList); .thenReturn(softwareModuleList);
when(systemSecurityContextMock.runAsSystem(anyObject())).thenReturn("securityToken");
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L); final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L);
@@ -393,10 +387,10 @@ public class AmqpMessageHandlerServiceTest {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent
.getValue(); .getValue();
assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id") assertThat(targetAssignDistributionSetEvent.getTarget().getControllerId()).as("event has wrong controller id")
.isEqualTo("target1"); .isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getTargetToken()).as("targetoken not filled correctly") assertThat(targetAssignDistributionSetEvent.getTarget().getSecurityToken())
.isEqualTo(action.getTarget().getSecurityToken()); .as("targetoken not filled correctly").isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L); assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules") assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules")
.isEqualTo(softwareModuleList); .isEqualTo(softwareModuleList);

View File

@@ -52,8 +52,8 @@ public class BaseAmqpServiceTest {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(1L); actionUpdateStatus.setActionId(1L);
actionUpdateStatus.setSoftwareModuleId(2L); actionUpdateStatus.setSoftwareModuleId(2L);
actionUpdateStatus.getMessage().add("Message 1"); actionUpdateStatus.addMessage("Message 1");
actionUpdateStatus.getMessage().add("Message 2"); actionUpdateStatus.addMessage("Message 2");
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus, final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
new MessageProperties()); new MessageProperties());

View File

@@ -1,95 +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.util;
import static org.junit.Assert.assertEquals;
import org.eclipse.hawkbit.AmqpTestConfiguration;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.UrlProtocol;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Tests for creating urls to download artifacts.
*/
@Features("Component Tests - Artifact URL Handler")
@Stories("Test to generate the artifact download URL")
@SpringApplicationConfiguration(classes = { AmqpTestConfiguration.class,
org.eclipse.hawkbit.RepositoryApplicationConfiguration.class })
public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTest {
private static final String HTTPS_LOCALHOST = "https://localhost/";
private static final String HTTP_LOCALHOST = "http://localhost/";
@Autowired
private ArtifactUrlHandler urlHandlerProperties;
private LocalArtifact localArtifact;
private static final String CONTROLLER_ID = "Test";
private String fileName;
private Long softwareModuleId;
private String sha1Hash;
@Before
public void setup() {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final SoftwareModule module = dsA.getModules().iterator().next();
localArtifact = testdataFactory.createLocalArtifacts(module.getId()).stream().findAny().get();
softwareModuleId = localArtifact.getSoftwareModule().getId();
fileName = localArtifact.getFilename();
sha1Hash = localArtifact.getSha1Hash();
}
@Test
@Description("Tests the generation of http download url.")
public void testHttpUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.HTTP);
assertEquals("http is build incorrect",
HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + CONTROLLER_ID
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(),
url);
}
@Test
@Description("Tests the generation of https download url.")
public void testHttpsUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.HTTPS);
assertEquals("https is build incorrect",
HTTPS_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + CONTROLLER_ID
+ "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/"
+ localArtifact.getFilename(),
url);
}
@Test
@Description("Tests the generation of coap download url.")
public void testCoapUrl() {
final String url = urlHandlerProperties.getUrl(CONTROLLER_ID, softwareModuleId, fileName, sha1Hash,
UrlProtocol.COAP);
assertEquals("coap is build incorrect", "coap://127.0.0.1:5683/fw/" + tenantAware.getCurrentTenant() + "/"
+ CONTROLLER_ID + "/sha1/" + localArtifact.getSha1Hash(), url);
}
}

View File

@@ -9,6 +9,8 @@
package org.eclipse.hawkbit.dmf.json.model; package org.eclipse.hawkbit.dmf.json.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -18,9 +20,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* JSON representation of action update status. * JSON representation of action update status.
*
*
*
*/ */
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@@ -32,7 +31,7 @@ public class ActionUpdateStatus {
@JsonProperty(required = true) @JsonProperty(required = true)
private ActionStatus actionStatus; private ActionStatus actionStatus;
@JsonProperty @JsonProperty
private final List<String> message = new ArrayList<>(); private List<String> message;
public Long getActionId() { public Long getActionId() {
return actionId; return actionId;
@@ -59,7 +58,32 @@ public class ActionUpdateStatus {
} }
public List<String> getMessage() { public List<String> getMessage() {
return message; if (message == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(message);
}
public boolean addMessage(final String message) {
if (this.message == null) {
this.message = new ArrayList<>();
}
return this.message.add(message);
}
public boolean addMessage(final Collection<String> messages) {
if (messages == null || messages.isEmpty()) {
return false;
}
if (this.message == null) {
this.message = new ArrayList<>(messages);
return true;
}
return this.message.addAll(messages);
} }
} }

View File

@@ -8,7 +8,7 @@
*/ */
package org.eclipse.hawkbit.dmf.json.model; package org.eclipse.hawkbit.dmf.json.model;
import java.util.EnumMap; import java.util.Collections;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -25,15 +25,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class Artifact { public class Artifact {
/**
* Represented the supported protocols for artifact url's.
*
*/
public enum UrlProtocol {
COAP, HTTP, HTTPS
}
@JsonProperty @JsonProperty
private String filename; private String filename;
@@ -44,13 +35,17 @@ public class Artifact {
private Long size; private Long size;
@JsonProperty @JsonProperty
private Map<UrlProtocol, String> urls = new EnumMap<>(UrlProtocol.class); private Map<String, String> urls;
public Map<UrlProtocol, String> getUrls() { public Map<String, String> getUrls() {
return urls; if (urls == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(urls);
} }
public void setUrls(final Map<UrlProtocol, String> urls) { public void setUrls(final Map<String, String> urls) {
this.urls = urls; this.urls = urls;
} }

View File

@@ -8,7 +8,8 @@
*/ */
package org.eclipse.hawkbit.dmf.json.model; package org.eclipse.hawkbit.dmf.json.model;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -30,7 +31,7 @@ public class DownloadAndUpdateRequest {
private String targetSecurityToken; private String targetSecurityToken;
@JsonProperty @JsonProperty
private final List<SoftwareModule> softwareModules = new LinkedList<>(); private List<SoftwareModule> softwareModules;
public Long getActionId() { public Long getActionId() {
return actionId; return actionId;
@@ -49,7 +50,11 @@ public class DownloadAndUpdateRequest {
} }
public List<SoftwareModule> getSoftwareModules() { public List<SoftwareModule> getSoftwareModules() {
return softwareModules; if (softwareModules == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(softwareModules);
} }
/** /**
@@ -59,6 +64,10 @@ public class DownloadAndUpdateRequest {
* the module * the module
*/ */
public void addSoftwareModule(final SoftwareModule createSoftwareModule) { public void addSoftwareModule(final SoftwareModule createSoftwareModule) {
if (softwareModules == null) {
softwareModules = new ArrayList<>();
}
softwareModules.add(createSoftwareModule); softwareModules.add(createSoftwareModule);
} }

View File

@@ -8,7 +8,7 @@
*/ */
package org.eclipse.hawkbit.dmf.json.model; package org.eclipse.hawkbit.dmf.json.model;
import java.util.LinkedList; import java.util.Collections;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -35,7 +35,7 @@ public class SoftwareModule {
@JsonProperty @JsonProperty
private String moduleVersion; private String moduleVersion;
@JsonProperty @JsonProperty
private List<Artifact> artifacts = new LinkedList<>(); private List<Artifact> artifacts;
public String getModuleType() { public String getModuleType() {
return moduleType; return moduleType;
@@ -54,7 +54,11 @@ public class SoftwareModule {
} }
public List<Artifact> getArtifacts() { public List<Artifact> getArtifacts() {
return artifacts; if (artifacts == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(artifacts);
} }
public Long getModuleId() { public Long getModuleId() {

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.dmf.json.model; package org.eclipse.hawkbit.dmf.json.model;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@@ -27,16 +28,47 @@ public class TenantSecurityToken {
public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_HEADER = "Authorization";
@JsonProperty @JsonProperty(required = false)
private final String tenant; private String tenant;
@JsonProperty @JsonProperty(required = false)
private final Long tenantId;
@JsonProperty(required = false)
private final String controllerId; private final String controllerId;
@JsonProperty(required = false) @JsonProperty(required = false)
private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private final Long targetId;
@JsonProperty(required = false)
private Map<String, String> headers;
@JsonProperty(required = false) @JsonProperty(required = false)
private final FileResource fileResource; private final FileResource fileResource;
/**
* Constructor.
*
* @param tenant
* the tenant for the security token
* @param tenantId
* alternative tenant identification by technical ID
* @param controllerId
* the ID of the controller for the security token
* @param targetId
* alternative target identification by technical ID
* @param fileResource
* the file to obtain
*/
@JsonCreator
public TenantSecurityToken(@JsonProperty("tenant") final String tenant,
@JsonProperty("tenantId") final Long tenantId, @JsonProperty("controllerId") final String controllerId,
@JsonProperty("targetId") final Long targetId,
@JsonProperty("fileResource") final FileResource fileResource) {
this.tenant = tenant;
this.tenantId = tenantId;
this.controllerId = controllerId;
this.targetId = targetId;
this.fileResource = fileResource;
}
/** /**
* Constructor. * Constructor.
* *
@@ -47,13 +79,26 @@ public class TenantSecurityToken {
* @param fileResource * @param fileResource
* the file to obtain * the file to obtain
*/ */
@JsonCreator public TenantSecurityToken(final String tenant, final String controllerId, final FileResource fileResource) {
public TenantSecurityToken(@JsonProperty("tenant") final String tenant, this(tenant, null, controllerId, null, fileResource);
@JsonProperty("controllerId") final String controllerId, }
@JsonProperty("fileResource") final FileResource fileResource) {
/**
* Constructor.
*
* @param tenantId
* the tenant for the security token
* @param targetId
* target identification by technical ID
* @param fileResource
* the file to obtain
*/
public TenantSecurityToken(final Long tenantId, final Long targetId, final FileResource fileResource) {
this(null, tenantId, null, targetId, fileResource);
}
public void setTenant(final String tenant) {
this.tenant = tenant; this.tenant = tenant;
this.controllerId = controllerId;
this.fileResource = fileResource;
} }
public String getTenant() { public String getTenant() {
@@ -65,13 +110,25 @@ public class TenantSecurityToken {
} }
public Map<String, String> getHeaders() { public Map<String, String> getHeaders() {
return headers; if (headers == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(headers);
} }
public FileResource getFileResource() { public FileResource getFileResource() {
return fileResource; return fileResource;
} }
public Long getTenantId() {
return tenantId;
}
public Long getTargetId() {
return targetId;
}
/** /**
* Gets a header value. * Gets a header value.
* *
@@ -80,6 +137,10 @@ public class TenantSecurityToken {
* @return the value * @return the value
*/ */
public String getHeader(final String name) { public String getHeader(final String name) {
if (headers == null) {
return null;
}
return headers.get(name); return headers.get(name);
} }
@@ -88,6 +149,24 @@ public class TenantSecurityToken {
this.headers.putAll(headers); this.headers.putAll(headers);
} }
/**
* Associates the specified header value with the specified name.
*
* @param name
* of the header
* @param value
* of the header
*
* @return the previous value associated with the <tt>name</tt>, or
* <tt>null</tt> if there was no mapping for <tt>name</tt>.
*/
public String putHeader(final String name, final String value) {
if (headers == null) {
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
return headers.put(name, value);
}
/** /**
* File resource descriptor which is used to ask for the resource to * File resource descriptor which is used to ask for the resource to
* download e.g. The lookup of the file can be different e.g. by SHA1 hash * download e.g. The lookup of the file can be different e.g. by SHA1 hash
@@ -99,6 +178,8 @@ public class TenantSecurityToken {
@JsonProperty(required = false) @JsonProperty(required = false)
private String sha1; private String sha1;
@JsonProperty(required = false) @JsonProperty(required = false)
private Long artifactId;
@JsonProperty(required = false)
private String filename; private String filename;
@JsonProperty(required = false) @JsonProperty(required = false)
private SoftwareModuleFilenameResource softwareModuleFilenameResource; private SoftwareModuleFilenameResource softwareModuleFilenameResource;
@@ -128,6 +209,14 @@ public class TenantSecurityToken {
this.softwareModuleFilenameResource = softwareModuleFilenameResource; this.softwareModuleFilenameResource = softwareModuleFilenameResource;
} }
public Long getArtifactId() {
return artifactId;
}
public void setArtifactId(final Long artifactId) {
this.artifactId = artifactId;
}
/** /**
* factory method to create a file resource for an SHA1 lookup. * factory method to create a file resource for an SHA1 lookup.
* *
@@ -141,6 +230,19 @@ public class TenantSecurityToken {
return resource; return resource;
} }
/**
* factory method to create a file resource for an artifact ID lookup.
*
* @param artifactId
* the artifact IF key of the file to obtain
* @return the {@link FileResource} with SHA1 key set
*/
public static FileResource createFileResourceByArtifactId(final Long artifactId) {
final FileResource resource = new FileResource();
resource.artifactId = artifactId;
return resource;
}
/** /**
* factory method to create a file resource for an filename lookup. * factory method to create a file resource for an filename lookup.
* *
@@ -173,7 +275,7 @@ public class TenantSecurityToken {
@Override @Override
public String toString() { public String toString() {
return "FileResource [sha1=" + sha1 + ", filename=" + filename + "]"; return "FileResource [sha1=" + sha1 + ", artifactId=" + artifactId + ", filename=" + filename + "]";
} }
/** /**

View File

@@ -168,10 +168,10 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac
private TenantSecurityToken createTenantSecruityTokenVariables(final HttpServletRequest request, private TenantSecurityToken createTenantSecruityTokenVariables(final HttpServletRequest request,
final String tenant, final String controllerId) { final String tenant, final String controllerId) {
final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, controllerId, final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, null, controllerId, null,
FileResource.createFileResourceBySha1("")); FileResource.createFileResourceBySha1(""));
final UnmodifiableIterator<String> forEnumeration = Iterators.forEnumeration(request.getHeaderNames()); final UnmodifiableIterator<String> forEnumeration = Iterators.forEnumeration(request.getHeaderNames());
forEnumeration.forEachRemaining(header -> secruityToken.getHeaders().put(header, request.getHeader(header))); forEnumeration.forEachRemaining(header -> secruityToken.putHeader(header, request.getHeader(header)));
return secruityToken; return secruityToken;
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.mgmt.json.model; package org.eclipse.hawkbit.mgmt.json.model;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -72,7 +73,7 @@ public class PagedList<T> extends ResourceSupport {
} }
public List<T> getContent() { public List<T> getContent() {
return content; return Collections.unmodifiableList(content);
} }
} }

View File

@@ -12,7 +12,10 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
@@ -34,10 +37,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/** /**
* A mapper which maps repository model to RESTful model representation and * A mapper which maps repository model to RESTful model representation and
* back. * back.
*
*
*
*
*/ */
public final class MgmtDistributionSetMapper { public final class MgmtDistributionSetMapper {
private MgmtDistributionSetMapper() { private MgmtDistributionSetMapper() {
@@ -75,15 +74,13 @@ public final class MgmtDistributionSetMapper {
* to use for conversion * to use for conversion
* @return converted list of {@link DistributionSet}s * @return converted list of {@link DistributionSet}s
*/ */
static List<DistributionSet> dsFromRequest(final Iterable<MgmtDistributionSetRequestBodyPost> sets, static List<DistributionSet> dsFromRequest(final Collection<MgmtDistributionSetRequestBodyPost> sets,
final SoftwareManagement softwareManagement, final DistributionSetManagement distributionSetManagement, final SoftwareManagement softwareManagement, final DistributionSetManagement distributionSetManagement,
final EntityFactory entityFactory) { final EntityFactory entityFactory) {
final List<DistributionSet> mappedList = new ArrayList<>(); return sets.stream()
for (final MgmtDistributionSetRequestBodyPost dsRest : sets) { .map(dsRest -> fromRequest(dsRest, softwareManagement, distributionSetManagement, entityFactory))
mappedList.add(fromRequest(dsRest, softwareManagement, distributionSetManagement, entityFactory)); .collect(Collectors.toList());
}
return mappedList;
} }
@@ -139,15 +136,12 @@ public final class MgmtDistributionSetMapper {
*/ */
static List<DistributionSetMetadata> fromRequestDsMetadata(final DistributionSet ds, static List<DistributionSetMetadata> fromRequestDsMetadata(final DistributionSet ds,
final List<MgmtMetadata> metadata, final EntityFactory entityFactory) { final List<MgmtMetadata> metadata, final EntityFactory entityFactory) {
final List<DistributionSetMetadata> mappedList = new ArrayList<>(metadata.size()); if (metadata == null) {
for (final MgmtMetadata metadataRest : metadata) { return Collections.emptyList();
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(
entityFactory.generateDistributionSetMetadata(ds, metadataRest.getKey(), metadataRest.getValue()));
} }
return mappedList;
return metadata.stream().map(metadataRest -> entityFactory.generateDistributionSetMetadata(ds,
metadataRest.getKey(), metadataRest.getValue())).collect(Collectors.toList());
} }
/** /**
@@ -196,15 +190,12 @@ public final class MgmtDistributionSetMapper {
return result; return result;
} }
static List<MgmtDistributionSet> toResponseDistributionSets(final Iterable<DistributionSet> sets) { static List<MgmtDistributionSet> toResponseDistributionSets(final Collection<DistributionSet> sets) {
final List<MgmtDistributionSet> response = new ArrayList<>(); if (sets == null) {
if (sets != null) { return Collections.emptyList();
for (final DistributionSet set : sets) {
response.add(toResponse(set));
}
} }
return response;
return sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList());
} }
static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) { static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) {
@@ -224,14 +215,10 @@ public final class MgmtDistributionSetMapper {
} }
static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) { static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) {
final List<MgmtDistributionSet> mappedList = new ArrayList<>(); if (sets == null) {
if (sets != null) { return Collections.emptyList();
for (final DistributionSet set : sets) {
final MgmtDistributionSet response = toResponse(set);
mappedList.add(response);
}
} }
return mappedList;
return sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList());
} }
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.mgmt.rest.resource; package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -129,7 +130,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
.runAsSystem(() -> this.systemManagement.getTenantMetadata().getDefaultDsType().getKey()); .runAsSystem(() -> this.systemManagement.getTenantMetadata().getDefaultDsType().getKey());
sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(defaultDsKey)); sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(defaultDsKey));
final Iterable<DistributionSet> createdDSets = this.distributionSetManagement final Collection<DistributionSet> createdDSets = this.distributionSetManagement
.createDistributionSets(MgmtDistributionSetMapper.dsFromRequest(sets, this.softwareManagement, .createDistributionSets(MgmtDistributionSetMapper.dsFromRequest(sets, this.softwareManagement,
this.distributionSetManagement, entityFactory)); this.distributionSetManagement, entityFactory));

View File

@@ -11,8 +11,10 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType; import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeRequestBodyPost; import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeRequestBodyPost;
@@ -39,13 +41,13 @@ final class MgmtDistributionSetTypeMapper {
static List<DistributionSetType> smFromRequest(final EntityFactory entityFactory, static List<DistributionSetType> smFromRequest(final EntityFactory entityFactory,
final SoftwareManagement softwareManagement, final SoftwareManagement softwareManagement,
final Iterable<MgmtDistributionSetTypeRequestBodyPost> smTypesRest) { final Collection<MgmtDistributionSetTypeRequestBodyPost> smTypesRest) {
final List<DistributionSetType> mappedList = new ArrayList<>(); if (smTypesRest == null) {
return Collections.emptyList();
for (final MgmtDistributionSetTypeRequestBodyPost smRest : smTypesRest) {
mappedList.add(fromRequest(entityFactory, softwareManagement, smRest));
} }
return mappedList;
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, softwareManagement, smRest))
.collect(Collectors.toList());
} }
static DistributionSetType fromRequest(final EntityFactory entityFactory, static DistributionSetType fromRequest(final EntityFactory entityFactory,
@@ -91,19 +93,19 @@ final class MgmtDistributionSetTypeMapper {
} }
static List<MgmtDistributionSetType> toTypesResponse(final List<DistributionSetType> types) { static List<MgmtDistributionSetType> toTypesResponse(final List<DistributionSetType> types) {
final List<MgmtDistributionSetType> response = new ArrayList<>(); if (types == null) {
for (final DistributionSetType dsType : types) { return Collections.emptyList();
response.add(toResponse(dsType));
} }
return response;
return types.stream().map(MgmtDistributionSetTypeMapper::toResponse).collect(Collectors.toList());
} }
static List<MgmtDistributionSetType> toListResponse(final List<DistributionSetType> types) { static List<MgmtDistributionSetType> toListResponse(final List<DistributionSetType> types) {
final List<MgmtDistributionSetType> response = new ArrayList<>(); if (types == null) {
for (final DistributionSetType dsType : types) { return Collections.emptyList();
response.add(toResponse(dsType));
} }
return response;
return types.stream().map(MgmtDistributionSetTypeMapper::toResponse).collect(Collectors.toList());
} }
static MgmtDistributionSetType toResponse(final DistributionSetType type) { static MgmtDistributionSetType toResponse(final DistributionSetType type) {

View File

@@ -8,6 +8,12 @@
*/ */
package org.eclipse.hawkbit.mgmt.rest.resource; package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtDistributionSetTypeMapper.toResponse;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtDistributionSetTypeMapper.toTypesResponse;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleTypeMapper.toResponse;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleTypeMapper.toTypesResponse;
import static org.springframework.http.HttpStatus.CREATED;
import java.util.List; import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
@@ -32,7 +38,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -42,8 +47,6 @@ import org.springframework.web.bind.annotation.RestController;
/** /**
* REST Resource handling for {@link SoftwareModule} and related * REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations. * {@link Artifact} CRUD operations.
*
*
*/ */
@RestController @RestController
public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeRestApi { public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeRestApi {
@@ -67,7 +70,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeDistributionSetTypeSortParam(sortParam); final Sort sorting = PagingUtility.sanitizeDistributionSetTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSetType> findModuleTypessAll; final Slice<DistributionSetType> findModuleTypessAll;
@@ -82,31 +84,32 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final List<MgmtDistributionSetType> rest = MgmtDistributionSetTypeMapper final List<MgmtDistributionSetType> rest = MgmtDistributionSetTypeMapper
.toListResponse(findModuleTypessAll.getContent()); .toListResponse(findModuleTypessAll.getContent());
return new ResponseEntity<>(new PagedList<>(rest, countModulesAll), HttpStatus.OK); return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
} }
@Override @Override
public ResponseEntity<MgmtDistributionSetType> getDistributionSetType( public ResponseEntity<MgmtDistributionSetType> getDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return new ResponseEntity<>(MgmtDistributionSetTypeMapper.toResponse(foundType), HttpStatus.OK); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return ResponseEntity.ok(toResponse(foundType));
} }
@Override @Override
public ResponseEntity<Void> deleteDistributionSetType( public ResponseEntity<Void> deleteDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
distributionSetManagement.deleteDistributionSetType(module); distributionSetManagement.deleteDistributionSetType(module);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
public ResponseEntity<MgmtDistributionSetType> updateDistributionSetType( public ResponseEntity<MgmtDistributionSetType> updateDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@RequestBody final MgmtDistributionSetTypeRequestBodyPut restDistributionSetType) { @RequestBody final MgmtDistributionSetTypeRequestBodyPut restDistributionSetType) {
final DistributionSetType type = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType type = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
// only description can be modified // only description can be modified
@@ -117,8 +120,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final DistributionSetType updatedDistributionSetType = distributionSetManagement final DistributionSetType updatedDistributionSetType = distributionSetManagement
.updateDistributionSetType(type); .updateDistributionSetType(type);
return new ResponseEntity<>(MgmtDistributionSetTypeMapper.toResponse(updatedDistributionSetType), return ResponseEntity.ok(toResponse(updatedDistributionSetType));
HttpStatus.OK);
} }
@Override @Override
@@ -128,16 +130,17 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final List<DistributionSetType> createdSoftwareModules = distributionSetManagement.createDistributionSetTypes( final List<DistributionSetType> createdSoftwareModules = distributionSetManagement.createDistributionSetTypes(
MgmtDistributionSetTypeMapper.smFromRequest(entityFactory, softwareManagement, distributionSetTypes)); MgmtDistributionSetTypeMapper.smFromRequest(entityFactory, softwareManagement, distributionSetTypes));
return new ResponseEntity<>(MgmtDistributionSetTypeMapper.toTypesResponse(createdSoftwareModules), return ResponseEntity.status(CREATED).body(toTypesResponse(createdSoftwareModules));
HttpStatus.CREATED);
} }
private DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(final Long distributionSetTypeId) { private DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(final Long distributionSetTypeId) {
final DistributionSetType module = distributionSetManagement.findDistributionSetTypeById(distributionSetTypeId); final DistributionSetType module = distributionSetManagement.findDistributionSetTypeById(distributionSetTypeId);
if (module == null) { if (module == null) {
throw new EntityNotFoundException( throw new EntityNotFoundException(
"DistributionSetType with Id {" + distributionSetTypeId + "} does not exist"); "DistributionSetType with Id {" + distributionSetTypeId + "} does not exist");
} }
return module; return module;
} }
@@ -146,8 +149,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toListResponse(foundType.getMandatoryModuleTypes()), return ResponseEntity.ok(toTypesResponse(foundType.getMandatoryModuleTypes()));
HttpStatus.OK);
} }
@Override @Override
@@ -156,7 +158,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) { @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsMandatoryModuleType(foundSmType)) { if (!foundType.containsMandatoryModuleType(foundSmType)) {
@@ -164,7 +165,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
"Software module with given ID is not part of this distribution set type!"); "Software module with given ID is not part of this distribution set type!");
} }
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK); return ResponseEntity.ok(toResponse(foundSmType));
} }
@Override @Override
@@ -173,7 +174,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) { @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsOptionalModuleType(foundSmType)) { if (!foundType.containsOptionalModuleType(foundSmType)) {
@@ -181,7 +181,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
"Software module with given ID is not part of this distribution set type!"); "Software module with given ID is not part of this distribution set type!");
} }
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK); return ResponseEntity.ok(toResponse(foundSmType));
} }
@Override @Override
@@ -189,9 +189,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return ResponseEntity.ok(toTypesResponse(foundType.getOptionalModuleTypes()));
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toListResponse(foundType.getOptionalModuleTypes()),
HttpStatus.OK);
} }
@Override @Override
@@ -200,7 +198,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) { @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsMandatoryModuleType(foundSmType)) { if (!foundType.containsMandatoryModuleType(foundSmType)) {
@@ -209,18 +206,17 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
} }
foundType.removeModuleType(softwareModuleTypeId); foundType.removeModuleType(softwareModuleTypeId);
distributionSetManagement.updateDistributionSetType(foundType); distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
public ResponseEntity<Void> removeOptionalModule( public ResponseEntity<Void> removeOptionalModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) { @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsOptionalModuleType(foundSmType)) { if (!foundType.containsOptionalModuleType(foundSmType)) {
@@ -229,10 +225,9 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
} }
foundType.removeModuleType(softwareModuleTypeId); foundType.removeModuleType(softwareModuleTypeId);
distributionSetManagement.updateDistributionSetType(foundType); distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
@@ -240,14 +235,11 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId()); final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addMandatoryModuleType(smType); foundType.addMandatoryModuleType(smType);
distributionSetManagement.updateDistributionSetType(foundType); distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
@@ -255,23 +247,22 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) { @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId()); final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addOptionalModuleType(smType); foundType.addOptionalModuleType(smType);
distributionSetManagement.updateDistributionSetType(foundType); distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) { private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId); final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId);
if (module == null) { if (module == null) {
throw new EntityNotFoundException( throw new EntityNotFoundException(
"SoftwareModuleType with Id {" + softwareModuleTypeId + "} does not exist"); "SoftwareModuleType with Id {" + softwareModuleTypeId + "} does not exist");
} }
return module; return module;
} }
} }

View File

@@ -11,8 +11,9 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition.Condition; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition.Condition;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction.ErrorAction; import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction.ErrorAction;
@@ -48,9 +49,11 @@ final class MgmtRolloutMapper {
} }
static List<MgmtRolloutResponseBody> toResponseRollout(final List<Rollout> rollouts) { static List<MgmtRolloutResponseBody> toResponseRollout(final List<Rollout> rollouts) {
final List<MgmtRolloutResponseBody> result = new ArrayList<>(rollouts.size()); if (rollouts == null) {
rollouts.forEach(r -> result.add(toResponseRollout(r))); return Collections.emptyList();
return result; }
return rollouts.stream().map(MgmtRolloutMapper::toResponseRollout).collect(Collectors.toList());
} }
static MgmtRolloutResponseBody toResponseRollout(final Rollout rollout) { static MgmtRolloutResponseBody toResponseRollout(final Rollout rollout) {
@@ -103,9 +106,11 @@ final class MgmtRolloutMapper {
} }
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) { static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
final List<MgmtRolloutGroupResponseBody> result = new ArrayList<>(rollouts.size()); if (rollouts == null) {
rollouts.forEach(r -> result.add(toResponseRolloutGroup(r))); return Collections.emptyList();
return result; }
return rollouts.stream().map(MgmtRolloutMapper::toResponseRolloutGroup).collect(Collectors.toList());
} }
static MgmtRolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup) { static MgmtRolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup) {

View File

@@ -11,8 +11,10 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact; import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
@@ -65,60 +67,46 @@ public final class MgmtSoftwareModuleMapper {
} }
static List<SoftwareModuleMetadata> fromRequestSwMetadata(final EntityFactory entityFactory, static List<SoftwareModuleMetadata> fromRequestSwMetadata(final EntityFactory entityFactory,
final SoftwareModule sw, final List<MgmtMetadata> metadata) { final SoftwareModule sw, final Collection<MgmtMetadata> metadata) {
final List<SoftwareModuleMetadata> mappedList = new ArrayList<>(metadata.size()); if (metadata == null) {
for (final MgmtMetadata metadataRest : metadata) { return Collections.emptyList();
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(
entityFactory.generateSoftwareModuleMetadata(sw, metadataRest.getKey(), metadataRest.getValue()));
} }
return mappedList;
return metadata.stream().map(metadataRest -> entityFactory.generateSoftwareModuleMetadata(sw,
metadataRest.getKey(), metadataRest.getValue())).collect(Collectors.toList());
} }
static List<SoftwareModule> smFromRequest(final EntityFactory entityFactory, static List<SoftwareModule> smFromRequest(final EntityFactory entityFactory,
final Iterable<MgmtSoftwareModuleRequestBodyPost> smsRest, final SoftwareManagement softwareManagement) { final Collection<MgmtSoftwareModuleRequestBodyPost> smsRest, final SoftwareManagement softwareManagement) {
final List<SoftwareModule> mappedList = new ArrayList<>(); if (smsRest == null) {
for (final MgmtSoftwareModuleRequestBodyPost smRest : smsRest) { return Collections.emptyList();
mappedList.add(fromRequest(entityFactory, smRest, softwareManagement));
} }
return mappedList;
return smsRest.stream().map(smRest -> fromRequest(entityFactory, smRest, softwareManagement))
.collect(Collectors.toList());
} }
/** /**
* Create response for sw modules. * Create response for sw modules.
* *
* @param baseSoftareModules * @param softwareModules
* the modules * the modules
* @return the response * @return the response
*/ */
public static List<MgmtSoftwareModule> toResponse(final List<SoftwareModule> baseSoftareModules) { public static List<MgmtSoftwareModule> toResponse(final Collection<SoftwareModule> softwareModules) {
final List<MgmtSoftwareModule> mappedList = new ArrayList<>(); if (softwareModules == null) {
if (baseSoftareModules != null) { return Collections.emptyList();
for (final SoftwareModule target : baseSoftareModules) {
final MgmtSoftwareModule response = toResponse(target);
mappedList.add(response);
}
} }
return mappedList;
return softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList());
} }
static List<MgmtSoftwareModule> toResponseSoftwareModules(final Iterable<SoftwareModule> softwareModules) { static List<MgmtMetadata> toResponseSwMetadata(final Collection<SoftwareModuleMetadata> metadata) {
final List<MgmtSoftwareModule> response = new ArrayList<>(); if (metadata == null) {
for (final SoftwareModule softwareModule : softwareModules) { return Collections.emptyList();
response.add(toResponse(softwareModule));
} }
return response;
}
static List<MgmtMetadata> toResponseSwMetadata(final List<SoftwareModuleMetadata> metadata) { return metadata.stream().map(MgmtSoftwareModuleMapper::toResponseSwMetadata).collect(Collectors.toList());
final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size());
for (final SoftwareModuleMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseSwMetadata(distributionSetMetadata));
}
return mappedList;
} }
static MgmtMetadata toResponseSwMetadata(final SoftwareModuleMetadata metadata) { static MgmtMetadata toResponseSwMetadata(final SoftwareModuleMetadata metadata) {
@@ -194,15 +182,11 @@ public final class MgmtSoftwareModuleMapper {
return artifactRest; return artifactRest;
} }
static List<MgmtArtifact> artifactsToResponse(final List<Artifact> artifacts) { static List<MgmtArtifact> artifactsToResponse(final Collection<Artifact> artifacts) {
final List<MgmtArtifact> mappedList = new ArrayList<>(); if (artifacts == null) {
return Collections.emptyList();
if (artifacts != null) {
for (final Artifact artifact : artifacts) {
final MgmtArtifact response = toResponse(artifact);
mappedList.add(response);
}
} }
return mappedList;
return artifacts.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList());
} }
} }

View File

@@ -8,7 +8,15 @@
*/ */
package org.eclipse.hawkbit.mgmt.rest.resource; package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleMapper.artifactsToResponse;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleMapper.toResponse;
import static org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleMapper.toResponseSwMetadata;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
@@ -46,10 +54,10 @@ import org.springframework.web.multipart.MultipartFile;
/** /**
* REST Resource handling for {@link SoftwareModule} and related * REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations. * {@link Artifact} CRUD operations.
*
*/ */
@RestController @RestController
public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi { public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
private static final Logger LOG = LoggerFactory.getLogger(MgmtSoftwareModuleResource.class); private static final Logger LOG = LoggerFactory.getLogger(MgmtSoftwareModuleResource.class);
@Autowired @Autowired
@@ -69,7 +77,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
@RequestParam(value = "sha1sum", required = false) final String sha1Sum) { @RequestParam(value = "sha1sum", required = false) final String sha1Sum) {
if (file.isEmpty()) { if (file.isEmpty()) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(BAD_REQUEST);
} }
String fileName = optionalFileName; String fileName = optionalFileName;
@@ -81,10 +89,10 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
final Artifact result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId, final Artifact result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId,
fileName, md5Sum == null ? null : md5Sum.toLowerCase(), fileName, md5Sum == null ? null : md5Sum.toLowerCase(),
sha1Sum == null ? null : sha1Sum.toLowerCase(), false, file.getContentType()); sha1Sum == null ? null : sha1Sum.toLowerCase(), false, file.getContentType());
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(result), HttpStatus.CREATED); return ResponseEntity.status(CREATED).body(toResponse(result));
} catch (final IOException e) { } catch (final IOException e) {
LOG.error("Failed to store artifact", e); LOG.error("Failed to store artifact", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(INTERNAL_SERVER_ERROR);
} }
} }
@@ -92,31 +100,34 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
@ResponseBody @ResponseBody
public ResponseEntity<List<MgmtArtifact>> getArtifacts( public ResponseEntity<List<MgmtArtifact>> getArtifacts(
@PathVariable("softwareModuleId") final Long softwareModuleId) { @PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.artifactsToResponse(module.getArtifacts()), HttpStatus.OK); final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return ResponseEntity.ok(artifactsToResponse(module.getArtifacts()));
} }
@Override @Override
@ResponseBody @ResponseBody
// Exception squid:S3655 - Optional access is checked in
// findSoftwareModuleWithExceptionIfNotFound
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, public ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) { @PathVariable("artifactId") final Long artifactId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(module.getLocalArtifact(artifactId).get()), return ResponseEntity.ok(toResponse(module.getLocalArtifact(artifactId).get()));
HttpStatus.OK);
} }
@Override @Override
@ResponseBody @ResponseBody
public ResponseEntity<Void> deleteArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, public ResponseEntity<Void> deleteArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) { @PathVariable("artifactId") final Long artifactId) {
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
artifactManagement.deleteLocalArtifact(artifactId); artifactManagement.deleteLocalArtifact(artifactId);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
@@ -143,33 +154,34 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
} }
final List<MgmtSoftwareModule> rest = MgmtSoftwareModuleMapper.toResponse(findModulesAll.getContent()); final List<MgmtSoftwareModule> rest = MgmtSoftwareModuleMapper.toResponse(findModulesAll.getContent());
return new ResponseEntity<>(new PagedList<>(rest, countModulesAll), HttpStatus.OK); return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
} }
@Override @Override
public ResponseEntity<MgmtSoftwareModule> getSoftwareModule( public ResponseEntity<MgmtSoftwareModule> getSoftwareModule(
@PathVariable("softwareModuleId") final Long softwareModuleId) { @PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule findBaseSoftareModule = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(findBaseSoftareModule), HttpStatus.OK); final SoftwareModule findBaseSoftareModule = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return ResponseEntity.ok(toResponse(findBaseSoftareModule));
} }
@Override @Override
public ResponseEntity<List<MgmtSoftwareModule>> createSoftwareModules( public ResponseEntity<List<MgmtSoftwareModule>> createSoftwareModules(
@RequestBody final List<MgmtSoftwareModuleRequestBodyPost> softwareModules) { @RequestBody final List<MgmtSoftwareModuleRequestBodyPost> softwareModules) {
LOG.debug("creating {} softwareModules", softwareModules.size()); LOG.debug("creating {} softwareModules", softwareModules.size());
final Iterable<SoftwareModule> createdSoftwareModules = softwareManagement.createSoftwareModule( final Collection<SoftwareModule> createdSoftwareModules = softwareManagement.createSoftwareModule(
MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules, softwareManagement)); MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules, softwareManagement));
LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED); LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponseSoftwareModules(createdSoftwareModules), return ResponseEntity.status(CREATED).body(toResponse(createdSoftwareModules));
HttpStatus.CREATED);
} }
@Override @Override
public ResponseEntity<MgmtSoftwareModule> updateSoftwareModule( public ResponseEntity<MgmtSoftwareModule> updateSoftwareModule(
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final MgmtSoftwareModuleRequestBodyPut restSoftwareModule) { @RequestBody final MgmtSoftwareModuleRequestBodyPut restSoftwareModule) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
// only description and vendor can be modified // only description and vendor can be modified
@@ -181,16 +193,16 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
} }
final SoftwareModule updateSoftwareModule = softwareManagement.updateSoftwareModule(module); final SoftwareModule updateSoftwareModule = softwareManagement.updateSoftwareModule(module);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(updateSoftwareModule), HttpStatus.OK); return ResponseEntity.ok(toResponse(updateSoftwareModule));
} }
@Override @Override
public ResponseEntity<Void> deleteSoftwareModule(@PathVariable("softwareModuleId") final Long softwareModuleId) { public ResponseEntity<Void> deleteSoftwareModule(@PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModule(module); softwareManagement.deleteSoftwareModule(module);
return new ResponseEntity<>(HttpStatus.OK); return ResponseEntity.ok().build();
} }
@Override @Override
@@ -218,34 +230,38 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
metaDataPage = softwareManagement.findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId, pageable); metaDataPage = softwareManagement.findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId, pageable);
} }
return new ResponseEntity<>( return ResponseEntity
new PagedList<>(MgmtSoftwareModuleMapper.toResponseSwMetadata(metaDataPage.getContent()), .ok(new PagedList<>(toResponseSwMetadata(metaDataPage.getContent()), metaDataPage.getTotalElements()));
metaDataPage.getTotalElements()),
HttpStatus.OK);
} }
@Override @Override
public ResponseEntity<MgmtMetadata> getMetadataValue(@PathVariable("softwareModuleId") final Long softwareModuleId, public ResponseEntity<MgmtMetadata> getMetadataValue(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) { @PathVariable("metadataKey") final String metadataKey) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata findOne = softwareManagement.findSoftwareModuleMetadata(sw, metadataKey); final SoftwareModuleMetadata findOne = softwareManagement.findSoftwareModuleMetadata(sw, metadataKey);
return ResponseEntity.<MgmtMetadata> ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(findOne));
return ResponseEntity.ok(toResponseSwMetadata(findOne));
} }
@Override @Override
public ResponseEntity<MgmtMetadata> updateMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId, public ResponseEntity<MgmtMetadata> updateMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey, @RequestBody final MgmtMetadata metadata) { @PathVariable("metadataKey") final String metadataKey, @RequestBody final MgmtMetadata metadata) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata updated = softwareManagement.updateSoftwareModuleMetadata( final SoftwareModuleMetadata updated = softwareManagement.updateSoftwareModuleMetadata(
entityFactory.generateSoftwareModuleMetadata(sw, metadataKey, metadata.getValue())); entityFactory.generateSoftwareModuleMetadata(sw, metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(updated));
return ResponseEntity.ok(toResponseSwMetadata(updated));
} }
@Override @Override
public ResponseEntity<Void> deleteMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId, public ResponseEntity<Void> deleteMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) { @PathVariable("metadataKey") final String metadataKey) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModuleMetadata(sw, metadataKey); softwareManagement.deleteSoftwareModuleMetadata(sw, metadataKey);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@@ -253,24 +269,25 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
public ResponseEntity<List<MgmtMetadata>> createMetadata( public ResponseEntity<List<MgmtMetadata>> createMetadata(
@PathVariable("softwareModuleId") final Long softwareModuleId, @PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final List<MgmtMetadata> metadataRest) { @RequestBody final List<MgmtMetadata> metadataRest) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final List<SoftwareModuleMetadata> created = softwareManagement.createSoftwareModuleMetadata( final List<SoftwareModuleMetadata> created = softwareManagement.createSoftwareModuleMetadata(
MgmtSoftwareModuleMapper.fromRequestSwMetadata(entityFactory, sw, metadataRest)); MgmtSoftwareModuleMapper.fromRequestSwMetadata(entityFactory, sw, metadataRest));
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponseSwMetadata(created), HttpStatus.CREATED); return ResponseEntity.status(CREATED).body(toResponseSwMetadata(created));
} }
private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId, private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId,
final Long artifactId) { final Long artifactId) {
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (module == null) { if (module == null) {
throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist"); throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist");
} else if (artifactId != null && !module.getLocalArtifact(artifactId).isPresent()) { }
if (artifactId != null && !module.getLocalArtifact(artifactId).isPresent()) {
throw new EntityNotFoundException("Artifact with Id {" + artifactId + "} does not exist"); throw new EntityNotFoundException("Artifact with Id {" + artifactId + "} does not exist");
} }
return module; return module;
} }
} }

View File

@@ -11,9 +11,10 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType; import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost; import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
@@ -25,9 +26,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
* A mapper which maps repository model to RESTful model representation and * A mapper which maps repository model to RESTful model representation and
* back. * back.
* *
*
*
*
*/ */
final class MgmtSoftwareModuleTypeMapper { final class MgmtSoftwareModuleTypeMapper {
@@ -37,13 +35,12 @@ final class MgmtSoftwareModuleTypeMapper {
} }
static List<SoftwareModuleType> smFromRequest(final EntityFactory entityFactory, static List<SoftwareModuleType> smFromRequest(final EntityFactory entityFactory,
final Iterable<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) { final Collection<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
final List<SoftwareModuleType> mappedList = new ArrayList<>(); if (smTypesRest == null) {
return Collections.emptyList();
for (final MgmtSoftwareModuleTypeRequestBodyPost smRest : smTypesRest) {
mappedList.add(fromRequest(entityFactory, smRest));
} }
return mappedList;
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, smRest)).collect(Collectors.toList());
} }
static SoftwareModuleType fromRequest(final EntityFactory entityFactory, static SoftwareModuleType fromRequest(final EntityFactory entityFactory,
@@ -57,20 +54,12 @@ final class MgmtSoftwareModuleTypeMapper {
return result; return result;
} }
static List<MgmtSoftwareModuleType> toTypesResponse(final List<SoftwareModuleType> types) { static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<SoftwareModuleType> types) {
final List<MgmtSoftwareModuleType> response = new ArrayList<>(); if (types == null) {
for (final SoftwareModuleType softwareModule : types) { return Collections.emptyList();
response.add(toResponse(softwareModule));
} }
return response;
}
static List<MgmtSoftwareModuleType> toListResponse(final Collection<SoftwareModuleType> types) { return types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).collect(Collectors.toList());
final List<MgmtSoftwareModuleType> response = new ArrayList<>();
for (final SoftwareModuleType softwareModule : types) {
response.add(toResponse(softwareModule));
}
return response;
} }
static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) { static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {

View File

@@ -72,7 +72,7 @@ public class MgmtSoftwareModuleTypeResource implements MgmtSoftwareModuleTypeRes
} }
final List<MgmtSoftwareModuleType> rest = MgmtSoftwareModuleTypeMapper final List<MgmtSoftwareModuleType> rest = MgmtSoftwareModuleTypeMapper
.toListResponse(findModuleTypessAll.getContent()); .toTypesResponse(findModuleTypessAll.getContent());
return new ResponseEntity<>(new PagedList<>(rest, countModulesAll), HttpStatus.OK); return new ResponseEntity<>(new PagedList<>(rest, countModulesAll), HttpStatus.OK);
} }

View File

@@ -13,9 +13,11 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.net.URI; import java.net.URI;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus; import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
@@ -92,17 +94,17 @@ public final class MgmtTargetMapper {
* the targets * the targets
* @return the response * @return the response
*/ */
public static List<MgmtTarget> toResponseWithLinksAndPollStatus(final Iterable<Target> targets) { public static List<MgmtTarget> toResponseWithLinksAndPollStatus(final Collection<Target> targets) {
final List<MgmtTarget> mappedList = new ArrayList<>(); if (targets == null) {
if (targets != null) { return Collections.emptyList();
for (final Target target : targets) {
final MgmtTarget response = toResponse(target);
addPollStatus(target, response);
addTargetLinks(response);
mappedList.add(response);
}
} }
return mappedList;
return targets.stream().map(target -> {
final MgmtTarget response = toResponse(target);
addPollStatus(target, response);
addTargetLinks(response);
return response;
}).collect(Collectors.toList());
} }
/** /**
@@ -112,15 +114,12 @@ public final class MgmtTargetMapper {
* list of targets * list of targets
* @return the response * @return the response
*/ */
public static List<MgmtTarget> toResponse(final Iterable<Target> targets) { public static List<MgmtTarget> toResponse(final Collection<Target> targets) {
final List<MgmtTarget> mappedList = new ArrayList<>(); if (targets == null) {
if (targets != null) { return Collections.emptyList();
for (final Target target : targets) {
final MgmtTarget response = toResponse(target);
mappedList.add(response);
}
} }
return mappedList;
return targets.stream().map(MgmtTargetMapper::toResponse).collect(Collectors.toList());
} }
/** /**
@@ -173,12 +172,13 @@ public final class MgmtTargetMapper {
} }
static List<Target> fromRequest(final EntityFactory entityFactory, static List<Target> fromRequest(final EntityFactory entityFactory,
final Iterable<MgmtTargetRequestBody> targetsRest) { final Collection<MgmtTargetRequestBody> targetsRest) {
final List<Target> mappedList = new ArrayList<>(); if (targetsRest == null) {
for (final MgmtTargetRequestBody targetRest : targetsRest) { return Collections.emptyList();
mappedList.add(fromRequest(entityFactory, targetRest));
} }
return mappedList;
return targetsRest.stream().map(targetRest -> fromRequest(entityFactory, targetRest))
.collect(Collectors.toList());
} }
static Target fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) { static Target fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) {
@@ -190,17 +190,12 @@ public final class MgmtTargetMapper {
return target; return target;
} }
static List<MgmtActionStatus> toActionStatusRestResponse(final List<ActionStatus> actionStatus) { static List<MgmtActionStatus> toActionStatusRestResponse(final Collection<ActionStatus> actionStatus) {
final List<MgmtActionStatus> mappedList = new ArrayList<>(); if (actionStatus == null) {
return Collections.emptyList();
if (actionStatus != null) {
for (final ActionStatus status : actionStatus) {
final MgmtActionStatus response = toResponse(status);
mappedList.add(response);
}
} }
return mappedList; return actionStatus.stream().map(MgmtTargetMapper::toResponse).collect(Collectors.toList());
} }
static MgmtAction toResponse(final String targetId, final Action action, final boolean isActive) { static MgmtAction toResponse(final String targetId, final Action action, final boolean isActive) {
@@ -222,14 +217,13 @@ public final class MgmtTargetMapper {
return result; return result;
} }
static List<MgmtAction> toResponse(final String targetId, final List<Action> actions) { static List<MgmtAction> toResponse(final String targetId, final Collection<Action> actions) {
final List<MgmtAction> mappedList = new ArrayList<>(); if (actions == null) {
return Collections.emptyList();
for (final Action action : actions) {
final MgmtAction response = toResponse(targetId, action, action.isActive());
mappedList.add(response);
} }
return mappedList;
return actions.stream().map(action -> toResponse(targetId, action, action.isActive()))
.collect(Collectors.toList());
} }
private static String getNameOfActionStatusType(final Action.Status type) { private static String getNameOfActionStatusType(final Action.Status type) {

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -108,7 +109,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
@Override @Override
public ResponseEntity<List<MgmtTarget>> createTargets(@RequestBody final List<MgmtTargetRequestBody> targets) { public ResponseEntity<List<MgmtTarget>> createTargets(@RequestBody final List<MgmtTargetRequestBody> targets) {
LOG.debug("creating {} targets", targets.size()); LOG.debug("creating {} targets", targets.size());
final Iterable<Target> createdTargets = this.targetManagement final Collection<Target> createdTargets = this.targetManagement
.createTargets(MgmtTargetMapper.fromRequest(entityFactory, targets)); .createTargets(MgmtTargetMapper.fromRequest(entityFactory, targets));
LOG.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED); LOG.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtTargetMapper.toResponse(createdTargets), HttpStatus.CREATED); return new ResponseEntity<>(MgmtTargetMapper.toResponse(createdTargets), HttpStatus.CREATED);

View File

@@ -494,9 +494,6 @@ public class MgmtDistributionSetTypeResourceTest extends AbstractRestIntegration
final SoftwareModuleType testSmType = softwareManagement.createSoftwareModuleType( final SoftwareModuleType testSmType = softwareManagement.createSoftwareModuleType(
entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5)); entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<DistributionSetType> types = new ArrayList<>();
types.add(testType);
// DST does not exist // DST does not exist
mvc.perform(get("/rest/v1/distributionsettypes/12345678")).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/rest/v1/distributionsettypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
@@ -554,9 +551,8 @@ public class MgmtDistributionSetTypeResourceTest extends AbstractRestIntegration
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
final DistributionSetType missingName = entityFactory.generateDistributionSetType("test123", null, "Desc123"); // Missing mandatory field name
mvc.perform(post("/rest/v1/distributionsettypes") mvc.perform(post("/rest/v1/distributionsettypes").content("[{\"description\":\"Desc123\",\"key\":\"test123\"}]")
.content(JsonBuilder.distributionSetTypes(Lists.newArrayList(missingName)))
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());

View File

@@ -438,8 +438,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractRestIntegrationTestW
SoftwareModule sm = entityFactory.generateSoftwareModule(osType, "name 1", "version 1", null, null); SoftwareModule sm = entityFactory.generateSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareManagement.createSoftwareModule(sm); sm = softwareManagement.createSoftwareModule(sm);
final List<SoftwareModule> modules = new ArrayList<>(); final List<SoftwareModule> modules = Lists.newArrayList(sm);
modules.add(sm);
// SM does not exist // SM does not exist
mvc.perform(get("/rest/v1/softwaremodules/12345678")).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/rest/v1/softwaremodules/12345678")).andDo(MockMvcResultPrinter.print())
@@ -457,11 +456,10 @@ public class MgmtSoftwareModuleResourceTest extends AbstractRestIntegrationTestW
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
final SoftwareModule missingName = entityFactory.generateSoftwareModule(osType, null, "version 1", null, null); mvc.perform(post("/rest/v1/softwaremodules")
mvc.perform( .content("[{\"description\":\"Desc123\",\"key\":\"test123\", \"type\":\"os\"}]")
post("/rest/v1/softwaremodules").content(JsonBuilder.softwareModules(Lists.newArrayList(missingName))) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest());
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
final SoftwareModule toLongName = entityFactory.generateSoftwareModule(osType, final SoftwareModule toLongName = entityFactory.generateSoftwareModule(osType,
RandomStringUtils.randomAscii(80), "version 1", null, null); RandomStringUtils.randomAscii(80), "version 1", null, null);

View File

@@ -318,8 +318,7 @@ public class MgmtSoftwareModuleTypeResourceTest extends AbstractRestIntegrationT
final SoftwareModuleType testType = softwareManagement.createSoftwareModuleType( final SoftwareModuleType testType = softwareManagement.createSoftwareModuleType(
entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5)); entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<SoftwareModuleType> types = new ArrayList<>(); final List<SoftwareModuleType> types = Lists.newArrayList(testType);
types.add(testType);
// SM does not exist // SM does not exist
mvc.perform(get("/rest/v1/softwaremoduletypes/12345678")).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/rest/v1/softwaremoduletypes/12345678")).andDo(MockMvcResultPrinter.print())
@@ -337,9 +336,9 @@ public class MgmtSoftwareModuleTypeResourceTest extends AbstractRestIntegrationT
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
final SoftwareModuleType missingName = entityFactory.generateSoftwareModuleType("test123", null, "Desc123", 5);
mvc.perform(post("/rest/v1/softwaremoduletypes") mvc.perform(post("/rest/v1/softwaremoduletypes")
.content(JsonBuilder.softwareModuleTypes(Lists.newArrayList(missingName))) .content(
"[{\"description\":\"Desc123\",\"id\":9223372036854775807,\"key\":\"test123\",\"maxAssignments\":5}]")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());

View File

@@ -23,7 +23,7 @@
<Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR" /> <Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR" />
<!-- <Logger name="org.eclipse.hawkbit.rest.util.MockMvcResultPrinter" level="DEBUG" /> --> <Logger name="org.eclipse.hawkbit.rest.util.MockMvcResultPrinter" level="DEBUG" />
<!-- Security Log with hints on potential attacks --> <!-- Security Log with hints on potential attacks -->
<logger name="server-security" level="INFO" /> <logger name="server-security" level="INFO" />

View File

@@ -208,15 +208,16 @@ public interface ArtifactManagement {
void deleteLocalArtifact(@NotNull Long id); void deleteLocalArtifact(@NotNull Long id);
/** /**
* Searches for {@link Artifact} with given {@link Identifiable}. * Searches for {@link LocalArtifact} with given {@link Identifiable}.
* *
* @param id * @param id
* to search for * to search for
* @return found {@link Artifact} or <code>null</code> is it could not be * @return found {@link LocalArtifact} or <code>null</code> is it could not
* found. * be found.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
Artifact findArtifact(@NotNull Long id); + SpringEvalExpressions.IS_CONTROLLER)
LocalArtifact findLocalArtifact(@NotNull Long id);
/** /**
* Find by artifact by software module id and filename. * Find by artifact by software module id and filename.

View File

@@ -183,22 +183,6 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
String getPollingTime(); String getPollingTime();
/**
* An direct access to the security token of an
* {@link Target#getSecurityToken()} without authorization. This is
* necessary to be able to access the security-token without any
* security-context information because the security-token is used for
* authentication.
*
* @param controllerId
* the ID of the controller to retrieve the security token for
* @return the security context of the target, in case no target exists for
* the given controllerId {@code null} is returned
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET_SEC_TOKEN)
String getSecurityTokenByControllerId(@NotEmpty String controllerId);
/** /**
* Checks if a given target has currently or has even been assigned to the * Checks if a given target has currently or has even been assigned to the
* given artifact through the action history list. This can e.g. indicate if * given artifact through the action history list. This can e.g. indicate if
@@ -218,6 +202,25 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotNull String controllerId, @NotNull LocalArtifact localArtifact); boolean hasTargetArtifactAssigned(@NotNull String controllerId, @NotNull LocalArtifact localArtifact);
/**
* Checks if a given target has currently or has even been assigned to the
* given artifact through the action history list. This can e.g. indicate if
* a target is allowed to download a given artifact because it has currently
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
*
* @param targetId
* the ID of the target to check
* @param localArtifact
* the artifact to verify if the given target had even been
* assigned to
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean hasTargetArtifactAssigned(@NotNull Long targetId, @NotNull LocalArtifact localArtifact);
/** /**
* Registers retrieved status for given {@link Target} and {@link Action} if * Registers retrieved status for given {@link Target} and {@link Action} if
* it does not exist yet. * it does not exist yet.
@@ -300,4 +303,32 @@ public interface ControllerManagement {
TargetInfo updateTargetStatus(@NotNull TargetInfo targetInfo, TargetUpdateStatus status, Long lastTargetQuery, TargetInfo updateTargetStatus(@NotNull TargetInfo targetInfo, TargetUpdateStatus status, Long lastTargetQuery,
URI address); URI address);
/**
* Finds {@link Target} based on given controller ID returns found Target
* without details, i.e. NO {@link Target#getTags()} and
* {@link Target#getActions()} possible.
*
* @param controllerId
* to look for.
* @return {@link Target} or {@code null} if it does not exist
* @see Target#getControllerId()
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Target findByControllerId(@NotEmpty final String controllerId);
/**
* Finds {@link Target} based on given ID returns found Target without
* details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()}
* possible.
*
* @param targetId
* to look for.
* @return {@link Target} or {@code null} if it does not exist
* @see Target#getId()
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Target findByTargetId(final long targetId);
} }

View File

@@ -57,7 +57,11 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
* @return the actionIds * @return the actionIds
*/ */
public List<Long> getActions() { public List<Long> getActions() {
return actions; if (actions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actions);
} }
@Override @Override

View File

@@ -205,8 +205,10 @@ public interface DistributionSetManagement {
/** /**
* deletes a distribution set meta data entry. * deletes a distribution set meta data entry.
* *
* @param id * @param distributionSet
* the ID of the distribution set meta data to delete * where meta data has to be deleted
* @param key
* of the meta data element
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteDistributionSetMetadata(@NotNull final DistributionSet distributionSet, @NotNull final String key); void deleteDistributionSetMetadata(@NotNull final DistributionSet distributionSet, @NotNull final String key);
@@ -429,7 +431,7 @@ public interface DistributionSetManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSetType findDistributionSetTypeByKey(@NotNull String key); DistributionSetType findDistributionSetTypeByKey(@NotEmpty String key);
/** /**
* @param name * @param name
@@ -469,15 +471,16 @@ public interface DistributionSetManagement {
/** /**
* finds a single distribution set meta data by its id. * finds a single distribution set meta data by its id.
* *
* @param id * @param distributionSet
* the id of the distribution set meta data containing the meta * where meta data has to rind
* data key and the ID of the distribution set * @param key
* of the meta data element
* @return the found DistributionSetMetadata or {@code null} if not exits * @return the found DistributionSetMetadata or {@code null} if not exits
* @throws EntityNotFoundException * @throws EntityNotFoundException
* in case the meta data does not exists for the given key * in case the meta data does not exists for the given key
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSetMetadata findOne(@NotNull DistributionSet distributionSet, @NotNull String key); DistributionSetMetadata findOne(@NotNull DistributionSet distributionSet, @NotEmpty String key);
/** /**
* Checks if a {@link DistributionSet} is currently in use by a target in * Checks if a {@link DistributionSet} is currently in use by a target in

View File

@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.repository;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -64,7 +66,7 @@ public interface EntityFactory {
* *
* @return {@link ActionStatus} object * @return {@link ActionStatus} object
*/ */
ActionStatus generateActionStatus(Action action, Status status, Long occurredAt); ActionStatus generateActionStatus(@NotNull Action action, @NotNull Status status, Long occurredAt);
/** /**
* Generates an {@link ActionStatus} object without persisting it. * Generates an {@link ActionStatus} object without persisting it.
@@ -81,7 +83,7 @@ public interface EntityFactory {
* *
* @return {@link ActionStatus} object * @return {@link ActionStatus} object
*/ */
ActionStatus generateActionStatus(Action action, final Status status, Long occurredAt, ActionStatus generateActionStatus(@NotNull Action action, @NotNull Status status, Long occurredAt,
final Collection<String> messages); final Collection<String> messages);
/** /**
@@ -99,7 +101,8 @@ public interface EntityFactory {
* *
* @return {@link ActionStatus} object * @return {@link ActionStatus} object
*/ */
ActionStatus generateActionStatus(Action action, Status status, Long occurredAt, final String message); ActionStatus generateActionStatus(@NotNull Action action, @NotNull Status status, Long occurredAt,
final String message);
/** /**
* Generates an empty {@link DistributionSet} without persisting it. * Generates an empty {@link DistributionSet} without persisting it.
@@ -124,8 +127,8 @@ public interface EntityFactory {
* *
* @return {@link DistributionSet} object * @return {@link DistributionSet} object
*/ */
DistributionSet generateDistributionSet(String name, String version, String description, DistributionSetType type, DistributionSet generateDistributionSet(@NotNull String name, @NotNull String version, String description,
Collection<SoftwareModule> moduleList); @NotNull DistributionSetType type, Collection<SoftwareModule> moduleList);
/** /**
* Generates an empty {@link DistributionSetMetadata} element without * Generates an empty {@link DistributionSetMetadata} element without
@@ -148,7 +151,8 @@ public interface EntityFactory {
* *
* @return {@link DistributionSetMetadata} object * @return {@link DistributionSetMetadata} object
*/ */
DistributionSetMetadata generateDistributionSetMetadata(DistributionSet distributionSet, String key, String value); DistributionSetMetadata generateDistributionSetMetadata(@NotNull DistributionSet distributionSet,
@NotNull String key, String value);
/** /**
* Generates an empty {@link DistributionSetTag} without persisting it. * Generates an empty {@link DistributionSetTag} without persisting it.
@@ -164,7 +168,7 @@ public interface EntityFactory {
* of the tag * of the tag
* @return {@link DistributionSetTag} object * @return {@link DistributionSetTag} object
*/ */
DistributionSetTag generateDistributionSetTag(String name); DistributionSetTag generateDistributionSetTag(@NotNull String name);
/** /**
* Generates a {@link DistributionSetTag} without persisting it. * Generates a {@link DistributionSetTag} without persisting it.
@@ -177,7 +181,7 @@ public interface EntityFactory {
* of the tag * of the tag
* @return {@link DistributionSetTag} object * @return {@link DistributionSetTag} object
*/ */
DistributionSetTag generateDistributionSetTag(String name, String description, String colour); DistributionSetTag generateDistributionSetTag(@NotNull String name, String description, String colour);
/** /**
* Generates an empty {@link DistributionSetType} without persisting it. * Generates an empty {@link DistributionSetType} without persisting it.
@@ -198,7 +202,7 @@ public interface EntityFactory {
* *
* @return {@link DistributionSetType} object * @return {@link DistributionSetType} object
*/ */
DistributionSetType generateDistributionSetType(String key, String name, String description); DistributionSetType generateDistributionSetType(@NotNull String key, @NotNull String name, String description);
/** /**
* Generates an empty {@link Rollout} without persisting it. * Generates an empty {@link Rollout} without persisting it.
@@ -237,8 +241,8 @@ public interface EntityFactory {
* *
* @return {@link SoftwareModule} object * @return {@link SoftwareModule} object
*/ */
SoftwareModule generateSoftwareModule(SoftwareModuleType type, String name, String version, String description, SoftwareModule generateSoftwareModule(@NotNull SoftwareModuleType type, @NotNull String name,
String vendor); @NotNull String version, String description, String vendor);
/** /**
* Generates an empty {@link SoftwareModuleMetadata} pair without persisting * Generates an empty {@link SoftwareModuleMetadata} pair without persisting
@@ -260,7 +264,8 @@ public interface EntityFactory {
* *
* @return {@link SoftwareModuleMetadata} object * @return {@link SoftwareModuleMetadata} object
*/ */
SoftwareModuleMetadata generateSoftwareModuleMetadata(SoftwareModule softwareModule, String key, String value); SoftwareModuleMetadata generateSoftwareModuleMetadata(@NotNull SoftwareModule softwareModule, @NotNull String key,
String value);
/** /**
* Generates an empty {@link SoftwareModuleType} without persisting it. * Generates an empty {@link SoftwareModuleType} without persisting it.
@@ -283,7 +288,8 @@ public interface EntityFactory {
* *
* @return {@link SoftwareModuleType} object * @return {@link SoftwareModuleType} object
*/ */
SoftwareModuleType generateSoftwareModuleType(String key, String name, String description, int maxAssignments); SoftwareModuleType generateSoftwareModuleType(@NotNull String key, @NotNull String name, String description,
int maxAssignments);
/** /**
* Generates an empty {@link Target} without persisting it. * Generates an empty {@link Target} without persisting it.
@@ -307,7 +313,7 @@ public interface EntityFactory {
* *
* @return {@link Target} object * @return {@link Target} object
*/ */
Target generateTarget(@NotEmpty String controllerID, @NotEmpty String securityToken); Target generateTarget(@NotEmpty String controllerID, String securityToken);
/** /**
* Generates an empty {@link TargetFilterQuery} without persisting it. * Generates an empty {@link TargetFilterQuery} without persisting it.
@@ -354,7 +360,7 @@ public interface EntityFactory {
* of the tag * of the tag
* @return {@link TargetTag} object * @return {@link TargetTag} object
*/ */
TargetTag generateTargetTag(String name); TargetTag generateTargetTag(@NotNull String name);
/** /**
* Generates a {@link TargetTag} without persisting it. * Generates a {@link TargetTag} without persisting it.
@@ -367,7 +373,7 @@ public interface EntityFactory {
* of the tag * of the tag
* @return {@link TargetTag} object * @return {@link TargetTag} object
*/ */
TargetTag generateTargetTag(String name, String description, String colour); TargetTag generateTargetTag(@NotNull String name, String description, String colour);
/** /**
* Generates an empty {@link LocalArtifact} without persisting it. * Generates an empty {@link LocalArtifact} without persisting it.

View File

@@ -155,11 +155,13 @@ public interface SoftwareManagement {
/** /**
* deletes a software module meta data entry. * deletes a software module meta data entry.
* *
* @param id * @param softwareModule
* the ID of the software module meta data to delete * where meta data has to be deleted
* @param key
* of the metda data element
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteSoftwareModuleMetadata(@NotNull SoftwareModule softwareModule, @NotNull String key); void deleteSoftwareModuleMetadata(@NotNull SoftwareModule softwareModule, @NotEmpty String key);
/** /**
* Deletes {@link SoftwareModule}s which is any if the given ids. * Deletes {@link SoftwareModule}s which is any if the given ids.
@@ -251,9 +253,10 @@ public interface SoftwareManagement {
/** /**
* finds a single software module meta data by its id. * finds a single software module meta data by its id.
* *
* @param id * @param softwareModule
* the id of the software module meta data containing the meta * where meta data has to be found
* data key and the ID of the software module * @param key
* of the meta data element
* @return the found SoftwareModuleMetadata or {@code null} if not exits * @return the found SoftwareModuleMetadata or {@code null} if not exits
* @throws EntityNotFoundException * @throws EntityNotFoundException
* in case the meta data does not exists for the given key * in case the meta data does not exists for the given key
@@ -280,8 +283,8 @@ public interface SoftwareManagement {
* *
* @param softwareModuleId * @param softwareModuleId
* the software module id to retrieve the meta data from * the software module id to retrieve the meta data from
* @param spec * @param rsqlParam
* the specification to filter the result * filter definition in RSQL syntax
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @return a paged result of all meta data entries for a given software * @return a paged result of all meta data entries for a given software
@@ -346,8 +349,8 @@ public interface SoftwareManagement {
/** /**
* Retrieves all {@link SoftwareModule}s with a given specification. * Retrieves all {@link SoftwareModule}s with a given specification.
* *
* @param spec * @param rsqlParam
* the specification to filter the software modules * filter definition in RSQL syntax
* @param pageable * @param pageable
* pagination parameter * pagination parameter
* @return the found {@link SoftwareModule}s * @return the found {@link SoftwareModule}s
@@ -392,7 +395,7 @@ public interface SoftwareManagement {
* {@link SoftwareModuleType#getKey()} * {@link SoftwareModuleType#getKey()}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
SoftwareModuleType findSoftwareModuleTypeByKey(@NotNull String key); SoftwareModuleType findSoftwareModuleTypeByKey(@NotEmpty String key);
/** /**
* *
@@ -415,8 +418,8 @@ public interface SoftwareManagement {
/** /**
* Retrieves all {@link SoftwareModuleType}s with a given specification. * Retrieves all {@link SoftwareModuleType}s with a given specification.
* *
* @param spec * @param rsqlParam
* the specification to filter the software modules types * filter definition in RSQL syntax
* @param pageable * @param pageable
* pagination parameter * pagination parameter
* @return the found {@link SoftwareModuleType}s * @return the found {@link SoftwareModuleType}s

View File

@@ -63,7 +63,8 @@ public interface SystemManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
TenantMetaData getTenantMetadata(); TenantMetaData getTenantMetadata();
/** /**
@@ -93,4 +94,14 @@ public interface SystemManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
TenantMetaData updateTenantMetadata(@NotNull TenantMetaData metaData); TenantMetaData updateTenantMetadata(@NotNull TenantMetaData metaData);
/**
* Returns {@link TenantMetaData} of given tenant ID.
*
* @param tenantId
* to retrieve data for
* @return {@link TenantMetaData} of given tenant
*/
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
TenantMetaData getTenantMetadata(@NotNull Long tenantId);
} }

View File

@@ -34,6 +34,8 @@ public class RolloutGroupCreatedEvent extends AbstractDistributedEvent {
* the revision of the event * the revision of the event
* @param rolloutId * @param rolloutId
* the ID of the rollout the group has been created * the ID of the rollout the group has been created
* @param rolloutGroupId
* identifier of this group
* @param totalRolloutGroup * @param totalRolloutGroup
* the total number of rollout groups for this rollout * the total number of rollout groups for this rollout
* @param createdRolloutGroup * @param createdRolloutGroup

View File

@@ -8,11 +8,11 @@
*/ */
package org.eclipse.hawkbit.repository.eventbus.event; package org.eclipse.hawkbit.repository.eventbus.event;
import java.net.URI;
import java.util.Collection; import java.util.Collection;
import org.eclipse.hawkbit.eventbus.event.DefaultEvent; import org.eclipse.hawkbit.eventbus.event.DefaultEvent;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
/** /**
* Event that gets sent when a distribution set gets assigned to a target. * Event that gets sent when a distribution set gets assigned to a target.
@@ -21,10 +21,8 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
public class TargetAssignDistributionSetEvent extends DefaultEvent { public class TargetAssignDistributionSetEvent extends DefaultEvent {
private final Collection<SoftwareModule> softwareModules; private final Collection<SoftwareModule> softwareModules;
private final String controllerId; private final Target target;
private final Long actionId; private final Long actionId;
private final URI targetAdress;
private final String targetToken;
/** /**
* Creates a new {@link TargetAssignDistributionSetEvent}. * Creates a new {@link TargetAssignDistributionSetEvent}.
@@ -33,26 +31,19 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
* the revision of the event * the revision of the event
* @param tenant * @param tenant
* the tenant of the event * the tenant of the event
* @param controllerId * @param target
* the ID of the controller * the assigned {@link Target}
* @param actionId * @param actionId
* the action id of the assignment * the action id of the assignment
* @param softwareModules * @param softwareModules
* the software modules which have been assigned to the target * the software modules which have been assigned to the target
* @param targetAdress
* the targetAdress of the target
* @param targetToken
* the authentication token of the target
*/ */
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId, public TargetAssignDistributionSetEvent(final long revision, final String tenant, final Target target,
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress, final Long actionId, final Collection<SoftwareModule> softwareModules) {
final String targetToken) {
super(revision, tenant); super(revision, tenant);
this.controllerId = controllerId; this.target = target;
this.actionId = actionId; this.actionId = actionId;
this.softwareModules = softwareModules; this.softwareModules = softwareModules;
this.targetAdress = targetAdress;
this.targetToken = targetToken;
} }
/** /**
@@ -63,11 +54,11 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
} }
/** /**
* @return the controllerId of the Target which has been assigned to the * @return the {@link Target} which has been assigned to the distribution
* distribution set * set
*/ */
public String getControllerId() { public Target getTarget() {
return controllerId; return target;
} }
/** /**
@@ -76,12 +67,4 @@ public class TargetAssignDistributionSetEvent extends DefaultEvent {
public Collection<SoftwareModule> getSoftwareModules() { public Collection<SoftwareModule> getSoftwareModules() {
return softwareModules; return softwareModules;
} }
public URI getTargetAdress() {
return targetAdress;
}
public String getTargetToken() {
return targetToken;
}
} }

View File

@@ -47,10 +47,10 @@ public interface ActionStatus extends TenantAwareBaseEntity {
* @return current {@link Status#DOWNLOAD} progress if known by the update * @return current {@link Status#DOWNLOAD} progress if known by the update
* server. * server.
*/ */
int getDownloadProgressPercent(); short getDownloadProgressPercent();
/** /**
* @return list of message entries that can be added to the * @return immutable list of message entries that in the
* {@link ActionStatus}. * {@link ActionStatus}.
*/ */
List<String> getMessages(); List<String> getMessages();

View File

@@ -37,6 +37,6 @@ public interface Artifact extends TenantAwareBaseEntity {
/** /**
* @return size of the artifact in bytes. * @return size of the artifact in bytes.
*/ */
Long getSize(); long getSize();
} }

View File

@@ -60,7 +60,7 @@ public class AssignedSoftwareModule implements Serializable {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + (assigned ? 1231 : 1237); result = prime * result + (assigned ? 1231 : 1237);
result = prime * result + (softwareModule == null ? 0 : softwareModule.hashCode()); result = prime * result + ((softwareModule == null) ? 0 : softwareModule.hashCode());
return result; return result;
} }
@@ -72,7 +72,7 @@ public class AssignedSoftwareModule implements Serializable {
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (!(obj instanceof AssignedSoftwareModule)) { if (getClass() != obj.getClass()) {
return false; return false;
} }
final AssignedSoftwareModule other = (AssignedSoftwareModule) obj; final AssignedSoftwareModule other = (AssignedSoftwareModule) obj;

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.repository.model; package org.eclipse.hawkbit.repository.model;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -82,14 +83,22 @@ public class AssignmentResult<T extends BaseEntity> {
* @return {@link List} of assigned entity. * @return {@link List} of assigned entity.
*/ */
public List<T> getAssignedEntity() { public List<T> getAssignedEntity() {
return assignedEntity; if (assignedEntity == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(assignedEntity);
} }
/** /**
* @return {@link List} of unassigned entity. * @return {@link List} of unassigned entity.
*/ */
public List<T> getUnassignedEntity() { public List<T> getUnassignedEntity() {
return unassignedEntity; if (unassignedEntity == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(unassignedEntity);
} }
} }

View File

@@ -44,6 +44,6 @@ public interface BaseEntity extends Serializable, Identifiable<Long> {
/** /**
* @return version of the {@link BaseEntity}. * @return version of the {@link BaseEntity}.
*/ */
long getOptLockRevision(); int getOptLockRevision();
} }

View File

@@ -26,10 +26,25 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
public interface DistributionSet extends NamedVersionedEntity { public interface DistributionSet extends NamedVersionedEntity {
/** /**
* @return {@link Set} of assigned {@link DistributionSetTag}s. * @return immutable {@link Set} of assigned {@link DistributionSetTag}s.
*/ */
Set<DistributionSetTag> getTags(); Set<DistributionSetTag> getTags();
/**
* @param tag
* to add
* @return <code>true</code> if tag could be added sucessfully (i.e. was not
* already in the list).
*/
boolean addTag(final DistributionSetTag tag);
/**
* @param tag
* to remove
* @return <code>true</code> if tag was in the list and removed
*/
boolean removeTag(final DistributionSetTag tag);
/** /**
* @return <code>true</code> if the set is deleted and only kept for history * @return <code>true</code> if the set is deleted and only kept for history
* purposes. * purposes.

View File

@@ -17,8 +17,8 @@ import java.util.List;
public interface DistributionSetTag extends Tag { public interface DistributionSetTag extends Tag {
/** /**
* @return {@link List} of {@link DistributionSet}s this {@link Tag} is * @return immutable {@link List} of {@link DistributionSet}s this
* assigned to. * {@link Tag} is assigned to.
*/ */
List<DistributionSet> getAssignedToDistributionSet(); List<DistributionSet> getAssignedToDistributionSet();

View File

@@ -27,15 +27,15 @@ public interface DistributionSetType extends NamedEntity {
boolean isDeleted(); boolean isDeleted();
/** /**
* @return set of {@link SoftwareModuleType}s that need to be in a * @return immutable set of {@link SoftwareModuleType}s that need to be in a
* {@link DistributionSet} of this type to be * {@link DistributionSet} of this type to be
* {@link DistributionSet#isComplete()}. * {@link DistributionSet#isComplete()}.
*/ */
Set<SoftwareModuleType> getMandatoryModuleTypes(); Set<SoftwareModuleType> getMandatoryModuleTypes();
/** /**
* @return set of optional {@link SoftwareModuleType}s that can be in a * @return immutable set of optional {@link SoftwareModuleType}s that can be
* {@link DistributionSet} of this type. * in a {@link DistributionSet} of this type.
*/ */
Set<SoftwareModuleType> getOptionalModuleTypes(); Set<SoftwareModuleType> getOptionalModuleTypes();

View File

@@ -11,6 +11,9 @@ package org.eclipse.hawkbit.repository.model;
/** /**
* Interface for the entity interceptor lifecycle. * Interface for the entity interceptor lifecycle.
*/ */
// Exception squid:EmptyStatementUsageCheck - don't want to force users to
// impelemnt all methods
@SuppressWarnings("squid:EmptyStatementUsageCheck")
public interface EntityInterceptor { public interface EntityInterceptor {
/** /**

View File

@@ -38,7 +38,7 @@ public interface Rollout extends NamedEntity {
void setDistributionSet(DistributionSet distributionSet); void setDistributionSet(DistributionSet distributionSet);
/** /**
* @return list of deployment groups of the rollout. * @return immutable list of deployment groups of the rollout.
*/ */
List<RolloutGroup> getRolloutGroups(); List<RolloutGroup> getRolloutGroups();

View File

@@ -155,7 +155,7 @@ public interface RolloutGroup extends NamedEntity {
/** /**
* @return the total amount of targets containing in this group * @return the total amount of targets containing in this group
*/ */
long getTotalTargets(); int getTotalTargets();
/** /**
* @return the totalTargetCountStatus * @return the totalTargetCountStatus

View File

@@ -11,6 +11,10 @@ package org.eclipse.hawkbit.repository.model;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
/**
* Software package as sub element of a {@link DistributionSet}.
*
*/
public interface SoftwareModule extends NamedVersionedEntity { public interface SoftwareModule extends NamedVersionedEntity {
/** /**
@@ -40,12 +44,12 @@ public interface SoftwareModule extends NamedVersionedEntity {
Optional<LocalArtifact> getLocalArtifactByFilename(String fileName); Optional<LocalArtifact> getLocalArtifactByFilename(String fileName);
/** /**
* @return the artifacts * @return immutable list of all artifacts
*/ */
List<Artifact> getArtifacts(); List<Artifact> getArtifacts();
/** /**
* @return local artifacts only * @return immutable list of local artifacts only
*/ */
List<LocalArtifact> getLocalArtifacts(); List<LocalArtifact> getLocalArtifacts();
@@ -104,7 +108,8 @@ public interface SoftwareModule extends NamedVersionedEntity {
List<SoftwareModuleMetadata> getMetadata(); List<SoftwareModuleMetadata> getMetadata();
/** /**
* @return the assignedTo * @return immutable list of {@link DistributionSet}s the module is assigned
* to
*/ */
List<DistributionSet> getAssignedTo(); List<DistributionSet> getAssignedTo();

View File

@@ -32,12 +32,12 @@ public interface Target extends NamedEntity {
String getControllerId(); String getControllerId();
/** /**
* @return assigned {@link TargetTag}s. * @return immutable set of assigned {@link TargetTag}s.
*/ */
Set<TargetTag> getTags(); Set<TargetTag> getTags();
/** /**
* @return {@link Action} history of the {@link Target}. * @return immutable {@link Action} history of the {@link Target}.
*/ */
List<Action> getActions(); List<Action> getActions();
@@ -64,4 +64,19 @@ public interface Target extends NamedEntity {
*/ */
void setSecurityToken(String token); void setSecurityToken(String token);
/**
* @param tag
* to add
* @return <code>true</code> if tag could be added sucessfully (i.e. was not
* already in the list).
*/
public boolean addTag(TargetTag tag);
/**
* @param tag
* to remove
* @return <code>true</code> if tag was in the list and removed
*/
public boolean removeTag(TargetTag tag);
} }

View File

@@ -36,7 +36,8 @@ public interface TargetInfo extends Serializable {
/** /**
* @return time in {@link TimeUnit#MILLISECONDS} GMT when the {@link Target} * @return time in {@link TimeUnit#MILLISECONDS} GMT when the {@link Target}
* polled the server the last time. * polled the server the last time or <code>null</code> if target
* has never queried yet.
*/ */
Long getLastTargetQuery(); Long getLastTargetQuery();

View File

@@ -17,7 +17,7 @@ import java.util.List;
public interface TargetTag extends Tag { public interface TargetTag extends Tag {
/** /**
* @return {@link List} of targets assigned to this {@link Tag}. * @return immutable {@link List} of targets assigned to this {@link Tag}.
*/ */
List<Target> getAssignedToTargets(); List<Target> getAssignedToTargets();

View File

@@ -0,0 +1,34 @@
/**
* 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.rsql;
/**
* An interface declaration which validates an RSQL based query syntax and
* allows providing suggestions e.g. in case of syntax errors or current cursor
* position.
*/
@FunctionalInterface
public interface RsqlValidationOracle {
/**
* Parses and validates an given RSQL based query syntax and provides
* suggestion based on syntax error and cursor positioning.
*
* @param rsqlQuery
* an RSQL based query string to parse.
* @param cursorPosition
* the position of the cursor to retrieve suggestions at the
* position. {@code -1} indicates for no cursor suggestion
* @return a validation oracle context providing information about syntax
* errors and possible suggestions for fixing the syntax error or at
* the cursor position to replace tokens
*/
ValidationOracleContext suggest(final String rsqlQuery, final int cursorPosition);
}

Some files were not shown because too many files have changed in this diff Show More