Merge branch 'master' into feature_target_filtering_supports_overdue
Conflicts: hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterQueryValidation.java hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/target-filter-query.scss
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ public class SpSenderService extends SenderService {
|
||||
headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant());
|
||||
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
||||
actionUpdateStatus.getMessage().addAll(updateResultMessages);
|
||||
actionUpdateStatus.addMessage(updateResultMessages);
|
||||
actionUpdateStatus.setActionId(cacheValue.getActionId());
|
||||
return convertMessage(actionUpdateStatus, messageProperties);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,12 +16,6 @@ hawkbit.server.ddi.security.authentication.anonymous.enabled=true
|
||||
hawkbit.server.ddi.security.authentication.targettoken.enabled=true
|
||||
hawkbit.server.ddi.security.authentication.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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,25 +24,22 @@ 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() {
|
||||
public HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) {
|
||||
return () -> {
|
||||
try {
|
||||
return new URL(serverProperties.getUrl());
|
||||
@@ -50,4 +49,16 @@ public class PropertyHostnameResolverAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param urlHandlerProperties
|
||||
* for bean configuration
|
||||
* @return PropertyBasedArtifactUrlHandler bean
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ArtifactUrlHandler.class)
|
||||
public PropertyBasedArtifactUrlHandler propertyBasedArtifactUrlHandler(
|
||||
final ArtifactUrlHandlerProperties urlHandlerProperties) {
|
||||
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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().toUpperCase(), entry.getValue().getRel(),
|
||||
generateUrl(entry.getValue(), placeholder)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
private static String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder) {
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder).entrySet();
|
||||
|
||||
String urlPattern = protocol.getRef();
|
||||
|
||||
String urlPattern = properties.getPattern();
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(targetId, softwareModuleId,
|
||||
UrlEscapers.urlFragmentEscaper().escape(filename), sha1Hash, protocolString, properties).entrySet();
|
||||
for (final Entry<String, String> entry : entrySet) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.api;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
|
||||
import org.eclipse.hawkbit.api.URLPlaceholder.SoftwareData;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
/**
|
||||
* Tests for creating urls to download artifacts.
|
||||
*/
|
||||
@Features("Unit Tests - Artifact URL Handler")
|
||||
@Stories("Test to generate the artifact download URL")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PropertyBasedArtifactUrlHandlerTest {
|
||||
|
||||
private static final String TEST_PROTO = "coap";
|
||||
private static final String TEST_REL = "download-udp";
|
||||
|
||||
private static final long TENANT_ID = 456789L;
|
||||
private static final String CONTROLLER_ID = "Test";
|
||||
private static final String FILENAME = "Afile1234";
|
||||
private static final long SOFTWAREMODULEID = 87654L;
|
||||
private static final long TARGETID = 3474366L;
|
||||
private static final String TARGETID_BASE62 = "EZqA";
|
||||
private static final String SHA1HASH = "test12345";
|
||||
private static final long ARTIFACTID = 1345678L;
|
||||
private static final String ARTIFACTID_BASE62 = "5e4U";
|
||||
private static final String TENANT = "TEST_TENANT";
|
||||
|
||||
private static final String HTTP_LOCALHOST = "http://localhost:8080/";
|
||||
|
||||
private ArtifactUrlHandler urlHandlerUnderTest;
|
||||
|
||||
private ArtifactUrlHandlerProperties properties;
|
||||
|
||||
private static URLPlaceholder placeholder = new URLPlaceholder(TENANT, TENANT_ID, CONTROLLER_ID, TARGETID,
|
||||
new SoftwareData(SOFTWAREMODULEID, FILENAME, ARTIFACTID, SHA1HASH));
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
properties = new ArtifactUrlHandlerProperties();
|
||||
urlHandlerUnderTest = new PropertyBasedArtifactUrlHandler(properties);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the generation of http download url.")
|
||||
public void urlGenerationWithDefaultConfiguration() {
|
||||
properties.getProtocols().put("download-http", new UrlProtocol());
|
||||
|
||||
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
|
||||
assertEquals(Lists.newArrayList(
|
||||
new ArtifactUrl("http".toUpperCase(), "download-http", HTTP_LOCALHOST + TENANT + "/controller/v1/"
|
||||
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
|
||||
ddiUrls);
|
||||
|
||||
final List<ArtifactUrl> dmfUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
|
||||
assertEquals(ddiUrls, dmfUrls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the generation of custom download url with a CoAP example that supports DMF only.")
|
||||
public void urlGenerationWithCustomConfiguration() {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setIp("127.0.0.1");
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(Lists.newArrayList(ApiType.DMF));
|
||||
proto.setRef("{protocol}://{ip}:{port}/fw/{tenant}/{controllerId}/sha1/{artifactSHA1}");
|
||||
properties.getProtocols().put(TEST_PROTO, proto);
|
||||
|
||||
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
|
||||
|
||||
assertThat(urls).isEmpty();
|
||||
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
|
||||
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO.toUpperCase(), TEST_REL,
|
||||
"coap://127.0.0.1:5683/fw/" + TENANT + "/" + CONTROLLER_ID + "/sha1/" + SHA1HASH)), urls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the generation of custom download url using Base62 references with a CoAP example that supports DMF only.")
|
||||
public void urlGenerationWithCustomShortConfiguration() {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setIp("127.0.0.1");
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(Lists.newArrayList(ApiType.DMF));
|
||||
proto.setRef("{protocol}://{ip}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
|
||||
properties.getProtocols().put("ftp", proto);
|
||||
|
||||
List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI);
|
||||
|
||||
assertThat(urls).isEmpty();
|
||||
urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
|
||||
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO.toUpperCase(), TEST_REL,
|
||||
TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGETID_BASE62 + "/" + ARTIFACTID_BASE62)),
|
||||
urls);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ public class DdiCancelActionToStop {
|
||||
* ID of the action to be stoppedW
|
||||
*/
|
||||
public DdiCancelActionToStop(final String stopId) {
|
||||
super();
|
||||
this.stopId = stopId;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ public class DdiConfig {
|
||||
* configuration of the SP target
|
||||
*/
|
||||
public DdiConfig(final DdiPolling polling) {
|
||||
super();
|
||||
this.polling = polling;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public class DdiResult {
|
||||
|
||||
private String name;
|
||||
|
||||
private FinalResult(final String name) {
|
||||
FinalResult(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,17 +39,17 @@ public interface DdiRootControllerRestApi {
|
||||
* Returns all artifacts of a given software module and target.
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* 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);
|
||||
|
||||
/**
|
||||
@@ -57,16 +57,16 @@ public interface DdiRootControllerRestApi {
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* @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
|
||||
@@ -74,8 +74,8 @@ public interface DdiRootControllerRestApi {
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* of the related target
|
||||
* @param controllerId
|
||||
* of the target
|
||||
* @param softwareModuleId
|
||||
* of the parent software module
|
||||
* @param fileName
|
||||
@@ -89,9 +89,9 @@ 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);
|
||||
|
||||
@@ -100,8 +100,8 @@ public interface DdiRootControllerRestApi {
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* of the related target
|
||||
* @param controllerId
|
||||
* of the target
|
||||
* @param softwareModuleId
|
||||
* of the parent software module
|
||||
* @param fileName
|
||||
@@ -114,10 +114,10 @@ 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);
|
||||
|
||||
@@ -126,8 +126,8 @@ public interface DdiRootControllerRestApi {
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* of the target that matches to controller id
|
||||
* @param controllerId
|
||||
* of the target
|
||||
* @param actionId
|
||||
* of the {@link DdiDeploymentBase} that matches to active
|
||||
* actions.
|
||||
@@ -139,10 +139,10 @@ 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);
|
||||
|
||||
@@ -150,10 +150,10 @@ public interface DdiRootControllerRestApi {
|
||||
* This is the feedback channel for the {@link DdiDeploymentBase} action.
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* 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
|
||||
@@ -162,37 +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 request
|
||||
* 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 tenant
|
||||
* of the request
|
||||
* @param targetid
|
||||
* @param controllerId
|
||||
* ID of the calling target
|
||||
* @param actionId
|
||||
* of the action
|
||||
@@ -201,10 +201,10 @@ 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);
|
||||
|
||||
/**
|
||||
@@ -212,10 +212,10 @@ public interface DdiRootControllerRestApi {
|
||||
* the target.
|
||||
*
|
||||
* @param tenant
|
||||
* of the request
|
||||
* 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
|
||||
@@ -225,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ 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;
|
||||
@@ -28,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;
|
||||
@@ -45,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());
|
||||
|
||||
}
|
||||
@@ -68,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 ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
|
||||
|
||||
return module.getLocalArtifacts().stream()
|
||||
.map(artifact -> createArtifact(targetid, artifactUrlHandler, artifact)).collect(Collectors.toList());
|
||||
.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,
|
||||
@@ -132,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -205,10 +213,10 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
// 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);
|
||||
@@ -231,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);
|
||||
@@ -247,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.");
|
||||
@@ -266,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())) {
|
||||
@@ -293,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();
|
||||
@@ -308,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;
|
||||
}
|
||||
|
||||
@@ -339,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!");
|
||||
@@ -362,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);
|
||||
@@ -391,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.");
|
||||
@@ -403,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())) {
|
||||
@@ -440,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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,8 +14,13 @@ 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;
|
||||
@@ -40,6 +45,7 @@ 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;
|
||||
@@ -49,7 +55,7 @@ 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.
|
||||
*
|
||||
*/
|
||||
@@ -252,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,18 +332,21 @@ 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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
package org.eclipse.hawkbit.dmf.json.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@@ -18,9 +20,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* JSON representation of action update status.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -32,7 +31,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 +58,32 @@ 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);
|
||||
}
|
||||
|
||||
public boolean addMessage(final Collection<String> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.message == null) {
|
||||
this.message = new ArrayList<>(messages);
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.message.addAll(messages);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.rsql;
|
||||
|
||||
/**
|
||||
* An interface declaration which validates an RSQL based query syntax and
|
||||
* allows providing suggestions e.g. in case of syntax errors or current cursor
|
||||
* position.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RsqlValidationOracle {
|
||||
|
||||
/**
|
||||
* Parses and validates an given RSQL based query syntax and provides
|
||||
* suggestion based on syntax error and cursor positioning.
|
||||
*
|
||||
* @param rsqlQuery
|
||||
* an RSQL based query string to parse.
|
||||
* @param cursorPosition
|
||||
* the position of the cursor to retrieve suggestions at the
|
||||
* position. {@code -1} indicates for no cursor suggestion
|
||||
* @return a validation oracle context providing information about syntax
|
||||
* errors and possible suggestions for fixing the syntax error or at
|
||||
* the cursor position to replace tokens
|
||||
*/
|
||||
ValidationOracleContext suggest(final String rsqlQuery, final int cursorPosition);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.rsql;
|
||||
|
||||
/**
|
||||
* A suggestion which contains the start and the end character position of the
|
||||
* suggested token of the suggestion of the token and the actual suggestion.
|
||||
*/
|
||||
public class SuggestToken {
|
||||
|
||||
private final int start;
|
||||
private final int end;
|
||||
private final String suggestion;
|
||||
private final String tokenImageName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param start
|
||||
* the character position of the start of the token
|
||||
* @param end
|
||||
* the character position of the end of the token
|
||||
* @param tokenImageName
|
||||
* the entered name of the token, e.g. could be the beginning of
|
||||
* the suggestion like 'na' or 'name'
|
||||
* @param suggestion
|
||||
* the token suggestion
|
||||
*/
|
||||
public SuggestToken(final int start, final int end, final String tokenImageName, final String suggestion) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.tokenImageName = tokenImageName;
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public String getSuggestion() {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
public String getTokenImageName() {
|
||||
return tokenImageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SuggestToken [start=" + start + ", end=" + end + ", suggestion=" + suggestion + ", tokenImageName="
|
||||
+ tokenImageName + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.rsql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The context which holds suggestions for the current cursor position.
|
||||
*/
|
||||
public class SuggestionContext {
|
||||
|
||||
private String rsqlQuery;
|
||||
private int cursorPosition;
|
||||
private List<SuggestToken> suggestions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SuggestionContext() {
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rsqlQuery
|
||||
* the original RSQL based query the suggestions based on
|
||||
* @param cursorPosition
|
||||
* the current cursor position
|
||||
* @param suggestions
|
||||
* the suggestions for the current cursor position
|
||||
*/
|
||||
public SuggestionContext(final String rsqlQuery, final int cursorPosition, final List<SuggestToken> suggestions) {
|
||||
this.rsqlQuery = rsqlQuery;
|
||||
this.cursorPosition = cursorPosition;
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
public List<SuggestToken> getSuggestions() {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
public int getCursorPosition() {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
public String getRsqlQuery() {
|
||||
return rsqlQuery;
|
||||
}
|
||||
|
||||
public void setRsqlQuery(final String rsqlQuery) {
|
||||
this.rsqlQuery = rsqlQuery;
|
||||
}
|
||||
|
||||
public void setCursorPosition(final int cursorPosition) {
|
||||
this.cursorPosition = cursorPosition;
|
||||
}
|
||||
|
||||
public void setSuggestions(final List<SuggestToken> suggestions) {
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.rsql;
|
||||
|
||||
/**
|
||||
* An syntax error context object which holds the character position of the
|
||||
* syntax error and message.
|
||||
*/
|
||||
public class SyntaxErrorContext {
|
||||
|
||||
private int characterPosition = -1;
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SyntaxErrorContext() {
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param characterPosition
|
||||
* the position of the character within the RSQL query string the
|
||||
* error occurs.
|
||||
* @param errorMessage
|
||||
* the error message with further information
|
||||
*/
|
||||
public SyntaxErrorContext(final int characterPosition, final String errorMessage) {
|
||||
this.characterPosition = characterPosition;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public int getCharacterPosition() {
|
||||
return characterPosition;
|
||||
}
|
||||
|
||||
public void setCharacterPosition(final int characterPosition) {
|
||||
this.characterPosition = characterPosition;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(final String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.rsql;
|
||||
|
||||
/**
|
||||
* A context object which contains information about validation and suggestions
|
||||
* of a parsed RSQL query.
|
||||
*/
|
||||
public class ValidationOracleContext {
|
||||
|
||||
private boolean syntaxError;
|
||||
|
||||
private SuggestionContext suggestionContext;
|
||||
|
||||
private SyntaxErrorContext syntaxErrorContext;
|
||||
|
||||
public boolean isSyntaxError() {
|
||||
return syntaxError;
|
||||
}
|
||||
|
||||
public SuggestionContext getSuggestionContext() {
|
||||
return suggestionContext;
|
||||
}
|
||||
|
||||
public SyntaxErrorContext getSyntaxErrorContext() {
|
||||
return syntaxErrorContext;
|
||||
}
|
||||
|
||||
public void setSyntaxError(final boolean syntaxError) {
|
||||
this.syntaxError = syntaxError;
|
||||
}
|
||||
|
||||
public void setSuggestionContext(final SuggestionContext suggestionContext) {
|
||||
this.suggestionContext = suggestionContext;
|
||||
}
|
||||
|
||||
public void setSyntaxErrorContext(final SyntaxErrorContext syntaxErrorContext) {
|
||||
this.syntaxErrorContext = syntaxErrorContext;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,8 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.SystemManagementHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantConfigurationManagementHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
@@ -92,6 +94,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
@Autowired
|
||||
private EventBus eventBus;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RsqlValidationOracle rsqlValidationOracle() {
|
||||
return new RsqlParserValidationOracle();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SystemSecurityContext} singleton bean which make it
|
||||
* accessible in beans which cannot access the service directly,
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifactProvider;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
|
||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
|
||||
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
|
||||
import org.eclipse.hawkbit.repository.model.LocalArtifact;
|
||||
@@ -194,7 +193,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Artifact findArtifact(final Long id) {
|
||||
public LocalArtifact findLocalArtifact(final Long id) {
|
||||
return localArtifactRepository.findOne(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,15 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTargetArtifactAssigned(final Long targetId, final LocalArtifact localArtifact) {
|
||||
final Target target = targetRepository.findOne(targetId);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActiveActionByTarget(final Target target) {
|
||||
return actionRepository.findByTargetAndActiveOrderByIdAsc((JpaTarget) target, true);
|
||||
@@ -456,12 +465,6 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
return actionStatusRepository.save((JpaActionStatus) statusMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecurityTokenByControllerId(final String controllerId) {
|
||||
final Target target = targetRepository.findByControllerId(controllerId);
|
||||
return target != null ? target.getSecurityToken() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Modifying
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@@ -475,4 +478,14 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
cacheWriteNotify.downloadProgress(statusId, requestedBytes, shippedBytesSinceLast, shippedBytesOverall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target findByControllerId(final String controllerId) {
|
||||
return targetRepository.findByControllerId(controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target findByTargetId(final long targetId) {
|
||||
return targetRepository.findOne(targetId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -356,14 +356,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
private void assignDistributionSetEvent(final JpaTarget target, final Long actionId,
|
||||
final List<JpaSoftwareModule> modules) {
|
||||
((JpaTargetInfo) target.getTargetInfo()).setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
final Collection<SoftwareModule> softwareModules = (Collection) modules;
|
||||
afterCommit.afterCommit(() -> {
|
||||
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
|
||||
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
|
||||
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
|
||||
targetSecurityToken));
|
||||
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), target,
|
||||
actionId, softwareModules));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -299,4 +299,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
Constants.DST_DEFAULT_OS_WITH_APPS_NAME, "Default type with Firmware/OS and optional app(s).")
|
||||
.addMandatoryModuleType(os).addOptionalModuleType(app));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TenantMetaData getTenantMetadata(final Long tenantId) {
|
||||
return tenantMetaDataRepository.findOne(tenantId);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class JpaTagManagement implements TagManagement {
|
||||
|
||||
final List<JpaTarget> changed = new LinkedList<>();
|
||||
for (final JpaTarget target : targetRepository.findByTag(tag)) {
|
||||
target.getTags().remove(tag);
|
||||
target.removeTag(tag);
|
||||
changed.add(target);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,24 +75,4 @@ public class JpaDistributionSetTag extends AbstractJpaTag implements Distributio
|
||||
|
||||
return Collections.unmodifiableList(assignedToDistributionSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + this.getClass().getName().hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) { // NOSONAR - as this is generated
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof DistributionSetTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
return tags;
|
||||
return Collections.unmodifiableSet(tags);
|
||||
}
|
||||
|
||||
public List<RolloutTargetGroup> getRolloutTargetGroup() {
|
||||
@@ -210,7 +210,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Lon
|
||||
|
||||
public boolean addAction(final Action action) {
|
||||
if (actions == null) {
|
||||
actions = new ArrayList<>(4);
|
||||
actions = new ArrayList<>();
|
||||
}
|
||||
|
||||
return actions.add(action);
|
||||
|
||||
@@ -73,24 +73,4 @@ public class JpaTargetTag extends AbstractJpaTag implements TargetTag {
|
||||
return Collections.unmodifiableList(assignedToTargets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + this.getClass().getName().hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof TargetTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import cz.jirutka.rsql.parser.ParseException;
|
||||
|
||||
/**
|
||||
* A {@link ParseException} wrapper which allows to access the parsing
|
||||
* information from the exception using reflection due there is no other access
|
||||
* of this information. See issue for requesting feature
|
||||
* <a href="https://github.com/jirutka/rsql-parser/issues/22">https://github.com
|
||||
* /jirutka/rsql-parser/issues/22</a>
|
||||
*/
|
||||
public class ParseExceptionWrapper {
|
||||
|
||||
private static final String FIELD_EXPECTED_TOKEN_SEQ = "expectedTokenSequences";
|
||||
private static final String FIELD_CURRENT_TOKEN = "currentToken";
|
||||
|
||||
private final ParseException parseException;
|
||||
private final Class<? extends ParseException> parseExceptionClass;
|
||||
private Field expectedTokenSequenceField;
|
||||
private Field currentTokenField;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param parseException
|
||||
* the original parsing exception object to access its field
|
||||
* using reflection
|
||||
*/
|
||||
public ParseExceptionWrapper(final ParseException parseException) {
|
||||
this.parseException = parseException;
|
||||
parseExceptionClass = parseException.getClass();
|
||||
|
||||
try {
|
||||
expectedTokenSequenceField = getAccessibleField(parseExceptionClass, FIELD_EXPECTED_TOKEN_SEQ);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
expectedTokenSequenceField = null;
|
||||
}
|
||||
|
||||
try {
|
||||
currentTokenField = getAccessibleField(parseExceptionClass, FIELD_CURRENT_TOKEN);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
currentTokenField = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int[][] getExpectedTokenSequence() {
|
||||
if (expectedTokenSequenceField == null) {
|
||||
return new int[0][0];
|
||||
}
|
||||
return (int[][]) getValue(expectedTokenSequenceField, parseException);
|
||||
}
|
||||
|
||||
public TokenWrapper getCurrentToken() {
|
||||
if (currentTokenField == null) {
|
||||
return null;
|
||||
}
|
||||
return new TokenWrapper(getValue(currentTokenField, parseException));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ParseExceptionWrapper [getExpectedTokenSequence()=" + Arrays.toString(getExpectedTokenSequence())
|
||||
+ ", getCurrentToken()=" + getCurrentToken() + "]";
|
||||
}
|
||||
|
||||
private static Field getAccessibleField(final Class<?> clazz, final String field) throws NoSuchFieldException {
|
||||
final Field declaredField = clazz.getDeclaredField(field);
|
||||
declaredField.setAccessible(true);
|
||||
return declaredField;
|
||||
}
|
||||
|
||||
private static Object getValue(final Field field, final Object instance) {
|
||||
try {
|
||||
return field.get(instance);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link TokenWrapper} which wraps the
|
||||
* {@code cz.jirutka.rsql.parser.Token} class of the {@link ParseException}
|
||||
* which otherwise is not accessible.
|
||||
*/
|
||||
public static final class TokenWrapper {
|
||||
|
||||
private static final String FIELD_NEXT = "next";
|
||||
private static final String FIELD_KIND = "kind";
|
||||
private static final String FIELD_IMAGE = "image";
|
||||
private static final String FIELD_BEGIN_COL = "beginColumn";
|
||||
private static final String FIELD_END_COL = "endColumn";
|
||||
|
||||
private final Object tokenInstance;
|
||||
|
||||
private Field nextTokenField;
|
||||
private Field kindTokenField;
|
||||
private Field imageTokenField;
|
||||
private Field beginColumnTokenField;
|
||||
private Field endColumnTokenField;
|
||||
|
||||
private TokenWrapper(final Object tokenField) {
|
||||
this.tokenInstance = tokenField;
|
||||
|
||||
try {
|
||||
nextTokenField = getAccessibleField(tokenField.getClass(), FIELD_NEXT);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
nextTokenField = null;
|
||||
}
|
||||
try {
|
||||
kindTokenField = getAccessibleField(tokenField.getClass(), FIELD_KIND);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
kindTokenField = null;
|
||||
}
|
||||
|
||||
try {
|
||||
imageTokenField = getAccessibleField(tokenField.getClass(), FIELD_IMAGE);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
imageTokenField = null;
|
||||
}
|
||||
|
||||
try {
|
||||
beginColumnTokenField = getAccessibleField(tokenField.getClass(), FIELD_BEGIN_COL);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
beginColumnTokenField = null;
|
||||
}
|
||||
|
||||
try {
|
||||
endColumnTokenField = getAccessibleField(tokenField.getClass(), FIELD_END_COL);
|
||||
} catch (@SuppressWarnings("squid:S1166") final NoSuchFieldException e) {
|
||||
endColumnTokenField = null;
|
||||
}
|
||||
}
|
||||
|
||||
public TokenWrapper getNext() {
|
||||
final Object nextToken = getValue(nextTokenField, tokenInstance);
|
||||
return nextToken != null ? new TokenWrapper(nextToken) : null;
|
||||
|
||||
}
|
||||
|
||||
public int getKind() {
|
||||
if (kindTokenField == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) getValue(kindTokenField, tokenInstance);
|
||||
}
|
||||
|
||||
public String getImage() {
|
||||
if (imageTokenField == null) {
|
||||
return null;
|
||||
}
|
||||
return (String) getValue(imageTokenField, tokenInstance);
|
||||
}
|
||||
|
||||
public int getBeginColumn() {
|
||||
if (beginColumnTokenField == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) getValue(beginColumnTokenField, tokenInstance);
|
||||
}
|
||||
|
||||
public int getEndColumn() {
|
||||
if (endColumnTokenField == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) getValue(endColumnTokenField, tokenInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TokenWrapper [tokenInstance=" + tokenInstance + ", getNext()=" + getNext() + ", getKind()="
|
||||
+ getKind() + ", getImage()=" + getImage() + ", getBeginColumn()=" + getBeginColumn()
|
||||
+ ", getEndColumn()=" + getEndColumn() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.ParseExceptionWrapper.TokenWrapper;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||
import org.eclipse.hawkbit.repository.rsql.SuggestToken;
|
||||
import org.eclipse.hawkbit.repository.rsql.SuggestionContext;
|
||||
import org.eclipse.hawkbit.repository.rsql.SyntaxErrorContext;
|
||||
import org.eclipse.hawkbit.repository.rsql.ValidationOracleContext;
|
||||
import org.eclipse.persistence.exceptions.ConversionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import cz.jirutka.rsql.parser.ParseException;
|
||||
import cz.jirutka.rsql.parser.RSQLParserException;
|
||||
|
||||
/**
|
||||
* An implementation of {@link RsqlValidationOracle} which retrieves the
|
||||
* exception using the {@link ParseException} to retrieve the suggestions.
|
||||
*
|
||||
* The suggestion only works when there are syntax errors existing because the
|
||||
* information about current and next tokens in the RSQL syntax are from the
|
||||
* {@link ParseException}.
|
||||
*
|
||||
* There is a feature request on the GitHub project
|
||||
* <a href="https://github.com/jirutka/rsql-parser/issues/22">https://github.com
|
||||
* /jirutka/rsql-parser/issues/22</a>
|
||||
*
|
||||
*/
|
||||
public class RsqlParserValidationOracle implements RsqlValidationOracle {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RsqlParserValidationOracle.class);
|
||||
|
||||
@Autowired
|
||||
private TargetManagement targetManagement;
|
||||
|
||||
@Override
|
||||
public ValidationOracleContext suggest(final String rsqlQuery, final int cursorPosition) {
|
||||
|
||||
final List<SuggestToken> expectedTokens = new ArrayList<>();
|
||||
final ValidationOracleContext context = new ValidationOracleContext();
|
||||
context.setSyntaxError(true);
|
||||
final SuggestionContext suggestionContext = new SuggestionContext();
|
||||
context.setSuggestionContext(suggestionContext);
|
||||
final SyntaxErrorContext errorContext = new SyntaxErrorContext();
|
||||
context.setSyntaxErrorContext(errorContext);
|
||||
|
||||
try {
|
||||
targetManagement.findTargetsAll(rsqlQuery, new PageRequest(0, 1));
|
||||
context.setSyntaxError(false);
|
||||
suggestionContext.getSuggestions().addAll(getLogicalOperatorSuggestion(rsqlQuery));
|
||||
} catch (final RSQLParameterSyntaxException | RSQLParserException ex) {
|
||||
setExceptionDetails(new Exception(ex.getCause().getCause()), expectedTokens);
|
||||
errorContext.setErrorMessage(getCustomMessage(ex.getCause().getMessage(), expectedTokens));
|
||||
suggestionContext.setSuggestions(expectedTokens);
|
||||
LOGGER.trace("Syntax exception on parsing :", ex);
|
||||
} catch (final RSQLParameterUnsupportedFieldException | IllegalArgumentException ex) {
|
||||
errorContext.setErrorMessage(getCustomMessage(ex.getMessage(), null));
|
||||
LOGGER.trace("Illegal argument on parsing :", ex);
|
||||
} catch (@SuppressWarnings("squid:S1166") final ConversionException | JpaSystemException e) {
|
||||
// noop
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private static Collection<? extends SuggestToken> getLogicalOperatorSuggestion(final String rsqlQuery) {
|
||||
if (!rsqlQuery.endsWith(" ")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (rsqlQuery.endsWith(" ")) {
|
||||
final int currentQueryLength = rsqlQuery.length();
|
||||
// only return and/or suggestion when there is a space at the end
|
||||
final Collection<String> tokenImages = TokenDescription.getTokenImage(TokenDescription.LOGICAL_OP);
|
||||
final List<SuggestToken> logicalOps = new ArrayList<>(tokenImages.size());
|
||||
for (final String tokenImage : tokenImages) {
|
||||
logicalOps.add(new SuggestToken(currentQueryLength, currentQueryLength + tokenImage.length(), null,
|
||||
tokenImage));
|
||||
}
|
||||
return logicalOps;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static void setExceptionDetails(final Exception ex, final List<SuggestToken> expectedTokens) {
|
||||
expectedTokens.addAll(getNextTokens(ex));
|
||||
}
|
||||
|
||||
private static List<SuggestToken> getNextTokens(final Exception e) {
|
||||
final ParseException parseException = findParseException(e);
|
||||
if (parseException == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<SuggestToken> listTokens = new ArrayList<>();
|
||||
final ParseExceptionWrapper parseExceptionWrapper = new ParseExceptionWrapper(parseException);
|
||||
final int[][] expectedTokenSequence = parseExceptionWrapper.getExpectedTokenSequence();
|
||||
final TokenWrapper currentToken = parseExceptionWrapper.getCurrentToken();
|
||||
final TokenWrapper nextToken = currentToken.getNext();
|
||||
final int currentTokenKind = currentToken.getKind();
|
||||
final String currentTokenImageName = currentToken.getImage();
|
||||
final int nextTokenBeginColumn = nextToken.getBeginColumn();
|
||||
final int currentTokenEndColumn = currentToken.getEndColumn();
|
||||
|
||||
// token == 5 is the field token, reverse engineering.
|
||||
if (currentTokenKind == 5) {
|
||||
final Optional<List<SuggestToken>> handleFieldTokenSuggestion = handleFieldTokenSuggestion(
|
||||
currentTokenImageName, nextTokenBeginColumn, currentTokenEndColumn);
|
||||
if (handleFieldTokenSuggestion.isPresent()) {
|
||||
return handleFieldTokenSuggestion.get();
|
||||
}
|
||||
}
|
||||
|
||||
for (final int[] is : expectedTokenSequence) {
|
||||
for (final int i : is) {
|
||||
final Collection<String> tokenImage = TokenDescription.getTokenImage(i);
|
||||
if (tokenImage != null && !tokenImage.isEmpty()) {
|
||||
tokenImage.forEach(image -> listTokens.add(new SuggestToken(currentTokenEndColumn + 1,
|
||||
nextTokenBeginColumn + image.length(), null, image)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return listTokens;
|
||||
}
|
||||
|
||||
private static Optional<List<SuggestToken>> handleFieldTokenSuggestion(final String currentTokenImageName,
|
||||
final int nextTokenBeginColumn, final int currentTokenEndColumn) {
|
||||
final boolean containsDot = currentTokenImageName.indexOf('.') != -1;
|
||||
if (shouldSuggestTopLevelFieldNames(currentTokenImageName, containsDot)) {
|
||||
return Optional
|
||||
.of(FieldNameDescription.toTopSuggestToken(nextTokenBeginColumn - currentTokenImageName.length(),
|
||||
nextTokenBeginColumn + currentTokenImageName.length(), currentTokenImageName));
|
||||
} else if (shouldSuggestDotToken(currentTokenImageName, containsDot)) {
|
||||
return Optional.of(
|
||||
Lists.newArrayList(new SuggestToken(currentTokenEndColumn, nextTokenBeginColumn + 1, null, ".")));
|
||||
} else if (shouldSuggestSubTokenFieldNames(currentTokenImageName, containsDot)) {
|
||||
return handleSubtokenSuggestion(currentTokenImageName, nextTokenBeginColumn);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static boolean shouldSuggestSubTokenFieldNames(final String currentTokenImageName,
|
||||
final boolean containsDot) {
|
||||
return containsDot && !FieldNameDescription.containsValue(currentTokenImageName);
|
||||
}
|
||||
|
||||
private static boolean shouldSuggestDotToken(final String currentTokenImageName, final boolean containsDot) {
|
||||
return !containsDot && FieldNameDescription.hasSubEntries(currentTokenImageName);
|
||||
}
|
||||
|
||||
private static boolean shouldSuggestTopLevelFieldNames(final String currentTokenImageName,
|
||||
final boolean containsDot) {
|
||||
return !containsDot && !FieldNameDescription.containsValue(currentTokenImageName)
|
||||
&& !FieldNameDescription.hasSubEntries(currentTokenImageName);
|
||||
}
|
||||
|
||||
private static Optional<List<SuggestToken>> handleSubtokenSuggestion(final String currentTokenImageName,
|
||||
final int nextTokenBeginColumn) {
|
||||
final String[] split = currentTokenImageName.split("\\.");
|
||||
for (final String string : split) {
|
||||
if (FieldNameDescription.containsValue(string)) {
|
||||
final String subTokenImage = split.length > 1 ? split[1] : null;
|
||||
final int subTokenBegin = nextTokenBeginColumn + currentTokenImageName.indexOf('.') + 1;
|
||||
return Optional.of(FieldNameDescription.toSubSuggestToken(subTokenBegin, subTokenBegin + 1, string,
|
||||
subTokenImage));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static ParseException findParseException(final Throwable e) {
|
||||
if (e instanceof ParseException) {
|
||||
return (ParseException) e;
|
||||
} else if (e.getCause() != null) {
|
||||
return findParseException(e.getCause());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getCustomMessage(final String message, final List<SuggestToken> expectedTokens) {
|
||||
String builder = message;
|
||||
|
||||
if (!message.contains(":")) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
builder = message.substring(message.indexOf(':') + 1, message.length());
|
||||
if (builder.indexOf("Was expecting") != -1) {
|
||||
builder = builder.substring(0, builder.lastIndexOf("Was expecting"));
|
||||
}
|
||||
|
||||
if (expectedTokens != null && !expectedTokens.isEmpty()) {
|
||||
final StringBuilder tokens = new StringBuilder();
|
||||
expectedTokens.stream().forEach(value -> tokens.append(value.getSuggestion() + ","));
|
||||
builder = builder.concat("Was expecting :" + tokens.toString().substring(0, tokens.length() - 1));
|
||||
}
|
||||
builder = builder.replace('\r', ' ');
|
||||
builder = builder.replace('\n', ' ');
|
||||
builder = builder.replaceAll(">", " ");
|
||||
builder = builder.replaceAll("<", " ");
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
// Token map with logical and comparator operator that are used for context
|
||||
// sensitive help on search query.
|
||||
private static final class TokenDescription {
|
||||
|
||||
private static final Multimap<Integer, String> TOKEN_MAP = ArrayListMultimap.create();
|
||||
|
||||
private static final int LOGICAL_OP = 8;
|
||||
private static final int COMPARATOR = 12;
|
||||
|
||||
static {
|
||||
TOKEN_MAP.put(LOGICAL_OP, "and");
|
||||
TOKEN_MAP.put(LOGICAL_OP, "or");
|
||||
TOKEN_MAP.put(COMPARATOR, "==");
|
||||
TOKEN_MAP.put(COMPARATOR, "!=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=ge=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=le=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=gt=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=lt=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=in=");
|
||||
TOKEN_MAP.put(COMPARATOR, "=out=");
|
||||
}
|
||||
|
||||
private TokenDescription() {
|
||||
|
||||
}
|
||||
|
||||
private static Collection<String> getTokenImage(final int tokenIndex) {
|
||||
return TOKEN_MAP.get(tokenIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class FieldNameDescription {
|
||||
|
||||
private static final Set<String> FIELD_NAMES = Arrays.stream(TargetFields.values())
|
||||
.map(field -> field.toString().toLowerCase()).collect(Collectors.toSet());
|
||||
|
||||
private static final Map<String, List<String>> SUB_NAMES = Arrays.stream(TargetFields.values()).collect(
|
||||
Collectors.toMap(field -> field.toString().toLowerCase(), field -> field.getSubEntityAttributes()));
|
||||
|
||||
private FieldNameDescription() {
|
||||
|
||||
}
|
||||
|
||||
private static boolean hasSubEntries(final String tokenImageName) {
|
||||
String tmpTokenName = tokenImageName;
|
||||
if (tokenImageName.contains(".")) {
|
||||
final String[] split = tokenImageName.split("\\.");
|
||||
if (split.length <= 0) {
|
||||
return false;
|
||||
}
|
||||
tmpTokenName = split[0];
|
||||
}
|
||||
final String finalTmpTokenName = tmpTokenName;
|
||||
return Arrays.stream(TargetFields.values())
|
||||
.filter(field -> field.toString().equalsIgnoreCase(finalTmpTokenName))
|
||||
.map(field -> field.getSubEntityAttributes()).flatMap(subentities -> subentities.stream())
|
||||
.count() > 0;
|
||||
}
|
||||
|
||||
private static List<SuggestToken> toTopSuggestToken(final int beginToken, final int endToken,
|
||||
final String tokenImageName) {
|
||||
return FIELD_NAMES.stream()
|
||||
.map(field -> new SuggestToken(beginToken, endToken, tokenImageName, field.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<SuggestToken> toSubSuggestToken(final int beginToken, final int endToken,
|
||||
final String topToken, final String tokenImageName) {
|
||||
return Arrays.stream(TargetFields.values()).filter(field -> field.toString().equalsIgnoreCase(topToken))
|
||||
.map(field -> field.getSubEntityAttributes()).flatMap(list -> list.stream())
|
||||
.map(subentity -> new SuggestToken(beginToken, endToken, tokenImageName, subentity))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static boolean containsValue(final String imageName) {
|
||||
if (!imageName.contains(".")) {
|
||||
return FIELD_NAMES.stream().filter(value -> value.equalsIgnoreCase(imageName)).count() > 0;
|
||||
}
|
||||
final String[] split = imageName.split("\\.");
|
||||
if (split.length > 1 && FIELD_NAMES.contains(split[0].toLowerCase())) {
|
||||
return SUB_NAMES.get(split[0].toLowerCase()).stream()
|
||||
.filter(subname -> new String(split[0] + "." + subname).equalsIgnoreCase(imageName))
|
||||
.count() > 0;
|
||||
}
|
||||
return FIELD_NAMES.stream().filter(value -> value.equalsIgnoreCase(imageName)).count() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -279,23 +279,23 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTestWithMongoD
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#findArtifact(java.lang.Long)}
|
||||
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#findLocalArtifact(java.lang.Long)}
|
||||
* .
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Test
|
||||
@Description("Loads an artifact based on given ID.")
|
||||
public void findArtifact() throws NoSuchAlgorithmException, IOException {
|
||||
@Description("Loads an local artifact based on given ID.")
|
||||
public void findLocalArtifact() throws NoSuchAlgorithmException, IOException {
|
||||
SoftwareModule sm = new JpaSoftwareModule(softwareManagement.findSoftwareModuleTypeByKey("os"), "name 1",
|
||||
"version 1", null, null);
|
||||
sm = softwareManagement.createSoftwareModule(sm);
|
||||
|
||||
final Artifact result = artifactManagement.createLocalArtifact(new RandomGeneratedInputStream(5 * 1024),
|
||||
final LocalArtifact result = artifactManagement.createLocalArtifact(new RandomGeneratedInputStream(5 * 1024),
|
||||
sm.getId(), "file1", false);
|
||||
|
||||
assertThat(artifactManagement.findArtifact(result.getId())).isEqualTo(result);
|
||||
assertThat(artifactManagement.findLocalArtifact(result.getId())).isEqualTo(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -937,7 +937,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
for (final Target myt : targets) {
|
||||
boolean found = false;
|
||||
for (final TargetAssignDistributionSetEvent event : events) {
|
||||
if (event.getControllerId().equals(myt.getControllerId())) {
|
||||
if (event.getTarget().getControllerId().equals(myt.getControllerId())) {
|
||||
found = true;
|
||||
final List<Action> activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(myt);
|
||||
assertThat(activeActionsByTarget).as("size of active actions for target is wrong").isNotEmpty();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||
import org.eclipse.hawkbit.repository.rsql.ValidationOracleContext;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Component Tests - Repository")
|
||||
@Stories("RSQL filter suggestion")
|
||||
public class RsqlParserValidationOracleTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private RsqlValidationOracle rsqlValidationOracle;
|
||||
|
||||
private static final String[] OP_SUGGESTIONS = new String[] { "==", "!=", "=ge=", "=le=", "=gt=", "=lt=", "=in=",
|
||||
"=out=" };
|
||||
private static final String[] FIELD_SUGGESTIONS = Arrays.stream(TargetFields.values())
|
||||
.map(field -> field.name().toLowerCase()).toArray(size -> new String[size]);
|
||||
private static final String[] AND_OR_SUGGESTIONS = new String[] { "and", "or" };
|
||||
private static final String[] NAME_VERSION_SUGGESTIONS = new String[] { "name", "version" };
|
||||
|
||||
@Test
|
||||
@Description("Verifies that suggestions contains all possible field names")
|
||||
public void suggestionContainsAllFieldNames() {
|
||||
final String rsqlQuery = "na";
|
||||
final List<String> currentSuggestions = getSuggestions(rsqlQuery);
|
||||
assertThat(currentSuggestions).containsOnly(FIELD_SUGGESTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that suggestions only contains the allowed operators")
|
||||
public void suggestionContainsOnlyOperators() {
|
||||
final String rsqlQuery = "name";
|
||||
final List<String> currentSuggestions = getSuggestions(rsqlQuery);
|
||||
assertThat(currentSuggestions).containsOnly(OP_SUGGESTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that suggestions only contains operator to combine RSQL filters (and, or)")
|
||||
public void suggestionContainsOnlyAndOrOperator() {
|
||||
final String rsqlQuery = "name==a ";
|
||||
final List<String> currentSuggestions = getSuggestions(rsqlQuery);
|
||||
assertThat(currentSuggestions).containsOnly(AND_OR_SUGGESTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that sub suggestions are shown")
|
||||
public void suggestionContainsSubFieldSuggestions() {
|
||||
final String rsqlQuery = "assignedds.";
|
||||
final List<String> currentSuggestions = getSuggestions(rsqlQuery);
|
||||
assertThat(currentSuggestions).containsOnly(NAME_VERSION_SUGGESTIONS);
|
||||
}
|
||||
|
||||
private List<String> getSuggestions(final String rsqlQuery) {
|
||||
final ValidationOracleContext suggest = rsqlValidationOracle.suggest(rsqlQuery, -1);
|
||||
final List<String> currentSuggestions = suggest.getSuggestionContext().getSuggestions().stream()
|
||||
.map(suggestion -> suggestion.getSuggestion()).collect(Collectors.toList());
|
||||
return currentSuggestions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,8 @@ package org.eclipse.hawkbit;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties;
|
||||
import org.eclipse.hawkbit.api.PropertyBasedArtifactUrlHandler;
|
||||
import org.eclipse.hawkbit.cache.CacheConstants;
|
||||
import org.eclipse.hawkbit.cache.TenancyCacheManager;
|
||||
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
|
||||
@@ -52,7 +54,8 @@ import com.mongodb.MongoClientOptions;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY, proxyTargetClass = false, securedEnabled = true)
|
||||
@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class })
|
||||
@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class,
|
||||
ArtifactUrlHandlerProperties.class })
|
||||
@Profile("test")
|
||||
@EnableAutoConfiguration
|
||||
public class TestConfiguration implements AsyncConfigurer {
|
||||
@@ -66,6 +69,12 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
return new TestdataFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler(
|
||||
final ArtifactUrlHandlerProperties urlHandlerProperties) {
|
||||
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoClientOptions options() {
|
||||
return MongoClientOptions.builder().connectTimeout(500).maxWaitTime(500).connectionsPerHost(2)
|
||||
|
||||
@@ -68,4 +68,35 @@ public class TenantUserPasswordAuthenticationToken extends UsernamePasswordAuthe
|
||||
public Object getTenant() {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TenantUserPasswordAuthenticationToken other = (TenantUserPasswordAuthenticationToken) obj;
|
||||
if (tenant == null) {
|
||||
if (other.tenant != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!tenant.equals(other.tenant)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,21 +8,16 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.security;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
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;
|
||||
|
||||
/**
|
||||
* An pre-authenticated processing filter which extracts (if enabled through
|
||||
@@ -68,11 +63,12 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedPrincipal(final TenantSecurityToken secruityToken) {
|
||||
final String controllerId = resolveControllerId(secruityToken);
|
||||
final String authHeader = secruityToken.getHeader(TenantSecurityToken.AUTHORIZATION_HEADER);
|
||||
if ((authHeader != null) && authHeader.startsWith(TARGET_SECURITY_TOKEN_AUTH_SCHEME)) {
|
||||
LOGGER.debug("found authorization header with scheme {} using target security token for authentication",
|
||||
TARGET_SECURITY_TOKEN_AUTH_SCHEME);
|
||||
return new HeaderAuthentication(secruityToken.getControllerId(), authHeader.substring(OFFSET_TARGET_TOKEN));
|
||||
return new HeaderAuthentication(controllerId, authHeader.substring(OFFSET_TARGET_TOKEN));
|
||||
}
|
||||
LOGGER.debug(
|
||||
"security token filter is enabled but requst does not contain either the necessary path variables {} or the authorization header with scheme {}",
|
||||
@@ -81,51 +77,36 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
|
||||
final String securityToken = tenantAware.runAsTenant(secruityToken.getTenant(),
|
||||
new GetSecurityTokenTenantRunner(secruityToken.getTenant(), secruityToken.getControllerId()));
|
||||
return new HeaderAuthentication(secruityToken.getControllerId(), securityToken);
|
||||
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken securityToken) {
|
||||
final Target target = systemSecurityContext.runAsSystemAsTenant(() -> {
|
||||
if (securityToken.getTargetId() != null) {
|
||||
return controllerManagement.findByTargetId(securityToken.getTargetId());
|
||||
}
|
||||
return controllerManagement.findByControllerId(securityToken.getControllerId());
|
||||
}, securityToken.getTenant());
|
||||
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
final String targetSecurityToken = systemSecurityContext.runAsSystemAsTenant(() -> target.getSecurityToken(),
|
||||
securityToken.getTenant());
|
||||
return new HeaderAuthentication(target.getControllerId(), targetSecurityToken);
|
||||
}
|
||||
|
||||
private String resolveControllerId(final TenantSecurityToken securityToken) {
|
||||
if (securityToken.getControllerId() != null) {
|
||||
return securityToken.getControllerId();
|
||||
}
|
||||
final Optional<Target> foundTarget = Optional.ofNullable(systemSecurityContext.runAsSystemAsTenant(
|
||||
() -> controllerManagement.findByTargetId(securityToken.getTargetId()), securityToken.getTenant()));
|
||||
if (!foundTarget.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
return foundTarget.get().getControllerId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TenantConfigurationKey getTenantConfigurationKey() {
|
||||
return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED;
|
||||
}
|
||||
|
||||
private final class GetSecurityTokenTenantRunner implements TenantAware.TenantRunner<String> {
|
||||
|
||||
private final String controllerId;
|
||||
private final String tenant;
|
||||
|
||||
private GetSecurityTokenTenantRunner(final String tenant, final String controllerId) {
|
||||
this.tenant = tenant;
|
||||
this.controllerId = controllerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String run() {
|
||||
LOGGER.trace("retrieving security token for controllerId {}", controllerId);
|
||||
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||
try {
|
||||
SecurityContextHolder.setContext(getSecurityTokenReadContext());
|
||||
return controllerManagement.getSecurityTokenByControllerId(controllerId);
|
||||
} finally {
|
||||
SecurityContextHolder.setContext(oldContext);
|
||||
}
|
||||
}
|
||||
|
||||
private SecurityContext getSecurityTokenReadContext() {
|
||||
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
|
||||
securityContextImpl.setAuthentication(getSecurityTokenReadAuthentication());
|
||||
return securityContextImpl;
|
||||
}
|
||||
|
||||
private Authentication getSecurityTokenReadAuthentication() {
|
||||
final AnonymousAuthenticationToken anonymousAuthenticationToken = new AnonymousAuthenticationToken(
|
||||
"anonymous-read-security-token", "anonymous", com.google.common.collect.Lists
|
||||
.newArrayList(new SimpleGrantedAuthority(SpPermission.READ_TARGET_SEC_TOKEN)));
|
||||
anonymousAuthenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenant, true));
|
||||
return anonymousAuthenticationToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,34 +11,31 @@
|
||||
-->
|
||||
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.5.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.5.1/distro-source/core/src/gwt-module.dtd">
|
||||
<module>
|
||||
<!-- This file is automatically updated based on new dependencies by the
|
||||
goal "vaadin:update-widgetset". -->
|
||||
<!-- This file is automatically updated based on new dependencies by the goal "vaadin:update-widgetset". -->
|
||||
|
||||
<!-- Inherit DefaultWidgetSet -->
|
||||
<inherits name="com.vaadin.DefaultWidgetSet" />
|
||||
<!-- Inherit DefaultWidgetSet -->
|
||||
<inherits name="com.vaadin.DefaultWidgetSet" />
|
||||
|
||||
<inherits
|
||||
name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
|
||||
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset" />
|
||||
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset" />
|
||||
|
||||
|
||||
<inherits
|
||||
name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
|
||||
|
||||
|
||||
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset" />
|
||||
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset" />
|
||||
|
||||
<inherits
|
||||
name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
|
||||
<inherits
|
||||
name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet" />
|
||||
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet" />
|
||||
<inherits name="org.eclipse.hawkbit.ui.filtermanagement.TextFieldSuggestionBox" />
|
||||
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
</module>
|
||||
|
||||
@@ -594,7 +594,9 @@ public class UploadLayout extends VerticalLayout {
|
||||
// delete file system zombies
|
||||
artifactUploadState.getFileSelected().forEach(customFile -> {
|
||||
final File file = new File(customFile.getFilePath());
|
||||
file.delete();
|
||||
if (!file.delete()) {
|
||||
LOG.warn("Failed to delete file {} in upload dialog", customFile.getFilePath());
|
||||
}
|
||||
});
|
||||
|
||||
artifactUploadState.getFileSelected().clear();
|
||||
|
||||
@@ -79,7 +79,6 @@ public class DsMetadataPopupLayout extends AbstractMetadataPopupLayout<Distribut
|
||||
|
||||
@Override
|
||||
protected void deleteMetadata(final DistributionSet entity, final String key, final String value) {
|
||||
final DistributionSetMetadata dsMetaData = entityFactory.generateDistributionSetMetadata(entity, key, value);
|
||||
distributionSetManagement.deleteDistributionSetMetadata(entity, key);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.filtermanagement;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.state.FilterManagementUIState;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.vaadin.spring.events.EventBus;
|
||||
import org.vaadin.spring.events.EventScope;
|
||||
import org.vaadin.spring.events.annotation.EventBusListenerMethod;
|
||||
|
||||
import com.vaadin.server.FontAwesome;
|
||||
import com.vaadin.shared.ui.label.ContentMode;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.TextField;
|
||||
import com.vaadin.ui.UI;
|
||||
|
||||
/**
|
||||
* An textfield with the {@link TextFieldSuggestionBox} extension which shows
|
||||
* suggestions in a suggestion-pop-up window while typing.
|
||||
*/
|
||||
@SpringComponent
|
||||
@ViewScope
|
||||
public class AutoCompleteTextFieldComponent extends HorizontalLayout {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Autowired
|
||||
private FilterManagementUIState filterManagementUIState;
|
||||
|
||||
@Autowired
|
||||
private transient EventBus.SessionEventBus eventBus;
|
||||
|
||||
@Autowired
|
||||
private transient RsqlValidationOracle rsqlValidationOracle;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("uiExecutor")
|
||||
private transient Executor executor;
|
||||
|
||||
private transient List<FilterQueryChangeListener> listeners = new LinkedList<>();
|
||||
|
||||
private final Label validationIcon;
|
||||
private final TextField queryTextField;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public AutoCompleteTextFieldComponent() {
|
||||
|
||||
queryTextField = createSearchField();
|
||||
validationIcon = createStatusIcon();
|
||||
|
||||
setSizeUndefined();
|
||||
setSpacing(true);
|
||||
addStyleName("custom-search-layout");
|
||||
addComponents(validationIcon, queryTextField);
|
||||
setComponentAlignment(validationIcon, Alignment.TOP_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the spring-framework when this bean has be post-constructed.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
eventBus.subscribe(this);
|
||||
new TextFieldSuggestionBox(rsqlValidationOracle, this).extend(queryTextField);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
eventBus.unsubscribe(this);
|
||||
}
|
||||
|
||||
@EventBusListenerMethod(scope = EventScope.SESSION)
|
||||
void onEvent(final CustomFilterUIEvent custFUIEvent) {
|
||||
if (custFUIEvent == CustomFilterUIEvent.UPDATE_TARGET_FILTER_SEARCH_ICON) {
|
||||
validationIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
if (!isValidationError()) {
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.SUCCESS_ICON);
|
||||
} else {
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.ERROR_ICON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the textfield and resets the validation icon.
|
||||
*/
|
||||
public void clear() {
|
||||
queryTextField.clear();
|
||||
validationIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
validationIcon.setStyleName("hide-status-label");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus() {
|
||||
queryTextField.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given listener
|
||||
*
|
||||
* @param textChangeListener
|
||||
* the listener to be called in case of text changed
|
||||
*/
|
||||
public void addTextChangeListener(final FilterQueryChangeListener textChangeListener) {
|
||||
listeners.add(textChangeListener);
|
||||
}
|
||||
|
||||
public void setValue(final String textValue) {
|
||||
queryTextField.setValue(textValue);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return queryTextField.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the filter-query has been changed in the textfield, e.g. from
|
||||
* client-side.
|
||||
*
|
||||
* @param currentText
|
||||
* the current text of the textfield which has been changed
|
||||
* @param valid
|
||||
* {@code boolean} if the current text is RSQL syntax valid
|
||||
* otherwise {@code false}
|
||||
* @param validationMessage
|
||||
* a message shown in case of syntax errors as tooltip
|
||||
*/
|
||||
public void onQueryFilterChange(final String currentText, final boolean valid, final String validationMessage) {
|
||||
if (valid) {
|
||||
showValidationSuccesIcon(currentText);
|
||||
} else {
|
||||
showValidationFailureIcon(validationMessage);
|
||||
}
|
||||
listeners.forEach(listener -> listener.queryChanged(valid, currentText));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the validation success icon in the textfield
|
||||
*
|
||||
* @param text
|
||||
* the text to store in the UI state object
|
||||
*/
|
||||
public void showValidationSuccesIcon(final String text) {
|
||||
validationIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.SUCCESS_ICON);
|
||||
filterManagementUIState.setFilterQueryValue(text);
|
||||
filterManagementUIState.setIsFilterByInvalidFilterQuery(Boolean.FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the validation error icon in the textfield
|
||||
*
|
||||
* @param validationMessage
|
||||
* the validation message which should be added to the error-icon
|
||||
* tooltip
|
||||
*/
|
||||
public void showValidationFailureIcon(final String validationMessage) {
|
||||
validationIcon.setValue(FontAwesome.TIMES_CIRCLE.getHtml());
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.ERROR_ICON);
|
||||
validationIcon.setDescription(validationMessage);
|
||||
filterManagementUIState.setFilterQueryValue(null);
|
||||
filterManagementUIState.setIsFilterByInvalidFilterQuery(Boolean.TRUE);
|
||||
}
|
||||
|
||||
public boolean isValidationError() {
|
||||
return validationIcon.getStyleName().equals(SPUIStyleDefinitions.ERROR_ICON);
|
||||
}
|
||||
|
||||
private TextField createSearchField() {
|
||||
final TextField textField = new TextFieldBuilder().immediate(true).id("custom.query.text.Id")
|
||||
.maxLengthAllowed(SPUILabelDefinitions.TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH).buildTextComponent();
|
||||
textField.addStyleName("target-filter-textfield");
|
||||
textField.setWidth(900.0F, Unit.PIXELS);
|
||||
textField.setTextChangeEventMode(TextChangeEventMode.EAGER);
|
||||
textField.setImmediate(true);
|
||||
textField.setTextChangeTimeout(100);
|
||||
return textField;
|
||||
}
|
||||
|
||||
private static Label createStatusIcon() {
|
||||
final Label statusIcon = new Label();
|
||||
statusIcon.setImmediate(true);
|
||||
statusIcon.setContentMode(ContentMode.HTML);
|
||||
statusIcon.setSizeFull();
|
||||
setInitialStatusIconStyle(statusIcon);
|
||||
statusIcon.setId(UIComponentIdProvider.VALIDATION_STATUS_ICON_ID);
|
||||
return statusIcon;
|
||||
}
|
||||
|
||||
private static void setInitialStatusIconStyle(final Label statusIcon) {
|
||||
statusIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
statusIcon.setStyleName("hide-status-label");
|
||||
}
|
||||
|
||||
class StatusCircledAsync implements Runnable {
|
||||
private final UI current;
|
||||
|
||||
public StatusCircledAsync(final UI current) {
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
UI.setCurrent(current);
|
||||
eventBus.publish(this, CustomFilterUIEvent.FILTER_TARGET_BY_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the spinner as progress indicator.
|
||||
*/
|
||||
public void showValidationInProgress() {
|
||||
validationIcon.setValue(null);
|
||||
validationIcon.addStyleName("show-status-label");
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.TARGET_FILTER_SEARCH_PROGRESS_INDICATOR_STYLE);
|
||||
}
|
||||
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change listener on the textfield.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FilterQueryChangeListener {
|
||||
/**
|
||||
* Called when the text has been changed and validated.
|
||||
*
|
||||
* @param valid
|
||||
* indicates if the entered query text is valid
|
||||
* @param query
|
||||
* the entered query text
|
||||
*/
|
||||
void queryChanged(final boolean valid, final String query);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.filtermanagement;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIButton;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.AutoCompleteTextFieldComponent.FilterQueryChangeListener;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.state.FilterManagementUIState;
|
||||
import org.eclipse.hawkbit.ui.utils.I18N;
|
||||
@@ -31,7 +32,6 @@ import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.UINotification;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.vaadin.spring.events.EventBus;
|
||||
import org.vaadin.spring.events.EventScope;
|
||||
import org.vaadin.spring.events.annotation.EventBusListenerMethod;
|
||||
@@ -40,16 +40,11 @@ import com.google.common.base.Strings;
|
||||
import com.vaadin.event.FieldEvents.BlurEvent;
|
||||
import com.vaadin.event.FieldEvents.BlurListener;
|
||||
import com.vaadin.event.FieldEvents.TextChangeEvent;
|
||||
import com.vaadin.event.FieldEvents.TextChangeListener;
|
||||
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
|
||||
import com.vaadin.event.LayoutEvents.LayoutClickListener;
|
||||
import com.vaadin.event.ShortcutAction.KeyCode;
|
||||
import com.vaadin.server.FontAwesome;
|
||||
import com.vaadin.shared.ui.label.ContentMode;
|
||||
import com.vaadin.spring.annotation.SpringComponent;
|
||||
import com.vaadin.spring.annotation.ViewScope;
|
||||
import com.vaadin.ui.AbstractField;
|
||||
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.Button.ClickEvent;
|
||||
@@ -94,11 +89,10 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
private transient UiProperties uiProperties;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("uiExecutor")
|
||||
private transient Executor executor;
|
||||
private transient EntityFactory entityFactory;
|
||||
|
||||
@Autowired
|
||||
private transient EntityFactory entityFactory;
|
||||
private AutoCompleteTextFieldComponent queryTextField;
|
||||
|
||||
private HorizontalLayout breadcrumbLayout;
|
||||
|
||||
@@ -108,8 +102,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
|
||||
private Label headerCaption;
|
||||
|
||||
private TextField queryTextField;
|
||||
|
||||
private TextField nameTextField;
|
||||
|
||||
private Label nameLabel;
|
||||
@@ -120,9 +112,7 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
|
||||
private Link helpLink;
|
||||
|
||||
private Label validationIcon;
|
||||
|
||||
private HorizontalLayout searchLayout;
|
||||
private Button searchIcon;
|
||||
|
||||
private String oldFilterName;
|
||||
|
||||
@@ -136,8 +126,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
|
||||
private LayoutClickListener nameLayoutClickListner;
|
||||
|
||||
private boolean validationFailed;
|
||||
|
||||
/**
|
||||
* Initialize the Campaign Status History Header.
|
||||
*/
|
||||
@@ -170,8 +158,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
} else if (custFUIEvent == CustomFilterUIEvent.CREATE_NEW_FILTER_CLICK) {
|
||||
setUpCaptionLayout(true);
|
||||
resetComponents();
|
||||
} else if (custFUIEvent == CustomFilterUIEvent.UPDATE_TARGET_FILTER_SEARCH_ICON) {
|
||||
UI.getCurrent().access(() -> updateStatusIconAfterTablePopulated());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,38 +169,22 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
oldFilterQuery = filterManagementUIState.getTfQuery().get().getQuery();
|
||||
}
|
||||
breadcrumbName.setValue(nameLabel.getValue());
|
||||
showValidationSuccesIcon();
|
||||
queryTextField.showValidationSuccesIcon(filterManagementUIState.getFilterQueryValue());
|
||||
titleFilterIconsLayout.addStyleName(SPUIStyleDefinitions.TARGET_FILTER_CAPTION_LAYOUT);
|
||||
headerCaption.setVisible(false);
|
||||
setUpCaptionLayout(false);
|
||||
}
|
||||
|
||||
private void resetComponents() {
|
||||
queryTextField.clear();
|
||||
queryTextField.focus();
|
||||
headerCaption.setVisible(true);
|
||||
breadcrumbName.setValue(headerCaption.getValue());
|
||||
nameLabel.setValue("");
|
||||
queryTextField.setValue("");
|
||||
setInitialStatusIconStyle(validationIcon);
|
||||
validationFailed = false;
|
||||
saveButton.setEnabled(false);
|
||||
titleFilterIconsLayout.removeStyleName(SPUIStyleDefinitions.TARGET_FILTER_CAPTION_LAYOUT);
|
||||
}
|
||||
|
||||
private Label createStatusIcon() {
|
||||
final Label statusIcon = new Label();
|
||||
statusIcon.setImmediate(true);
|
||||
statusIcon.setContentMode(ContentMode.HTML);
|
||||
statusIcon.setSizeFull();
|
||||
setInitialStatusIconStyle(statusIcon);
|
||||
statusIcon.setId(UIComponentIdProvider.VALIDATION_STATUS_ICON_ID);
|
||||
return statusIcon;
|
||||
}
|
||||
|
||||
private void setInitialStatusIconStyle(final Label statusIcon) {
|
||||
statusIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
statusIcon.setStyleName("hide-status-label");
|
||||
}
|
||||
|
||||
private void createComponents() {
|
||||
|
||||
breadcrumbButton = createBreadcrumbButton();
|
||||
@@ -227,11 +197,8 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
nameTextField = createNameTextField();
|
||||
nameTextField.setWidth(380, Unit.PIXELS);
|
||||
|
||||
queryTextField = createSearchField();
|
||||
addSearchLisenter();
|
||||
|
||||
validationIcon = createStatusIcon();
|
||||
saveButton = createSaveButton();
|
||||
searchIcon = createSearchIcon();
|
||||
|
||||
helpLink = SPUIComponentProvider.getHelpLink(uiProperties.getLinks().getDocumentation().getTargetfilterView());
|
||||
|
||||
@@ -282,6 +249,14 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
queryTextField.addTextChangeListener(new FilterQueryChangeListener() {
|
||||
@Override
|
||||
public void queryChanged(final boolean valid, final String query) {
|
||||
enableDisableSaveButton(!valid, query);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void onFilterNameChange(final TextChangeEvent event) {
|
||||
@@ -318,24 +293,15 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
titleFilterLayout.setComponentAlignment(titleFilterIconsLayout, Alignment.TOP_LEFT);
|
||||
titleFilterLayout.setComponentAlignment(closeIcon, Alignment.TOP_RIGHT);
|
||||
|
||||
validationIcon = createStatusIcon();
|
||||
|
||||
searchLayout = new HorizontalLayout();
|
||||
searchLayout.setSizeUndefined();
|
||||
searchLayout.setSpacing(false);
|
||||
searchLayout.addComponents(validationIcon, queryTextField);
|
||||
searchLayout.addStyleName("custom-search-layout");
|
||||
searchLayout.setComponentAlignment(validationIcon, Alignment.TOP_CENTER);
|
||||
|
||||
final HorizontalLayout iconLayout = new HorizontalLayout();
|
||||
iconLayout.setSizeUndefined();
|
||||
iconLayout.setSpacing(false);
|
||||
iconLayout.addComponents(helpLink, saveButton);
|
||||
iconLayout.addComponents(helpLink, searchIcon, saveButton);
|
||||
|
||||
final HorizontalLayout queryLayout = new HorizontalLayout();
|
||||
queryLayout.setSizeUndefined();
|
||||
queryLayout.setSpacing(true);
|
||||
queryLayout.addComponents(searchLayout, iconLayout);
|
||||
queryLayout.addComponents(queryTextField, iconLayout);
|
||||
|
||||
addComponent(breadcrumbLayout);
|
||||
addComponent(titleFilterLayout);
|
||||
@@ -358,66 +324,16 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
}
|
||||
}
|
||||
|
||||
private void addSearchLisenter() {
|
||||
queryTextField.addTextChangeListener(new TextChangeListener() {
|
||||
private static final long serialVersionUID = -6668604418942689391L;
|
||||
|
||||
@Override
|
||||
public void textChange(final TextChangeEvent event) {
|
||||
validationIcon.addStyleName("show-status-label");
|
||||
showValidationInProgress();
|
||||
onQueryChange(event.getText());
|
||||
executor.execute(new StatusCircledAsync(UI.getCurrent()));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class StatusCircledAsync implements Runnable {
|
||||
private final UI current;
|
||||
|
||||
public StatusCircledAsync(final UI current) {
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
UI.setCurrent(current);
|
||||
eventBus.publish(this, CustomFilterUIEvent.FILTER_TARGET_BY_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
private void onQueryChange(final String input) {
|
||||
if (!Strings.isNullOrEmpty(input)) {
|
||||
final ValidationResult validationResult = FilterQueryValidation.getExpectedTokens(input);
|
||||
if (!validationResult.getIsValidationFailed()) {
|
||||
filterManagementUIState.setFilterQueryValue(input);
|
||||
filterManagementUIState.setIsFilterByInvalidFilterQuery(Boolean.FALSE);
|
||||
validationFailed = false;
|
||||
} else {
|
||||
validationFailed = true;
|
||||
filterManagementUIState.setFilterQueryValue(null);
|
||||
filterManagementUIState.setIsFilterByInvalidFilterQuery(Boolean.TRUE);
|
||||
validationIcon.setDescription(validationResult.getMessage());
|
||||
showValidationFailureIcon();
|
||||
}
|
||||
enableDisableSaveButton(validationFailed, input);
|
||||
} else {
|
||||
setInitialStatusIconStyle(validationIcon);
|
||||
filterManagementUIState.setFilterQueryValue(null);
|
||||
filterManagementUIState.setIsFilterByInvalidFilterQuery(Boolean.TRUE);
|
||||
}
|
||||
queryTextField.setValue(input);
|
||||
}
|
||||
|
||||
private void enableDisableSaveButton(final boolean validationFailed, final String query) {
|
||||
if (validationFailed || (isNameAndQueryEmpty(nameTextField.getValue(), query)
|
||||
|| (query.equals(oldFilterQuery) && nameTextField.getValue().equals(oldFilterName)))) {
|
||||
saveButton.setEnabled(false);
|
||||
searchIcon.setEnabled(false);
|
||||
} else {
|
||||
if (hasSavePermission()) {
|
||||
saveButton.setEnabled(true);
|
||||
}
|
||||
searchIcon.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,21 +344,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showValidationSuccesIcon() {
|
||||
validationIcon.setValue(FontAwesome.CHECK_CIRCLE.getHtml());
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.SUCCESS_ICON);
|
||||
}
|
||||
|
||||
private void showValidationFailureIcon() {
|
||||
validationIcon.setValue(FontAwesome.TIMES_CIRCLE.getHtml());
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.ERROR_ICON);
|
||||
}
|
||||
|
||||
private void showValidationInProgress() {
|
||||
validationIcon.setValue(null);
|
||||
validationIcon.setStyleName(SPUIStyleDefinitions.TARGET_FILTER_SEARCH_PROGRESS_INDICATOR_STYLE);
|
||||
}
|
||||
|
||||
private SPUIButton createSearchResetIcon() {
|
||||
final SPUIButton button = (SPUIButton) SPUIComponentProvider.getButton(
|
||||
UIComponentIdProvider.CUSTOM_FILTER_CLOSE, "", "", null, false, FontAwesome.TIMES,
|
||||
@@ -451,18 +352,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
return button;
|
||||
}
|
||||
|
||||
private static TextField createSearchField() {
|
||||
final TextField textField = new TextFieldBuilder().immediate(true).id("custom.query.text.Id")
|
||||
.maxLengthAllowed(SPUILabelDefinitions.TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH).buildTextComponent();
|
||||
textField.addStyleName("target-filter-textfield");
|
||||
textField.setWidth(900.0F, Unit.PIXELS);
|
||||
textField.setTextChangeEventMode(TextChangeEventMode.LAZY);
|
||||
textField.setTextChangeTimeout(1000);
|
||||
|
||||
textField.addShortcutListener(new AbstractField.FocusShortcut(textField, KeyCode.ENTER));
|
||||
return textField;
|
||||
}
|
||||
|
||||
private void closeFilterLayout() {
|
||||
filterManagementUIState.setFilterQueryValue(null);
|
||||
filterManagementUIState.setCreateFilterBtnClicked(false);
|
||||
@@ -480,12 +369,26 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
return saveButton;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.vaadin.ui.Button.ClickListener#buttonClick(com.vaadin.ui.Button.
|
||||
* ClickEvent)
|
||||
*/
|
||||
private Button createSearchIcon() {
|
||||
searchIcon = SPUIComponentProvider.getButton(UIComponentIdProvider.FILTER_SEARCH_ICON_ID, "", "", null, false,
|
||||
FontAwesome.SEARCH, SPUIButtonStyleSmallNoBorder.class);
|
||||
searchIcon.addClickListener(event -> onSearchIconClick());
|
||||
searchIcon.setEnabled(false);
|
||||
searchIcon.setData(false);
|
||||
return searchIcon;
|
||||
}
|
||||
|
||||
private void onSearchIconClick() {
|
||||
|
||||
if (queryTextField.isValidationError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryTextField.showValidationInProgress();
|
||||
queryTextField.getExecutor().execute(queryTextField.new StatusCircledAsync(UI.getCurrent()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buttonClick(final ClickEvent event) {
|
||||
if (UIComponentIdProvider.CUSTOM_FILTER_SAVE_ICON.equals(event.getComponent().getId())
|
||||
@@ -509,11 +412,11 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
}
|
||||
|
||||
private void updateCustomFilter() {
|
||||
if (!filterManagementUIState.getTfQuery().isPresent()) {
|
||||
final Optional<TargetFilterQuery> tfQuery = filterManagementUIState.getTfQuery();
|
||||
if (!tfQuery.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TargetFilterQuery targetFilterQuery = filterManagementUIState.getTfQuery().get();
|
||||
final TargetFilterQuery targetFilterQuery = tfQuery.get();
|
||||
targetFilterQuery.setName(nameTextField.getValue());
|
||||
targetFilterQuery.setQuery(queryTextField.getValue());
|
||||
final TargetFilterQuery updatedTargetFilter = targetFilterQueryManagement
|
||||
@@ -550,13 +453,6 @@ public class CreateOrUpdateFilterHeader extends VerticalLayout implements Button
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateStatusIconAfterTablePopulated() {
|
||||
queryTextField.focus();
|
||||
if (!validationFailed && !Strings.isNullOrEmpty(queryTextField.getValue())) {
|
||||
showValidationSuccesIcon();
|
||||
}
|
||||
}
|
||||
|
||||
private void showCustomFiltersView() {
|
||||
eventBus.publish(this, CustomFilterUIEvent.SHOW_FILTER_MANAGEMENT);
|
||||
}
|
||||
|
||||
@@ -94,8 +94,7 @@ public class FilterManagementView extends VerticalLayout implements View {
|
||||
void onEvent(final CustomFilterUIEvent custFilterUIEvent) {
|
||||
if (custFilterUIEvent == CustomFilterUIEvent.TARGET_FILTER_DETAIL_VIEW) {
|
||||
viewTargetFilterDetailLayout();
|
||||
} else if (custFilterUIEvent == CustomFilterUIEvent.CREATE_NEW_FILTER_CLICK
|
||||
|| custFilterUIEvent == CustomFilterUIEvent.FILTER_TARGET_BY_QUERY) {
|
||||
} else if (custFilterUIEvent == CustomFilterUIEvent.CREATE_NEW_FILTER_CLICK) {
|
||||
this.getUI().access(() -> viewCreateTargetFilterLayout());
|
||||
} else if (custFilterUIEvent == CustomFilterUIEvent.EXIT_CREATE_OR_UPDATE_FILTRER_VIEW
|
||||
|| custFilterUIEvent == CustomFilterUIEvent.SHOW_FILTER_MANAGEMENT) {
|
||||
|
||||
@@ -1,181 +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.ui.filtermanagement;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import cz.jirutka.rsql.parser.ParseException;
|
||||
import cz.jirutka.rsql.parser.RSQLParserException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates the target filter query.
|
||||
*
|
||||
*/
|
||||
public final class FilterQueryValidation {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FilterQueryValidation.class);
|
||||
|
||||
private FilterQueryValidation() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* method for get ExpectedTokens.
|
||||
*
|
||||
* @param input RSQL filter
|
||||
* @return Validation result
|
||||
*/
|
||||
public static ValidationResult getExpectedTokens(final String input) {
|
||||
|
||||
final TokenDescription tokenDesc = new TokenDescription();
|
||||
final ValidationResult result = new ValidationResult();
|
||||
final List<String> expectedTokens = new ArrayList<>();
|
||||
try {
|
||||
|
||||
final TargetManagement management = SpringContextHelper.getBean(TargetManagement.class);
|
||||
management.findTargetsAll(input, new PageRequest(0, 100));
|
||||
} catch (final RSQLParameterSyntaxException ex) {
|
||||
setExceptionDetails(new Exception(ex.getCause().getCause()), expectedTokens, result, tokenDesc);
|
||||
result.setMessage(getCustomMessage(ex.getCause().getMessage(), result.getExpectedTokens()));
|
||||
result.setIsValidationFailed(Boolean.TRUE);
|
||||
LOGGER.trace("Syntax exception on parsing :", ex);
|
||||
} catch (final RSQLParserException ex) {
|
||||
setExceptionDetails(ex, expectedTokens, result, tokenDesc);
|
||||
result.setMessage(getCustomMessage(ex.getMessage(), result.getExpectedTokens()));
|
||||
result.setIsValidationFailed(Boolean.TRUE);
|
||||
LOGGER.trace("Exception on parsing :", ex);
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
result.setMessage(getCustomMessage(ex.getMessage(), null));
|
||||
result.setIsValidationFailed(Boolean.TRUE);
|
||||
LOGGER.trace("Illegal argument on parsing :", ex);
|
||||
} catch (final RSQLParameterUnsupportedFieldException ex) {
|
||||
result.setMessage(getCustomMessage(ex.getMessage(), null));
|
||||
result.setIsValidationFailed(Boolean.TRUE);
|
||||
LOGGER.trace("Unsupported field on parsing :", ex);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static void setExceptionDetails(final Exception ex, final List<String> expectedTokens,
|
||||
final ValidationResult result, final TokenDescription tokenDesc) {
|
||||
for (final Integer node : getNextTokens(ex)) {
|
||||
if (node != 12) {
|
||||
expectedTokens.add(tokenDesc.getTokenImage()[node]);
|
||||
}
|
||||
}
|
||||
final List<String> customExpectTokenList = processExpectedTokens(getNextTokens(ex));
|
||||
if (!customExpectTokenList.isEmpty()) {
|
||||
result.setExpectedTokens(customExpectTokenList);
|
||||
} else {
|
||||
result.setExpectedTokens(expectedTokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method for process ExpectedTokens.
|
||||
*
|
||||
* @param expectedTokens
|
||||
* @return
|
||||
*/
|
||||
// Exception squid:S2095 - see
|
||||
// https://jira.sonarsource.com/browse/SONARJAVA-1478
|
||||
@SuppressWarnings({ "squid:S2095" })
|
||||
public static List<String> processExpectedTokens(final List<Integer> expectedTokens) {
|
||||
final List<String> expectToken = new ArrayList<>();
|
||||
if (expectedTokens.size() == 2 && expectedTokens.contains(9) && expectedTokens.contains(4)) {
|
||||
final List<String> expectedFieldList = Arrays.stream(TargetFields.values()).map(v -> v.name().toLowerCase())
|
||||
.collect(Collectors.toList());
|
||||
expectToken.addAll(expectedFieldList);
|
||||
expectToken.add("assignedds.name");
|
||||
expectToken.add("assignedds.version");
|
||||
}
|
||||
return expectToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method To Get Next Token.
|
||||
*
|
||||
* @param e
|
||||
* .
|
||||
* @return list.
|
||||
*/
|
||||
public static List<Integer> getNextTokens(final Exception e) {
|
||||
Throwable parseException = e.getCause();
|
||||
final List<Integer> listTokens = new ArrayList<>();
|
||||
if (parseException != null) {
|
||||
do {
|
||||
if (parseException instanceof ParseException) {
|
||||
try {
|
||||
Field declaredField;
|
||||
declaredField = parseException.getClass().getDeclaredField("expectedTokenSequences");
|
||||
int[][] tokens;
|
||||
tokens = (int[][]) declaredField.get(parseException);
|
||||
for (final int[] is : tokens) {
|
||||
for (final int i : is) {
|
||||
listTokens.add(i);
|
||||
}
|
||||
}
|
||||
return listTokens;
|
||||
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException
|
||||
| IllegalAccessException ex) {
|
||||
LOGGER.info("Exception on parsing :", ex);
|
||||
}
|
||||
|
||||
} else {
|
||||
return listTokens;
|
||||
}
|
||||
} while ((parseException = parseException.getCause()) != null);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* To Get Custom Message.
|
||||
*
|
||||
* @param message
|
||||
* @param expectedTokens
|
||||
* @return String.
|
||||
*/
|
||||
public static String getCustomMessage(final String message, final List<String> expectedTokens) {
|
||||
String builder = message;
|
||||
if (message.contains(":")) {
|
||||
builder = message.substring(message.indexOf(':') + 1, message.length());
|
||||
if (builder.indexOf("Was expecting") != -1) {
|
||||
builder = builder.substring(0, builder.lastIndexOf("Was expecting"));
|
||||
}
|
||||
if (null != expectedTokens && !expectedTokens.isEmpty()) {
|
||||
final StringBuilder tokens = new StringBuilder();
|
||||
expectedTokens.stream().forEach(value -> tokens.append(value + ","));
|
||||
builder = builder.concat("Was expecting :" + tokens.toString().substring(0, tokens.length() - 1));
|
||||
}
|
||||
builder = builder.replace('\r', ' ');
|
||||
builder = builder.replace('\n', ' ');
|
||||
builder = builder.replaceAll(">", " ");
|
||||
builder = builder.replaceAll("<", " ");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -114,6 +114,7 @@ public class TargetFilterHeader extends VerticalLayout {
|
||||
}
|
||||
|
||||
private void addNewFilter() {
|
||||
filterManagementUIState.setTfQuery(null);
|
||||
filterManagementUIState.setFilterQueryValue(null);
|
||||
filterManagementUIState.setCreateFilterBtnClicked(true);
|
||||
eventBus.publish(this, CustomFilterUIEvent.CREATE_NEW_FILTER_CLICK);
|
||||
|
||||
@@ -226,9 +226,8 @@ public class TargetFilterTable extends Table {
|
||||
final String targetFilterName = (String) ((Button) event.getComponent()).getData();
|
||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||
.findTargetFilterQueryByName(targetFilterName);
|
||||
filterManagementUIState.setTfQuery(targetFilterQuery);
|
||||
filterManagementUIState.setFilterQueryValue(targetFilterQuery.getQuery());
|
||||
|
||||
filterManagementUIState.setTfQuery(targetFilterQuery);
|
||||
filterManagementUIState.setEditViewDisplayed(true);
|
||||
eventBus.publish(this, CustomFilterUIEvent.TARGET_FILTER_DETAIL_VIEW);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<module>
|
||||
<inherits name="com.vaadin.DefaultWidgetSet" />
|
||||
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.AppWidgetSet" />
|
||||
|
||||
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset" />
|
||||
|
||||
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset" />
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet" />
|
||||
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset" />
|
||||
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
|
||||
</module>
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.filtermanagement;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
|
||||
import org.eclipse.hawkbit.repository.rsql.SuggestionContext;
|
||||
import org.eclipse.hawkbit.repository.rsql.ValidationOracleContext;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.client.SuggestTokenDto;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.client.SuggestionContextDto;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.client.TextFieldSuggestionBoxClientRpc;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.client.TextFieldSuggestionBoxServerRpc;
|
||||
|
||||
import com.vaadin.server.AbstractExtension;
|
||||
import com.vaadin.ui.TextField;
|
||||
import com.vaadin.ui.UI;
|
||||
|
||||
/**
|
||||
* Extension for the AutoCompleteTexfield.
|
||||
*
|
||||
*/
|
||||
public class TextFieldSuggestionBox extends AbstractExtension implements TextFieldSuggestionBoxServerRpc {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final transient RsqlValidationOracle rsqlValidationOracle;
|
||||
private final AutoCompleteTextFieldComponent autoCompleteTextFieldComponent;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param autoCompleteTextFieldComponent
|
||||
* @param rsqlValidationOracle
|
||||
* the suggestion oracle where to retrieve the suggestions from
|
||||
*/
|
||||
public TextFieldSuggestionBox(final RsqlValidationOracle rsqlValidationOracle,
|
||||
final AutoCompleteTextFieldComponent autoCompleteTextFieldComponent) {
|
||||
this.rsqlValidationOracle = rsqlValidationOracle;
|
||||
this.autoCompleteTextFieldComponent = autoCompleteTextFieldComponent;
|
||||
|
||||
registerRpc(this, TextFieldSuggestionBoxServerRpc.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this extension to the target connector. This method is protected to
|
||||
* allow subclasses to require a more specific type of target.
|
||||
*
|
||||
* @param target
|
||||
* the connector to attach this extension to
|
||||
*/
|
||||
public void extend(final TextField target) {
|
||||
super.extend(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggest(final String text, final int cursor) {
|
||||
final ValidationOracleContext suggest = rsqlValidationOracle.suggest(text, cursor);
|
||||
updateValidationIcon(suggest, text);
|
||||
getRpcProxy(TextFieldSuggestionBoxClientRpc.class).showSuggestions(mapToDto(suggest.getSuggestionContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeQuery(final String text, final int cursor) {
|
||||
if (!autoCompleteTextFieldComponent.isValidationError()) {
|
||||
autoCompleteTextFieldComponent.showValidationInProgress();
|
||||
autoCompleteTextFieldComponent.getExecutor()
|
||||
.execute(autoCompleteTextFieldComponent.new StatusCircledAsync(UI.getCurrent()));
|
||||
}
|
||||
}
|
||||
|
||||
private static SuggestionContextDto mapToDto(final SuggestionContext suggestionContext) {
|
||||
return new SuggestionContextDto(suggestionContext.getCursorPosition(),
|
||||
suggestionContext.getSuggestions().stream()
|
||||
.filter(suggestion -> suggestion.getTokenImageName() == null
|
||||
|| suggestion.getSuggestion().contains(suggestion.getTokenImageName()))
|
||||
.map(suggestion -> new SuggestTokenDto(suggestion.getStart(), suggestion.getEnd(),
|
||||
suggestion.getSuggestion()))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
}
|
||||
|
||||
private void updateValidationIcon(final ValidationOracleContext suggest, final String text) {
|
||||
final String errorMessage = (suggest.getSyntaxErrorContext() != null)
|
||||
? suggest.getSyntaxErrorContext().getErrorMessage() : null;
|
||||
autoCompleteTextFieldComponent.onQueryFilterChange(text, !suggest.isSyntaxError(), errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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.ui.filtermanagement;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
*
|
||||
* Available token details.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class TokenDescription {
|
||||
|
||||
/** Literal token values. */
|
||||
private static final String[] TOKEN_IMAGE = { "<EOF>", "\" \"", "\"\\t\"", "<ALPHA>", "<UNRESERVED_STR>",
|
||||
"<SINGLE_QUOTED_STR>", "<DOUBLE_QUOTED_STR>", "<AND>", "<OR>", "\"(\"", "\")\"", "<==>|<!=>", ">=|<=", };
|
||||
|
||||
public String[] getTokenImage() {
|
||||
return Arrays.copyOf(TOKEN_IMAGE, TOKEN_IMAGE.length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +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.ui.filtermanagement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Query validation result with expected token on error.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ValidationResult {
|
||||
|
||||
private List<String> expectedTokens = new ArrayList<>();
|
||||
|
||||
private String message;
|
||||
|
||||
private Boolean isValidationFailed = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* @return the isValidationFailed
|
||||
*/
|
||||
public Boolean getIsValidationFailed() {
|
||||
return isValidationFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isValidationFailed
|
||||
* the isValidationFailed to set
|
||||
*/
|
||||
public void setIsValidationFailed(final Boolean isValidationFailed) {
|
||||
this.isValidationFailed = isValidationFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expectedTokens
|
||||
*/
|
||||
public List<String> getExpectedTokens() {
|
||||
return expectedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expectedTokens
|
||||
* the expectedTokens to set
|
||||
*/
|
||||
public void setExpectedTokens(final List<String> expectedTokens) {
|
||||
this.expectedTokens = expectedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* the message to set
|
||||
*/
|
||||
public void setMessage(final String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.filtermanagement.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.TextFieldSuggestionBox;
|
||||
|
||||
import com.google.gwt.event.dom.client.KeyCodes;
|
||||
import com.google.gwt.event.dom.client.KeyUpEvent;
|
||||
import com.google.gwt.event.dom.client.KeyUpHandler;
|
||||
import com.google.gwt.user.client.ui.MenuItem;
|
||||
import com.vaadin.client.ComponentConnector;
|
||||
import com.vaadin.client.ServerConnector;
|
||||
import com.vaadin.client.extensions.AbstractExtensionConnector;
|
||||
import com.vaadin.client.ui.VOverlay;
|
||||
import com.vaadin.client.ui.VTextField;
|
||||
import com.vaadin.shared.ui.Connect;
|
||||
|
||||
/**
|
||||
* Connector for the AutoCompleteTextField which automatically listens to
|
||||
* key-events to show pop-up panel with entered suggestions based on the
|
||||
* {@link TextFieldSuggestionBoxServerRpc} call.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({ "deprecation", "squid:CallToDeprecatedMethod" })
|
||||
// need to use VOverlay because otherwise it's not in the correct theme
|
||||
// widget @see com.vaadin.client.ui.VOverlay.getOverlayContainer()
|
||||
@Connect(TextFieldSuggestionBox.class)
|
||||
public class AutoCompleteTextFieldConnector extends AbstractExtensionConnector {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final transient SuggestionsSelectList select = new SuggestionsSelectList();
|
||||
private transient VTextField textFieldWidget;
|
||||
|
||||
private final TextFieldSuggestionBoxServerRpc rpc = getRpcProxy(TextFieldSuggestionBoxServerRpc.class);
|
||||
|
||||
private final transient VOverlay panel = new VOverlay(true, false, true);
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
registerRpc(TextFieldSuggestionBoxClientRpc.class, new TextFieldSuggestionBoxClientRpc() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void showSuggestions(final SuggestionContextDto suggestContext) {
|
||||
select.clearItems();
|
||||
if (suggestContext == null) {
|
||||
panel.hide();
|
||||
return;
|
||||
}
|
||||
final List<SuggestTokenDto> suggestions = suggestContext.getSuggestions();
|
||||
if (suggestions != null && !suggestions.isEmpty()) {
|
||||
select.addItems(suggestions, textFieldWidget, panel, rpc);
|
||||
panel.showRelativeTo(textFieldWidget);
|
||||
select.moveSelectionDown();
|
||||
return;
|
||||
}
|
||||
panel.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extend(final ServerConnector target) {
|
||||
textFieldWidget = (VTextField) ((ComponentConnector) target).getWidget();
|
||||
textFieldWidget.setImmediate(true);
|
||||
textFieldWidget.textChangeEventMode = "EAGER";
|
||||
panel.setWidget(select);
|
||||
panel.setStyleName("suggestion-popup");
|
||||
panel.setOwner(textFieldWidget);
|
||||
|
||||
textFieldWidget.addKeyUpHandler(new KeyUpHandler() {
|
||||
@Override
|
||||
public void onKeyUp(final KeyUpEvent event) {
|
||||
if (panel.isAttached()) {
|
||||
handlePanelEventDelegation(event);
|
||||
} else if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
|
||||
rpc.executeQuery(textFieldWidget.getValue(), textFieldWidget.getCursorPos());
|
||||
} else {
|
||||
doAskForSuggestion();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePanelEventDelegation(final KeyUpEvent event) {
|
||||
switch (event.getNativeKeyCode()) {
|
||||
case KeyCodes.KEY_DOWN:
|
||||
arrowKeyDown(event);
|
||||
break;
|
||||
case KeyCodes.KEY_UP:
|
||||
arrorKeyUp(event);
|
||||
break;
|
||||
case KeyCodes.KEY_ESCAPE:
|
||||
escapeKey();
|
||||
break;
|
||||
case KeyCodes.KEY_ENTER:
|
||||
enterKey();
|
||||
break;
|
||||
default:
|
||||
doAskForSuggestion();
|
||||
}
|
||||
}
|
||||
|
||||
private void escapeKey() {
|
||||
panel.hide();
|
||||
}
|
||||
|
||||
private void enterKey() {
|
||||
final MenuItem item = select.getSelectedItem();
|
||||
if (item != null) {
|
||||
item.getScheduledCommand().execute();
|
||||
}
|
||||
}
|
||||
|
||||
private void arrorKeyUp(final KeyUpEvent event) {
|
||||
select.moveSelectionUp();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private void arrowKeyDown(final KeyUpEvent event) {
|
||||
select.moveSelectionDown();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private void doAskForSuggestion() {
|
||||
rpc.suggest(textFieldWidget.getValue(), textFieldWidget.getCursorPos());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.filtermanagement.client;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A suggestion which contains the start and the end character position of the
|
||||
* suggested token of the suggestion of the token and the actual suggestion.
|
||||
*/
|
||||
public class SuggestTokenDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int start;
|
||||
private int end;
|
||||
private String suggestion;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public SuggestTokenDto() {
|
||||
// necessary for java serialization with GWT.
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param start
|
||||
* the character position of the start of the token
|
||||
* @param end
|
||||
* the character position of the end of the token
|
||||
* @param suggestion
|
||||
* the token suggestion
|
||||
*/
|
||||
public SuggestTokenDto(final int start, final int end, final String suggestion) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public String getSuggestion() {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
public void setStart(final int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public void setEnd(final int end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public void setSuggestion(final String suggestion) {
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SuggestTokenDto [start=" + start + ", end=" + end + ", suggestion=" + suggestion + "]";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user