merge master

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>
This commit is contained in:
Michael Hirsch
2016-09-30 09:28:20 +02:00
183 changed files with 3246 additions and 2267 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 />
# 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.
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
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
*/
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.ui.HawkbitUI;
import org.eclipse.hawkbit.ui.UIEventProvider;
import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy;
@@ -38,8 +40,8 @@ public class MyUI extends HawkbitUI {
private static final long serialVersionUID = 1L;
@Autowired
public MyUI(final EventBus systemEventBus, final org.vaadin.spring.events.EventBus.SessionEventBus eventBus,
final UIEventProvider provider) {
super(new DelayedEventBusPushStrategy(eventBus, systemEventBus, provider));
public MyUI(final ScheduledExecutorService executorService, final EventBus systemEventBus,
final org.vaadin.spring.events.EventBus.SessionEventBus eventBus, final UIEventProvider 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.HttpClients;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.Artifact.UrlProtocol;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
@@ -206,12 +205,12 @@ public class DeviceSimulatorUpdater {
private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status,
final Artifact artifact) {
if (artifact.getUrls().containsKey(UrlProtocol.HTTPS)) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTPS), targetToken,
artifact.getHashes().getSha1(), artifact.getSize()));
} else if (artifact.getUrls().containsKey(UrlProtocol.HTTP)) {
status.add(downloadUrl(artifact.getUrls().get(UrlProtocol.HTTP), targetToken,
artifact.getHashes().getSha1(), artifact.getSize()));
if (artifact.getUrls().containsKey("https")) {
status.add(downloadUrl(artifact.getUrls().get("https"), targetToken, artifact.getHashes().getSha1(),
artifact.getSize()));
} else if (artifact.getUrls().containsKey("http")) {
status.add(downloadUrl(artifact.getUrls().get("http"), targetToken, artifact.getHashes().getSha1(),
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 java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
@@ -36,6 +35,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
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
* server.
@@ -200,13 +201,13 @@ public class AmqpConfiguration {
}
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());
return args;
}
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-max-length", 100_000);
return args;

View File

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

View File

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

@@ -7,17 +7,15 @@
# http://www.eclipse.org/legal/epl-v10.html
#
# User Security
security.user.name=admin
security.user.password=admin
# DDI authentication configuration
hawkbit.server.ddi.security.authentication.anonymous.enabled=true
hawkbit.server.ddi.security.authentication.targettoken.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.servlet.productionMode=false

View File

@@ -50,11 +50,21 @@ public class SoftwareModuleTypeBuilder {
return this;
}
/**
* @param description
* of the module
* @return the builder itself
*/
public SoftwareModuleTypeBuilder description(final String description) {
this.description = description;
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) {
this.maxAssignments = maxAssignments;
return this;
@@ -99,4 +109,4 @@ public class SoftwareModuleTypeBuilder {
return body;
}
}
}

View File

@@ -227,7 +227,7 @@ public class ArtifactStore implements ArtifactRepository {
* @return a paged list of artifacts mapped from the given 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.
* @return a mapped artifact from the given dbFile
*/
private GridFsArtifact map(final GridFSFile fsFile) {
private static GridFsArtifact map(final GridFSFile fsFile) {
if (fsFile == null) {
return null;
}

View File

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

View File

@@ -13,7 +13,9 @@ import java.util.ArrayList;
import org.eclipse.hawkbit.im.authentication.MultitenancyIndicator;
import org.eclipse.hawkbit.im.authentication.PermissionUtils;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -34,6 +36,9 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@ConditionalOnMissingBean(UserDetailsService.class)
public class InMemoryUserManagementConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
final DaoAuthenticationProvider userDaoAuthenticationProvider = new TenantDaoAuthenticationProvider();
@@ -49,7 +54,8 @@ public class InMemoryUserManagementConfiguration extends GlobalAuthenticationCon
public UserDetailsService userDetailsService() {
final InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(new ArrayList<>());
inMemoryUserDetailsManager.setAuthenticationManager(null);
inMemoryUserDetailsManager.createUser(new User("admin", "admin", PermissionUtils.createAllAuthorityList()));
inMemoryUserDetailsManager.createUser(new User(securityProperties.getUser().getName(),
securityProperties.getUser().getPassword(), PermissionUtils.createAllAuthorityList()));
return inMemoryUserDetailsManager;
}

View File

@@ -148,7 +148,7 @@ public class SecurityManagedConfiguration {
private DdiSecurityProperties ddiSecurityConfiguration;
@Autowired
private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties;
private SecurityProperties springSecurityProperties;
@Autowired
private SystemSecurityContext systemSecurityContext;
@@ -478,7 +478,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
public void onAuthenticationSuccess(final Authentication authentication) throws Exception {
if (authentication.getClass().equals(TenantUserPasswordAuthenticationToken.class)) {
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(),
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata,
((TenantUserPasswordAuthenticationToken) authentication).getTenant().toString());
} else if (authentication.getClass().equals(UsernamePasswordAuthenticationToken.class)) {
// TODO: vaadin4spring-ext-security does not give us the
@@ -489,7 +489,7 @@ class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends
// vaadin4spring 0.0.7 because it
// has been fixed.
final String defaultTenant = "DEFAULT";
systemSecurityContext.runAsSystemAsTenant(() -> systemManagement.getTenantMetadata(), defaultTenant);
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant);
}
super.onAuthenticationSuccess(authentication);
@@ -526,7 +526,7 @@ class AuthenticationSuccessTenantMetadataCreationFilter implements Filter {
private void lazyCreateTenantMetadata() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
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 org.eclipse.hawkbit.HawkbitServerProperties;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties;
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.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -22,35 +24,41 @@ import org.springframework.context.annotation.Configuration;
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
@EnableConfigurationProperties(HawkbitServerProperties.class)
@EnableConfigurationProperties({ HawkbitServerProperties.class, ArtifactUrlHandlerProperties.class })
public class PropertyHostnameResolverAutoConfiguration {
@Autowired
private HawkbitServerProperties serverProperties;
/**
* @param serverProperties
* to get the servers URL
* @return the default autoconfigure hostname resolver implementation which
* is property based specified by the property {@link #url}
*/
@Bean
@ConditionalOnMissingBean(value = HostnameResolver.class)
public HostnameResolver hostnameResolver() {
return new HostnameResolver() {
@Override
public URL resolveHostname() {
try {
return new URL(serverProperties.getUrl());
} catch (final MalformedURLException e) {
throw Throwables.propagate(e);
}
public HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) {
return () -> {
try {
return new URL(serverProperties.getUrl());
} 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

@@ -10,6 +10,10 @@
# Displayed basic auth realm
security.basic.realm=HawkBit
# User Security
security.user.name=admin
security.user.password=admin
# JPA / Datasource
spring.jpa.eclipselink.eclipselink.weaving=false
spring.jpa.database=H2
@@ -37,9 +41,25 @@ hawkbit.controller.maxPollingTime=23:59:59
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
# Configuration for RabbitMQ integration
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl
hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter
hawkbit.dmf.rabbitmq.receiverQueue=dmf_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>
</dependency>
<!-- 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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -9,8 +9,18 @@
package org.eclipse.hawkbit.api;
/**
* Represented the supported protocols for artifact url's.
* hawkBit API type.
*
*/
public enum UrlProtocol {
COAP, HTTP, HTTPS
public enum ApiType {
/**
* 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;
import java.util.List;
/**
* Interface declaration of the {@link ArtifactUrlHandler} which generates the
* URLs to specific artifacts.
*
*/
@FunctionalInterface
public interface ArtifactUrlHandler {
/**
* Returns a generated download URL for a given artifact parameters for a
* specific protocol.
*
* @param controllerId
* the authenticated controller id
* @param softwareModuleId
* the softwareModuleId belonging to the artifact
* @param filename
* the filename of the artifact
* @param sha1Hash
* the sha1Hash of the artifact
* @param protocol
* the protocol the URL should be generated
* @param placeholder
* data for URL generation
* @param api
* given protocol that URL needs to support
*
* @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,
final UrlProtocol protocol);
/**
* @param protocol
* to check support for
* @return <code>true</code> of the handler supports given protocol.
*/
boolean protocolSupported(UrlProtocol protocol);
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api);
}

View File

@@ -8,90 +8,153 @@
*/
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 com.google.common.collect.Lists;
/**
* Artifact handler properties class for holding all supported protocols with
* host, ip, port and download pattern.
*
* @see PropertyBasedArtifactUrlHandler
*/
@ConfigurationProperties("hawkbit.artifact.url")
public class ArtifactUrlHandlerProperties {
private final Http http = new Http();
private final Https https = new Https();
private final Coap coap = new Coap();
public Http getHttp() {
return http;
}
public Https getHttps() {
return https;
}
public Coap getCoap() {
return coap;
}
/**
* Rel as key and complete protocol as value.
*/
private final Map<String, UrlProtocol> protocols = new HashMap<>();
/**
* @param protocol
* 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
* Protocol specific properties to generate URLs accordingly.
*
*/
public ProtocolProperties getProperties(final String protocol) {
switch (protocol) {
case "http":
return getHttp();
case "https":
return getHttps();
case "coap":
return getCoap();
default:
return null;
}
}
public static class UrlProtocol {
/**
* Object to hold the properties for the HTTP protocol.
*/
public static class Http extends DefaultProtocolProperties {
private static final int DEFAULT_HTTP_PORT = 8080;
/**
* Constructor.
* Set to true if enabled.
*/
public 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 Https extends DefaultProtocolProperties {
private boolean enabled = true;
/**
* Constructor.
* Hypermedia rel value for this protocol.
*/
public Https() {
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 {
private String rel = "download-http";
/**
* 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() {
setPattern("{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}");
setPort("5683");
private String ref = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* 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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
import com.google.common.base.Strings;
import com.google.common.net.UrlEscapers;
/**
* 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 {
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 PORT_PLACEHOLDER = "port";
private static final String HOSTNAME_PLACEHOLDER = "hostname";
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
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 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 ArtifactUrlHandlerProperties urlHandlerProperties;
private final ArtifactUrlHandlerProperties urlHandlerProperties;
@Autowired
private TenantAware tenantAware;
/**
* @param urlHandlerProperties
* for URL generation configuration
*/
public PropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties) {
this.urlHandlerProperties = urlHandlerProperties;
}
@Override
public String getUrl(final String targetId, final Long softwareModuleId, final String filename,
final String sha1Hash, final UrlProtocol protocol) {
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return null;
}
return urlHandlerProperties.getProtocols().entrySet().stream()
.filter(entry -> entry.getValue().getSupports().contains(api))
.filter(entry -> entry.getValue().isEnabled())
.map(entry -> new ArtifactUrl(entry.getValue().getProtocol(), 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) {
if (entry.getKey().equals(PORT_PLACEHOLDER)) {
urlPattern = urlPattern.replace(":{" + entry.getKey() + "}",
@@ -69,30 +94,29 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
return urlPattern;
}
private Map<String, String> getReplaceMap(final String targetId, final Long softwareModuleId, final String filename,
final String sha1Hash, final String protocol, final ProtocolProperties properties) {
private static Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder) {
final Map<String, String> replaceMap = new HashMap<>();
replaceMap.put(IP_PLACEHOLDER, properties.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, properties.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER, filename);
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, sha1Hash);
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol);
replaceMap.put(PORT_PLACEHOLDER, properties.getPort());
replaceMap.put(TENANT_PLACEHOLDER, tenantAware.getCurrentTenant());
replaceMap.put(TARGET_ID_PLACEHOLDER, targetId);
replaceMap.put(SOFTWARE_MODULE_ID_PLACDEHOLDER, String.valueOf(softwareModuleId));
replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
UrlEscapers.urlFragmentEscaper().escape(placeholder.getSoftwareData().getFilename()));
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
replaceMap.put(PORT_PLACEHOLDER, protocol.getPort() == null ? null : String.valueOf(protocol.getPort()));
replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
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;
}
@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
* 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) {
this.keyName = key;
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", "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, 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, 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
*/
public DdiCancelActionToStop(final String stopId) {
super();
this.stopId = stopId;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -65,7 +66,11 @@ public class DdiChunk {
}
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
*/
public DdiConfig(final DdiPolling polling) {
super();
this.polling = polling;
}

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.ddi.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -57,7 +58,11 @@ public class DdiStatus {
}
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 ExecutionStatus(final String name) {
ExecutionStatus(final String name) {
this.name = name;
}

View File

@@ -37,39 +37,45 @@ public interface DdiRootControllerRestApi {
/**
* 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
* @param softwareModuleId
* of the software module
* @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 })
ResponseEntity<List<org.eclipse.hawkbit.ddi.json.model.DdiArtifact>> getSoftwareModulesArtifacts(
@PathVariable("tenant") final String tenant, @PathVariable("targetid") final String targetid,
ResponseEntity<List<DdiArtifact>> getSoftwareModulesArtifacts(@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId);
/**
* Root resource for an individual {@link Target}.
*
* @param targetid
* @param tenant
* of the request
* @param controllerId
* of the target that matches to controller id
* @param request
* the HTTP request injected by spring
* @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 })
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
* partial (as specified by RFC7233 (Range Requests)) download request.
*
* @param targetid
* of the related target
* @param tenant
* of the request
* @param controllerId
* of the target
* @param softwareModuleId
* of the parent software module
* @param fileName
@@ -83,17 +89,19 @@ public interface DdiRootControllerRestApi {
* {@link HttpStatus#OK} or in case of partial download
* {@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,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName);
/**
* Handles GET {@link DdiArtifact} MD5 checksum file download request.
*
* @param targetid
* of the related target
* @param tenant
* of the request
* @param controllerId
* of the target
* @param softwareModuleId
* of the parent software module
* @param fileName
@@ -106,18 +114,20 @@ public interface DdiRootControllerRestApi {
* @return {@link ResponseEntity} with status {@link HttpStatus#OK} if
* 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)
ResponseEntity<Void> downloadArtifactMd5(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid,
@PathVariable("controllerId") final String controllerId,
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("fileName") final String fileName);
/**
* Resource for software module.
*
* @param targetid
* of the target that matches to controller id
* @param tenant
* of the request
* @param controllerId
* of the target
* @param actionId
* of the {@link DdiDeploymentBase} that matches to active
* actions.
@@ -129,19 +139,21 @@ public interface DdiRootControllerRestApi {
* the HTTP request injected by spring
* @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)
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,
@RequestParam(value = "c", required = false, defaultValue = "-1") final int resource);
/**
* This is the feedback channel for the {@link DdiDeploymentBase} action.
*
* @param tenant
* of the client
* @param feedback
* to provide
* @param targetid
* @param controllerId
* of the target that matches to controller id
* @param actionId
* of the action we have feedback for
@@ -150,33 +162,37 @@ public interface DdiRootControllerRestApi {
*
* @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)
ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid,
ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@PathVariable("actionId") @NotEmpty final Long actionId);
/**
* This is the feedback channel for the config data action.
*
* @param tenant
* of the client
* @param configData
* as body
* @param targetid
* @param controllerId
* to provide data for
* @param request
* the HTTP request injected by spring
*
* @return status of the request
*/
@RequestMapping(value = "/{targetid}/"
@RequestMapping(value = "/{controllerId}/"
+ DdiRestConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant,
@Valid final DdiConfigData configData, @PathVariable("targetid") final String targetid);
ResponseEntity<Void> putConfigData(@Valid final DdiConfigData configData,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId);
/**
* RequestMethod.GET method for the {@link DdiCancel} action.
*
* @param targetid
* @param tenant
* of the request
* @param controllerId
* ID of the calling target
* @param actionId
* of the action
@@ -185,19 +201,21 @@ public interface DdiRootControllerRestApi {
*
* @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)
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);
/**
* RequestMethod.POST method receiving the {@link DdiActionFeedback} from
* the target.
*
* @param tenant
* of the client
* @param feedback
* the {@link DdiActionFeedback} from the target.
* @param targetid
* @param controllerId
* the ID of the calling target
* @param actionId
* of the action we have feedback for
@@ -207,10 +225,11 @@ public interface DdiRootControllerRestApi {
* @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)
ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant,
@Valid final DdiActionFeedback feedback, @PathVariable("targetid") @NotEmpty final String targetid,
ResponseEntity<Void> postCancelActionFeedback(@Valid final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@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.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.RequestMapping;
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
* request.
*
*
* @param tenant
* name of the client
* @param fileName
* to search for
* @param targetid
@@ -40,12 +42,14 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME
+ "/{fileName}")
@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);
/**
* Handles GET MD5 checksum file download request.
*
* @param tenant
* name of the client
* @param fileName
* to search for
*
@@ -54,7 +58,7 @@ public interface DdiDlArtifactStoreControllerRestApi {
@RequestMapping(method = RequestMethod.GET, value = DdiDlRestConstants.ARTIFACT_DOWNLOAD_BY_FILENAME + "/{fileName}"
+ DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX)
@ResponseBody
public ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
ResponseEntity<Void> downloadArtifactMD5ByFilename(@PathVariable("tenant") final String tenant,
@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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.api.ApiType;
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.json.model.DdiArtifact;
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.DdiPolling;
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.LocalArtifact;
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,
final ArtifactUrlHandler artifactUrlHandler) {
static List<DdiChunk> createChunks(final Target target, final Action uAction,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
return uAction.getDistributionSet().getModules().stream()
.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());
}
@@ -69,43 +71,42 @@ public final class DataConversionHelper {
/**
* Creates all (rest) artifacts for a given software module.
*
* @param targetid
* of the target
* @param target
* for create URLs for
* @param 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>.
*/
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 ArtifactUrlHandler artifactUrlHandler) {
final List<DdiArtifact> files = new ArrayList<>();
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
module.getLocalArtifacts()
.forEach(artifact -> files.add(createArtifact(targetid, artifactUrlHandler, artifact)));
return files;
return module.getLocalArtifacts().stream()
.map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement))
.collect(Collectors.toList());
}
private static DdiArtifact createArtifact(final String targetid, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact) {
private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler,
final LocalArtifact artifact, final SystemManagement systemManagement) {
final DdiArtifact file = new DdiArtifact();
file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
file.setFilename(artifact.getFilename());
file.setSize(artifact.getSize());
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
final String linkHttp = artifactUrlHandler.getUrl(targetid, artifact.getSoftwareModule().getId(),
artifact.getFilename(), artifact.getSha1Hash(), UrlProtocol.HTTP);
file.add(new Link(linkHttp).withRel("download-http"));
file.add(new Link(linkHttp + DdiDlRestConstants.ARTIFACT_MD5_DWNL_SUFFIX).withRel("md5sum-http"));
}
artifactUrlHandler
.getUrls(new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(),
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;
}
static DdiControllerBase fromTarget(final Target target, final Optional<Action> action,
@@ -133,8 +134,8 @@ public final class DataConversionHelper {
}
if (target.getTargetInfo().isRequestControllerAttributes()) {
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant())
.putConfigData(tenantAware.getCurrentTenant(), null, target.getControllerId()))
result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()).putConfigData(null,
tenantAware.getCurrentTenant(), target.getControllerId()))
.withRel(DdiRestConstants.CONFIG_DATA_ACTION));
}
return result;

View File

@@ -35,7 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpStatus;
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.RestController;
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.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -88,6 +89,9 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Autowired
private TenantAware tenantAware;
@Autowired
private SystemManagement systemManagement;
@Autowired
private ArtifactUrlHandler artifactUrlHandler;
@@ -99,9 +103,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
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) {
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);
@@ -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);
}
@Override
public ResponseEntity<DdiControllerBase> getControllerBase(@PathVariable("tenant") final String tenant,
@PathVariable("targetid") final String targetid) {
LOG.debug("getControllerBase({})", targetid);
@PathVariable("controllerId") final String controllerId) {
LOG.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid, IpUtil
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
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,
System.currentTimeMillis(), IpUtil.getClientIpFromRequest(
requestResponseContextHolder.getHttpServletRequest(), securityProperties));
@@ -138,12 +146,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
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("fileName") final String fileName) {
ResponseEntity<InputStream> result;
final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil
final Target target = controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
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);
result = new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
// Exception squid:S3655 - Optional access is checked in checkModule
// subroutine
@SuppressWarnings("squid:S3655")
final LocalArtifact artifact = module.getLocalArtifactByFilename(fileName).get();
final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact);
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match");
@@ -196,11 +209,14 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
// Exception squid:S3655 - Optional access is checked in checkModule
// subroutine
@SuppressWarnings("squid:S3655")
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("fileName") final String fileName) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil
controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
@@ -223,12 +239,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Override
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,
@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));
final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -239,14 +255,15 @@ public class DdiRootController implements DdiRootControllerRestApi {
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 DdiDeploymentBase base = new DdiDeploymentBase(Long.toString(action.getId()),
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
+ "Target retrieved update action and should start now the download.");
@@ -258,12 +275,12 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> postBasedeploymentActionFeedback(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiActionFeedback feedback, @PathVariable("targetid") final String targetid,
public ResponseEntity<Void> postBasedeploymentActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId,
@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));
if (!actionId.equals(feedback.getId())) {
@@ -285,13 +302,14 @@ public class DdiRootController implements DdiRootControllerRestApi {
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);
}
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 ActionStatus actionStatus = entityFactory.generateActionStatus();
@@ -300,22 +318,22 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) {
case CANCELED:
LOG.debug("Controller confirmed cancel (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.debug("Controller confirmed cancel (actionid: {}, controllerId: {}) as we got {} report.", actionid,
controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.CANCELED);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed cancelation.");
break;
case REJECTED:
LOG.info("Controller reported internal error (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.info("Controller reported internal error (actionid: {}, controllerId: {}) as we got {} report.",
actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.WARNING);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target REJECTED update.");
break;
case CLOSED:
handleClosedUpdateStatus(feedback, targetid, actionid, actionStatus);
handleClosedUpdateStatus(feedback, controllerId, actionid, actionStatus);
break;
default:
handleDefaultUpdateStatus(feedback, targetid, actionid, actionStatus);
handleDefaultUpdateStatus(feedback, controllerId, actionid, actionStatus);
break;
}
@@ -331,19 +349,19 @@ public class DdiRootController implements DdiRootControllerRestApi {
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) {
LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid,
targetid, feedback.getStatus().getExecution());
LOG.debug("Controller reported intermediate status (actionid: {}, controllerId: {}) as we got {} report.",
actionid, controllerId, feedback.getStatus().getExecution());
actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage(
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) {
LOG.debug("Controller reported closed (actionid: {}, targetid: {}) as we got {} report.", actionid, targetid,
feedback.getStatus().getExecution());
LOG.debug("Controller reported closed (actionid: {}, controllerId: {}) as we got {} report.", actionid,
controllerId, feedback.getStatus().getExecution());
if (feedback.getStatus().getResult().getFinished() == FinalResult.FAILURE) {
actionStatus.setStatus(Status.ERROR);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target reported CLOSED with ERROR!");
@@ -354,23 +372,23 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> putConfigData(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiConfigData configData, @PathVariable("targetid") final String targetid) {
controllerManagement.updateLastTargetQuery(targetid, IpUtil
public ResponseEntity<Void> putConfigData(@Valid @RequestBody final DdiConfigData configData,
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) {
controllerManagement.updateLastTargetQuery(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
controllerManagement.updateControllerAttributes(targetid, configData.getData());
controllerManagement.updateControllerAttributes(controllerId, configData.getData());
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
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) {
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));
final Action action = findActionWithExceptionIfNotFound(actionId);
@@ -383,7 +401,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
final DdiCancel cancel = new DdiCancel(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
+ "Target retrieved cancel action and should start now the cancelation.");
@@ -395,13 +413,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<Void> postCancelActionFeedback(@PathVariable("tenant") final String tenant,
@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("targetid") @NotEmpty final String targetid,
public ResponseEntity<Void> postCancelActionFeedback(@Valid @RequestBody final DdiActionFeedback feedback,
@PathVariable("tenant") final String tenant,
@PathVariable("controllerId") @NotEmpty final String controllerId,
@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));
if (!actionId.equals(feedback.getId())) {
@@ -432,13 +450,13 @@ public class DdiRootController implements DdiRootControllerRestApi {
switch (feedback.getStatus().getExecution()) {
case CANCELED:
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());
actionStatus.setStatus(Status.WARNING);
break;
case REJECTED:
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, targetid: {}).", actionid,
target.getControllerId());
LOG.info("Controller rejected the cancelation request (too late) (actionid: {}, controllerId: {}).",
actionid, target.getControllerId());
actionStatus.setStatus(Status.WARNING);
break;
case CLOSED:

View File

@@ -61,8 +61,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("Deployment Action Resource")
public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoDB {
private static final String HTTP_LOCALHOST = "http://localhost/";
private static final String HTTPS_LOCALHOST = "https://localhost/";
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
@Test()
@Description("Ensures that artifacts are not found, when softare module does not exists.")
@@ -174,25 +173,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.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(
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()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.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()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -203,27 +191,17 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
contains("test1.signature")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.md5",
contains(artifactSignature.getMd5Hash())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
contains(artifactSignature.getSha1Hash())))
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
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",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.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()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
@@ -359,16 +337,18 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.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(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[0]._links.download.href",
contains(HTTP_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(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/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].filename",
contains("test1.signature")))
@@ -377,24 +357,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
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",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.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()
+ "/controller/v1/4712/softwaremodules/"
+ 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].hashes.md5",
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(
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()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1")))
.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()
+ "/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].filename",
contains("test1.signature")))
@@ -519,25 +480,14 @@ public class DdiDeploymentBaseTest extends AbstractRestIntegrationTestWithMongoD
.andExpect(
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1].hashes.sha1",
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",
jsonPath("$.deployment.chunks[?(@.part==os)].artifacts[1]._links.download.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant()
+ "/controller/v1/4712/softwaremodules/"
+ findDistributionSetByAction.findFirstModuleByType(osType).getId()
+ "/artifacts/test1.signature")))
.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()
+ "/controller/v1/4712/softwaremodules/"
+ 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;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
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.repository.ArtifactManagement;
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.security.DdiSecurityProperties;
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.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
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.
*
*/
@@ -251,17 +258,51 @@ public class AmqpConfiguration {
}
/**
* Create amqp handler service bean.
* Create AMQP handler service bean.
*
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService
* to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
*
* @return handler service bean
*/
@Bean
public AmqpMessageHandlerService amqpMessageHandlerService(
final AmqpMessageDispatcherService amqpMessageDispatcherService) {
return new AmqpMessageHandlerService(rabbitTemplate(), amqpMessageDispatcherService);
public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService 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
@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 DdiSecurityProperties ddiSecruityProperties, final SystemSecurityContext systemSecurityContext) {
return new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement, tenantAware,
ddiSecruityProperties, systemSecurityContext);
return new AmqpControllerAuthentication(systemManagement, controllerManagement, tenantConfigurationManagement,
tenantAware, ddiSecruityProperties, systemSecurityContext);
}
@Bean
@ConditionalOnMissingBean(AmqpMessageDispatcherService.class)
public AmqpMessageDispatcherService amqpMessageDispatcherService(final RabbitTemplate rabbitTemplate,
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler);
final AmqpSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemSecurityContext, systemManagement);
}
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-max-length", 1_000);
return args;

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,6 @@
package org.eclipse.hawkbit.amqp;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@@ -18,68 +17,45 @@ import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
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.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
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.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.exception.TooManyStatusEntriesException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
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.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil;
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.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.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.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
* {@link AmqpMessageHandlerService} handles all incoming AMQP messages for the
* queue which is configure for the property hawkbit.dmf.rabbitmq.receiverQueue.
* {@link AmqpMessageHandlerService} handles all incoming target interaction
* 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 {
@@ -88,40 +64,29 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final AmqpMessageDispatcherService amqpMessageDispatcherService;
@Autowired
private ControllerManagement controllerManagement;
private final ControllerManagement controllerManagement;
@Autowired
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;
private final EntityFactory entityFactory;
/**
* Constructor.
*
* @param defaultTemplate
* the configured amqp template.
* @param rabbitTemplate
* for converting messages
* @param amqpMessageDispatcherService
* to sending events to DMF client
* @param controllerManagement
* for target repo access
* @param entityFactory
* to create entities
*/
public AmqpMessageHandlerService(final RabbitTemplate defaultTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService) {
super(defaultTemplate);
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
super(rabbitTemplate);
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());
}
/**
* 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.
*
@@ -206,108 +149,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
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) {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(authentication);
@@ -361,10 +202,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final DistributionSet distributionSet = action.get().getDistributionSet();
final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet);
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
amqpMessageDispatcherService.targetAssignDistributionSet(new TargetAssignDistributionSetEvent(
target.getOptLockRevision(), target.getTenant(), target.getControllerId(), action.get().getId(),
softwareModuleList, target.getTargetInfo().getAddress(), targetSecurityToken));
target.getOptLockRevision(), target.getTenant(), target, action.get().getId(), softwareModuleList));
}
@@ -481,7 +320,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
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()) {
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")
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
*/
@@ -54,24 +64,24 @@ public class AmqpProperties {
/**
* 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.
*/
private int maxConcurrentConsumers = 10;
private int maxConcurrentConsumers = DEFAULT_MAX_CONSUMERS;
/**
* 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.
*/
private int prefetchCount = 10;
private int prefetchCount = DEFAULT_PREFETCH_COUNT;
/**
* Initial number of consumers. Is scaled up if necessary up to
* {@link #maxConcurrentConsumers}.
*/
private int initialConcurrentConsumers = 3;
private int initialConcurrentConsumers = DEFAULT_INITIAL_CONSUMERS;
/**
* 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
* initialization.
*/
private int declarationRetries = 50;
private int declarationRetries = DEFAULT_QUEUE_DECLARATION_RETRIES;
public int getDeclarationRetries() {
return declarationRetries;

View File

@@ -17,6 +17,7 @@ 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.core.RabbitTemplate;
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.MessageConverter;
@@ -39,6 +40,14 @@ public class BaseAmqpService {
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.
*
@@ -98,7 +107,7 @@ public class BaseAmqpService {
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);
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.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.MessageType;
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.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
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.TenantMetaData;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymous;
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.junit.Before;
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.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.cache.Cache;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
@@ -54,15 +71,44 @@ import ru.yandex.qatools.allure.annotations.Stories;
*/
@Features("Component Tests - Device Management Federation API")
@Stories("AmqpController Authentication Test")
@RunWith(MockitoJUnitRunner.class)
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 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 AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter;
private TenantConfigurationManagement tenantConfigurationManagement;
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
.<Boolean> builder().value(Boolean.FALSE).build();
@@ -74,8 +120,6 @@ public class AmqpControllerAuthenticationTest {
messageConverter = new Jackson2JsonMessageConverter();
final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class));
final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class);
final Rp rp = mock(Rp.class);
@@ -88,30 +132,57 @@ public class AmqpControllerAuthenticationTest {
when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
when(anonymous.isEnabled()).thenReturn(false);
tenantConfigurationManagement = mock(TenantConfigurationManagement.class);
when(tenantConfigurationManagement.getConfigurationValue(any(), eq(Boolean.class)))
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE);
final ControllerManagement controllerManagement = mock(ControllerManagement.class);
when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID);
amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class));
when(controllerManagement.findByControllerId(anyString())).thenReturn(targteMock);
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 SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(controllerManagement, tenantConfigurationManagement,
tenantAware, secruityProperties, systemSecurityContext);
final TenantMetaData tenantMetaData = mock(TenantMetaData.class);
when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
tenantConfigurationManagementMock, tenantAware, secruityProperties, systemSecurityContext);
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
@Description("Tests authentication manager without principal")
public void testAuthenticationeBadCredantialsWithoutPricipal() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
try {
authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted since principal was missing");
@@ -124,12 +195,12 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication manager without wrong credential")
public void testAuthenticationBadCredantialsWithWrongCredential() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, 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.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
try {
authenticationManager.doAuthenticate(securityToken);
fail("BadCredentialsException was excepeted due to wrong credential");
@@ -142,12 +213,12 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication successfull")
public void testSuccessfullAuthentication() {
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, 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.getHeaders().put(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID);
securityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Authentication authentication = authenticationManager.doAuthenticate(securityToken);
assertThat(authentication).isNotNull();
}
@@ -157,13 +228,13 @@ public class AmqpControllerAuthenticationTest {
public void testAuthenticationMessageBadCredantialsWithoutPricipal() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, TARGET_ID,
FileResource.createFileResourceBySha1(SHA1));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -175,17 +246,17 @@ public class AmqpControllerAuthenticationTest {
@Description("Tests authentication message without wrong credential")
public void testAuthenticationMessageBadCredantialsWithWrongCredential() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, 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.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,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -195,24 +266,81 @@ public class AmqpControllerAuthenticationTest {
@Test
@Description("Tests authentication message successfull")
public void testSuccessfullMessageAuthentication() {
public void successfullMessageAuthentication() {
final MessageProperties messageProperties = createMessageProperties(null);
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, CONTROLLLER_ID,
FileResource.createFileResourceBySha1("12345"));
when(tenantConfigurationManagement.getConfigurationValue(
final TenantSecurityToken securityToken = new TenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.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,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
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().getAuthentication()).isNotNull();
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.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -24,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
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.json.model.DownloadAndUpdateRequest;
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.model.Artifact;
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.Target;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.util.IpUtil;
import org.junit.Test;
@@ -49,6 +51,8 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.test.SpringApplicationConfiguration;
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.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@@ -60,6 +64,7 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private static final String TENANT = "default";
private static final Long TENANT_ID = 4711L;
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
@@ -71,31 +76,47 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private DefaultAmqpSenderService senderService;
private SystemManagement systemManagement;
private static final String CONTROLLER_ID = "1";
private Target testTarget;
@Override
public void before() throws Exception {
super.before();
testTarget = entityFactory.generateTarget(CONTROLLER_ID, TEST_TOKEN);
testTarget.getTargetInfo().setAddress(AMQP_URI.toString());
this.rabbitTemplate = Mockito.mock(RabbitTemplate.class);
when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter());
senderService = Mockito.mock(DefaultAmqpSenderService.class);
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
when(artifactUrlHandlerMock.getUrl(anyString(), anyLong(), anyString(), anyString(), anyObject()))
.thenReturn("http://mockurl");
when(artifactUrlHandlerMock.getUrls(anyObject(), anyObject()))
.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,
artifactUrlHandlerMock);
artifactUrlHandlerMock, systemSecurityContext, systemManagement);
}
@Test
@Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() {
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);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertThat(downloadAndUpdateRequest.getTargetSecurityToken()).isEqualTo(TEST_TOKEN);
assertTrue("No softwaremmodule should be contained in the request",
@@ -107,9 +128,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
public void testSendDownloadRequesWithSoftwareModulesAndNoArtifacts() {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
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);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("Expecting a size of 3 software modules in the reuqest", 3,
downloadAndUpdateRequest.getSoftwareModules().size());
@@ -146,9 +168,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
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);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final Message sendMessage = createArgumentCapture(
targetAssignDistributionSetEvent.getTarget().getTargetInfo().getAddress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3,
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.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,8 +80,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
public class AmqpMessageHandlerServiceTest {
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 AmqpAuthenticationMessageHandler amqpAuthenticationMessageHandlerService;
private MessageConverter messageConverter;
@@ -113,22 +116,16 @@ public class AmqpMessageHandlerServiceTest {
@Mock
private RabbitTemplate rabbitTemplate;
@Mock
private SystemSecurityContext systemSecurityContextMock;
@Before
public void before() throws Exception {
messageConverter = new Jackson2JsonMessageConverter();
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
@@ -279,13 +276,13 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which does not exists")
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
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"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
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")
public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() {
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"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -309,7 +306,7 @@ public class AmqpMessageHandlerServiceTest {
.thenThrow(EntityNotFoundException.class);
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
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")
public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException {
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"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -340,7 +337,7 @@ public class AmqpMessageHandlerServiceTest {
when(hostnameResolverMock.resolveHostname()).thenReturn(new URL("http://localhost"));
// test
final Message onMessage = amqpMessageHandlerService.onAuthenticationRequest(message);
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
// verify
final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage);
@@ -364,15 +361,12 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.addUpdateActionStatus(Matchers.any())).thenReturn(action);
when(entityFactoryMock.generateActionStatus()).thenReturn(new JpaActionStatus());
// for the test the same action can be used
when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any()))
.thenReturn(Optional.of(action));
when(controllerManagementMock.findOldestActiveActionByTarget(Matchers.any())).thenReturn(Optional.of(action));
final List<SoftwareModule> softwareModuleList = createSoftwareModuleList();
when(controllerManagementMock.findSoftwareModulesByDistributionSet(Matchers.any()))
.thenReturn(softwareModuleList);
when(systemSecurityContextMock.runAsSystem(anyObject())).thenReturn("securityToken");
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final ActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(ActionStatus.FINISHED, 23L);
@@ -393,10 +387,10 @@ public class AmqpMessageHandlerServiceTest {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent
.getValue();
assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id")
assertThat(targetAssignDistributionSetEvent.getTarget().getControllerId()).as("event has wrong controller id")
.isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getTargetToken()).as("targetoken not filled correctly")
.isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getTarget().getSecurityToken())
.as("targetoken not filled correctly").isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules")
.isEqualTo(softwareModuleList);

View File

@@ -52,8 +52,8 @@ public class BaseAmqpServiceTest {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(1L);
actionUpdateStatus.setSoftwareModuleId(2L);
actionUpdateStatus.getMessage().add("Message 1");
actionUpdateStatus.getMessage().add("Message 2");
actionUpdateStatus.addMessage("Message 1");
actionUpdateStatus.addMessage("Message 2");
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
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,7 @@
package org.eclipse.hawkbit.dmf.json.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -18,9 +19,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* JSON representation of action update status.
*
*
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -32,7 +30,7 @@ public class ActionUpdateStatus {
@JsonProperty(required = true)
private ActionStatus actionStatus;
@JsonProperty
private final List<String> message = new ArrayList<>();
private List<String> message;
public Long getActionId() {
return actionId;
@@ -59,7 +57,19 @@ public class ActionUpdateStatus {
}
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);
}
}

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
@@ -27,16 +28,47 @@ public class TenantSecurityToken {
public static final String AUTHORIZATION_HEADER = "Authorization";
@JsonProperty
private final String tenant;
@JsonProperty
@JsonProperty(required = false)
private String tenant;
@JsonProperty(required = false)
private final Long tenantId;
@JsonProperty(required = false)
private final String controllerId;
@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)
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.
*
@@ -47,13 +79,26 @@ public class TenantSecurityToken {
* @param fileResource
* the file to obtain
*/
@JsonCreator
public TenantSecurityToken(@JsonProperty("tenant") final String tenant,
@JsonProperty("controllerId") final String controllerId,
@JsonProperty("fileResource") final FileResource fileResource) {
public TenantSecurityToken(final String tenant, final String controllerId, final FileResource fileResource) {
this(tenant, null, controllerId, null, 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.controllerId = controllerId;
this.fileResource = fileResource;
}
public String getTenant() {
@@ -65,13 +110,25 @@ public class TenantSecurityToken {
}
public Map<String, String> getHeaders() {
return headers;
if (headers == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(headers);
}
public FileResource getFileResource() {
return fileResource;
}
public Long getTenantId() {
return tenantId;
}
public Long getTargetId() {
return targetId;
}
/**
* Gets a header value.
*
@@ -80,6 +137,10 @@ public class TenantSecurityToken {
* @return the value
*/
public String getHeader(final String name) {
if (headers == null) {
return null;
}
return headers.get(name);
}
@@ -88,6 +149,24 @@ public class TenantSecurityToken {
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
* 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)
private String sha1;
@JsonProperty(required = false)
private Long artifactId;
@JsonProperty(required = false)
private String filename;
@JsonProperty(required = false)
private SoftwareModuleFilenameResource softwareModuleFilenameResource;
@@ -128,6 +209,14 @@ public class TenantSecurityToken {
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.
*
@@ -141,6 +230,19 @@ public class TenantSecurityToken {
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.
*
@@ -173,7 +275,7 @@ public class TenantSecurityToken {
@Override
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,
final String tenant, final String controllerId) {
final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, controllerId,
final TenantSecurityToken secruityToken = new TenantSecurityToken(tenant, null, controllerId, null,
FileResource.createFileResourceBySha1(""));
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;
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.mgmt.json.model;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
@@ -72,7 +73,7 @@ public class PagedList<T> extends ResourceSupport {
}
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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
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
* back.
*
*
*
*
*/
public final class MgmtDistributionSetMapper {
private MgmtDistributionSetMapper() {
@@ -75,15 +74,13 @@ public final class MgmtDistributionSetMapper {
* to use for conversion
* @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 EntityFactory entityFactory) {
final List<DistributionSet> mappedList = new ArrayList<>();
for (final MgmtDistributionSetRequestBodyPost dsRest : sets) {
mappedList.add(fromRequest(dsRest, softwareManagement, distributionSetManagement, entityFactory));
}
return mappedList;
return sets.stream()
.map(dsRest -> fromRequest(dsRest, softwareManagement, distributionSetManagement, entityFactory))
.collect(Collectors.toList());
}
@@ -139,15 +136,12 @@ public final class MgmtDistributionSetMapper {
*/
static List<DistributionSetMetadata> fromRequestDsMetadata(final DistributionSet ds,
final List<MgmtMetadata> metadata, final EntityFactory entityFactory) {
final List<DistributionSetMetadata> mappedList = new ArrayList<>(metadata.size());
for (final MgmtMetadata metadataRest : metadata) {
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(
entityFactory.generateDistributionSetMetadata(ds, metadataRest.getKey(), metadataRest.getValue()));
if (metadata == null) {
return Collections.emptyList();
}
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;
}
static List<MgmtDistributionSet> toResponseDistributionSets(final Iterable<DistributionSet> sets) {
final List<MgmtDistributionSet> response = new ArrayList<>();
if (sets != null) {
for (final DistributionSet set : sets) {
response.add(toResponse(set));
}
static List<MgmtDistributionSet> toResponseDistributionSets(final Collection<DistributionSet> sets) {
if (sets == null) {
return Collections.emptyList();
}
return response;
return sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList());
}
static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) {
@@ -224,14 +215,10 @@ public final class MgmtDistributionSetMapper {
}
static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) {
final List<MgmtDistributionSet> mappedList = new ArrayList<>();
if (sets != null) {
for (final DistributionSet set : sets) {
final MgmtDistributionSet response = toResponse(set);
mappedList.add(response);
}
if (sets == null) {
return Collections.emptyList();
}
return mappedList;
return sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList());
}
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -123,7 +124,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
.runAsSystem(() -> this.systemManagement.getTenantMetadata().getDefaultDsType().getKey());
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,
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.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.MgmtDistributionSetTypeRequestBodyPost;
@@ -39,13 +41,13 @@ final class MgmtDistributionSetTypeMapper {
static List<DistributionSetType> smFromRequest(final EntityFactory entityFactory,
final SoftwareManagement softwareManagement,
final Iterable<MgmtDistributionSetTypeRequestBodyPost> smTypesRest) {
final List<DistributionSetType> mappedList = new ArrayList<>();
for (final MgmtDistributionSetTypeRequestBodyPost smRest : smTypesRest) {
mappedList.add(fromRequest(entityFactory, softwareManagement, smRest));
final Collection<MgmtDistributionSetTypeRequestBodyPost> smTypesRest) {
if (smTypesRest == null) {
return Collections.emptyList();
}
return mappedList;
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, softwareManagement, smRest))
.collect(Collectors.toList());
}
static DistributionSetType fromRequest(final EntityFactory entityFactory,
@@ -91,19 +93,19 @@ final class MgmtDistributionSetTypeMapper {
}
static List<MgmtDistributionSetType> toTypesResponse(final List<DistributionSetType> types) {
final List<MgmtDistributionSetType> response = new ArrayList<>();
for (final DistributionSetType dsType : types) {
response.add(toResponse(dsType));
if (types == null) {
return Collections.emptyList();
}
return response;
return types.stream().map(MgmtDistributionSetTypeMapper::toResponse).collect(Collectors.toList());
}
static List<MgmtDistributionSetType> toListResponse(final List<DistributionSetType> types) {
final List<MgmtDistributionSetType> response = new ArrayList<>();
for (final DistributionSetType dsType : types) {
response.add(toResponse(dsType));
if (types == null) {
return Collections.emptyList();
}
return response;
return types.stream().map(MgmtDistributionSetTypeMapper::toResponse).collect(Collectors.toList());
}
static MgmtDistributionSetType toResponse(final DistributionSetType type) {

View File

@@ -8,6 +8,12 @@
*/
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 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.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
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
* {@link Artifact} CRUD operations.
*
*
*/
@RestController
public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeRestApi {
@@ -67,7 +70,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeDistributionSetTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSetType> findModuleTypessAll;
@@ -82,31 +84,32 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final List<MgmtDistributionSetType> rest = MgmtDistributionSetTypeMapper
.toListResponse(findModuleTypessAll.getContent());
return new ResponseEntity<>(new PagedList<>(rest, countModulesAll), HttpStatus.OK);
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
}
@Override
public ResponseEntity<MgmtDistributionSetType> getDistributionSetType(
@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
public ResponseEntity<Void> deleteDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
distributionSetManagement.deleteDistributionSetType(module);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtDistributionSetType> updateDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@RequestBody final MgmtDistributionSetTypeRequestBodyPut restDistributionSetType) {
final DistributionSetType type = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
// only description can be modified
@@ -117,8 +120,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final DistributionSetType updatedDistributionSetType = distributionSetManagement
.updateDistributionSetType(type);
return new ResponseEntity<>(MgmtDistributionSetTypeMapper.toResponse(updatedDistributionSetType),
HttpStatus.OK);
return ResponseEntity.ok(toResponse(updatedDistributionSetType));
}
@Override
@@ -128,16 +130,17 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
final List<DistributionSetType> createdSoftwareModules = distributionSetManagement.createDistributionSetTypes(
MgmtDistributionSetTypeMapper.smFromRequest(entityFactory, softwareManagement, distributionSetTypes));
return new ResponseEntity<>(MgmtDistributionSetTypeMapper.toTypesResponse(createdSoftwareModules),
HttpStatus.CREATED);
return ResponseEntity.status(CREATED).body(toTypesResponse(createdSoftwareModules));
}
private DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(final Long distributionSetTypeId) {
final DistributionSetType module = distributionSetManagement.findDistributionSetTypeById(distributionSetTypeId);
if (module == null) {
throw new EntityNotFoundException(
"DistributionSetType with Id {" + distributionSetTypeId + "} does not exist");
}
return module;
}
@@ -146,8 +149,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toListResponse(foundType.getMandatoryModuleTypes()),
HttpStatus.OK);
return ResponseEntity.ok(toTypesResponse(foundType.getMandatoryModuleTypes()));
}
@Override
@@ -156,7 +158,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
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!");
}
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK);
return ResponseEntity.ok(toResponse(foundSmType));
}
@Override
@@ -173,7 +174,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
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!");
}
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK);
return ResponseEntity.ok(toResponse(foundSmType));
}
@Override
@@ -189,9 +189,7 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toListResponse(foundType.getOptionalModuleTypes()),
HttpStatus.OK);
return ResponseEntity.ok(toTypesResponse(foundType.getOptionalModuleTypes()));
}
@Override
@@ -200,7 +198,6 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsMandatoryModuleType(foundSmType)) {
@@ -209,18 +206,17 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
}
foundType.removeModuleType(softwareModuleTypeId);
distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> removeOptionalModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsOptionalModuleType(foundSmType)) {
@@ -229,10 +225,9 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
}
foundType.removeModuleType(softwareModuleTypeId);
distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
@@ -240,14 +235,11 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addMandatoryModuleType(smType);
distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
@@ -255,23 +247,22 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType smType = findSoftwareModuleTypeWithExceptionIfNotFound(smtId.getId());
foundType.addOptionalModuleType(smType);
distributionSetManagement.updateDistributionSetType(foundType);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId);
if (module == null) {
throw new EntityNotFoundException(
"SoftwareModuleType with Id {" + softwareModuleTypeId + "} does not exist");
}
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.methodOn;
import java.util.ArrayList;
import java.util.Collections;
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.MgmtRolloutErrorAction.ErrorAction;
@@ -48,9 +49,11 @@ final class MgmtRolloutMapper {
}
static List<MgmtRolloutResponseBody> toResponseRollout(final List<Rollout> rollouts) {
final List<MgmtRolloutResponseBody> result = new ArrayList<>(rollouts.size());
rollouts.forEach(r -> result.add(toResponseRollout(r)));
return result;
if (rollouts == null) {
return Collections.emptyList();
}
return rollouts.stream().map(MgmtRolloutMapper::toResponseRollout).collect(Collectors.toList());
}
static MgmtRolloutResponseBody toResponseRollout(final Rollout rollout) {
@@ -103,9 +106,11 @@ final class MgmtRolloutMapper {
}
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
final List<MgmtRolloutGroupResponseBody> result = new ArrayList<>(rollouts.size());
rollouts.forEach(r -> result.add(toResponseRolloutGroup(r)));
return result;
if (rollouts == null) {
return Collections.emptyList();
}
return rollouts.stream().map(MgmtRolloutMapper::toResponseRolloutGroup).collect(Collectors.toList());
}
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.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
@@ -65,60 +67,46 @@ public final class MgmtSoftwareModuleMapper {
}
static List<SoftwareModuleMetadata> fromRequestSwMetadata(final EntityFactory entityFactory,
final SoftwareModule sw, final List<MgmtMetadata> metadata) {
final List<SoftwareModuleMetadata> mappedList = new ArrayList<>(metadata.size());
for (final MgmtMetadata metadataRest : metadata) {
if (metadataRest.getKey() == null) {
throw new IllegalArgumentException("the key of the metadata must be present");
}
mappedList.add(
entityFactory.generateSoftwareModuleMetadata(sw, metadataRest.getKey(), metadataRest.getValue()));
final SoftwareModule sw, final Collection<MgmtMetadata> metadata) {
if (metadata == null) {
return Collections.emptyList();
}
return mappedList;
return metadata.stream().map(metadataRest -> entityFactory.generateSoftwareModuleMetadata(sw,
metadataRest.getKey(), metadataRest.getValue())).collect(Collectors.toList());
}
static List<SoftwareModule> smFromRequest(final EntityFactory entityFactory,
final Iterable<MgmtSoftwareModuleRequestBodyPost> smsRest, final SoftwareManagement softwareManagement) {
final List<SoftwareModule> mappedList = new ArrayList<>();
for (final MgmtSoftwareModuleRequestBodyPost smRest : smsRest) {
mappedList.add(fromRequest(entityFactory, smRest, softwareManagement));
final Collection<MgmtSoftwareModuleRequestBodyPost> smsRest, final SoftwareManagement softwareManagement) {
if (smsRest == null) {
return Collections.emptyList();
}
return mappedList;
return smsRest.stream().map(smRest -> fromRequest(entityFactory, smRest, softwareManagement))
.collect(Collectors.toList());
}
/**
* Create response for sw modules.
*
* @param baseSoftareModules
* @param softwareModules
* the modules
* @return the response
*/
public static List<MgmtSoftwareModule> toResponse(final List<SoftwareModule> baseSoftareModules) {
final List<MgmtSoftwareModule> mappedList = new ArrayList<>();
if (baseSoftareModules != null) {
for (final SoftwareModule target : baseSoftareModules) {
final MgmtSoftwareModule response = toResponse(target);
mappedList.add(response);
}
public static List<MgmtSoftwareModule> toResponse(final Collection<SoftwareModule> softwareModules) {
if (softwareModules == null) {
return Collections.emptyList();
}
return mappedList;
return softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList());
}
static List<MgmtSoftwareModule> toResponseSoftwareModules(final Iterable<SoftwareModule> softwareModules) {
final List<MgmtSoftwareModule> response = new ArrayList<>();
for (final SoftwareModule softwareModule : softwareModules) {
response.add(toResponse(softwareModule));
static List<MgmtMetadata> toResponseSwMetadata(final Collection<SoftwareModuleMetadata> metadata) {
if (metadata == null) {
return Collections.emptyList();
}
return response;
}
static List<MgmtMetadata> toResponseSwMetadata(final List<SoftwareModuleMetadata> metadata) {
final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size());
for (final SoftwareModuleMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseSwMetadata(distributionSetMetadata));
}
return mappedList;
return metadata.stream().map(MgmtSoftwareModuleMapper::toResponseSwMetadata).collect(Collectors.toList());
}
static MgmtMetadata toResponseSwMetadata(final SoftwareModuleMetadata metadata) {
@@ -194,15 +182,11 @@ public final class MgmtSoftwareModuleMapper {
return artifactRest;
}
static List<MgmtArtifact> artifactsToResponse(final List<Artifact> artifacts) {
final List<MgmtArtifact> mappedList = new ArrayList<>();
if (artifacts != null) {
for (final Artifact artifact : artifacts) {
final MgmtArtifact response = toResponse(artifact);
mappedList.add(response);
}
static List<MgmtArtifact> artifactsToResponse(final Collection<Artifact> artifacts) {
if (artifacts == null) {
return Collections.emptyList();
}
return mappedList;
return artifacts.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList());
}
}

View File

@@ -8,7 +8,15 @@
*/
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.util.Collection;
import java.util.List;
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
* {@link Artifact} CRUD operations.
*
*/
@RestController
public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
private static final Logger LOG = LoggerFactory.getLogger(MgmtSoftwareModuleResource.class);
@Autowired
@@ -69,7 +77,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
@RequestParam(value = "sha1sum", required = false) final String sha1Sum) {
if (file.isEmpty()) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(BAD_REQUEST);
}
String fileName = optionalFileName;
@@ -81,10 +89,10 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
final Artifact result = artifactManagement.createLocalArtifact(file.getInputStream(), softwareModuleId,
fileName, md5Sum == null ? null : md5Sum.toLowerCase(),
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) {
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
public ResponseEntity<List<MgmtArtifact>> getArtifacts(
@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
@ResponseBody
// Exception squid:S3655 - Optional access is checked in
// findSoftwareModuleWithExceptionIfNotFound
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(module.getLocalArtifact(artifactId).get()),
HttpStatus.OK);
return ResponseEntity.ok(toResponse(module.getLocalArtifact(artifactId).get()));
}
@Override
@ResponseBody
public ResponseEntity<Void> deleteArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) {
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
artifactManagement.deleteLocalArtifact(artifactId);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
@@ -143,33 +154,34 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
}
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
public ResponseEntity<MgmtSoftwareModule> getSoftwareModule(
@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
public ResponseEntity<List<MgmtSoftwareModule>> createSoftwareModules(
@RequestBody final List<MgmtSoftwareModuleRequestBodyPost> softwareModules) {
LOG.debug("creating {} softwareModules", softwareModules.size());
final Iterable<SoftwareModule> createdSoftwareModules = softwareManagement.createSoftwareModule(
final Collection<SoftwareModule> createdSoftwareModules = softwareManagement.createSoftwareModule(
MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules, softwareManagement));
LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponseSoftwareModules(createdSoftwareModules),
HttpStatus.CREATED);
return ResponseEntity.status(CREATED).body(toResponse(createdSoftwareModules));
}
@Override
public ResponseEntity<MgmtSoftwareModule> updateSoftwareModule(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final MgmtSoftwareModuleRequestBodyPut restSoftwareModule) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
// only description and vendor can be modified
@@ -181,16 +193,16 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
}
final SoftwareModule updateSoftwareModule = softwareManagement.updateSoftwareModule(module);
return new ResponseEntity<>(MgmtSoftwareModuleMapper.toResponse(updateSoftwareModule), HttpStatus.OK);
return ResponseEntity.ok(toResponse(updateSoftwareModule));
}
@Override
public ResponseEntity<Void> deleteSoftwareModule(@PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModule(module);
return new ResponseEntity<>(HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
@@ -218,34 +230,38 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
metaDataPage = softwareManagement.findSoftwareModuleMetadataBySoftwareModuleId(softwareModuleId, pageable);
}
return new ResponseEntity<>(
new PagedList<>(MgmtSoftwareModuleMapper.toResponseSwMetadata(metaDataPage.getContent()),
metaDataPage.getTotalElements()),
HttpStatus.OK);
return ResponseEntity
.ok(new PagedList<>(toResponseSwMetadata(metaDataPage.getContent()), metaDataPage.getTotalElements()));
}
@Override
public ResponseEntity<MgmtMetadata> getMetadataValue(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata findOne = softwareManagement.findSoftwareModuleMetadata(sw, metadataKey);
return ResponseEntity.<MgmtMetadata> ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(findOne));
return ResponseEntity.ok(toResponseSwMetadata(findOne));
}
@Override
public ResponseEntity<MgmtMetadata> updateMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey, @RequestBody final MgmtMetadata metadata) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModuleMetadata updated = softwareManagement.updateSoftwareModuleMetadata(
entityFactory.generateSoftwareModuleMetadata(sw, metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(updated));
return ResponseEntity.ok(toResponseSwMetadata(updated));
}
@Override
public ResponseEntity<Void> deleteMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareManagement.deleteSoftwareModuleMetadata(sw, metadataKey);
return ResponseEntity.ok().build();
}
@@ -253,24 +269,25 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
public ResponseEntity<List<MgmtMetadata>> createMetadata(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final List<MgmtMetadata> metadataRest) {
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final SoftwareModule sw = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final List<SoftwareModuleMetadata> created = softwareManagement.createSoftwareModuleMetadata(
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,
final Long artifactId) {
final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId);
if (module == null) {
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");
}
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.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.MgmtSoftwareModuleTypeRequestBodyPost;
@@ -25,9 +26,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*
*
*/
final class MgmtSoftwareModuleTypeMapper {
@@ -37,13 +35,12 @@ final class MgmtSoftwareModuleTypeMapper {
}
static List<SoftwareModuleType> smFromRequest(final EntityFactory entityFactory,
final Iterable<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
final List<SoftwareModuleType> mappedList = new ArrayList<>();
for (final MgmtSoftwareModuleTypeRequestBodyPost smRest : smTypesRest) {
mappedList.add(fromRequest(entityFactory, smRest));
final Collection<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
if (smTypesRest == null) {
return Collections.emptyList();
}
return mappedList;
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, smRest)).collect(Collectors.toList());
}
static SoftwareModuleType fromRequest(final EntityFactory entityFactory,
@@ -57,20 +54,12 @@ final class MgmtSoftwareModuleTypeMapper {
return result;
}
static List<MgmtSoftwareModuleType> toTypesResponse(final List<SoftwareModuleType> types) {
final List<MgmtSoftwareModuleType> response = new ArrayList<>();
for (final SoftwareModuleType softwareModule : types) {
response.add(toResponse(softwareModule));
static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<SoftwareModuleType> types) {
if (types == null) {
return Collections.emptyList();
}
return response;
}
static List<MgmtSoftwareModuleType> toListResponse(final Collection<SoftwareModuleType> types) {
final List<MgmtSoftwareModuleType> response = new ArrayList<>();
for (final SoftwareModuleType softwareModule : types) {
response.add(toResponse(softwareModule));
}
return response;
return types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).collect(Collectors.toList());
}
static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {

View File

@@ -72,7 +72,7 @@ public class MgmtSoftwareModuleTypeResource implements MgmtSoftwareModuleTypeRes
}
final List<MgmtSoftwareModuleType> rest = MgmtSoftwareModuleTypeMapper
.toListResponse(findModuleTypessAll.getContent());
.toTypesResponse(findModuleTypessAll.getContent());
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.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
@@ -92,17 +94,17 @@ public final class MgmtTargetMapper {
* the targets
* @return the response
*/
public static List<MgmtTarget> toResponseWithLinksAndPollStatus(final Iterable<Target> targets) {
final List<MgmtTarget> mappedList = new ArrayList<>();
if (targets != null) {
for (final Target target : targets) {
final MgmtTarget response = toResponse(target);
addPollStatus(target, response);
addTargetLinks(response);
mappedList.add(response);
}
public static List<MgmtTarget> toResponseWithLinksAndPollStatus(final Collection<Target> targets) {
if (targets == null) {
return Collections.emptyList();
}
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
* @return the response
*/
public static List<MgmtTarget> toResponse(final Iterable<Target> targets) {
final List<MgmtTarget> mappedList = new ArrayList<>();
if (targets != null) {
for (final Target target : targets) {
final MgmtTarget response = toResponse(target);
mappedList.add(response);
}
public static List<MgmtTarget> toResponse(final Collection<Target> targets) {
if (targets == null) {
return Collections.emptyList();
}
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,
final Iterable<MgmtTargetRequestBody> targetsRest) {
final List<Target> mappedList = new ArrayList<>();
for (final MgmtTargetRequestBody targetRest : targetsRest) {
mappedList.add(fromRequest(entityFactory, targetRest));
final Collection<MgmtTargetRequestBody> targetsRest) {
if (targetsRest == null) {
return Collections.emptyList();
}
return mappedList;
return targetsRest.stream().map(targetRest -> fromRequest(entityFactory, targetRest))
.collect(Collectors.toList());
}
static Target fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) {
@@ -190,17 +190,12 @@ public final class MgmtTargetMapper {
return target;
}
static List<MgmtActionStatus> toActionStatusRestResponse(final List<ActionStatus> actionStatus) {
final List<MgmtActionStatus> mappedList = new ArrayList<>();
if (actionStatus != null) {
for (final ActionStatus status : actionStatus) {
final MgmtActionStatus response = toResponse(status);
mappedList.add(response);
}
static List<MgmtActionStatus> toActionStatusRestResponse(final Collection<ActionStatus> actionStatus) {
if (actionStatus == null) {
return Collections.emptyList();
}
return mappedList;
return actionStatus.stream().map(MgmtTargetMapper::toResponse).collect(Collectors.toList());
}
static MgmtAction toResponse(final String targetId, final Action action, final boolean isActive) {
@@ -222,14 +217,13 @@ public final class MgmtTargetMapper {
return result;
}
static List<MgmtAction> toResponse(final String targetId, final List<Action> actions) {
final List<MgmtAction> mappedList = new ArrayList<>();
for (final Action action : actions) {
final MgmtAction response = toResponse(targetId, action, action.isActive());
mappedList.add(response);
static List<MgmtAction> toResponse(final String targetId, final Collection<Action> actions) {
if (actions == null) {
return Collections.emptyList();
}
return mappedList;
return actions.stream().map(action -> toResponse(targetId, action, action.isActive()))
.collect(Collectors.toList());
}
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.methodOn;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -108,7 +109,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
@Override
public ResponseEntity<List<MgmtTarget>> createTargets(@RequestBody final List<MgmtTargetRequestBody> targets) {
LOG.debug("creating {} targets", targets.size());
final Iterable<Target> createdTargets = this.targetManagement
final Collection<Target> createdTargets = this.targetManagement
.createTargets(MgmtTargetMapper.fromRequest(entityFactory, targets));
LOG.debug("{} targets created, return status {}", targets.size(), 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(
entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<DistributionSetType> types = new ArrayList<>();
types.add(testType);
// DST does not exist
mvc.perform(get("/rest/v1/distributionsettypes/12345678")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
@@ -554,9 +551,8 @@ public class MgmtDistributionSetTypeResourceTest extends AbstractRestIntegration
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
final DistributionSetType missingName = entityFactory.generateDistributionSetType("test123", null, "Desc123");
mvc.perform(post("/rest/v1/distributionsettypes")
.content(JsonBuilder.distributionSetTypes(Lists.newArrayList(missingName)))
// Missing mandatory field name
mvc.perform(post("/rest/v1/distributionsettypes").content("[{\"description\":\"Desc123\",\"key\":\"test123\"}]")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.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);
sm = softwareManagement.createSoftwareModule(sm);
final List<SoftwareModule> modules = new ArrayList<>();
modules.add(sm);
final List<SoftwareModule> modules = Lists.newArrayList(sm);
// SM does not exist
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())
.andExpect(status().isBadRequest());
final SoftwareModule missingName = entityFactory.generateSoftwareModule(osType, null, "version 1", null, null);
mvc.perform(
post("/rest/v1/softwaremodules").content(JsonBuilder.softwareModules(Lists.newArrayList(missingName)))
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
mvc.perform(post("/rest/v1/softwaremodules")
.content("[{\"description\":\"Desc123\",\"key\":\"test123\", \"type\":\"os\"}]")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
final SoftwareModule toLongName = entityFactory.generateSoftwareModule(osType,
RandomStringUtils.randomAscii(80), "version 1", null, null);

View File

@@ -318,8 +318,7 @@ public class MgmtSoftwareModuleTypeResourceTest extends AbstractRestIntegrationT
final SoftwareModuleType testType = softwareManagement.createSoftwareModuleType(
entityFactory.generateSoftwareModuleType("test123", "TestName123", "Desc123", 5));
final List<SoftwareModuleType> types = new ArrayList<>();
types.add(testType);
final List<SoftwareModuleType> types = Lists.newArrayList(testType);
// SM does not exist
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())
.andExpect(status().isBadRequest());
final SoftwareModuleType missingName = entityFactory.generateSoftwareModuleType("test123", null, "Desc123", 5);
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())
.andExpect(status().isBadRequest());

View File

@@ -23,7 +23,7 @@
<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 -->
<logger name="server-security" level="INFO" />

View File

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

View File

@@ -183,22 +183,6 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
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
* given artifact through the action history list. This can e.g. indicate if
@@ -218,6 +202,25 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
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
* it does not exist yet.
@@ -300,4 +303,32 @@ public interface ControllerManagement {
TargetInfo updateTargetStatus(@NotNull TargetInfo targetInfo, TargetUpdateStatus status, Long lastTargetQuery,
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
*/
public List<Long> getActions() {
return actions;
if (actions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actions);
}
@Override

View File

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

View File

@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.repository;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -64,7 +66,7 @@ public interface EntityFactory {
*
* @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.
@@ -81,7 +83,7 @@ public interface EntityFactory {
*
* @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);
/**
@@ -99,7 +101,8 @@ public interface EntityFactory {
*
* @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.
@@ -124,8 +127,8 @@ public interface EntityFactory {
*
* @return {@link DistributionSet} object
*/
DistributionSet generateDistributionSet(String name, String version, String description, DistributionSetType type,
Collection<SoftwareModule> moduleList);
DistributionSet generateDistributionSet(@NotNull String name, @NotNull String version, String description,
@NotNull DistributionSetType type, Collection<SoftwareModule> moduleList);
/**
* Generates an empty {@link DistributionSetMetadata} element without
@@ -148,7 +151,8 @@ public interface EntityFactory {
*
* @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.
@@ -164,7 +168,7 @@ public interface EntityFactory {
* of the tag
* @return {@link DistributionSetTag} object
*/
DistributionSetTag generateDistributionSetTag(String name);
DistributionSetTag generateDistributionSetTag(@NotNull String name);
/**
* Generates a {@link DistributionSetTag} without persisting it.
@@ -177,7 +181,7 @@ public interface EntityFactory {
* of the tag
* @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.
@@ -198,7 +202,7 @@ public interface EntityFactory {
*
* @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.
@@ -237,8 +241,8 @@ public interface EntityFactory {
*
* @return {@link SoftwareModule} object
*/
SoftwareModule generateSoftwareModule(SoftwareModuleType type, String name, String version, String description,
String vendor);
SoftwareModule generateSoftwareModule(@NotNull SoftwareModuleType type, @NotNull String name,
@NotNull String version, String description, String vendor);
/**
* Generates an empty {@link SoftwareModuleMetadata} pair without persisting
@@ -260,7 +264,8 @@ public interface EntityFactory {
*
* @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.
@@ -283,7 +288,8 @@ public interface EntityFactory {
*
* @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.
@@ -307,7 +313,7 @@ public interface EntityFactory {
*
* @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.
@@ -330,7 +336,7 @@ public interface EntityFactory {
* of the tag
* @return {@link TargetTag} object
*/
TargetTag generateTargetTag(String name);
TargetTag generateTargetTag(@NotNull String name);
/**
* Generates a {@link TargetTag} without persisting it.
@@ -343,7 +349,7 @@ public interface EntityFactory {
* of the tag
* @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.

View File

@@ -155,11 +155,13 @@ public interface SoftwareManagement {
/**
* deletes a software module meta data entry.
*
* @param id
* the ID of the software module meta data to delete
* @param softwareModule
* where meta data has to be deleted
* @param key
* of the metda data element
*/
@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.
@@ -251,9 +253,10 @@ public interface SoftwareManagement {
/**
* finds a single software module meta data by its id.
*
* @param id
* the id of the software module meta data containing the meta
* data key and the ID of the software module
* @param softwareModule
* where meta data has to be found
* @param key
* of the meta data element
* @return the found SoftwareModuleMetadata or {@code null} if not exits
* @throws EntityNotFoundException
* in case the meta data does not exists for the given key
@@ -280,8 +283,8 @@ public interface SoftwareManagement {
*
* @param softwareModuleId
* the software module id to retrieve the meta data from
* @param spec
* the specification to filter the result
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* the page request to page the result
* @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.
*
* @param spec
* the specification to filter the software modules
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* pagination parameter
* @return the found {@link SoftwareModule}s
@@ -392,7 +395,7 @@ public interface SoftwareManagement {
* {@link SoftwareModuleType#getKey()}
*/
@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.
*
* @param spec
* the specification to filter the software modules types
* @param rsqlParam
* filter definition in RSQL syntax
* @param pageable
* pagination parameter
* @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
+ 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();
/**
@@ -93,4 +94,14 @@ public interface SystemManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
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
* @param rolloutId
* the ID of the rollout the group has been created
* @param rolloutGroupId
* identifier of this group
* @param totalRolloutGroup
* the total number of rollout groups for this rollout
* @param createdRolloutGroup

View File

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

View File

@@ -37,6 +37,6 @@ public interface Artifact extends TenantAwareBaseEntity {
/**
* @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;
int result = 1;
result = prime * result + (assigned ? 1231 : 1237);
result = prime * result + (softwareModule == null ? 0 : softwareModule.hashCode());
result = prime * result + ((softwareModule == null) ? 0 : softwareModule.hashCode());
return result;
}
@@ -72,7 +72,7 @@ public class AssignedSoftwareModule implements Serializable {
if (obj == null) {
return false;
}
if (!(obj instanceof AssignedSoftwareModule)) {
if (getClass() != obj.getClass()) {
return false;
}
final AssignedSoftwareModule other = (AssignedSoftwareModule) obj;

View File

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

View File

@@ -26,10 +26,25 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
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();
/**
* @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
* purposes.

View File

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

View File

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

View File

@@ -11,6 +11,9 @@ package org.eclipse.hawkbit.repository.model;
/**
* 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 {
/**

View File

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

View File

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

View File

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

View File

@@ -32,12 +32,12 @@ public interface Target extends NamedEntity {
String getControllerId();
/**
* @return assigned {@link TargetTag}s.
* @return immutable set of assigned {@link TargetTag}s.
*/
Set<TargetTag> getTags();
/**
* @return {@link Action} history of the {@link Target}.
* @return immutable {@link Action} history of the {@link Target}.
*/
List<Action> getActions();
@@ -64,4 +64,19 @@ public interface Target extends NamedEntity {
*/
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}
* polled the server the last time.
* polled the server the last time or <code>null</code> if target
* has never queried yet.
*/
Long getLastTargetQuery();

View File

@@ -17,7 +17,7 @@ import java.util.List;
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();

View File

@@ -89,6 +89,10 @@
<groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- Test -->
<dependency>

View File

@@ -8,7 +8,6 @@
*/
package org.eclipse.hawkbit;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.repository.ArtifactManagement;
@@ -75,6 +74,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import com.google.common.collect.Maps;
import com.google.common.eventbus.EventBus;
/**
@@ -205,7 +205,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Override
protected Map<String, Object> getVendorProperties() {
final Map<String, Object> properties = new HashMap<>();
final Map<String, Object> properties = Maps.newHashMapWithExpectedSize(5);
// Turn off dynamic weaving to disable LTW lookup in static weaving mode
properties.put("eclipselink.weaving", "false");
// needed for reports

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