Feature target type entity (#1162)

* Added Target Type model

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added Target Type JPA model

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added Target Type repository model classes

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Removed the name entity from Target Type

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Refactored the Target Type models

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added the DB migration script and updated the Target Type models

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added target type in target Mapper

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Changed the target type ID to Long

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added MYSQL DB migration script and removed the deleted column for target type

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Updated the DB migration script for target table

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added manyToMany reltation between target type and Ds type

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added POSTGRESQL DB migration script

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added MSSQL SERVER DB migration script

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added DB2 DB migration script

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added missing license header and java docs

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added on delete cascade in DB migration script

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added Target Type specification

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Removed the delete cascade and Added type API
Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* fixed API doc build

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added target type management test

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added target type events test

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added target type update and unassign to target

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added API tests for assigning target type to target

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added missing license header

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added missing docs

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Fixed sonar issues

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Fixed license header build issue

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Updated the attribute name to target type

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Fixed the review comments

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Removed unused error status variable

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added target API to assign target type

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Added the tests for assigning target type to target

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>

* Fixed the review comments for null check

Signed-off-by: Anand kumar <anand.kumar@bosch-si.com>
This commit is contained in:
Anand Kumar
2021-08-25 12:13:27 +02:00
committed by GitHub
parent 2574581b2c
commit 3fa1dd1be4
76 changed files with 4915 additions and 41 deletions

View File

@@ -29,6 +29,7 @@ Supported HTTP-methods are:
Available Management APIs resources are:
- [Targets](/hawkbit/apis/mgmt/targets/)
- [Target types](/hawkbit/apis/mgmt/targettypess/)
- [Distribution sets](/hawkbit/apis/mgmt/distributionsets/)
- [Distribution set types](/hawkbit/apis/mgmt/distributionsettypes/)
- [Software modules](/hawkbit/apis/mgmt/softwaremodules/)

View File

@@ -0,0 +1,7 @@
---
title: Target Types API
parent: Management API
weight: -100
---
<iframe width="100%" height="800px" frameborder="0" src="../../../rest-api/targettypes-api-guide/"></iframe>

View File

@@ -275,7 +275,9 @@ public enum SpServerError {
*
*/
SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE("hawkbit.server.error.noWeightProvidedInMultiAssignmentMode",
"The requested operation requires a weight to be specified when multi assignments is enabled.");
"The requested operation requires a weight to be specified when multi assignments is enabled."),
SP_TARGET_TYPE_IN_USE("hawkbit.server.error.target.type.used", "Target type is still in use by a target.");
private final String key;
private final String message;

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2021 Bosch.IO 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;
/**
* Describing the fields of the TargetType model which can be used in
* the REST API
*/
public enum TargetTypeFields implements FieldNameProvider {
/**
* The name field.
*/
NAME("name"),
/**
* The description field.
*/
DESCRIPTION("description"),
/**
* The id field.
*/
ID("id");
private final String fieldName;
TargetTypeFields(final String fieldName) {
this.fieldName = fieldName;
}
@Override
public String getFieldName() {
return fieldName;
}
}

View File

@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.builder.TagBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.MetaData;
@@ -109,6 +110,11 @@ public interface EntityFactory {
*/
TargetBuilder target();
/**
* @return {@link TargetTypeBuilder} object
*/
TargetTypeBuilder targetType();
/**
* @return {@link TargetFilterQueryBuilder} object
*/

View File

@@ -106,4 +106,9 @@ public interface QuotaManagement {
*/
long getMaxArtifactStorage();
/**
* @return the maximum number of distribution set types per target type
*/
int getMaxDistributionSetTypesPerTargetType();
}

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.Page;
@@ -598,6 +599,30 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Target unAssignTag(@NotEmpty String controllerID, long targetTagId);
/**
* Un-assign a {@link TargetType} assignment to given {@link Target}.
*
* @param controllerID
* to un-assign for
* @return the unassigned target
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Target unAssignType(@NotEmpty String controllerID);
/**
* Assign a {@link TargetType} assignment to given {@link Target}.
*
* @param controllerID
* to un-assign for
* @return the unassigned target
*
* @throws EntityNotFoundException
* if TargetType with given target ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Target assignType(@NotEmpty String controllerID, @NotNull long targetTypeId);
/**
* updates the {@link Target}.
*

View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) 2021 Bosch.IO 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;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.access.prepost.PreAuthorize;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
* Management service for {@link TargetType}s.
*
*/
public interface TargetTypeManagement {
/**
* @param name
* as {@link TargetType#getName()}
* @return {@link TargetType}
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Optional<TargetType> getByName(@NotEmpty String name);
/**
* @return total count
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long count();
/**
* @param create
* TargetTypeCreate
* @return targetType
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
TargetType create(@NotNull @Valid TargetTypeCreate create);
/**
* @param creates
* List of TargetTypeCreate
* @return List of targetType
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
List<TargetType> create(@NotEmpty @Valid Collection<TargetTypeCreate> creates);
/**
* @param id targetTypeId
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_DELETE_TARGET)
void delete(@NotNull Long id);
/**
* @param pageable
* Page
* @return TargetType page
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetType> findAll(@NotNull Pageable pageable);
/**
* @param pageable
* Page
* @param rsqlParam
* query param
* @return Target type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetType> findByRsql(@NotNull Pageable pageable, @NotEmpty String rsqlParam);
/**
* @param id
* Target type ID
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Optional<TargetType> get(long id);
/**
* @param targetId
* Target ID
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Optional<TargetType> findByTargetId(long targetId);
/**
* @param targetIds
* List of Target ID
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<TargetType> findByTargetIds(Collection<Long> targetIds);
/**
* @param controllerId
* Target controller ID
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Optional<TargetType> findByTargetControllerId(String controllerId);
/**
* @param controllerIds
* List of Target controller ID
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<TargetType> findByTargetControllerIds(Collection<String> controllerIds);
/**
* @param ids
* List of Target type ID
* @return Target type list
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<TargetType> get(@NotEmpty Collection<Long> ids);
/**
* @param update
* TargetTypeUpdate
* @return Target Type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetType update(@NotNull @Valid TargetTypeUpdate update);
/**
* @param targetTypeId
* Target type ID
* @param distributionSetTypeIds
* Distribution set ID
* @return Target type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
TargetType assignCompatibleDistributionSetTypes(long targetTypeId,
@NotEmpty Collection<Long> distributionSetTypeIds);
/**
* @param targetTypeId
* Target type ID
* @param distributionSetTypeIds
* Distribution set ID
* @return Target type
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
TargetType unassignDistributionSetType(long targetTypeId, long distributionSetTypeIds);
}

View File

@@ -46,6 +46,13 @@ public interface TargetCreate {
*/
TargetCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/**
* @param targetTypeId
* for {@link Target#getTargetType()}
* @return updated builder instance
*/
TargetCreate targetType(Long targetTypeId);
/**
* @param securityToken
* for {@link Target#getSecurityToken()} is generated with a

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
* Builder for {@link TargetType}.
*
*/
public interface TargetTypeBuilder {
/**
* @param id
* of the updatable entity
* @return builder instance
*/
TargetTypeUpdate update(long id);
/**
* @return builder instance
*/
TargetTypeCreate create();
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TargetType;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Collections;
/**
* Builder to create a new {@link TargetType} entry. Defines all fields
* that can be set at creation time. Other fields are set by the repository
* automatically, e.g. {@link BaseEntity#getCreatedAt()}.
*
*/
public interface TargetTypeCreate {
/**
* @param name
* for {@link TargetType#getName()}
* @return updated builder instance
*/
TargetTypeCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotEmpty String name);
/**
* @param description
* for {@link TargetType#getDescription()}
* @return updated builder instance
*/
TargetTypeCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/**
* @param colour
* for {@link TargetType#getColour()}
* @return updated builder instance
*/
TargetTypeCreate colour(@Size(max = TargetType.COLOUR_MAX_SIZE) String colour);
/**
* @param compatible
* for {@link TargetType#getCompatibleDistributionSetTypes()}
* @return updated builder instance
*/
TargetTypeCreate compatible(@NotEmpty Collection<Long> compatible);
/**
* @param compatible
* for {@link TargetType#getCompatibleDistributionSetTypes()}
* @return updated builder instance
*/
default TargetTypeCreate compatible(@NotNull final Long compatible) {
return compatible(Collections.singletonList(compatible));
}
/**
* @param compatible
* for {@link TargetType#getCompatibleDistributionSetTypes()}
* @return updated builder instance
*/
default TargetTypeCreate compatible(@NotNull final DistributionSetType compatible) {
return compatible(compatible.getId());
}
/**
* @return peek on current state of {@link TargetType} in the
* builder
*/
TargetType build();
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TargetType;
import javax.validation.constraints.Size;
/**
* Builder to update an existing {@link TargetType} entry. Defines all
* fields that can be updated.
*
*/
public interface TargetTypeUpdate {
/**
* @param description
* for {@link TargetType#getDescription()}
* @return updated builder instance
*/
TargetTypeUpdate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/**
* @param colour
* for {@link TargetType#getColour()}
* @return updated builder instance
*/
TargetTypeUpdate colour(@Size(max = TargetType.COLOUR_MAX_SIZE) String colour);
/**
* @param name
* Name
* @return updated builder instance
*/
TargetTypeUpdate name(@Size(max = TargetType.NAME_MAX_SIZE) String name);
}

View File

@@ -43,6 +43,13 @@ public interface TargetUpdate {
*/
TargetUpdate securityToken(@Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE) @NotNull String securityToken);
/**
* @param targetTypeId
* for {@link Target#getTargetType()}
* @return updated builder instance
*/
TargetUpdate targetType(@NotNull Long targetTypeId);
/**
* @param address
* for {@link Target#getAddress()}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2021 Bosch.IO 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.event.remote;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
*
* Defines the remote event of deleting a {@link TargetType}.
*/
public class TargetTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public TargetTypeDeletedEvent() {
// for serialization libs like jackson
}
/**
* Constructor for json serialization.
*
* @param tenant
* the tenant
* @param entityId
* the entity id
* @param entityClass
* the entity class
* @param applicationId
* the origin application id
*/
public TargetTypeDeletedEvent(final String tenant, final Long entityId, final String entityClass,
final String applicationId) {
super(entityId, tenant, entityClass, applicationId);
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 Bosch.IO 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.event.remote.entity;
import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
* Defines the remote event of creating a new {@link TargetType}.
*
*/
public class TargetTypeCreatedEvent extends RemoteEntityEvent<TargetType>
implements EntityCreatedEvent {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public TargetTypeCreatedEvent() {
// for serialization libs like jackson
}
/**
* Constructor.
*
* @param baseEntity
* the TargetType
* @param applicationId
* the origin application id
*/
public TargetTypeCreatedEvent(final TargetType baseEntity, final String applicationId) {
super(baseEntity, applicationId);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 Bosch.IO 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.event.remote.entity;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
* Defines the remote event for updating a {@link TargetType}.
*
*/
public class TargetTypeUpdatedEvent extends RemoteEntityEvent<TargetType>
implements EntityUpdatedEvent {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public TargetTypeUpdatedEvent() {
// for serialization libs like jackson
}
/**
* Constructor.
*
* @param baseEntity
* TargetType
* @param applicationId
* the origin application id
*/
public TargetTypeUpdatedEvent(final TargetType baseEntity, final String applicationId) {
super(baseEntity, applicationId);
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2021 Bosch.IO 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.exception;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
* the {@link DistributionSetTypeNotInTargetTypeException} is thrown when a
* {@link DistributionSetType} is requested as part of a {@link TargetType} but
* is not returned in {@link TargetType#getCompatibleDistributionSetTypes()}.
*/
public class DistributionSetTypeNotInTargetTypeException extends EntityNotFoundException {
private static final long serialVersionUID = 1L;
/**
* Constructor
*
* @param distributionSetTypeId
* that is not part of given {@link TargetType}
* @param targetTypeId
* of the {@link TargetType} where given
* {@link DistributionSetType} is not part of
*/
public DistributionSetTypeNotInTargetTypeException(final Long distributionSetTypeId,
final Long targetTypeId) {
super(DistributionSetType.class.getSimpleName() + " with id {" + distributionSetTypeId + "} is not part of "
+ TargetType.class.getSimpleName() + " with id {" + targetTypeId + "}.");
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2021 Bosch.IO 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.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Thrown if target type is assigned
*/
public class TargetTypeInUseException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_TARGET_TYPE_IN_USE;
/**
* Default constructor.
*/
public TargetTypeInUseException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public TargetTypeInUseException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public TargetTypeInUseException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public TargetTypeInUseException(final String message) {
super(message, THIS_ERROR);
}
}

View File

@@ -85,6 +85,11 @@ public interface Target extends NamedEntity {
*/
TargetUpdateStatus getUpdateStatus();
/**
* @return Target type {@link TargetType}.
*/
TargetType getTargetType();
/**
* @return the poll time which holds the last poll time of the target, the
* next poll time and the overdue time. In case the

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 Bosch.IO 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.model;
import java.util.Set;
/**
* A {@link TargetType} is an abstract definition for
* {@link Target}
*
*/
public interface TargetType extends NamedEntity {
/**
* Maximum length of color in Management UI.
*/
int COLOUR_MAX_SIZE = 16;
/**
* @return immutable set of optional {@link DistributionSetType}s
*/
Set<DistributionSetType> getCompatibleDistributionSetTypes();
/**
* @return immutable set of optional {@link Target}s
*/
Set<Target> getTargets();
/**
* Checks if the given {@link DistributionSetType} is in
* {@link #getCompatibleDistributionSetTypes()}.
*
* @param distributionSetType
* search for
* @return <code>true</code> if found
*/
default boolean containsCompatibleDistributionSetType(final DistributionSetType distributionSetType) {
return containsCompatibleDistributionSetType(distributionSetType.getId());
}
/**
* Checks if the given {@link DistributionSetType} is in
* {@link #getCompatibleDistributionSetTypes()}.
*
* @param distributionSetTypeId
* search by {@link DistributionSetType#getId()}
* @return <code>true</code> if found
*/
default boolean containsCompatibleDistributionSetType(final Long distributionSetTypeId) {
return getCompatibleDistributionSetTypes().stream().anyMatch(element -> element.getId().equals(distributionSetTypeId));
}
/**
* Unassigns a distribution set type from target types
* @param dsTypeId
* @return the resulting target type
*/
TargetType removeDistributionSetType(final Long dsTypeId);
/**
* @return get color code to be used in management UI views.
*/
String getColour();
}

View File

@@ -108,4 +108,8 @@ public class PropertiesQuotaManagement implements QuotaManagement {
return securityProperties.getDos().getMaxArtifactStorage();
}
@Override
public int getMaxDistributionSetTypesPerTargetType() {
return securityProperties.getDos().getMaxDistributionSetTypesPerTargetType();
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.ValidString;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Optional;
/**
* Create and update builder DTO.
*
* @param <T>
* update or create builder interface
*/
public abstract class AbstractTargetTypeUpdateCreate<T> extends AbstractNamedEntityBuilder<T> {
@ValidString
protected String colour;
protected Collection<Long> compatible;
/**
* @param compatible
* list of ID
* @return generic type
*/
public T compatible(final Collection<Long> compatible) {
this.compatible = compatible;
return (T) this;
}
/**
* @return List of ID
*/
public Optional<Collection<Long>> getCompatible() {
return Optional.ofNullable(compatible);
}
/**
* @param colour
* Colour value
* @return generic type
*/
public T colour(final String colour) {
this.colour = StringUtils.trimWhitespace(colour);
return (T) this;
}
/**
* @return colour
*/
public Optional<String> getColour() {
return Optional.ofNullable(colour);
}
}

View File

@@ -11,8 +11,11 @@ package org.eclipse.hawkbit.repository.builder;
import java.net.URI;
import java.util.Optional;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.ValidString;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.util.StringUtils;
@@ -36,6 +39,8 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
protected Boolean requestAttributes;
protected Long targetTypeId;
protected AbstractTargetUpdateCreate(final String controllerId) {
this.controllerId = StringUtils.trimWhitespace(controllerId);
}
@@ -79,6 +84,11 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
return (TargetCreate) this;
}
public T targetType(final Long targetTypeId) {
this.targetTypeId = targetTypeId;
return (T) this;
}
public String getControllerId() {
return controllerId;
}
@@ -99,4 +109,8 @@ public class AbstractTargetUpdateCreate<T> extends AbstractNamedEntityBuilder<T>
return Optional.ofNullable(status);
}
public Long getTargetTypeId() {
return targetTypeId;
}
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
/**
* Update implementation.
*/
public class GenericTargetTypeUpdate extends AbstractTargetTypeUpdateCreate<TargetTypeUpdate>
implements TargetTypeUpdate {
/**
* @param id
* Target type ID
*/
public GenericTargetTypeUpdate(final Long id) {
super.id = id;
}
}

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
@@ -66,6 +67,8 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
private final DistributionSetRepository distributionSetRepository;
private final TargetTypeRepository targetTypeRepository;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private final Database database;
@@ -74,12 +77,13 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository,
final DistributionSetRepository distributionSetRepository, final TargetTypeRepository targetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database,
final QuotaManagement quotaManagement) {
this.distributionSetTypeRepository = distributionSetTypeRepository;
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
this.distributionSetRepository = distributionSetRepository;
this.targetTypeRepository = targetTypeRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.database = database;
this.quotaManagement = quotaManagement;
@@ -190,7 +194,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
}
/**
* Enforces the quota specifiying the maximum number of
* Enforces the quota specifying the maximum number of
* {@link SoftwareModuleType}s per {@link DistributionSetType}.
*
* @param id
@@ -268,18 +272,28 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long typeId) {
final JpaDistributionSetType toDelete = distributionSetTypeRepository.findById(typeId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, typeId));
unassignDsTypeFromTargetTypes(typeId);
if (distributionSetRepository.countByTypeId(typeId) > 0) {
toDelete.setDeleted(true);
distributionSetTypeRepository.save(toDelete);
} else {
}
else {
distributionSetTypeRepository.deleteById(typeId);
}
}
private void unassignDsTypeFromTargetTypes(long typeId) {
List<JpaTargetType> targetTypesByDsType = targetTypeRepository.findByDsType(typeId);
targetTypesByDsType.forEach(targetType -> {
targetType.removeDistributionSetType(typeId);
targetTypeRepository.save(targetType);
});
}
@Override
@Transactional
@Retryable(include = {

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.builder.TagBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
@@ -42,6 +43,9 @@ public class JpaEntityFactory implements EntityFactory {
@Autowired
private DistributionSetBuilder distributionSetBuilder;
@Autowired
private TargetBuilder targetBuilder;
@Autowired
private DistributionSetTypeBuilder distributionSetTypeBuilder;
@@ -57,6 +61,9 @@ public class JpaEntityFactory implements EntityFactory {
@Autowired
private SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder;
@Autowired
private TargetTypeBuilder targetTypeBuilder;
@Override
public MetaData generateDsMetadata(final String key, final String value) {
return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value));
@@ -79,7 +86,12 @@ public class JpaEntityFactory implements EntityFactory {
@Override
public TargetBuilder target() {
return new JpaTargetBuilder();
return targetBuilder;
}
@Override
public TargetTypeBuilder targetType() {
return targetTypeBuilder;
}
@Override

View File

@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -93,6 +94,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private TargetTypeRepository targetTypeRepository;
@Autowired
private DistributionSetTagRepository distributionSetTagRepository;
@@ -255,6 +259,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
targetRepository.deleteByTenant(tenant);
targetFilterQueryRepository.deleteByTenant(tenant);
rolloutRepository.deleteByTenant(tenant);
targetTypeRepository.deleteByTenant(tenant);
targetTagRepository.deleteByTenant(tenant);
distributionSetTagRepository.deleteByTenant(tenant);
distributionSetRepository.deleteByTenant(tenant);

View File

@@ -49,6 +49,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
@@ -63,6 +64,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -98,6 +100,8 @@ public class JpaTargetManagement implements TargetManagement {
private final TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository;
private final TargetMetadataRepository targetMetadataRepository;
private final RolloutGroupRepository rolloutGroupRepository;
@@ -119,7 +123,7 @@ public class JpaTargetManagement implements TargetManagement {
private final Database database;
public JpaTargetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
@@ -130,6 +134,7 @@ public class JpaTargetManagement implements TargetManagement {
this.entityManager = entityManager;
this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository;
this.targetTypeRepository = targetTypeRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.distributionSetRepository = distributionSetRepository;
@@ -152,6 +157,10 @@ public class JpaTargetManagement implements TargetManagement {
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
}
private JpaTargetType getTargetTypeByIdAndThrowIfNotFound(final long id) {
return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id));
}
@Override
public List<Target> getByControllerID(final Collection<String> controllerIDs) {
return Collections.unmodifiableList(
@@ -333,6 +342,10 @@ public class JpaTargetManagement implements TargetManagement {
update.getDescription().ifPresent(target::setDescription);
update.getAddress().ifPresent(target::setAddress);
update.getSecurityToken().ifPresent(target::setSecurityToken);
if (update.getTargetTypeId() != null){
TargetType targetType = getTargetTypeByIdAndThrowIfNotFound(update.getTargetTypeId());
target.setTargetType(targetType);
}
return targetRepository.save(target);
}
@@ -589,6 +602,24 @@ public class JpaTargetManagement implements TargetManagement {
return result;
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Target unAssignType(final String controllerID) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID);
target.setTargetType(null);
return targetRepository.save(target);
}
@Override
public Target assignType(String controllerID, long targetTypeId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID);
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType);
return targetRepository.save(target);
}
@Override
public Slice<Target> findByFilterOrderByLinkedDistributionSet(final Pageable pageable,
final long orderByDistributionId, final FilterParams filterParams) {

View File

@@ -0,0 +1,250 @@
/**
* Copyright (c) 2021 Bosch.IO 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;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetTypeFields;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.GenericTargetTypeUpdate;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.TargetTypeInUseException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetTypeCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* JPA implementation of {@link TargetTypeManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
public class JpaTargetTypeManagement implements TargetTypeManagement {
private final TargetTypeRepository targetTypeRepository;
private final TargetRepository targetRepository;
private final DistributionSetTypeRepository distributionSetTypeRepository;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private final Database database;
private final QuotaManagement quotaManagement;
/**
* Constructor
*
* @param targetTypeRepository
* Target type repository
* @param targetRepository
* Target repository
* @param virtualPropertyReplacer
* replacer
* @param database
* database
*/
public JpaTargetTypeManagement(final TargetTypeRepository targetTypeRepository,
final TargetRepository targetRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database,
final QuotaManagement quotaManagement) {
this.targetTypeRepository = targetTypeRepository;
this.targetRepository = targetRepository;
this.distributionSetTypeRepository = distributionSetTypeRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.database = database;
this.quotaManagement = quotaManagement;
}
@Override
public Optional<TargetType> getByName(String name) {
return targetTypeRepository.findByName(name).map(TargetType.class::cast);
}
@Override
public long count() {
return targetTypeRepository.count();
}
@Override
public TargetType create(TargetTypeCreate create) {
final JpaTargetTypeCreate typeCreate = (JpaTargetTypeCreate) create;
return targetTypeRepository.save(typeCreate.build());
}
@Override
public List<TargetType> create(Collection<TargetTypeCreate> creates) {
return creates.stream().map(this::create).collect(Collectors.toList());
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final Long targetTypeId) {
throwExceptionIfTargetTypeDoesNotExist(targetTypeId);
if (targetRepository.countByTargetTypeId(targetTypeId) > 0) {
throw new TargetTypeInUseException("Cannot delete target type that is in use");
}
targetTypeRepository.deleteById(targetTypeId);
}
@Override
public Page<TargetType> findAll(Pageable pageable) {
return convertPage(targetTypeRepository.findAll(pageable), pageable);
}
@Override
public Page<TargetType> findByRsql(Pageable pageable, String rsqlParam) {
final Specification<JpaTargetType> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetTypeFields.class,
virtualPropertyReplacer, database);
return convertPage(targetTypeRepository.findAll(spec, pageable), pageable);
}
@Override
public Optional<TargetType> get(long id) {
return targetTypeRepository.findById(id).map(targetType -> targetType);
}
@Override
public Optional<TargetType> findByTargetId(long targetId) {
return targetTypeRepository
.findOne(TargetTypeSpecification.hasTarget(targetId)).map(TargetType.class::cast);
}
@Override
public List<TargetType> findByTargetIds(Collection<Long> targetIds) {
return targetTypeRepository.findAll(TargetTypeSpecification.hasTarget(targetIds))
.stream().map(TargetType.class::cast).collect(Collectors.toList());
}
@Override
public Optional<TargetType> findByTargetControllerId(String controllerId) {
return targetTypeRepository
.findOne(TargetTypeSpecification.hasTargetControllerId(controllerId)).map(TargetType.class::cast);
}
@Override
public List<TargetType> findByTargetControllerIds(Collection<String> controllerIds) {
return targetTypeRepository.findAll(TargetTypeSpecification.hasTargetControllerIdIn(controllerIds))
.stream().map(TargetType.class::cast).collect(Collectors.toList());
}
@Override
public List<TargetType> get(Collection<Long> ids) {
return Collections.unmodifiableList(targetTypeRepository.findAllById(ids));
}
@Override
public TargetType update(TargetTypeUpdate update) {
final GenericTargetTypeUpdate typeUpdate = (GenericTargetTypeUpdate) update;
final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(typeUpdate.getId());
typeUpdate.getName().ifPresent((type::setName));
typeUpdate.getDescription().ifPresent(type::setDescription);
typeUpdate.getColour().ifPresent(type::setColour);
return targetTypeRepository.save(type);
}
@Override
public TargetType assignCompatibleDistributionSetTypes(long targetTypeId, Collection<Long> distributionSetTypeIds) {
final Collection<JpaDistributionSetType> dsTypes = distributionSetTypeRepository
.findAllById(distributionSetTypeIds);
if (dsTypes.size() < distributionSetTypeIds.size()) {
throw new EntityNotFoundException(DistributionSetType.class, distributionSetTypeIds,
dsTypes.stream().map(DistributionSetType::getId).collect(Collectors.toList()));
}
final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(targetTypeId);
assertDistributionSetTypeQuota(targetTypeId, distributionSetTypeIds.size());
dsTypes.forEach(type::addCompatibleDistributionSetType);
return targetTypeRepository.save(type);
}
@Override
public TargetType unassignDistributionSetType(long targetTypeId, long distributionSetTypeId) {
final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(targetTypeId);
findDsTypeAndThrowExceptionIfNotFound(distributionSetTypeId);
type.removeDistributionSetType(distributionSetTypeId);
return targetTypeRepository.save(type);
}
private JpaTargetType findTargetTypeAndThrowExceptionIfNotFound(final Long typeId) {
return (JpaTargetType) get(typeId).orElseThrow(() -> new EntityNotFoundException(TargetType.class, typeId));
}
private void findDsTypeAndThrowExceptionIfNotFound(final Long typeId) {
distributionSetTypeRepository.findById(typeId).orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, typeId));
}
private void throwExceptionIfTargetTypeDoesNotExist(final Long typeId) {
if (!targetTypeRepository.existsById(typeId)) {
throw new EntityNotFoundException(TargetType.class, typeId);
}
}
/**
* Enforces the quota specifying the maximum number of
* {@link DistributionSetType}s per {@link TargetType}.
*
* @param id
* of the target type
* @param requested
* number of distribution set types to check
*
* @throws AssignmentQuotaExceededException
* if the software module type quota is exceeded
*/
private void assertDistributionSetTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxDistributionSetTypesPerTargetType(),
DistributionSetType.class, TargetType.class, targetTypeRepository::countDsSetTypesById);
}
private static Page<TargetType> convertPage(final Page<JpaTargetType> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Slice<TargetType> convertPage(final Slice<JpaTargetType> findAll, final Pageable pageable) {
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
}
}

View File

@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
@@ -46,7 +47,9 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetTypeBuilder;
import org.eclipse.hawkbit.repository.builder.RolloutBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
import org.eclipse.hawkbit.repository.event.ApplicationEventFilter;
import org.eclipse.hawkbit.repository.event.remote.EventEntityManager;
import org.eclipse.hawkbit.repository.event.remote.EventEntityManagerHolder;
@@ -62,7 +65,9 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitDefaultServiceExecutor;
@@ -85,6 +90,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
@@ -231,6 +237,21 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new JpaDistributionSetBuilder(distributionSetTypeManagement, softwareManagement);
}
@Bean
TargetBuilder targetBuilder(final TargetTypeManagement targetTypeManagement){
return new JpaTargetBuilder(targetTypeManagement);
}
/**
* @param dsTypeManagement
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean
*/
@Bean
TargetTypeBuilder targetTypeBuilder(final DistributionSetTypeManagement dsTypeManagement) {
return new JpaTargetTypeBuilder(dsTypeManagement);
}
@Bean
SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder(
final SoftwareModuleManagement softwareModuleManagement) {
@@ -465,10 +486,26 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository,
final TargetTypeRepository targetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties,
final QuotaManagement quotaManagement) {
return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
distributionSetRepository, virtualPropertyReplacer, properties.getDatabase(), quotaManagement);
distributionSetRepository, targetTypeRepository, virtualPropertyReplacer, properties.getDatabase(), quotaManagement);
}
/**
* {@link JpaTargetTypeManagement} bean.
*
* @return a new {@link TargetTypeManagement}
*/
@Bean
@ConditionalOnMissingBean
TargetTypeManagement targetTypeManagement(final TargetTypeRepository targetTypeRepository,
final TargetRepository targetRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties,
final QuotaManagement quotaManagement) {
return new JpaTargetTypeManagement(targetTypeRepository, targetRepository, distributionSetTypeRepository,
virtualPropertyReplacer, properties.getDatabase(), quotaManagement);
}
/**
@@ -504,11 +541,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTypeRepository targetTypeRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetMetadataRepository,
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository, targetMetadataRepository,
rolloutGroupRepository, distributionSetRepository, targetFilterQueryRepository, targetTagRepository,
eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer, properties.getDatabase());
}

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.domain.Page;
@@ -200,6 +201,15 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
return this.count(Specification.where(null));
}
/**
* Counts {@link Target} instances of given type in the repository.
*
* @param targetTypeId
* to search for
* @return number of found {@link Target}s
*/
long countByTargetTypeId(Long targetTypeId);
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
* reasons (this is a "delete everything" query after all) we add the tenant

View File

@@ -0,0 +1,186 @@
/**
* Copyright (c) 2021 Bosch.IO 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;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* {@link PagingAndSortingRepository} for {@link JpaTargetType}.
*
*/
@Transactional(readOnly = true)
public interface TargetTypeRepository
extends BaseEntityRepository<JpaTargetType, Long>, JpaSpecificationExecutor<JpaTargetType> {
/**
* Finds {@link TargetType} in the repository matching an id.
*
* Calls version based on spec to allow injecting further specs
*
* @param id id to filter for
* @return {@link Optional} of {@link TargetType}
*/
@Override
@NonNull
default Optional<JpaTargetType> findById(@NonNull Long id) {
return this.findOne(Specification.where(TargetTypeSpecification.hasId(id)));
}
/**
* @param ids
* List of ID
* @return Target type list
*/
@Override
@NonNull
default List<JpaTargetType> findAllById(Iterable<Long> ids) {
final List<Long> collectedIds = StreamSupport.stream(ids.spliterator(), true).collect(Collectors.toList());
return this.findAll(Specification.where(TargetTypeSpecification.hasIdIn(collectedIds)));
}
/**
* Deletes the {@link TargetType}s with the given target IDs.
*
* @param targetTypeIDs
* to be deleted
*/
@Modifying
@Transactional
@Query("DELETE FROM JpaTargetType t WHERE t.id IN ?1")
void deleteByIdIn(Collection<Long> targetTypeIDs);
/**
* Finds all {@link TargetType}s in the repository.
*
* Calls version with (empty) spec to allow injecting further specs
*
* @return {@link List} of {@link TargetType}s
*/
@Override
@NonNull
default List<JpaTargetType> findAll() {
return this.findAll(Specification.where(null));
}
/**
* Finds all {@link TargetType}s in the repository sorted.
*
* Calls version with (empty) spec to allow injecting further specs
*
* @param sort instructions to sort result by
* @return {@link List} of {@link TargetType}s
*/
@Override
@NonNull
default Iterable<JpaTargetType> findAll(@NonNull Sort sort) {
return this.findAll(Specification.where(null), sort);
}
/**
* Finds a page of {@link TargetType}s in the repository.
*
* Calls version with (empty) spec to allow injecting further specs
*
* @param pageable paging context
* @return {@link List} of {@link TargetType}s
*/
@Override
@NonNull
default Page<JpaTargetType> findAll(@NonNull Pageable pageable) {
return this.findAll(Specification.where(null), pageable);
}
/**
* Checks whether {@link TargetType} in the repository matching an id exists or not.
*
* Calls version based on spec to allow injecting further specs
*
* @param id id to check for
* @return true if TargetType with id exists
*/
@Override
default boolean existsById(@NonNull Long id) {
return this.exists(TargetTypeSpecification.hasId(id));
}
/**
* Checks whether {@link TargetType} in the repository matching a spec exists or not.
*
* @param spec to check for existence
* @return true if target with id exists
*/
default boolean exists(@NonNull Specification<JpaTargetType> spec) {
return this.count(spec) > 0;
}
/**
* Count number of {@link TargetType}s in the repository.
*
* Calls version with an empty spec to allow injecting further specs
*
* @return number of targetTypes in the repository
*/
@Override
default long count() {
return this.count(Specification.where(null));
}
/**
* @param tenant
* Tenant
*/
@Modifying
@Transactional
@Query("DELETE FROM JpaTargetType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
@Query(value = "SELECT COUNT (t.id) FROM JpaDistributionSetType t JOIN t.compatibleToTargetTypes tt WHERE tt.id = :id")
long countDsSetTypesById(@Param("id") Long id);
/**
*
* @param dsTypeId
* to search for
* @return all {@link TargetType}s in the repository with given
* {@link TargetType#getName()}
*/
default List<JpaTargetType> findByDsType(@Param("id") Long dsTypeId) {
return this.findAll(Specification.where(TargetTypeSpecification.hasDsSetType(dsTypeId)));
}
/**
*
* @param name
* to search for
* @return all {@link TargetType}s in the repository with given
* {@link TargetType#getName()}
*/
default Optional<JpaTargetType> findByName(String name){
return this.findOne(Specification.where(TargetTypeSpecification.hasName(name)));
};
}

View File

@@ -8,15 +8,26 @@
*/
package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.model.Target;
/**
* Builder implementation for {@link Target}.
*
*/
public class JpaTargetBuilder implements TargetBuilder {
final private TargetTypeManagement targetTypeManagement;
/**
* @param targetTypeManagement
* Target type management
*/
public JpaTargetBuilder(TargetTypeManagement targetTypeManagement) {
this.targetTypeManagement = targetTypeManagement;
}
@Override
public TargetUpdate update(final String controllerId) {
@@ -25,7 +36,7 @@ public class JpaTargetBuilder implements TargetBuilder {
@Override
public TargetCreate create() {
return new JpaTargetCreate();
return new JpaTargetCreate(targetTypeManagement);
}
}

View File

@@ -8,9 +8,12 @@
*/
package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.AbstractTargetUpdateCreate;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.util.StringUtils;
@@ -20,8 +23,17 @@ import org.springframework.util.StringUtils;
*/
public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> implements TargetCreate {
JpaTargetCreate() {
private final TargetTypeManagement targetTypeManagement;
/**
* Constructor
*
* @param targetTypeManagement
* Target type management
*/
JpaTargetCreate(final TargetTypeManagement targetTypeManagement) {
super(null);
this.targetTypeManagement = targetTypeManagement;
}
@Override
@@ -38,6 +50,12 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
target.setName(name);
}
if (targetTypeId != null){
TargetType targetType = targetTypeManagement.get(targetTypeId)
.orElseThrow(() -> new EntityNotFoundException(TargetType.class, targetTypeId));
target.setTargetType(targetType);
}
target.setDescription(description);
target.setAddress(address);
target.setUpdateStatus(getStatus().orElse(TargetUpdateStatus.UNKNOWN));

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.GenericTargetTypeUpdate;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate;
import org.eclipse.hawkbit.repository.model.TargetType;
/**
* Builder implementation for {@link TargetType}.
*
*/
public class JpaTargetTypeBuilder implements TargetTypeBuilder {
private final DistributionSetTypeManagement distributionSetTypeManagement;
/**
* Constructor
*
* @param distributionSetTypeManagement
* Distribution set type management
*/
public JpaTargetTypeBuilder(DistributionSetTypeManagement distributionSetTypeManagement) {
this.distributionSetTypeManagement = distributionSetTypeManagement;
}
@Override
public TargetTypeUpdate update(long id) {
return new GenericTargetTypeUpdate(id);
}
@Override
public TargetTypeCreate create() {
return new JpaTargetTypeCreate(distributionSetTypeManagement);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2021 Bosch.IO 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.builder;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.builder.AbstractTargetTypeUpdateCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
/**
* Create/build implementation.
*
*/
public class JpaTargetTypeCreate extends AbstractTargetTypeUpdateCreate<TargetTypeCreate>
implements TargetTypeCreate {
private final DistributionSetTypeManagement distributionSetTypeManagement;
/**
* Constructor
*
* @param distributionSetTypeManagement
* Distribution set type management
*/
JpaTargetTypeCreate(final DistributionSetTypeManagement distributionSetTypeManagement) {
this.distributionSetTypeManagement = distributionSetTypeManagement;
}
@Override
public JpaTargetType build() {
final JpaTargetType result = new JpaTargetType(name, description, colour);
findDistributionSetTypeWithExceptionIfNotFound(compatible).forEach(result::addCompatibleDistributionSetType);
return result;
}
private Collection<DistributionSetType> findDistributionSetTypeWithExceptionIfNotFound(
final Collection<Long> distributionSetTypeId) {
if (CollectionUtils.isEmpty(distributionSetTypeId)) {
return Collections.emptyList();
}
final Collection<DistributionSetType> type = distributionSetTypeManagement.get(distributionSetTypeId);
if (type.size() < distributionSetTypeId.size()) {
throw new EntityNotFoundException(SoftwareModuleType.class, distributionSetTypeId);
}
return type;
}
}

View File

@@ -8,24 +8,6 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeUpdatedEvent;
@@ -33,11 +15,30 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.springframework.util.CollectionUtils;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A distribution set type defines which software module types can or have to be
* {@link DistributionSet}.
@@ -72,6 +73,10 @@ public class JpaDistributionSetType extends AbstractJpaNamedEntity implements Di
@Column(name = "deleted")
private boolean deleted;
@CascadeOnDelete
@ManyToMany(mappedBy = "distributionSetTypes", targetEntity = JpaTargetType.class, fetch = FetchType.LAZY)
private List<TargetType> compatibleToTargetTypes;
public JpaDistributionSetType() {
// default public constructor for JPA
}

View File

@@ -55,6 +55,7 @@ import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
@@ -162,6 +163,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@JoinColumn(name = "target_id", nullable = false, updatable = false) }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
private Map<String, String> controllerAttributes;
@ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = JpaTargetType.class)
@JoinColumn(name = "target_type", nullable = true, updatable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_relation_target_type"))
private TargetType targetType;
// set default request controller attributes to true, because we want to
// request them the first
// time
@@ -201,10 +206,16 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
: controllerId;
}
JpaTarget() {
/**
* Constructor
*/
public JpaTarget() {
// empty constructor for JPA.
}
/**
* @return assigned distribution set
*/
public DistributionSet getAssignedDistributionSet() {
return assignedDistributionSet;
}
@@ -214,6 +225,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return controllerId;
}
/**
* @return tags
*/
public Set<TargetTag> getTags() {
if (tags == null) {
return Collections.emptySet();
@@ -222,6 +236,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return Collections.unmodifiableSet(tags);
}
/**
* @return rollouts target group
*/
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
@@ -230,6 +247,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return Collections.unmodifiableList(rolloutTargetGroup);
}
/**
* @param tag
* tag
* @return boolean true or false
*/
public boolean addTag(final TargetTag tag) {
if (tags == null) {
tags = new HashSet<>();
@@ -238,6 +260,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return tags.add(tag);
}
/**
* @param tag
* tag
* @return boolean true or false
*/
public boolean removeTag(final TargetTag tag) {
if (tags == null) {
return false;
@@ -246,14 +273,25 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return tags.remove(tag);
}
/**
* @param assignedDistributionSet
* Distribution set
*/
public void setAssignedDistributionSet(final DistributionSet assignedDistributionSet) {
this.assignedDistributionSet = (JpaDistributionSet) assignedDistributionSet;
}
/**
* @param controllerId
* Controller ID
*/
public void setControllerId(final String controllerId) {
this.controllerId = controllerId;
}
/**
* @return list of action
*/
public List<Action> getActions() {
if (actions == null) {
return Collections.emptyList();
@@ -262,6 +300,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return Collections.unmodifiableList(actions);
}
/**
* @param action
* Action
* @return boolean true or false
*/
public boolean addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>();
@@ -285,6 +328,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return null;
}
/**
* @param securityToken
* token value
*/
public void setSecurityToken(final String securityToken) {
this.securityToken = securityToken;
}
@@ -348,10 +395,29 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return updateStatus;
}
@Override
public TargetType getTargetType() {
return targetType;
}
/**
* @param type
* Target type
*/
public void setTargetType(final TargetType type) {
this.targetType = type;
}
/**
* @return Distribution set
*/
public JpaDistributionSet getInstalledDistributionSet() {
return installedDistributionSet;
}
/**
* @return controller attributes
*/
public Map<String, String> getControllerAttributes() {
return controllerAttributes;
}
@@ -361,6 +427,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return requestControllerAttributes;
}
/**
* @return target metadata
*/
public List<TargetMetadata> getMetadata() {
if (metadata == null) {
return Collections.emptyList();
@@ -375,26 +444,50 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
+ "]";
}
/**
* @param address
* Address
*/
public void setAddress(final String address) {
this.address = address;
}
/**
* @param lastTargetQuery
* last query ID
*/
public void setLastTargetQuery(final Long lastTargetQuery) {
this.lastTargetQuery = lastTargetQuery;
}
/**
* @param installationDate
* installation date
*/
public void setInstallationDate(final Long installationDate) {
this.installationDate = installationDate;
}
/**
* @param installedDistributionSet
* Distribution set
*/
public void setInstalledDistributionSet(final JpaDistributionSet installedDistributionSet) {
this.installedDistributionSet = installedDistributionSet;
}
/**
* @param updateStatus
* Status
*/
public void setUpdateStatus(final TargetUpdateStatus updateStatus) {
this.updateStatus = updateStatus;
}
/**
* @param requestControllerAttributes
* Attributes
*/
public void setRequestControllerAttributes(final boolean requestControllerAttributes) {
this.requestControllerAttributes = requestControllerAttributes;
}

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) 2021 Bosch.IO 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.model;
import org.eclipse.hawkbit.repository.event.remote.TargetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Size;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* A target type defines which distribution set types can or have to be
* {@link Target}.
*
*/
@Entity
@Table(name = "sp_target_type", indexes = {
@Index(name = "sp_idx_target_type_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_target_type_name")})
public class JpaTargetType extends AbstractJpaNamedEntity implements TargetType, EventAwareEntity{
private static final long serialVersionUID = 1L;
@Column(name = "colour", nullable = true, length = TargetType.COLOUR_MAX_SIZE)
@Size(max = TargetType.COLOUR_MAX_SIZE)
private String colour;
@CascadeOnDelete
@ManyToMany(targetEntity = JpaDistributionSetType.class)
@JoinTable(name = "sp_target_type_ds_type_relation", joinColumns = {
@JoinColumn(name = "target_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_type_relation_target_type"))}, inverseJoinColumns = {
@JoinColumn(name = "distribution_set_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_type_relation_ds_type"))})
private Set<DistributionSetType> distributionSetTypes;
@OneToMany(targetEntity = JpaTarget.class, mappedBy = "targetType", fetch = FetchType.LAZY)
private Set<Target> targets;
/**
* Constructor
*/
public JpaTargetType() {
// default public constructor for JPA
}
/**
* Constructor
*
* @param name
* Type name
* @param description
* Description
* @param colour
* Colour
*/
public JpaTargetType(String name, String description, String colour) {
super(name,description);
this.colour = colour;
}
/**
* @param dsSetType
* Distribution set type
* @return Target type
*/
public JpaTargetType addCompatibleDistributionSetType(final DistributionSetType dsSetType) {
if (distributionSetTypes == null) {
distributionSetTypes = new HashSet<>();
}
distributionSetTypes.add(dsSetType);
return this;
}
/**
* @param dsTypeId
* Distribution set type ID
* @return Target type
*/
public JpaTargetType removeDistributionSetType(final Long dsTypeId) {
if (distributionSetTypes == null) {
return this;
}
distributionSetTypes.stream().filter(element -> element.getId().equals(dsTypeId)).findAny()
.ifPresent(distributionSetTypes::remove);
return this;
}
@Override
public Set<DistributionSetType> getCompatibleDistributionSetTypes() {
if (distributionSetTypes == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(distributionSetTypes);
}
@Override
public Set<Target> getTargets() {
return targets;
}
@Override
public String getColour() {
return colour;
}
public void setColour(final String colour) {
this.colour = colour;
}
@Override
public void fireCreateEvent(DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new TargetTypeCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new TargetTypeUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireDeleteEvent(DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new TargetTypeDeletedEvent(
getTenant(), getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId()));
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2021 Bosch.IO 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.specifications;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.SetJoin;
import java.util.Collection;
/**
* Specifications class for {@link TargetType}s. The class provides Spring Data JPQL
* Specifications.
*
*/
public final class TargetTypeSpecification {
private TargetTypeSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param id
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasId(final Long id) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTargetType_.id), id);
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param id
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasTarget(final long id) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTargetType, JpaTarget> join = targetRoot.join(JpaTargetType_.targets);
return cb.equal(join.get(JpaTarget_.id), id);
};
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param ids
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasTarget(final Collection<Long> ids) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTargetType, JpaTarget> join = targetRoot.join(JpaTargetType_.targets);
return join.get(JpaTarget_.id).in(ids);
};
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param controllerId
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasTargetControllerId(final String controllerId) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTargetType, JpaTarget> join = targetRoot.join(JpaTargetType_.targets);
return cb.equal(join.get(JpaTarget_.controllerId), controllerId);
};
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param controllerIds
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasTargetControllerIdIn(final Collection<String> controllerIds) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTargetType, JpaTarget> join = targetRoot.join(JpaTargetType_.targets);
return join.get(JpaTarget_.controllerId).in(controllerIds);
};
}
/**
* {@link Specification} for retrieving {@link TargetType}s by controllerId
*
* @param ids
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasIdIn(final Collection<Long> ids) {
return (targetRoot, query, cb) -> targetRoot.get(JpaTargetType_.id).in(ids);
}
/**
* {@link Specification} for retrieving {@link TargetType}s based on a
* {@link DistributionSetType} name.
*
* @param dsTypeId
* to search for
*
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasDsSetType(final Long dsTypeId) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaTargetType, JpaDistributionSetType> join = targetRoot.join(JpaTargetType_.distributionSetTypes);
return cb.equal(join.get(JpaDistributionSetType_.id), dsTypeId);
};
}
/**
* {@link Specification} for retrieving {@link TargetType} with
* given {@link TargetType#getName()} including fetching the
* elements list.
*
* @param name
* to search
* @return the {@link TargetType} {@link Specification}
*/
public static Specification<JpaTargetType> hasName(final String name) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTargetType_.name), name);
}
}

