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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user