View File

@@ -0,0 +1,32 @@
CREATE TABLE sp_target_type
(
id BIGINT GENERATED always AS IDENTITY NOT NULL,
tenant VARCHAR(40) NOT NULL,
colour VARCHAR(16),
created_at BIGINT NOT NULL,
created_by VARCHAR(64) NOT NULL,
description VARCHAR(512),
last_modified_at BIGINT NOT NULL,
last_modified_by VARCHAR(64) NOT NULL,
name VARCHAR(64) NOT NULL,
optlock_revision INTEGER,
PRIMARY KEY (id)
);
CREATE INDEX sp_idx_target_type_prim
ON sp_target_type (tenant, id);
CREATE TABLE sp_target_type_ds_type_relation
(
target_type BIGINT NOT NULL,
distribution_set_type BIGINT NOT NULL,
PRIMARY KEY (target_type, distribution_set_type)
);
ALTER TABLE sp_target_type ADD CONSTRAINT uk_target_type_name UNIQUE (name, tenant);
ALTER TABLE sp_target ADD COLUMN target_type BIGINT;
ALTER TABLE sp_target ADD CONSTRAINT fk_target_relation_target_type FOREIGN KEY (target_type) REFERENCES sp_target_type (id) ON DELETE SET NULL;
ALTER TABLE sp_target_type_ds_type_relation ADD CONSTRAINT fk_target_type_relation_target_type FOREIGN KEY (target_type) REFERENCES sp_target_type (id) ON DELETE CASCADE;
ALTER TABLE sp_target_type_ds_type_relation ADD CONSTRAINT fk_target_type_relation_ds_type FOREIGN KEY (distribution_set_type) REFERENCES sp_distribution_set_type (id) ON DELETE CASCADE;

View File

@@ -0,0 +1,50 @@
create table sp_target_type
(
id bigint generated by default as identity,
created_at bigint,
created_by varchar(64),
last_modified_at bigint,
last_modified_by varchar(64),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
colour varchar(16),
primary key (id)
);
create table sp_target_type_ds_type_relation
(
target_type bigint not null,
distribution_set_type bigint not null,
primary key (target_type, distribution_set_type)
);
alter table sp_target_type
add constraint uk_target_type_name unique (name, tenant);
create index sp_idx_target_type_prim on sp_target_type (tenant, id);
alter table sp_target
add column target_type bigint;
alter table sp_target
add constraint fk_target_relation_target_type
foreign key (target_type)
references sp_target_type
on delete set null;
alter table sp_target_type_ds_type_relation
add constraint fk_target_type_relation_target_type
foreign key (target_type)
references sp_target_type
on delete cascade;
alter table sp_target_type_ds_type_relation
add constraint fk_target_type_relation_ds_type
foreign key (distribution_set_type)
references sp_distribution_set_type
on delete cascade;

View File

@@ -0,0 +1,47 @@
create table sp_target_type
(
id bigint not null auto_increment,
created_at bigint,
created_by varchar(64),
last_modified_at bigint,
last_modified_by varchar(64),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
colour varchar(16),
primary key (id)
);
create table sp_target_type_ds_type_relation
(
target_type bigint not null,
distribution_set_type bigint not null,
primary key (target_type, distribution_set_type)
);
alter table sp_target_type
add constraint uk_target_type_name unique (name, tenant);
create index sp_idx_target_type_prim on sp_target_type (tenant, id);
alter table sp_target
add column target_type bigint;
alter table sp_target
add constraint fk_target_relation_target_type
foreign key (target_type)
references sp_target_type (id)
on delete set null;
alter table sp_target_type_ds_type_relation
add constraint fk_target_type_relation_target_type
foreign key (target_type)
references sp_target_type (id)
on delete cascade;
alter table sp_target_type_ds_type_relation
add constraint fk_target_type_relation_ds_type
foreign key (distribution_set_type)
references sp_distribution_set_type (id)
on delete cascade;

View File

@@ -0,0 +1,65 @@
-- ------------ Write CREATE-SEQUENCE-stage scripts -----------
CREATE SEQUENCE IF NOT EXISTS sp_target_type_seq
INCREMENT BY 1
START WITH 1
NO CYCLE;
-- ------------ Write CREATE-TABLE-stage scripts -----------
CREATE TABLE sp_target_type(
id BIGINT NOT NULL DEFAULT nextval('sp_target_type_seq'),
created_at BIGINT,
created_by VARCHAR(64),
last_modified_at BIGINT,
last_modified_by VARCHAR(64),
optlock_revision BIGINT,
tenant VARCHAR(40) NOT NULL,
description VARCHAR(512),
name VARCHAR(64),
colour VARCHAR(16)
)
WITH (
OIDS=FALSE
);
CREATE TABLE sp_target_type_ds_type_relation(
target_type BIGINT NOT NULL,
distribution_set_type BIGINT NOT NULL
)
WITH (
OIDS=FALSE
);
-- ------------ Alter Table and Write INDEX scripts -----------
ALTER TABLE sp_target_type
ADD CONSTRAINT pk_sp_target_type PRIMARY KEY (id);
ALTER TABLE sp_target_type
ADD CONSTRAINT uk_target_type_name UNIQUE (name, tenant);
CREATE INDEX sp_idx_target_type_prim
ON sp_target_type
USING BTREE (tenant, id);
ALTER TABLE sp_target
ADD COLUMN target_type BIGINT;
ALTER TABLE sp_target
ADD CONSTRAINT fk_target_relation_target_type FOREIGN KEY (target_type)
REFERENCES sp_target_type (id)
ON UPDATE RESTRICT
ON DELETE SET NULL;
ALTER TABLE sp_target_type_ds_type_relation
ADD CONSTRAINT fk_target_type_relation_target_type FOREIGN KEY (target_type)
REFERENCES sp_target_type (id)
ON UPDATE RESTRICT
ON DELETE CASCADE;
ALTER TABLE sp_target_type_ds_type_relation
ADD CONSTRAINT fk_target_type_relation_ds_type FOREIGN KEY (distribution_set_type)
REFERENCES sp_distribution_set_type (id)
ON UPDATE RESTRICT
ON DELETE CASCADE;

View File

@@ -0,0 +1,27 @@
CREATE TABLE sp_target_type
(
id NUMERIC(19) IDENTITY NOT NULL,
tenant VARCHAR(40) NOT NULL,
colour VARCHAR(16) NULL,
created_at NUMERIC(19) NOT NULL,
created_by VARCHAR(64) NOT NULL,
description VARCHAR(512) NULL,
last_modified_at NUMERIC(19) NOT NULL,
last_modified_by VARCHAR(64) NOT NULL,
name VARCHAR(64) NOT NULL,
optlock_revision INTEGER NULL,
PRIMARY KEY (id)
);
CREATE INDEX sp_idx_target_type_prim ON sp_target_type (tenant, id);
CREATE TABLE sp_target_type_ds_type_relation
(
target_type NUMERIC(19) NOT NULL,
distribution_set_type NUMERIC(19) NOT NULL,
PRIMARY KEY (target_type, distribution_set_type)
);
ALTER TABLE sp_target_type ADD CONSTRAINT uk_target_type_name UNIQUE (name, tenant);
ALTER TABLE sp_target ADD target_type NUMERIC(19) NULL;
ALTER TABLE sp_target ADD CONSTRAINT fk_target_relation_target_type FOREIGN KEY (target_type) REFERENCES sp_target_type (id) ON DELETE SET NULL;
ALTER TABLE sp_target_type_ds_type_relation ADD CONSTRAINT fk_target_type_relation_target_type FOREIGN KEY (target_type) REFERENCES sp_target_type (id) ON DELETE CASCADE;
ALTER TABLE sp_target_type_ds_type_relation ADD CONSTRAINT fk_target_type_relation_ds_type FOREIGN KEY (distribution_set_type) REFERENCES sp_distribution_set_type (id) ON DELETE CASCADE;

View File

@@ -76,6 +76,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected TargetTagRepository targetTagRepository;
@Autowired
protected TargetTypeRepository targetTypeRepository;
@Autowired
protected DistributionSetTagRepository distributionSetTagRepository;

View File

@@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
@@ -27,6 +28,7 @@ import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
@@ -53,6 +55,7 @@ import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule;
@@ -1038,6 +1041,67 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
assertThat(updated.getTarget().getId()).isEqualTo(target.getId());
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that target type for a target can be created, updated and unassigned.")
public void createAndUpdateTargetTypeInTarget() {
// create a target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype", 2);
assertThat(targetTypes).hasSize(2);
// create a target
final Target target = testdataFactory.createTarget("target1", "testtarget", targetTypes.get(0).getId());
// initial opt lock revision must be one
Optional<JpaTarget> targetFound = targetRepository.findById(target.getId());
assertThat(targetFound).isPresent();
assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1);
assertThat(targetFound.get().getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
// update the target type
TargetUpdate targetUpdate = entityFactory.target().update(target.getControllerId()).targetType(targetTypes.get(1).getId());
targetManagement.update(targetUpdate);
// opt lock revision must be changed
Optional<JpaTarget> targetFound1 = targetRepository.findById(target.getId());
assertThat(targetFound1).isPresent();
assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(2);
assertThat(targetFound1.get().getTargetType().getId()).isEqualTo(targetTypes.get(1).getId());
// unassign the target type
targetManagement.unAssignType(target.getControllerId());
// opt lock revision must be changed
Optional<JpaTarget> targetFound2 = targetRepository.findById(target.getId());
assertThat(targetFound2).isPresent();
assertThat(targetFound2.get().getOptLockRevision()).isEqualTo(3);
assertThat(targetFound2.get().getTargetType()).isNull();
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that target type to a target can be assigned.")
public void assignTargetTypeInTarget() {
// create a target
final Target target = testdataFactory.createTarget("target1", "testtarget");
// initial opt lock revision must be one
Optional<JpaTarget> targetFound = targetRepository.findById(target.getId());
assertThat(targetFound).isPresent();
assertThat(targetFound.get().getOptLockRevision()).isEqualTo(1);
assertThat(targetFound.get().getTargetType()).isNull();
// create a target type
TargetType targetType = testdataFactory.findOrCreateTargetType("targettype");
assertThat(targetType).isNotNull();
// assign target type to target
targetManagement.assignType(targetFound.get().getControllerId(), targetType.getId());
// opt lock revision must be changed
Optional<JpaTarget> targetFound1 = targetRepository.findById(target.getId());
assertThat(targetFound1).isPresent();
assertThat(targetFound1.get().getOptLockRevision()).isEqualTo(2);
assertThat(targetFound1.get().getTargetType().getId()).isEqualTo(targetType.getId());
}
@Test
@Description("Queries and loads the metadata related to a given target.")
public void findAllTargetMetadataByControllerId() {

View File

@@ -0,0 +1,230 @@
/**
* Copyright (c) 2021 Bosch.IO 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;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Step;
import io.qameta.allure.Story;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolationException;
import java.util.Collections;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertThrows;
@Feature("Component Tests - Repository")
@Story("Target Type Management")
public class TargetTypeManagementTest extends AbstractJpaIntegrationTest{
@Test
@Description("Verifies that management get access react as specified on calls for non existing entities by means "
+ "of Optional not present.")
@ExpectEvents({ @Expect(type = TargetTypeCreatedEvent.class, count = 0) })
public void nonExistingEntityAccessReturnsNotPresent() {
assertThat(targetTypeManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(targetTypeManagement.getByName(NOT_EXIST_ID)).isNotPresent();
}
@Test
@Description("Verifies that management queries react as specified on calls for non existing entities "
+ " by means of throwing EntityNotFoundException.")
@ExpectEvents({ @Expect(type = TargetTypeUpdatedEvent.class, count = 0) })
public void entityQueriesReferringToNotExistingEntitiesThrowsException() {
verifyThrownExceptionBy(() -> targetTypeManagement.delete(NOT_EXIST_IDL), "TargetType");
verifyThrownExceptionBy(() -> targetTypeManagement.update(entityFactory.targetType().update(NOT_EXIST_IDL)),
"TargetType");
}
@Test
@Description("Verify that a target type with invalid properties cannot be created or updated")
public void createAndUpdateTargetTypeWithInvalidFields() {
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype1").description("targettypedes1"));
createAndUpdateTargetTypeWithInvalidDescription(targetType);
createAndUpdateTargetTypeWithInvalidColour(targetType);
createAndUpdateTargetTypeWithInvalidName(targetType);
}
@Step
private void createAndUpdateTargetTypeWithInvalidDescription(final TargetType targetType) {
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.create(
entityFactory.targetType().create().name("a").description(RandomStringUtils.randomAlphanumeric(TargetType.DESCRIPTION_MAX_SIZE + 1))))
.as("targetType with too long description should not be created");
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> targetTypeManagement.create(entityFactory.targetType().create().name("a").description(INVALID_TEXT_HTML)))
.as("targetType with invalid description should not be created");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.update(
entityFactory.targetType().update(targetType.getId()).description(RandomStringUtils.randomAlphanumeric(TargetType.DESCRIPTION_MAX_SIZE + 1))))
.as("targetType with too long description should not be updated");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement
.update(entityFactory.targetType().update(targetType.getId()).description(INVALID_TEXT_HTML)))
.as("targetType with invalid description should not be updated");
}
@Step
private void createAndUpdateTargetTypeWithInvalidColour(final TargetType targetType) {
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.create(
entityFactory.targetType().create().name("a").colour(RandomStringUtils.randomAlphanumeric(TargetType.COLOUR_MAX_SIZE + 1))))
.as("targetType with too long colour should not be created");
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> targetTypeManagement.create(entityFactory.targetType().create().name("a").colour(INVALID_TEXT_HTML)))
.as("targetType with invalid colour should not be created");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.update(
entityFactory.targetType().update(targetType.getId()).colour(RandomStringUtils.randomAlphanumeric(TargetType.COLOUR_MAX_SIZE + 1))))
.as("targetType with too long colour should not be updated");
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> targetTypeManagement.update(entityFactory.targetType().update(targetType.getId()).colour(INVALID_TEXT_HTML)))
.as("targetType with invalid colour should not be updated");
}
@Step
private void createAndUpdateTargetTypeWithInvalidName(final TargetType targetType) {
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement
.create(entityFactory.targetType().create().name(RandomStringUtils.randomAlphanumeric(
NamedEntity.NAME_MAX_SIZE + 1))))
.as("targetType with too long name should not be created");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.create(entityFactory.targetType().create().name(INVALID_TEXT_HTML)))
.as("targetType with invalid name should not be created");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement
.update(entityFactory.targetType().update(targetType.getId()).name(RandomStringUtils.randomAlphanumeric(
NamedEntity.NAME_MAX_SIZE + 1))))
.as("targetType with too long name should not be updated");
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> targetTypeManagement.update(entityFactory.targetType().update(targetType.getId()).name(INVALID_TEXT_HTML)))
.as("targetType with invalid name should not be updated");
assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> targetTypeManagement.update(entityFactory.targetType().update(targetType.getId()).name("")))
.as("targetType with too short name should not be updated");
}
@Test
@Description("Tests the successful assignment of compatible distribution set types to a target type")
public void assignCompatibleDistributionSetTypesToTargetType(){
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype1").description("targettypedes1"));
DistributionSetType distributionSetType = testdataFactory.findOrCreateDistributionSetType("testDst", "dst1");
targetTypeManagement.assignCompatibleDistributionSetTypes(targetType.getId(), Collections.singletonList(distributionSetType.getId()));
Optional<JpaTargetType> targetTypeWithDsTypes = targetTypeRepository.findById(targetType.getId());
assertThat(targetTypeWithDsTypes).isPresent();
assertThat(targetTypeWithDsTypes.get().getCompatibleDistributionSetTypes()).extracting("key").contains("testDst");
}
@Test
@Description("Tests the successful removal of compatible distribution set types to a target type")
public void unassignCompatibleDistributionSetTypesToTargetType(){
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype11").description("targettypedes11"));
DistributionSetType distributionSetType = testdataFactory.findOrCreateDistributionSetType("testDst1", "dst11");
targetTypeManagement.assignCompatibleDistributionSetTypes(targetType.getId(), Collections.singletonList(distributionSetType.getId()));
Optional<JpaTargetType> targetTypeWithDsTypes = targetTypeRepository.findById(targetType.getId());
assertThat(targetTypeWithDsTypes).isPresent();
assertThat(targetTypeWithDsTypes.get().getCompatibleDistributionSetTypes()).extracting("key").contains("testDst1");
targetTypeManagement.unassignDistributionSetType(targetType.getId(),distributionSetType.getId());
Optional<JpaTargetType> targetTypeWithDsTypes1 = targetTypeRepository.findById(targetType.getId());
assertThat(targetTypeWithDsTypes1).isPresent();
assertThat(targetTypeWithDsTypes1.get().getCompatibleDistributionSetTypes()).hasSize(0);
}
@Test
@Description("Ensures that all types are retrieved through repository.")
public void findAllTargetTypes() {
testdataFactory.createTargetTypes("targettype", 10);
assertThat(targetTypeRepository.findAll()).as("Target type size").hasSize(10);
}
@Test
@Description("Ensures that a created target type is persisted in the repository as defined.")
public void createTargetType() {
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype1").description("targettypedes1").colour("colour1"));
assertThat(targetTypeRepository.findByName("targettype1").get().getDescription()).as("type found")
.isEqualTo("targettypedes1");
assertThat(targetTypeManagement.getByName("targettype1").get().getColour()).as("type found").isEqualTo("colour1");
assertThat(targetTypeManagement.get(targetType.getId()).get().getColour()).as("type found").isEqualTo("colour1");
}
@Test
@Description("Ensures that a deleted target type is removed from the repository as defined.")
public void deleteTargetType() {
// create test data
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype11").description("targettypedes11"));
assertThat(targetTypeRepository.findByName("targettype11").get().getDescription()).as("type found")
.isEqualTo("targettypedes11");
targetTypeManagement.delete(targetType.getId());
assertThat(targetTypeRepository.findById(targetType.getId())).as("No target type should be found").isNotPresent();
}
@Test
@Description("Tests the name update of a target type.")
public void updateTargetType() {
final TargetType targetType = targetTypeManagement
.create(entityFactory.targetType().create().name("targettype111").description("targettypedes111"));
assertThat(targetTypeRepository.findByName("targettype111").get().getDescription()).as("type found")
.isEqualTo("targettypedes111");
targetTypeManagement.update(entityFactory.targetType().update(targetType.getId()).name("updatedtargettype111"));
assertThat(targetTypeRepository.findByName("updatedtargettype111")).as("Updated target type should be found").isPresent();
}
@Test
@Description("Ensures that a target type cannot be created if one exists already with that name (expects EntityAlreadyExistsException).")
public void failedDuplicateTargetTypeNameException() {
targetTypeManagement.create(entityFactory.targetType().create().name("targettype123"));
assertThrows(EntityAlreadyExistsException.class, () -> targetTypeManagement.create(entityFactory.targetType().create().name("targettype123")));
}
@Test
@Description("Ensures that a target type cannot be updated to a name that already exists (expects EntityAlreadyExistsException).")
public void failedDuplicateTargetTypeNameExceptionAfterUpdate() {
targetTypeManagement.create(entityFactory.targetType().create().name("targettype1234"));
TargetType targetType = targetTypeManagement.create(entityFactory.targetType().create().name("targettype12345"));
assertThrows(EntityAlreadyExistsException.class, () -> targetTypeManagement.update(entityFactory.targetType().update(targetType.getId()).name("targettype1234")));
}
}

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.event;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -20,10 +21,13 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.event.RepositoryEntityEventTest.RepositoryTestConfiguration;
@@ -31,6 +35,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -88,6 +93,38 @@ public class RepositoryEntityEventTest extends AbstractJpaIntegrationTest {
assertThat(targetDeletedEvent.getEntityId()).isEqualTo(createdTarget.getId());
}
@Test
@Description("Verifies that the target type created event is published when a target type has been created")
public void targetTypeCreatedEventIsPublished() throws InterruptedException {
TargetType createdTargetType = testdataFactory.findOrCreateTargetType("targettype");
TargetTypeCreatedEvent targetTypeCreatedEvent = eventListener.waitForEvent(TargetTypeCreatedEvent.class);
assertThat(targetTypeCreatedEvent).isNotNull();
assertThat(targetTypeCreatedEvent.getEntity().getId()).isEqualTo(createdTargetType.getId());
}
@Test
@Description("Verifies that the target type updated event is published when a target type has been updated")
public void targetTypeUpdatedEventIsPublished() throws InterruptedException {
TargetType createdTargetType = testdataFactory.findOrCreateTargetType("targettype");
targetTypeManagement.update(entityFactory.targetType().update(createdTargetType.getId()).name("updatedtargettype"));
TargetTypeUpdatedEvent targetTypeUpdatedEvent = eventListener.waitForEvent(TargetTypeUpdatedEvent.class);
assertThat(targetTypeUpdatedEvent).isNotNull();
assertThat(targetTypeUpdatedEvent.getEntity().getId()).isEqualTo(createdTargetType.getId());
}
@Test
@Description("Verifies that the target type deleted event is published when a target type has been deleted")
public void targetTypeDeletedEventIsPublished() throws InterruptedException {
TargetType createdTargetType = testdataFactory.findOrCreateTargetType("targettype");
targetTypeManagement.delete(createdTargetType.getId());
TargetTypeDeletedEvent targetTypeDeletedEvent = eventListener.waitForEvent(TargetTypeDeletedEvent.class);
assertThat(targetTypeDeletedEvent).isNotNull();
assertThat(targetTypeDeletedEvent.getEntityId()).isEqualTo(createdTargetType.getId());
}
@Test
@Description("Verifies that the rollout deleted event is published when a rollout has been deleted")
public void rolloutDeletedEventIsPublished() throws InterruptedException {

View File

@@ -44,6 +44,7 @@ import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -144,6 +145,9 @@ public abstract class AbstractIntegrationTest {
@Autowired
protected TargetManagement targetManagement;
@Autowired
protected TargetTypeManagement targetTypeManagement;
@Autowired
protected TargetFilterQueryManagement targetFilterQueryManagement;

View File

@@ -63,7 +63,7 @@ public class JpaTestRepositoryManagement {
return null;
});
} catch (final Exception e) {
LOGGER.error("Error hile delete tenant", e);
LOGGER.error("Error while delete tenant", e);
}
});
}

View File

@@ -37,8 +37,10 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.TagCreate;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -61,6 +63,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
@@ -123,6 +126,8 @@ public class TestdataFactory {
*/
public static final String SM_TYPE_APP = "application";
public static final String DEFAULT_COLOUR = "#000000";
@Autowired
private ControllerManagement controllerManagament;
@@ -141,6 +146,9 @@ public class TestdataFactory {
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetTypeManagement targetTypeManagement;
@Autowired
private DeploymentManagement deploymentManagement;
@@ -638,6 +646,22 @@ public class TestdataFactory {
return target;
}
/**
* @param controllerId
* of the target
* @param targetName
* name of the target
* @param targetTypeId
* target type id
* @return persisted {@link Target}
*/
public Target createTarget(final String controllerId, final String targetName, final Long targetTypeId) {
final Target target = targetManagement
.create(entityFactory.target().create().controllerId(controllerId).name(targetName).targetType(targetTypeId));
assertTargetProperlyCreated(target);
return target;
}
private void assertTargetProperlyCreated(final Target target) {
assertThat(target.getCreatedBy()).isNotNull();
assertThat(target.getCreatedAt()).isNotNull();
@@ -1160,4 +1184,54 @@ public class TestdataFactory {
rolloutManagement.handleRollouts();
return newRollout;
}
/**
* Finds {@link TargetType} in repository with given
* {@link TargetType#getName()} or creates if it does not exist yet.
* No ds types are assigned on creation.
*
* @param targetTypeName
* {@link TargetType#getName()}
*
* @return persisted {@link TargetType}
*/
public TargetType findOrCreateTargetType(final String targetTypeName) {
return targetTypeManagement.getByName(targetTypeName)
.orElseGet(() -> targetTypeManagement.create(entityFactory.targetType().create()
.name(targetTypeName).description(targetTypeName + " description").colour(DEFAULT_COLOUR)));
}
/**
* Creates {@link TargetType} in repository with given
* {@link TargetType#getName()}. Compatible distribution set types are assigned on creation
*
* @param targetTypeName
* {@link TargetType#getName()}
*
* @return persisted {@link TargetType}
*/
public TargetType createTargetType(final String targetTypeName, List<DistributionSetType> compatibleDsTypes) {
return targetTypeManagement.create(entityFactory.targetType().create()
.name(targetTypeName).description(targetTypeName + " description").colour(DEFAULT_COLOUR)
.compatible(compatibleDsTypes.stream().map(DistributionSetType::getId).collect(Collectors.toList())));
}
/**
* Creates {@link TargetType} in repository with given
* {@link TargetType#getName()}. No ds types are assigned on creation.
*
* @param targetTypePrefix
* {@link TargetType#getName()}
*
* @return persisted {@link TargetType}
*/
public List<TargetType> createTargetTypes(final String targetTypePrefix, int count) {
final List<TargetTypeCreate> result = Lists.newArrayListWithExpectedSize(count);
for (int i = 0; i < count; i++) {
result.add(entityFactory.targetType().create().name(targetTypePrefix + i).description(targetTypePrefix + " description")
.colour(DEFAULT_COLOUR));
}
return targetTypeManagement.create(result);
}
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.json.model.distributionsettype;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
/**
* Request Body of DistributionSetType for assignment operations (ID only).
*
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtDistributionSetTypeAssignment extends MgmtId {
}

View File

@@ -49,6 +49,24 @@ public class MgmtTarget extends MgmtNamedEntity {
@JsonProperty
private boolean requestAttributes;
@JsonProperty
private Long targetType;
/**
* @return Target type ID
*/
public Long getTargetType() {
return targetType;
}
/**
* @param targetType
* Target type ID
*/
public void setTargetType(Long targetType) {
this.targetType = targetType;
}
/**
* @return the controllerId
*/
@@ -150,6 +168,9 @@ public class MgmtTarget extends MgmtNamedEntity {
return securityToken;
}
/**
* @return Address
*/
public String getAddress() {
return address;
}
@@ -171,6 +192,9 @@ public class MgmtTarget extends MgmtNamedEntity {
this.securityToken = securityToken;
}
/**
* @return boolean true or false
*/
public boolean isRequestAttributes() {
return requestAttributes;
}

View File

@@ -27,10 +27,34 @@ public class MgmtTargetRequestBody {
@JsonProperty
private Boolean requestAttributes;
@JsonProperty
private Long targetType;
/**
* @return Target type ID
*/
public Long getTargetType() {
return targetType;
}
/**
* @param targetType
* Target type ID
*/
public void setTargetType(Long targetType) {
this.targetType = targetType;
}
/**
* @return token
*/
public String getSecurityToken() {
return securityToken;
}
/**
* @param securityToken Token
*/
public void setSecurityToken(final String securityToken) {
this.securityToken = securityToken;
}
@@ -83,18 +107,32 @@ public class MgmtTargetRequestBody {
return this;
}
/**
* @return address
*/
public String getAddress() {
return address;
}
/**
* @param address
* Address
*/
public void setAddress(final String address) {
this.address = address;
}
/**
* @return boolean true or false
*/
public Boolean isRequestAttributes() {
return requestAttributes;
}
/**
* @param requestAttributes
* Attributes
*/
public void setRequestAttributes(final Boolean requestAttributes) {
this.requestAttributes = requestAttributes;
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.json.model.targettype;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
/**
* A json annotated rest model for TargetType to RESTful API
* representation.
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtTargetType extends MgmtNamedEntity {
@JsonProperty(value = "id", required = true)
private Long typeId;
@JsonProperty
private String colour;
/**
* @return target type ID
*/
public Long getTypeId() {
return typeId;
}
/**
* @param typeId
* Target type ID
*/
public void setTypeId(final Long typeId) {
this.typeId = typeId;
}
/**
*
* @return the color in format #000000
*/
public String getColour() {
return colour;
}
/**
* @param colour
* in format #000000
*/
public void setColour(String colour) {
this.colour = colour;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.json.model.targettype;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeAssignment;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeAssigment;
/**
* Request Body for TargetType POST.
*
*/
public class MgmtTargetTypeRequestBodyPost extends MgmtTargetTypeRequestBodyPut{
@JsonProperty
private List<MgmtDistributionSetTypeAssignment> compatibledistributionsettypes;
/**
* @param name
* the name to set
* @return post request body
*/
@Override
public MgmtTargetTypeRequestBodyPost setName(final String name) {
super.setName(name);
return this;
}
@Override
public MgmtTargetTypeRequestBodyPost setDescription(final String description) {
super.setDescription(description);
return this;
}
@Override
public MgmtTargetTypeRequestBodyPost setColour(final String colour) {
super.setColour(colour);
return this;
}
/**
* @return the compatible ds types
*/
public List<MgmtDistributionSetTypeAssignment> getCompatibleDsTypes() {
return compatibledistributionsettypes;
}
/**
* @param compatibleDsTypes
* the compatible ds types to set
*
* @return updated body
*/
public MgmtTargetTypeRequestBodyPost setCompatibleDsTypes(
final List<MgmtDistributionSetTypeAssignment> compatibleDsTypes) {
this.compatibledistributionsettypes = compatibleDsTypes;
return this;
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.json.model.targettype;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Request Body for TargetType PUT.
*
*/
public class MgmtTargetTypeRequestBodyPut {
@JsonProperty(required = true)
private String name;
@JsonProperty
private String description;
@JsonProperty
private String colour;
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name
* the name to set
*
* @return updated body
*/
public MgmtTargetTypeRequestBodyPut setName(final String name) {
this.name = name;
return this;
}
/**
* @return description
*/
public String getDescription() {
return description;
}
/**
* @param description
* Description
* @return Updated body
*/
public MgmtTargetTypeRequestBodyPut setDescription(final String description) {
this.description = description;
return this;
}
/**
* @return Colour
*/
public String getColour() {
return colour;
}
/**
* @param colour
* Colour
* @return Updated body
*/
public MgmtTargetTypeRequestBodyPut setColour(final String colour) {
this.colour = colour;
return this;
}
}

View File

@@ -60,6 +60,11 @@ public final class MgmtRestConstants {
public static final String SYSTEM_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + BASE_SYSTEM_MAPPING;
/**
* The target URL mapping, href link for assigned target type.
*/
public static final String TARGET_V1_ASSIGNED_TARGET_TYPE= "targetType";
/**
* The target URL mapping, href link for assigned distribution set.
*/
@@ -99,6 +104,21 @@ public final class MgmtRestConstants {
*/
public static final String TARGET_TAG_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targettags";
/**
* The target URL mapping rest resource.
*/
public static final String TARGET_TARGET_TYPE_V1_REQUEST_MAPPING = "/{targetId}/targettype";
/**
* The target type URL mapping rest resource.
*/
public static final String TARGETTYPE_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targettypes";
/**
* The target type URL mapping rest resource.
*/
public static final String TARGETTYPE_V1_DS_TYPES = "compatibledistributionsettypes";
/**
* The tag URL mapping rest resource.
*

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.mgmt.rest.api;
import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
@@ -123,6 +124,31 @@ public interface MgmtTargetRestApi {
@DeleteMapping(value = "/{targetId}")
ResponseEntity<Void> deleteTarget(@PathVariable("targetId") String targetId);
/**
* Handles the DELETE (unassign) request of a target type.
*
* @param targetId
* the ID of the target
* @return If the given targetId could exists and could be unassign Http OK.
* In any failure the JsonResponseExceptionHandler is handling the
* response.
*/
@DeleteMapping(value = MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING)
ResponseEntity<Void> unassignTargetType(@PathVariable("targetId") String targetId);
/**
* Handles the POST (assign) request of a target type.
*
* @param targetId
* the ID of the target
* @return If the given targetId could exists and could be assign Http OK.
* In any failure the JsonResponseExceptionHandler is handling the
* response.
*/
@PostMapping(value = MgmtRestConstants.TARGET_TARGET_TYPE_V1_REQUEST_MAPPING, consumes = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> assignTargetType(@PathVariable("targetId") String targetId, MgmtId targetTypeId);
/**
* Handles the GET request of retrieving the attributes of a specific
* target.

View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.rest.api;
import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetTypeRequestBodyPut;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* REST Resource handling for TargetType CRUD operations.
*
*/
@RequestMapping(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING)
public interface MgmtTargetTypeRestApi {
/**
* Handles the GET request of retrieving all TargetTypes.
*
* @param pagingOffsetParam
* the offset of list of target types for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the rest
* request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
*
* @return a list of all TargetTypes for a defined or default page request with
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@GetMapping(produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<PagedList<MgmtTargetType>> getTargetTypes(
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam);
/**
* Handles the GET request of retrieving a single TargetType.
*
* @param targetTypeId
* the ID of the target type to retrieve
*
* @return a single target type with status OK.
*/
@GetMapping(value = "/{targetTypeId}", produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTargetType> getTargetType(@PathVariable("targetTypeId") Long targetTypeId);
/**
* Handles the DELETE request for a single Target Type.
*
* @param targetTypeId
* the ID of the target type to retrieve
* @return status OK if delete is successful.
*
*/
@DeleteMapping(value = "/{targetTypeId}")
ResponseEntity<Void> deleteTargetType(@PathVariable("targetTypeId") Long targetTypeId);
/**
* Handles the PUT request of updating a Target Type.
*
* @param targetTypeId
* the ID of the target type in the URL
* @param restTargetType
* the target type to be updated.
* @return status OK if update is successful
*/
@PutMapping(value = "/{targetTypeId}", consumes = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<MgmtTargetType> updateTargetType(@PathVariable("targetTypeId") Long targetTypeId,
MgmtTargetTypeRequestBodyPut restTargetType);
/**
* Handles the POST request of creating new Target Types. The request body must
* always be a list of types.
*
* @param targetTypes
* the target types to be created.
* @return In case all target types could be successfully created the
* ResponseEntity with status code 201 - Created but without
* ResponseBody. In any failure the JsonResponseExceptionHandler is
* handling the response.
*/
@PostMapping(consumes = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtTargetType>> createTargetTypes(List<MgmtTargetTypeRequestBodyPost> targetTypes);
/**
* Handles the GET request of retrieving the list of compatible distribution set
* types in that target type.
*
* @param targetTypeId
* of the TargetType.
* @return Unpaged list of distribution set types and OK in case of success.
*/
@GetMapping(value = "/{targetTypeId}/" + MgmtRestConstants.TARGETTYPE_V1_DS_TYPES, produces = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtDistributionSetType>> getCompatibleDistributionSets(
@PathVariable("targetTypeId") Long targetTypeId);
/**
* Handles DELETE request for removing the compatibility of a distribution set
* type from the target type.
*
* @param targetTypeId
* of the TargetType.
* @param distributionSetTypeId
* of the DistributionSetType.
*
* @return OK if the request was successful
*/
@DeleteMapping(value = "/{targetTypeId}/" + MgmtRestConstants.TARGETTYPE_V1_DS_TYPES + "/{distributionSetTypeId}")
ResponseEntity<Void> removeCompatibleDistributionSet(@PathVariable("targetTypeId") Long targetTypeId,
@PathVariable("distributionSetTypeId") Long distributionSetTypeId);
/**
* Handles the POST request for adding the compatibility of a distribution set
* type to a target type.
*
* @param targetTypeId
* of the TargetType.
* @param distributionSetTypeIds
* of the DistributionSetTypes as a List.
*
* @return OK if the request was successful
*/
@PostMapping(value = "/{targetTypeId}/" + MgmtRestConstants.TARGETTYPE_V1_DS_TYPES, consumes = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> addCompatibleDistributionSets(@PathVariable("targetTypeId") final Long targetTypeId,
final List<MgmtDistributionSetTypeAssignment> distributionSetTypeIds);
}

View File

@@ -67,7 +67,7 @@ final class MgmtDistributionSetTypeMapper {
.orElse(Collections.emptyList());
}
static List<MgmtDistributionSetType> toListResponse(final List<DistributionSetType> types) {
static List<MgmtDistributionSetType> toListResponse(final Collection<DistributionSetType> types) {
if (types == null) {
return Collections.emptyList();
}

View File

@@ -17,6 +17,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow;
@@ -30,6 +31,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTypeRestApi;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -79,6 +81,10 @@ public final class MgmtTargetMapper {
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getMetadata(response.getControllerId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata"));
if (response.getTargetType() != null) {
response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class)
.getTargetType(response.getTargetType())).withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE));
}
}
static void addPollStatus(final Target target, final MgmtTarget targetRest) {
@@ -153,6 +159,9 @@ public final class MgmtTargetMapper {
if (installationDate != null) {
targetRest.setInstalledAt(installationDate);
}
if (target.getTargetType() != null){
targetRest.setTargetType(target.getTargetType().getId());
}
targetRest.add(linkTo(methodOn(MgmtTargetRestApi.class).getTarget(target.getControllerId())).withSelfRel());
@@ -172,7 +181,7 @@ public final class MgmtTargetMapper {
private static TargetCreate fromRequest(final EntityFactory entityFactory, final MgmtTargetRequestBody targetRest) {
return entityFactory.target().create().controllerId(targetRest.getControllerId()).name(targetRest.getName())
.description(targetRest.getDescription()).securityToken(targetRest.getSecurityToken())
.address(targetRest.getAddress());
.address(targetRest.getAddress()).targetType(targetRest.getTargetType());
}
static List<MetaData> fromRequestTargetMetadata(final List<MgmtMetadata> metadata,

View File

@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.ValidationException;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
@@ -140,7 +141,8 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
final Target updateTarget = this.targetManagement.update(entityFactory.target().update(targetId)
.name(targetRest.getName()).description(targetRest.getDescription()).address(targetRest.getAddress())
.securityToken(targetRest.getSecurityToken()).requestAttributes(targetRest.isRequestAttributes()));
.targetType(targetRest.getTargetType()).securityToken(targetRest.getSecurityToken())
.requestAttributes(targetRest.isRequestAttributes()));
final MgmtTarget response = MgmtTargetMapper.toResponse(updateTarget);
MgmtTargetMapper.addPollStatus(updateTarget, response);
@@ -156,6 +158,18 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignTargetType(@PathVariable("targetId") final String targetId) {
this.targetManagement.unAssignType(targetId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> assignTargetType(@PathVariable("targetId") final String targetId, @RequestBody final MgmtId targetTypeId) {
this.targetManagement.assignType(targetId, targetTypeId.getId());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("targetId") final String targetId) {
final Map<String, String> controllerAttributes = targetManagement.getControllerAttributes(targetId);

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.rest.resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTypeRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.rest.data.ResponseList;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*/
public final class MgmtTargetTypeMapper {
// private constructor, utility class
private MgmtTargetTypeMapper() {
}
static List<TargetTypeCreate> targetFromRequest(final EntityFactory entityFactory,
final Collection<MgmtTargetTypeRequestBodyPost> targetTypesRest) {
if (targetTypesRest == null) {
return Collections.emptyList();
}
return targetTypesRest.stream().map(targetRest -> fromRequest(entityFactory, targetRest)).collect(Collectors.toList());
}
private static TargetTypeCreate fromRequest(final EntityFactory entityFactory,
final MgmtTargetTypeRequestBodyPost targetTypesRest) {
return entityFactory.targetType().create().name(targetTypesRest.getName()).description(targetTypesRest.getDescription())
.colour(targetTypesRest.getColour()).compatible(getDistributionSets(targetTypesRest));
}
private static Collection<Long> getDistributionSets(final MgmtTargetTypeRequestBodyPost targetTypesRest) {
return Optional.ofNullable(targetTypesRest.getCompatibleDsTypes())
.map(ds -> ds.stream().map(MgmtDistributionSetTypeAssignment::getId).collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
static List<MgmtTargetType> toListResponse(final List<TargetType> types) {
if (types == null) {
return Collections.emptyList();
}
return new ResponseList<>(types.stream().map(MgmtTargetTypeMapper::toResponse).collect(Collectors.toList()));
}
static MgmtTargetType toResponse(final TargetType type) {
final MgmtTargetType result = new MgmtTargetType();
MgmtRestModelMapper.mapNamedToNamed(result, type);
result.setTypeId(type.getId());
result.setColour(type.getColour());
result.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(result.getTypeId())).withSelfRel());
return result;
}
static void addLinks(final MgmtTargetType result) {
result.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getCompatibleDistributionSets(result.getTypeId()))
.withRel(MgmtRestConstants.TARGETTYPE_V1_DS_TYPES));
}
}

View File

@@ -0,0 +1,148 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.rest.resource;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetType;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.targettype.MgmtTargetTypeRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTypeRestApi;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling for {@link TargetType} CRUD operations.
*/
@RestController
public class MgmtTargetTypeResource implements MgmtTargetTypeRestApi {
private static final Logger LOG = LoggerFactory.getLogger(MgmtTargetTypeResource.class);
private final TargetTypeManagement targetTypeManagement;
private final EntityFactory entityFactory;
public MgmtTargetTypeResource(TargetTypeManagement targetTypeManagement, final EntityFactory entityFactory) {
this.targetTypeManagement = targetTypeManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<PagedList<MgmtTargetType>> getTargetTypes(
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<TargetType> findTargetTypesAll;
long countTargetTypesAll;
if (rsqlParam != null) {
findTargetTypesAll= targetTypeManagement.findByRsql(pageable, rsqlParam);
} else {
findTargetTypesAll = targetTypeManagement.findAll(pageable);
}
countTargetTypesAll = findTargetTypesAll.getTotalElements();
final List<MgmtTargetType> rest = MgmtTargetTypeMapper.toListResponse(findTargetTypesAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countTargetTypesAll));
}
@Override
public ResponseEntity<MgmtTargetType> getTargetType(@PathVariable("targetTypeId") final Long targetTypeId) {
final TargetType foundType = findTargetTypeWithExceptionIfNotFound(targetTypeId);
final MgmtTargetType response = MgmtTargetTypeMapper.toResponse(foundType);
MgmtTargetTypeMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteTargetType(@PathVariable("targetTypeId") final Long targetTypeId) {
LOG.debug("Delete {} target type", targetTypeId);
targetTypeManagement.delete(targetTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtTargetType> updateTargetType(@PathVariable("targetTypeId") final Long targetTypeId,
@RequestBody final MgmtTargetTypeRequestBodyPut restTargetType) {
final TargetType updated = targetTypeManagement
.update(entityFactory.targetType().update(targetTypeId).name(restTargetType.getName())
.description(restTargetType.getDescription()).colour(restTargetType.getColour()));
final MgmtTargetType response = MgmtTargetTypeMapper.toResponse(updated);
MgmtTargetTypeMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtTargetType>> createTargetTypes(
@RequestBody final List<MgmtTargetTypeRequestBodyPost> targetTypes) {
final List<TargetType> createdTargetTypes = targetTypeManagement
.create(MgmtTargetTypeMapper.targetFromRequest(entityFactory, targetTypes));
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtTargetTypeMapper.toListResponse(createdTargetTypes));
}
@Override
public ResponseEntity<List<MgmtDistributionSetType>> getCompatibleDistributionSets(
@PathVariable("targetTypeId") final Long targetTypeId) {
final TargetType foundType = findTargetTypeWithExceptionIfNotFound(targetTypeId);
return ResponseEntity
.ok(MgmtDistributionSetTypeMapper.toListResponse(foundType.getCompatibleDistributionSetTypes()));
}
@Override
public ResponseEntity<Void> removeCompatibleDistributionSet(@PathVariable("targetTypeId") final Long targetTypeId,
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
targetTypeManagement.unassignDistributionSetType(targetTypeId, distributionSetTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> addCompatibleDistributionSets(@PathVariable("targetTypeId") final Long targetTypeId,
@RequestBody final List<MgmtDistributionSetTypeAssignment> distributionSetTypeIds) {
targetTypeManagement.assignCompatibleDistributionSetTypes(targetTypeId,
distributionSetTypeIds.stream().map(MgmtDistributionSetTypeAssignment::getId).collect(Collectors.toList()));
return ResponseEntity.ok().build();
}
private TargetType findTargetTypeWithExceptionIfNotFound(final Long targetTypeId) {
return targetTypeManagement.get(targetTypeId)
.orElseThrow(() -> new EntityNotFoundException(TargetType.class, targetTypeId));
}
}

View File

@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.TagFields;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetTypeFields;
import org.eclipse.hawkbit.rest.util.SortUtility;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
@@ -61,6 +62,14 @@ public final class PagingUtility {
return Sort.by(SortUtility.parse(TargetFields.class, sortParam));
}
static Sort sanitizeTargetTypeSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, TargetTypeFields.ID.getFieldName());
}
return Sort.by(SortUtility.parse(TargetFields.class, sortParam));
}
static Sort sanitizeTagSortParam(final String sortParam) {
if (sortParam == null) {
// default

View File

@@ -33,6 +33,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
@@ -56,6 +57,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
@@ -63,6 +65,7 @@ import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.eclipse.hawkbit.util.IpUtil;
import org.hamcrest.Matchers;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
@@ -105,6 +108,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
private static final String JSON_PATH_FIELD_SIZE = ".size";
private static final String JSON_PATH_FIELD_TOTAL = ".total";
private static final String JSON_PATH_FIELD_LAST_REQUEST_AT = ".lastControllerRequestAt";
private static final String JSON_PATH_FIELD_TARGET_TYPE = ".targetType";
// target
// $.field
@@ -117,6 +121,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
private static final String JSON_PATH_CONTROLLERID = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTROLLERID;
private static final String JSON_PATH_DESCRIPTION = JSON_PATH_ROOT + JSON_PATH_FIELD_DESCRIPTION;
private static final String JSON_PATH_LAST_REQUEST_AT = JSON_PATH_ROOT + JSON_PATH_FIELD_LAST_REQUEST_AT;
private static final String JSON_PATH_TYPE = JSON_PATH_ROOT + JSON_PATH_FIELD_TARGET_TYPE;
@Autowired
private JpaProperties jpaProperties;
@@ -2075,4 +2080,192 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
"$._links.rollout.href", containsString("/rest/v1/rollouts/" + rollout.getId().intValue())));
}
@Test
@Description("Ensures that a post request for creating targets with target type works.")
public void createTargetsWithTargetType() throws Exception {
final TargetType type1 = testdataFactory.createTargetType("typeWithDs", Collections.singletonList(standardDsType));
final TargetType type2 = testdataFactory.createTargetType("typeWithOutDs", Collections.singletonList(standardDsType));
final Target test1 = entityFactory.target().create().controllerId("id1").name("targetWithoutType")
.securityToken("token").address("amqp://test123/foobar").description("testid1").build();
final Target test2 = entityFactory.target().create().controllerId("id2").name("targetOfType1")
.targetType(type1.getId()).description("testid2").build();
final Target test3 = entityFactory.target().create().controllerId("id3").name("targetOfType2")
.targetType(type2.getId()).description("testid3").build();
final String hrefType1 = "http://localhost/rest/v1/targettypes/" + type1.getId();
final List<Target> targets = Arrays.asList(test1, test2, test3);
final MvcResult mvcPostResult = mvc
.perform(post("/rest/v1/targets/").content(JsonBuilder.targets(targets, true))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("[0].name", equalTo("targetWithoutType")))
.andExpect(jsonPath("[0].controllerId", equalTo("id1")))
.andExpect(jsonPath("[0].description", equalTo("testid1")))
.andExpect(jsonPath("[0].createdAt", not(equalTo(0))))
.andExpect(jsonPath("[0].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[0].securityToken", equalTo("token")))
.andExpect(jsonPath("[0].address", equalTo("amqp://test123/foobar")))
.andExpect(jsonPath("[0].targetType").doesNotExist())
.andExpect(jsonPath("[1].name", equalTo("targetOfType1")))
.andExpect(jsonPath("[1].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[1].controllerId", equalTo("id2")))
.andExpect(jsonPath("[1].description", equalTo("testid2")))
.andExpect(jsonPath("[1].createdAt", not(equalTo(0))))
.andExpect(jsonPath("[1].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[1].targetType", equalTo(type1.getId().intValue())))
.andExpect(jsonPath("[2].name", equalTo("targetOfType2")))
.andExpect(jsonPath("[2].controllerId", equalTo("id3")))
.andExpect(jsonPath("[2].description", equalTo("testid3")))
.andExpect(jsonPath("[2].createdAt", not(equalTo(0))))
.andExpect(jsonPath("[2].createdBy", equalTo("bumlux")))
.andExpect(jsonPath("[2].targetType", equalTo(type2.getId().intValue())))
.andReturn();
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + test2.getControllerId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(JSON_PATH_NAME, equalTo("targetOfType1")))
.andExpect(jsonPath(JSON_PATH_CONTROLLERID, equalTo("id2")))
.andExpect(jsonPath(JSON_PATH_TYPE, equalTo(type1.getId().intValue())))
.andExpect(jsonPath(JSON_PATH_DESCRIPTION, equalTo("testid2")))
.andExpect(jsonPath("$._links.targetType.href", equalTo(hrefType1))).andReturn();
assertThat(targetManagement.getByControllerID("id1")).isNotNull();
assertThat(targetManagement.getByControllerID("id1").get().getName()).isEqualTo("targetWithoutType");
assertThat(targetManagement.getByControllerID("id1").get().getDescription()).isEqualTo("testid1");
assertThat(targetManagement.getByControllerID("id1").get().getTargetType()).isNull();
assertThat(targetManagement.getByControllerID("id1").get().getSecurityToken()).isEqualTo("token");
assertThat(targetManagement.getByControllerID("id1").get().getAddress().toString())
.isEqualTo("amqp://test123/foobar");
assertThat(targetManagement.getByControllerID("id2")).isNotNull();
assertThat(targetManagement.getByControllerID("id2").get().getName()).isEqualTo("targetOfType1");
assertThat(targetManagement.getByControllerID("id2").get().getDescription()).isEqualTo("testid2");
assertThat(targetManagement.getByControllerID("id2").get().getTargetType().getName()).isEqualTo("typeWithDs");
assertThat(targetManagement.getByControllerID("id3")).isNotNull();
assertThat(targetManagement.getByControllerID("id3").get().getName()).isEqualTo("targetOfType2");
assertThat(targetManagement.getByControllerID("id3").get().getDescription()).isEqualTo("testid3");
assertThat(targetManagement.getByControllerID("id3").get().getTargetType().getName()).isEqualTo("typeWithOutDs");
}
@Test
@Description("Ensures that a post request for creating target with target type works.")
public void createTargetWithExistingTargetType() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",1);
assertThat(targetTypes).hasSize(1);
final Target target = entityFactory.target().create().controllerId("targetcontroller").name("testtarget").targetType(targetTypes.get(0).getId()).build();
final String targetList = JsonBuilder.targets(Collections.singletonList(target), false);
// test query target over rest resource
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING).content(targetList)
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isCreated())
.andExpect(jsonPath("[0].controllerId", equalTo("targetcontroller")))
.andExpect(jsonPath("[0].targetType", equalTo(targetTypes.get(0).getId().intValue())));
assertThat(targetManagement.getByControllerID("targetcontroller").get().getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
}
@Test
@Description("Ensures that a put request for updating targets with target type works.")
public void updateTargetTypeInTarget() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",2);
assertThat(targetTypes).hasSize(2);
String controllerId = "targetcontroller";
Target target = testdataFactory.createTarget(controllerId, "testtarget", targetTypes.get(0).getId());
assertThat(target).isNotNull();
assertThat(target.getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
// update target over rest resource
final String body = new JSONObject().put("targetType", targetTypes.get(1).getId().intValue()).toString();
mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + controllerId).content(body)
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(jsonPath("controllerId", equalTo(controllerId)))
.andExpect(jsonPath("targetType", equalTo(targetTypes.get(1).getId().intValue())));
}
@Test
@Description("Ensures that a post request for creating targets with unknown target type fails.")
public void addingNonExistingTargetTypeInTargetShouldFail() throws Exception {
long unknownTargetTypeId = 999;
String errorMsg = String.format("TargetType with given identifier {%s} does not exist.", unknownTargetTypeId);
Optional<TargetType> targetType = targetTypeManagement.get(unknownTargetTypeId);
assertThat(targetType).isNotPresent();
String controllerId = "targetcontroller";
final Target target = entityFactory.target().create().controllerId(controllerId).name("testtarget").build();
final String targetList = JsonBuilder.targets(Collections.singletonList(target), false, unknownTargetTypeId);
// post target over rest resource
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING).content(targetList)
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNotFound())
.andExpect(jsonPath("message", Matchers.containsString(errorMsg)));
}
@Test
@Description("Ensures that a post request for assign target type to target works.")
public void assignTargetTypeToTarget() throws Exception {
// create target type
TargetType targetType = testdataFactory.findOrCreateTargetType("targettype");
assertThat(targetType).isNotNull();
// create target
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
assertThat(target).isNotNull();
// assign target type over rest resource
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.content("{\"id\":" + targetType.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
assertThat(targetManagement.getByControllerID(targetControllerId).get().getTargetType().getId()).isEqualTo(targetType.getId());
}
@Test
@Description("Ensures that a post request for assign a invalid target type to target fails.")
public void assignInvalidTargetTypeToTargetFails() throws Exception {
// Invalid target type ID
long invalidTargetTypeId = 999;
// create target
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget");
assertThat(target).isNotNull();
// assign invalid target type over rest resource
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.content("{\"id\":" + invalidTargetTypeId + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
}
@Test
@Description("Ensures that a delete request for unassign target type from target works.")
public void unassignTargetTypeFromTarget() throws Exception {
// create target type
List<TargetType> targetTypes = testdataFactory.createTargetTypes("targettype",1);
assertThat(targetTypes).hasSize(1);
String targetControllerId = "targetcontroller";
Target target = testdataFactory.createTarget(targetControllerId, "testtarget", targetTypes.get(0).getId());
assertThat(target).isNotNull();
assertThat(target.getTargetType().getId()).isEqualTo(targetTypes.get(0).getId());
// unassign target type over rest resource
mvc.perform(delete(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetControllerId + "/targettype")
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
assertThat(targetManagement.getByControllerID(targetControllerId).get().getTargetType()).isNull();
}
}

View File

@@ -0,0 +1,616 @@
/**
* Copyright (c) 2021 Bosch.IO 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.mgmt.rest.resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.jayway.jsonpath.JsonPath;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Step;
import io.qameta.allure.Story;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Spring MVC Tests against the MgmtTargetTypeResource.
*
*/
@Feature("Component Tests - Management API")
@Story("Target Type Resource")
class MgmtTargetTypeResourceTest extends AbstractManagementApiIntegrationTest {
private final static String TARGETTYPES_ENDPOINT = MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING;
private final static String TARGETTYPE_SINGLE_ENDPOINT = MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING
+ "/{typeid}";
private final static String TARGETTYPE_DSTYPES_ENDPOINT = TARGETTYPE_SINGLE_ENDPOINT + "/"
+ MgmtRestConstants.TARGETTYPE_V1_DS_TYPES;
private final static String TARGETTYPE_DSTYPE_SINGLE_ENDPOINT = TARGETTYPE_DSTYPES_ENDPOINT + "/{dstypeid}";
private final static String TEST_USER = "targetTypeTester";
@Test
@WithUser(principal = "targetTypeTester", allSpPermissions = true, removeFromAllPermission = { SpPermission.READ_TARGET })
@Description("GET targettypes returns Forbidden when permission is missing")
void getTargetTypesWithoutPermission() throws Exception {
mvc.perform(get(TARGETTYPES_ENDPOINT).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isForbidden());
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{id} GET request.")
void getTargetType() throws Exception {
String typeName = "TestTypeGET";
TargetType testType = createTestTargetTypeInDB(typeName);
Long typeId = testType.getId();
mvc.perform(get(TARGETTYPE_SINGLE_ENDPOINT, typeId).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.id", is(typeId), Long.class)).andExpect(jsonPath("$.name", equalTo(typeName)))
.andExpect(jsonPath("$.colour", is("#000000")))
.andExpect(jsonPath("$.description", equalTo(typeName + " description")))
.andExpect(jsonPath("$.createdBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$.lastModifiedBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.lastModifiedAt", equalTo(testType.getLastModifiedAt())))
.andExpect(jsonPath("$._links.self.href", equalTo("http://localhost/rest/v1/targettypes/" + typeId)))
.andExpect(jsonPath("$.deleted").doesNotExist())
.andExpect(jsonPath("$.key").doesNotExist())
.andExpect(jsonPath("$._links.compatibledistributionsettypes.href",
equalTo("http://localhost/rest/v1/targettypes/" + typeId + "/compatibledistributionsettypes")));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes GET requests.")
void getTargetTypes() throws Exception {
String typeName = "TestTypeGET";
int count = 5;
List<TargetType> testTypes = createTestTargetTypesInDB(typeName, count);
ResultActions resultActions = mvc.perform(get(TARGETTYPES_ENDPOINT).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print());
for (int index = 0; index < count; index++) {
Long typeId = testTypes.get(index).getId();
resultActions.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].id", contains(typeId.intValue())))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].name", contains(typeName + index)))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].colour", contains("#000000")))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].description",
contains(typeName + " description")))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].createdBy", contains(TEST_USER)))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].createdAt",
contains(testTypes.get(index).getCreatedAt())))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].lastModifiedBy", contains(TEST_USER)))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].lastModifiedAt",
contains(testTypes.get(index).getLastModifiedAt())))
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].deleted").doesNotExist())
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')].key").doesNotExist())
.andExpect(jsonPath("$.content.[?(@.id=='" + typeId + "')]._links.self.href",
contains("http://localhost/rest/v1/targettypes/" + typeId)))
.andExpect(
jsonPath("$.content.[?(@.id=='" + typeId + "')]._links.compatibledistributionsettypes.href")
.doesNotExist())
.andExpect(jsonPath("$.total", equalTo(count))).andExpect(jsonPath("$.size", equalTo(count)));
}
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes GET requests without prior created target types.")
void getDefaultTargetTypes() throws Exception {
// 0 types overall (no default types are created)
final int types = 0;
mvc.perform(get(TARGETTYPES_ENDPOINT)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(types)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(types)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(types)));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes GET requests with sorting by name.")
void getTargetTypesSortedByName() throws Exception {
String typeNameA = "ATestTypeGETsorted";
String typeNameB = "BTestTypeGETsorted";
String typeNameC = "CTestTypeGETsorted";
TargetType testTypeB = createTestTargetTypeInDB(typeNameB);
TargetType testTypeC = createTestTargetTypeInDB(typeNameC);
TargetType testTypeA = createTestTargetTypeInDB(typeNameA);
testTypeA = targetTypeManagement
.update(entityFactory.targetType().update(testTypeA.getId()).description("Updated description"));
// descending
mvc.perform(get(TARGETTYPES_ENDPOINT).accept(MediaType.APPLICATION_JSON)
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "name:DESC")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.content.[0].id", equalTo(testTypeC.getId().intValue())))
.andExpect(jsonPath("$.content.[0].name", equalTo(typeNameC)))
.andExpect(jsonPath("$.content.[0].colour", equalTo("#000000")))
.andExpect(jsonPath("$.content.[0].description", equalTo(typeNameC + " description")))
.andExpect(jsonPath("$.content.[0].createdBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.content.[0].createdAt", equalTo(testTypeC.getCreatedAt())))
.andExpect(jsonPath("$.content.[0].lastModifiedBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.content.[0].lastModifiedAt", equalTo(testTypeC.getLastModifiedAt())))
.andExpect(jsonPath("$.content.[0].deleted").doesNotExist())
.andExpect(jsonPath("$.content.[0].key").doesNotExist())
.andExpect(jsonPath("$.content.[0]._links.self.href",
equalTo("http://localhost/rest/v1/targettypes/" + testTypeC.getId())))
.andExpect(jsonPath("$.content.[0]._links.compatibledistributionsettypes.href").doesNotExist())
.andExpect(jsonPath("$.total", equalTo(3))).andExpect(jsonPath("$.size", equalTo(3)))
.andExpect(jsonPath("$.content.[1].name", equalTo(typeNameB)))
.andExpect(jsonPath("$.content.[2].name", equalTo(typeNameA)));
// ascending
mvc.perform(get(TARGETTYPES_ENDPOINT).accept(MediaType.APPLICATION_JSON)
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "name:ASC")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.content.[0].id", equalTo(testTypeA.getId().intValue())))
.andExpect(jsonPath("$.content.[0].name", equalTo(typeNameA)))
.andExpect(jsonPath("$.content.[0].description", equalTo("Updated description")))
.andExpect(jsonPath("$.content.[0].colour", equalTo("#000000")))
.andExpect(jsonPath("$.content.[0].createdBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.content.[0].createdAt", equalTo(testTypeA.getCreatedAt())))
.andExpect(jsonPath("$.content.[0].lastModifiedBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.content.[0].lastModifiedAt", equalTo(testTypeA.getLastModifiedAt())))
.andExpect(jsonPath("$.content.[0].deleted").doesNotExist())
.andExpect(jsonPath("$.content.[0].key").doesNotExist())
.andExpect(jsonPath("$.content.[0]._links.self.href",
equalTo("http://localhost/rest/v1/targettypes/" + testTypeA.getId())))
.andExpect(jsonPath("$.content.[0]._links.compatibledistributionsettypes.href").doesNotExist())
.andExpect(jsonPath("$.total", equalTo(3))).andExpect(jsonPath("$.size", equalTo(3)))
.andExpect(jsonPath("$.content.[1].name", equalTo(typeNameB)))
.andExpect(jsonPath("$.content.[2].name", equalTo(typeNameC)));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes GET requests with paging.")
void getTargetTypesWithPagingLimitRequestParameter() throws Exception {
final String typePrefix = "TestTypeGETPaging";
final int count = 10;
final int limit = 3;
createTestTargetTypesInDB(typePrefix, count);
mvc.perform(get(TARGETTYPES_ENDPOINT).param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT,
String.valueOf(limit))).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(count)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(limit)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(limit)));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes GET requests with paging and offset.")
void getTargetTypesWithPagingLimitAndOffsetRequestParameter() throws Exception {
final int count = 10;
final int offset = 2;
final int expectedSize = count - offset;
final String typePrefix = "TestTypeGETPaging";
createTestTargetTypesInDB(typePrefix, count);
mvc.perform(get(TARGETTYPES_ENDPOINT)
.param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offset))
.param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(count)))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(count)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize)))
.andExpect(jsonPath(MgmtTargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize)));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID} PUT requests.")
void updateTargetType() throws Exception {
String typeName = "TestTypePUT";
final TargetType testType = createTestTargetTypeInDB(typeName);
final String body = new JSONObject().put("id", testType.getId()).put("description", "updated description")
.put("name", "TestTypePUTupdated").put("colour", "#ffffff").toString();
mvc.perform(
put(TARGETTYPE_SINGLE_ENDPOINT, testType.getId()).content(body).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$.description", equalTo("updated description")))
.andExpect(jsonPath("$.name", equalTo("TestTypePUTupdated")))
.andExpect(jsonPath("$.colour", equalTo("#ffffff"))).andReturn();
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{id} GET requests.")
void getUpdatedTargetType() throws Exception {
TargetType testType = createTestTargetTypeInDB("TestTypeGET");
String typeNameUpdated = "TestTypeGETupdated";
testType = targetTypeManagement.update(entityFactory.targetType().update(testType.getId()).name(typeNameUpdated)
.description("Updated Description").colour("#ffffff"));
mvc.perform(get(TARGETTYPE_SINGLE_ENDPOINT, testType.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.name", equalTo(typeNameUpdated)))
.andExpect(jsonPath("$.description", equalTo("Updated Description")))
.andExpect(jsonPath("$.colour", equalTo("#ffffff")))
.andExpect(jsonPath("$.createdBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.createdAt", equalTo(testType.getCreatedAt())))
.andExpect(jsonPath("$.lastModifiedBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$.lastModifiedAt", equalTo(testType.getLastModifiedAt())))
.andExpect(jsonPath("$.deleted").doesNotExist())
.andExpect(jsonPath("$.key").doesNotExist());
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes POST requests.")
void createTargetTypes() throws Exception {
String typeName = "TestTypePOST";
final List<TargetType> types = buildTestTargetTypesWithoutDsTypes(typeName, 5);
runPostTargetTypeAndVerify(types);
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID}/compatibledistributionsettypes POST requests.")
void addDistributionSetTypeToTargetType() throws Exception {
String typeName = "TestTypeAddDs";
TargetType testType = createTestTargetTypeInDB(typeName);
assertThat(testType.getOptLockRevision()).isEqualTo(1);
mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId())
.content("[{\"id\":" + standardDsType.getId() + "}]").contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
testType = targetTypeManagement.get(testType.getId()).get();
assertThat(testType.getLastModifiedBy()).isEqualTo(TEST_USER);
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getCompatibleDistributionSetTypes()).containsExactly(standardDsType);
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID}/compatibledistributionsettypes GET requests.")
void getDistributionSetsOfTargetType() throws Exception {
String typeName = "TestTypeGetDs";
final TargetType testType = createTestTargetTypeInDB(typeName, Collections.singletonList(standardDsType));
mvc.perform(get(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$[0].name", equalTo(standardDsType.getName())))
.andExpect(jsonPath("$[0].description", equalTo(standardDsType.getDescription())))
.andExpect(jsonPath("$[0].key", equalTo("test_default_ds_type")))
.andExpect(jsonPath("$[0]._links.self.href",
equalTo("http://localhost/rest/v1/distributionsettypes/" + standardDsType.getId())));
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID}/compatibledistributionsettypes/{ID} GET requests.")
void getDistributionSetOfTargetTypeReturnsNotAllowed() throws Exception {
String typeName = "TestTypeAddDs";
final TargetType testType = createTestTargetTypeInDB(typeName);
mvc.perform(get(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), standardDsType.getId())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID}/compatibledistributionsettypes/{ID} DELETE requests.")
void removeDsTypeFromTargetType() throws Exception {
String typeName = "TestTypeRemoveDs";
TargetType testType = createTestTargetTypeInDB(typeName, Collections.singletonList(standardDsType));
mvc.perform(delete(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), standardDsType.getId())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
testType = targetTypeManagement.get(testType.getId()).get();
assertThat(testType.getLastModifiedBy()).isEqualTo(TEST_USER);
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getCompatibleDistributionSetTypes()).isEmpty();
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/distributionsettypes/{ID} DELETE requests.")
void deletingDsTypeRemovesAssignmentFromTargetType() throws Exception {
String typeName = "TestTypeRemoveDs";
TargetType testType = createTestTargetTypeInDB(typeName, Collections.singletonList(standardDsType));
assertThat(testType.getCompatibleDistributionSetTypes()).hasSize(1);
assertThat(distributionSetTypeManagement.getByKey(standardDsType.getKey())).isNotEmpty();
mvc.perform(delete(MgmtRestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING + "/" + standardDsType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
testType = targetTypeManagement.get(testType.getId()).get();
assertThat(testType.getLastModifiedBy()).isEqualTo(TEST_USER);
assertThat(testType.getOptLockRevision()).isEqualTo(2);
assertThat(testType.getCompatibleDistributionSetTypes()).isEmpty();
assertThat(distributionSetTypeManagement.getByKey(standardDsType.getKey())).isEmpty();
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID} DELETE requests - Deletion when not in use.")
void deleteTargetTypeUnused() throws Exception {
String typeName = "TestTypeUnusedDelete";
final TargetType testType = createTestTargetTypeInDB(typeName);
assertThat(targetTypeManagement.count()).isEqualTo(1);
mvc.perform(delete(TARGETTYPE_SINGLE_ENDPOINT, testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
assertThat(targetTypeManagement.count()).isZero();
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Checks the correct behaviour of /rest/v1/targettypes/{ID} DELETE requests - Deletion not possible when in use.")
void deleteTargetTypeUsed() throws Exception {
String typeName = "TestTypeUsedDelete";
final TargetType testType = createTestTargetTypeInDB(typeName);
targetManagement.create(entityFactory.target().create().controllerId("target").name("TargetOfTestType")
.description("target description").targetType(testType.getId()));
assertThat(targetTypeManagement.count()).isEqualTo(1);
assertThat(targetManagement.count()).isEqualTo(1);
mvc.perform(delete(TARGETTYPE_SINGLE_ENDPOINT, testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isConflict());
assertThat(targetManagement.count()).isEqualTo(1);
assertThat(targetTypeManagement.count()).isEqualTo(1);
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Ensures that target type deletion request to API on an entity that does not exist results in NOT_FOUND.")
void deleteTargetTypeThatDoesNotExistLeadsToNotFound() throws Exception {
mvc.perform(delete(TARGETTYPE_SINGLE_ENDPOINT, 1234)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
}
@Test
@WithUser(principal = TEST_USER, allSpPermissions = true)
@Description("Tests the update of the deletion flag. It is verified that the target type can't be marked as deleted through update operation.")
void updateTargetTypeDeletedFlag() throws Exception {
String typeName = "TestTypePUT";
final TargetType testType = createTestTargetTypeInDB(typeName);
final String body = new JSONObject().put("id", testType.getId()).put("deleted", true).toString();
mvc.perform(
put(TARGETTYPE_SINGLE_ENDPOINT, testType.getId()).content(body).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$.id", equalTo(testType.getId().intValue())))
.andExpect(jsonPath("$.deleted").doesNotExist());
}
@Test
@Description("Ensures that the server is behaving as expected on invalid requests (wrong media type, wrong ID etc.).")
void invalidRequestsOnTargetTypesResource() throws Exception {
String typeName = "TestTypeInvalidReq";
final TargetType testType = createTestTargetTypeInDB(typeName, Collections.singletonList(standardDsType));
// target type does not exist
mvc.perform(get(TARGETTYPE_SINGLE_ENDPOINT, 12345678)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
mvc.perform(get(TARGETTYPE_DSTYPES_ENDPOINT, 123456789)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
mvc.perform(delete(TARGETTYPE_SINGLE_ENDPOINT, 123456789)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// target types at creation time invalid
final TargetType testNewType = createTestTargetTypeInDB(typeName + "Another",
Collections.singletonList(standardDsType));
mvc.perform(post(TARGETTYPES_ENDPOINT).content(JsonBuilder.targetTypes(Collections.singletonList(testNewType)))
.contentType(MediaType.APPLICATION_OCTET_STREAM)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isUnsupportedMediaType());
// bad request - no content
mvc.perform(post(TARGETTYPES_ENDPOINT).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
// bad request - bad content
mvc.perform(post(TARGETTYPES_ENDPOINT).content("sdfjsdlkjfskdjf".getBytes())
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
// Missing mandatory field name
mvc.perform(post(TARGETTYPES_ENDPOINT).content("[{\"description\":\"Desc123\"}]")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
final TargetType tooLongName = entityFactory.targetType().create()
.name(RandomStringUtils.randomAlphanumeric(NamedEntity.NAME_MAX_SIZE + 1)).build();
mvc.perform(post(TARGETTYPES_ENDPOINT).content(JsonBuilder.targetTypes(Collections.singletonList(tooLongName)))
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
// ds types
mvc.perform(get(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(delete(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), 565765))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).content("{\"id\":1}")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest());
mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).content("[{\"id\":44456}]")
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
// not allowed methods
mvc.perform(put(TARGETTYPES_ENDPOINT)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete(TARGETTYPES_ENDPOINT)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(put(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(delete(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId(), 565765)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(put(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), 565765))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
mvc.perform(post(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), 565765)
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
mvc.perform(get(TARGETTYPE_DSTYPE_SINGLE_ENDPOINT, testType.getId(), 565765))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isMethodNotAllowed());
}
@Test
@Description("Search request of target types.")
void searchTargetTypeRsql() throws Exception {
targetTypeManagement.create(entityFactory.targetType().create().name("TestName123"));
targetTypeManagement.create(entityFactory.targetType().create().name("TestName1234"));
final String rsqlFindLikeDs1OrDs2 = "name==TestName123,name==TestName1234";
mvc.perform(get(TARGETTYPES_ENDPOINT + "?q=" + rsqlFindLikeDs1OrDs2)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(jsonPath("size", equalTo(2)))
.andExpect(jsonPath("total", equalTo(2))).andExpect(jsonPath("content[0].name", equalTo("TestName123")))
.andExpect(jsonPath("content[1].name", equalTo("TestName1234")));
}
@Step
private TargetType buildTestTargetTypeBody(String name) {
return prepareTestTargetType(name, null).build();
}
private TargetTypeCreate prepareTestTargetType(String name, Collection<DistributionSetType> dsTypes) {
TargetTypeCreate create = entityFactory.targetType().create().name(name)
.description("Description of the test type").colour("#aaaaaa");
if (dsTypes != null && !dsTypes.isEmpty()) {
create.compatible(Collections.singletonList(standardDsType.getId()));
}
return create;
}
@Step
private List<TargetType> createTestTargetTypesInDB(String namePrefix, int count) {
return testdataFactory.createTargetTypes(namePrefix, count);
}
@Step
private TargetType createTestTargetTypeInDB(String name) {
return testdataFactory.findOrCreateTargetType(name);
}
@Step
private TargetType createTestTargetTypeInDB(String name, List<DistributionSetType> dsTypes) {
TargetType targetType = testdataFactory.createTargetType(name, dsTypes);
assertThat(targetType.getOptLockRevision()).isEqualTo(1);
return targetType;
}
@Step
private List<TargetType> buildTestTargetTypesWithoutDsTypes(String namePrefix, int count) {
final List<TargetType> types = new ArrayList<>();
for (int index = 0; index < count; index++) {
types.add(buildTestTargetTypeBody(namePrefix + index));
}
return types;
}
@Step
private void runPostTargetTypeAndVerify(final List<TargetType> types) throws Exception {
int size = types.size();
ResultActions resultActions = mvc
.perform(post(TARGETTYPES_ENDPOINT).content(JsonBuilder.targetTypes(types))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print());
for (int index = 0; index < size; index++) {
resultActions.andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$[" + index + "].id").exists())
.andExpect(jsonPath("$[" + index + "].name", startsWith("TestTypePOST")))
.andExpect(jsonPath("$[" + index + "].colour", hasToString("#aaaaaa")))
.andExpect(jsonPath("$[" + index + "].description",
equalTo("Description of the test type")))
.andExpect(jsonPath("$[" + index + "].createdBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$[" + index + "].createdAt").exists())
.andExpect(jsonPath("$[" + index + "].lastModifiedBy", equalTo(TEST_USER)))
.andExpect(jsonPath("$[" + index + "].lastModifiedAt").exists())
.andExpect(jsonPath("$[" + index + "].key").doesNotExist())
.andExpect(jsonPath("$[" + index + "].deleted").doesNotExist())
.andExpect(jsonPath("$[" + index + "]._links.self.href",
startsWith("http://localhost/rest/v1/targettypes/")))
.andExpect(
jsonPath("$[" + index + "]._links.compatibledistributionsettypes.href")
.doesNotExist());
}
MvcResult mvcResult = resultActions.andReturn();
for (int index = 0; index < size; index++) {
String name = "TestTypePOST" + index;
final TargetType created = targetTypeManagement.getByName(name).get();
assertThat(JsonPath.compile("$[ ?(@.name=='" + name + "') ].id")
.read(mvcResult.getResponse().getContentAsString()).toString()).contains(String.valueOf(created.getId()));
assertThat(JsonPath.compile("$[ ?(@.name=='" + name + "') ]._links.self.href")
.read(mvcResult.getResponse().getContentAsString()).toString()).contains("/"+created.getId());
}
assertThat(targetTypeManagement.count()).isEqualTo(size);
}
}

View File

@@ -87,6 +87,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT);
}
private static HttpStatus getStatusOrDefault(final SpServerError error) {

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -426,13 +427,13 @@ public abstract class JsonBuilder {
int i = 0;
for (final Target target : targets) {
final String address = target.getAddress() != null ? target.getAddress().toString() : null;
final String targetType = target.getTargetType() != null ? target.getTargetType().getId().toString() : null;
final String token = withToken ? target.getSecurityToken() : null;
builder.append(new JSONObject().put("controllerId", target.getControllerId())
.put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0")
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.put("address", address).put("securityToken", token).toString());
.put("updatedAt", "0").put("createdBy", "systemtest").put("updatedBy", "systemtest")
.put("address", address).put("securityToken", token).put("targetType", targetType).toString());
if (++i < targets.size()) {
builder.append(",");
@@ -444,6 +445,83 @@ public abstract class JsonBuilder {
return builder.toString();
}
public static String targets(final List<Target> targets, final boolean withToken, final long targetTypeId) throws JSONException {
final StringBuilder builder = new StringBuilder();
builder.append("[");
int i = 0;
for (final Target target : targets) {
final String address = target.getAddress() != null ? target.getAddress().toString() : null;
final String type = target.getTargetType() != null ? target.getTargetType().getId().toString() : null;
final String token = withToken ? target.getSecurityToken() : null;
builder.append(new JSONObject().put("controllerId", target.getControllerId())
.put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0")
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.put("address", address).put("securityToken", token).put("targetType", targetTypeId).toString());
if (++i < targets.size()) {
builder.append(",");
}
}
builder.append("]");
return builder.toString();
}
public static String targetTypes(final List<TargetType> types) throws JSONException {
final JSONArray result = new JSONArray();
for (final TargetType type : types) {
final JSONArray dsTypes = new JSONArray();
type.getCompatibleDistributionSetTypes().forEach(dsType -> {
try {
dsTypes.put(new JSONObject().put("id", dsType.getId()));
} catch (final JSONException e1) {
e1.printStackTrace();
}
});
result.put(new JSONObject().put("name", type.getName()).put("description", type.getDescription())
.put("id", Long.MAX_VALUE).put("colour", type.getColour()).put("createdAt", "0").put("updatedAt", "0")
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.put("distributionsets", dsTypes));
}
return result.toString();
}
public static String targetTypesCreatableFieldsOnly(final List<TargetType> types) throws JSONException {
final JSONArray result = new JSONArray();
for (final TargetType type : types) {
final JSONArray dsTypes = new JSONArray();
type.getCompatibleDistributionSetTypes().forEach(dsType -> {
try {
dsTypes.put(new JSONObject().put("id", dsType.getId()));
} catch (final JSONException e1) {
e1.printStackTrace();
}
});
JSONObject json = new JSONObject().put("name", type.getName()).put("description", type.getDescription())
.put("colour", type.getColour());
if(dsTypes.length() != 0)
{
json.put("compatibledistributionsettypes", dsTypes);
}
result.put(json);
}
return result.toString();
}
public static String rollout(final String name, final String description, final int groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null, null,

View File

@@ -0,0 +1,396 @@
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: macro
:toclevels: 1
:sectlinks:
:linkattrs:
[[target-types]]
= Target Types
toc::[]
== GET /rest/v1/targettypes
=== Implementation notes
Handles the GET request of retrieving all target types within Hawkbit. Required Permission: READ_TARGET
=== Get target types
==== CURL
include::{snippets}/targettypes/get-target-types/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/get-target-types/http-request.adoc[]
==== Request query parameter
include::{snippets}/targettypes/get-target-types-with-parameters/request-parameters.adoc[]
==== Request parameter example
include::{snippets}/targettypes/get-target-types-with-parameters/http-request.adoc[]
=== Response (Status 200)
==== Response fields
include::{snippets}/targettypes/get-target-types/response-fields.adoc[]
==== Response example
include::{snippets}/targettypes/get-target-types/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== POST /rest/v1/targettypes
=== Implementation notes
Handles the POST request for creating new target types within Hawkbit. The request body must always be a list of types. Required Permission: CREATE_TARGET
=== Create target types
==== CURL
include::{snippets}/targettypes/post-target-types/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/post-target-types/http-request.adoc[]
==== Request fields
include::{snippets}/targettypes/post-target-types/request-fields.adoc[]
=== Response (Status 201)
==== Response fields
include::{snippets}/targettypes/post-target-types/response-fields.adoc[]
==== Response example
include::{snippets}/targettypes/post-target-types/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Target type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
| `409 Conflict`
| Target type already exists
| See <<error-body>>
include::../errors/415.adoc[]
include::../errors/429.adoc[]
|===
== DELETE /rest/v1/targettypes/{targetTypeId}
=== Implementation Notes
Handles the DELETE request for a single target type within Hawkbit. Required Permission: DELETE_TARGET
=== Delete target type
==== CURL
include::{snippets}/targettypes/delete-target-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/delete-target-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/delete-target-type/path-parameters.adoc[]
=== Response (Status 200)
==== Response example
include::{snippets}/targettypes/delete-target-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Target type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== GET /rest/v1/targettypes/{targetTypeId}
=== Implementation notes
Handles the GET request of retrieving a single target type within Hawkbit. Required Permission: READ_TARGET
=== Get target type
==== CURL
include::{snippets}/targettypes/get-target-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/get-target-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/get-target-type/path-parameters.adoc[]
=== Response (Status 200)
==== Response fields
include::{snippets}/targettypes/get-target-type/response-fields.adoc[]
==== Response example
include::{snippets}/targettypes/get-target-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Target type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== PUT /rest/v1/targettypes/{targetTypeId}
=== Implementation notes
Handles the PUT request for a single target type within Hawkbit. Required Permission: UPDATE_TARGET
=== Update target type
==== CURL
include::{snippets}/targettypes/put-target-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/put-target-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/put-target-type/path-parameters.adoc[]
==== Request fields
include::{snippets}/targettypes/put-target-type/request-fields.adoc[]
=== Response (Status 200)
==== Response fields
include::{snippets}/targettypes/put-target-type/response-fields.adoc[]
==== Response example
include::{snippets}/targettypes/put-target-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Target type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/409.adoc[]
include::../errors/415.adoc[]
include::../errors/429.adoc[]
|===
== GET /rest/v1/targettypes/{targetTypeId}/compatibledistributionsettypes
=== Implementation notes
Handles the GET request of retrieving the list of compatible distribution set types in that target type. Required Permission: READ_TARGET, READ_REPOSITORY
=== Lists all compatible distribution set types
==== CURL
include::{snippets}/targettypes/get-compatible-distribution-set-types/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/get-compatible-distribution-set-types/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/get-compatible-distribution-set-types/path-parameters.adoc[]
=== Response (Status 200)
==== Response fields
include::{snippets}/targettypes/get-compatible-distribution-set-types/response-fields.adoc[]
==== Response example
include::{snippets}/targettypes/get-compatible-distribution-set-types/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Distribution set type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
== POST /rest/v1/targettypes/{targetTypeId}/compatibledistributionsettypes
=== Implementation notes
Handles the POST request for adding compatible distribution set types to a target type. Required Permission: UPDATE_TARGET and READ_REPOSITORY
=== Add compatible distribution set type
==== CURL
include::{snippets}/targettypes/post-compatible-distribution-set-types/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/post-compatible-distribution-set-types/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/post-compatible-distribution-set-types/path-parameters.adoc[]
==== Request fields
include::{snippets}/targettypes/post-compatible-distribution-set-types/request-fields.adoc[]
=== Response (Status 201)
==== Response example
include::{snippets}/targettypes/post-compatible-distribution-set-types/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Distribution set type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
| `409 Conflict`
| Distribution set type already exists
| See <<error-body>>
include::../errors/415.adoc[]
include::../errors/429.adoc[]
|===
== DELETE /rest/v1/targettypes/{targetTypeId}/compatibledistributionsettypes/{distributionSetTypeId}
=== Implementation Notes
Handles the DELETE request for removing a distribution set type from a single target type. Required Permission: UPDATE_TARGET and READ_REPOSITORY
=== Remove compatible distribution set type from target type
==== CURL
include::{snippets}/targettypes/delete-compatible-distribution-set-type/curl-request.adoc[]
==== Request URL
include::{snippets}/targettypes/delete-compatible-distribution-set-type/http-request.adoc[]
==== Request path parameter
include::{snippets}/targettypes/delete-compatible-distribution-set-type/path-parameters.adoc[]
=== Response (Status 200)
==== Response example
include::{snippets}/targettypes/delete-compatible-distribution-set-type/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
| `404 Not Found`
| Distribution set type was not found.
| See <<error-body>>
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===

View File

@@ -19,12 +19,12 @@ public final class ApiModelPropertiesGeneric {
public static final String ITEM_ID = "The technical identifier " + ENDING;
public static final String NAME = "The name" + ENDING;
public static final String DESCRPTION = "The description" + ENDING;
public static final String COLOUR = "The colour" + ENDING;
public static final String COLOUR = "The colour" + ENDING + ". In HEX format, e.g. #800080";
public static final String DELETED = "Deleted flag, used for soft deleted entities";
public static final String CREATED_BY = "Entity was originally created by User, AMQP-Controller, anonymous etc.)";
public static final String CREATED_BY = "Entity was originally created by (User, AMQP-Controller, anonymous etc.)";
public static final String CREATED_AT = "Entity was originally created at (timestamp UTC in milliseconds)";
public static final String LAST_MODIFIED_BY = "Entity was last modified by User, AMQP-Controller, anonymous etc.)";
public static final String LAST_MODIFIED_BY = "Entity was last modified by (User, AMQP-Controller, anonymous etc.)";
public static final String LAST_MODIFIED_AT = "Entity was last modified at (timestamp UTC in milliseconds)";
// Paging elements

View File

@@ -78,6 +78,10 @@ public final class MgmtApiModelProperties {
public static final String POLL_STATUS = "Poll status of the target. In many scenarios that target will poll the update server on a regular basis to look for potential updates. If that poll does not happen it might imply that the target is offline.";
public static final String POLL_OVERDUE = "Defines if the target poll time is overdue based on the next expected poll time plus the configured overdue poll time threshold.";
// Target type
public static final String COMPATIBLE_DS_TYPES = "Array of distribution set types that are compatible to that target type";
public static final String LINK_COMPATIBLE_DS_TYPES = "Link to the compatible distribution set types in this target type";
// rollout
public static final String ROLLOUT_FILTER_QUERY = "target filter query language expression";
public static final String ROLLOUT_GROUP_FILTER_QUERY = "target filter query language expression that selects a subset of targets which match the target filter of the Rollout";
@@ -118,6 +122,8 @@ public final class MgmtApiModelProperties {
public static final String TARGET_LIST = "List of provisioning targets.";
public static final String TARGET_TYPE_LIST = "List of target types";
public static final String SM_LIST = "List of software modules.";
public static final String ROLLOUT_LIST = "list of rollouts";

View File

@@ -0,0 +1,273 @@
/**
* Copyright (c) 2021 Bosch.IO 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.rest.mgmt.documentation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
import org.eclipse.hawkbit.rest.documentation.MgmtApiModelProperties;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Documentation generation for Management API for {@link TargetType}.
*
*/
@Feature("Spring Rest Docs Tests - TargetType")
@Story("TargetTypes Resource")
public class TargetTypesDocumentationTest extends AbstractApiRestDocumentation {
@Override
public String getResourceName() {
return "targettypes";
}
@Test
@Description("Handles the GET request of retrieving all target types within Hawkbit. Required Permission: "
+ SpPermission.READ_TARGET)
public void getTargetTypes() throws Exception {
testdataFactory.findOrCreateTargetType("targetType1");
testdataFactory.createTargetType("targetType2", Collections.singletonList(standardDsType));
mockMvc.perform(get(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(responseFields(
fieldWithPath("size").type(JsonFieldType.NUMBER).description(ApiModelPropertiesGeneric.SIZE),
fieldWithPath("total").description(ApiModelPropertiesGeneric.TOTAL_ELEMENTS),
fieldWithPath("content").description(MgmtApiModelProperties.TARGET_TYPE_LIST),
fieldWithPath("content[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("content[].name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("content[].description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("content[].colour").description(ApiModelPropertiesGeneric.COLOUR),
fieldWithPath("content[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("content[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("content[].lastModifiedAt")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
fieldWithPath("content[].lastModifiedBy")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
fieldWithPath("content[]._links.self").ignored())));
}
@Test
@Description("Handles the GET request of retrieving all target types within Hawkbit with a defined page size and "
+ "offset, sorted by name in descending order and filtered down to all targets which name starts with 'targetType'. "
+ "Required Permission: " + SpPermission.READ_TARGET)
public void getTargetTypesWithParameters() throws Exception {
testdataFactory.findOrCreateTargetType("targetType1");
testdataFactory.createTargetType("targetType2", Collections.singletonList(standardDsType));
mockMvc.perform(get(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING).accept(MediaType.APPLICATION_JSON)
.param("offset", "0").param("limit", "2").param("sort", "name:ASC").param("q", "name==targetType*"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andDo(this.document.document(getFilterRequestParamter()));
}
@Test
@Description("Handles the GET request for a single target type within Hawkbit. Required Permission: "
+ SpPermission.READ_TARGET)
public void getTargetType() throws Exception {
TargetType testType = testdataFactory.createTargetType("TargetType", Collections.singletonList(standardDsType));
mockMvc.perform(get(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING + "/{targetTypeId}", testType.getId())
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID)),
responseFields(fieldWithPath("id").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("colour").description(ApiModelPropertiesGeneric.COLOUR),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
.type("Number"),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"),
fieldWithPath("_links.compatibledistributionsettypes.href")
.description(MgmtApiModelProperties.LINK_COMPATIBLE_DS_TYPES),
fieldWithPath("_links.self").ignored())));
}
@Test
@Description("Handles the POST request for creating new target types within Hawkbit. The request body "
+ "must always be a list of types. Required Permission: " + SpPermission.CREATE_TARGET)
public void postTargetTypes() throws Exception {
final List<TargetType> types = new ArrayList<>();
types.add(entityFactory.targetType().create().name("targetType1").description("targetType1 description")
.colour("#ffffff").build());
types.add(entityFactory.targetType().create().name("targetType2").description("targetType2 description")
.colour("#000000").compatible(Collections.singletonList(standardDsType.getId())).build());
mockMvc.perform(post(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING)
.content(JsonBuilder.targetTypesCreatableFieldsOnly(types)).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated())
.andDo(this.document.document(
requestFields(requestFieldWithPath("[]name").description(ApiModelPropertiesGeneric.NAME),
optionalRequestFieldWithPath("[]description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("[]colour").description(ApiModelPropertiesGeneric.COLOUR),
optionalRequestFieldWithPath("[]compatibledistributionsettypes")
.description(MgmtApiModelProperties.COMPATIBLE_DS_TYPES)),
responseFields(fieldWithPath("[]id").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("[]name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("[]description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("[]colour").description(ApiModelPropertiesGeneric.COLOUR),
fieldWithPath("[]createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("[]createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("[]lastModifiedAt")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
fieldWithPath("[]lastModifiedBy")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
fieldWithPath("[]_links.self").ignored())));
}
@Test
@Description("Handles the DELETE request of retrieving a single target type within Hawkbit. Required Permission: "
+ SpPermission.DELETE_TARGET)
public void deleteTargetType() throws Exception {
TargetType testType = testdataFactory.createTargetType("TargetType", Collections.singletonList(standardDsType));
mockMvc.perform(delete(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING + "/{targetTypeId}", testType.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID))));
}
@Test
@Description("Handles the PUT request for a single target type within Hawkbit. " + "Required Permission: "
+ SpPermission.UPDATE_TARGET)
public void putTargetType() throws Exception {
TargetType testType = testdataFactory.createTargetType("targetType", Collections.singletonList(standardDsType));
final String body = new JSONObject().put("description", "an updated description").put("name", "updatedTypeName")
.put("colour", "#aaafff").toString();
this.mockMvc
.perform(put(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING + "/{targetTypeId}", testType.getId())
.content(body).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestFields(
optionalRequestFieldWithPath("description")
.description(ApiModelPropertiesGeneric.DESCRPTION),
optionalRequestFieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
optionalRequestFieldWithPath("colour").description(ApiModelPropertiesGeneric.COLOUR)),
responseFields(fieldWithPath("id").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("colour").description(ApiModelPropertiesGeneric.COLOUR),
fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
.type("Number"),
fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
.type("String"),
fieldWithPath("_links.compatibledistributionsettypes.href")
.description(MgmtApiModelProperties.LINK_COMPATIBLE_DS_TYPES),
fieldWithPath("_links.self").ignored())));
}
@Test
@Description("Handles the GET request of retrieving the list of compatible distribution set types in that target type. "
+ "Required Permission: " + SpPermission.READ_TARGET + " and " + SpPermission.READ_REPOSITORY)
public void getCompatibleDistributionSetTypes() throws Exception {
TargetType testType = testdataFactory.createTargetType("targetType", Collections.singletonList(standardDsType));
mockMvc.perform(
get(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING + "/{targetTypeId}/compatibledistributionsettypes",
testType.getId()).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andDo(this.document.document(
pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID)),
responseFields(fieldWithPath("[]id").description(ApiModelPropertiesGeneric.ITEM_ID),
fieldWithPath("[]key").description(MgmtApiModelProperties.DS_TYPE_KEY),
fieldWithPath("[]name").description(ApiModelPropertiesGeneric.NAME),
fieldWithPath("[]description").description(ApiModelPropertiesGeneric.DESCRPTION),
fieldWithPath("[]createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
fieldWithPath("[]createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
fieldWithPath("[]lastModifiedAt")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("Number"),
fieldWithPath("[]lastModifiedBy")
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
fieldWithPath("[]deleted").description(ApiModelPropertiesGeneric.DELETED),
fieldWithPath("[]_links.self").ignored())));
}
@Test
@Description("Handles the POST request for adding a compatible distribution set type to a target type."
+ " Required Permission: " + SpPermission.UPDATE_TARGET + " and " + SpPermission.READ_REPOSITORY)
public void postCompatibleDistributionSetTypes() throws Exception {
final DistributionSetType dsType1 = distributionSetTypeManagement.create(
entityFactory.distributionSetType().create().key("test1").name("TestName1").description("Desc1"));
final DistributionSetType dsType2 = distributionSetTypeManagement.create(
entityFactory.distributionSetType().create().key("test2").name("TestName2").description("Desc2"));
final TargetType targetType = testdataFactory.createTargetType("targetType",
Collections.singletonList(standardDsType));
mockMvc.perform(
post(MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING + "/{targetTypeId}/compatibledistributionsettypes",
targetType.getId())
.content("[{\"id\":" + dsType1.getId() + "},{\"id\":" + dsType2.getId() + "}]")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestFields(requestFieldWithPath("[]id").description(ApiModelPropertiesGeneric.ITEM_ID))));
}
@Test
@Description("Handles the DELETE request to unassign the list of compatible distribution set types in that target type. "
+ SpPermission.UPDATE_TARGET + " and " + SpPermission.READ_REPOSITORY)
public void deleteCompatibleDistributionSetType() throws Exception {
final DistributionSetType dsType = distributionSetTypeManagement.create(
entityFactory.distributionSetType().create().key("test1").name("TestName1").description("Desc1"));
final TargetType targetType = testdataFactory.createTargetType("targetType", Collections.singletonList(dsType));
mockMvc.perform(delete(
MgmtRestConstants.TARGETTYPE_V1_REQUEST_MAPPING
+ "/{targetTypeId}/compatibledistributionsettypes/{distributionSetTypeId}",
targetType.getId(), dsType.getId()).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(pathParameters(
parameterWithName("targetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID),
parameterWithName("distributionSetTypeId").description(ApiModelPropertiesGeneric.ITEM_ID))));
}
}

View File

@@ -298,6 +298,11 @@ public class HawkbitSecurityProperties {
*/
private long maxArtifactStorage = 21_474_836_480L;
/**
* Maximum number of distribution set types per target types
*/
private int maxDistributionSetTypesPerTargetType = 50;
private final Filter filter = new Filter();
private final Filter uiFilter = new Filter();
@@ -439,6 +444,10 @@ public class HawkbitSecurityProperties {
this.maxArtifactStorage = maxArtifactStorage;
}
public int getMaxDistributionSetTypesPerTargetType() {
return maxDistributionSetTypesPerTargetType;
}
/**
* Configuration for hawkBits DOS prevention filter. This is usually an
* infrastructure topic (e.g. Web Application Firewall (WAF)) but might