diff --git a/docs/content/apis/management_api.md b/docs/content/apis/management_api.md
index 7062eaa6d..d7497c922 100644
--- a/docs/content/apis/management_api.md
+++ b/docs/content/apis/management_api.md
@@ -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/)
diff --git a/docs/content/apis/mgmt/targettypes.md b/docs/content/apis/mgmt/targettypes.md
new file mode 100644
index 000000000..319d5c65e
--- /dev/null
+++ b/docs/content/apis/mgmt/targettypes.md
@@ -0,0 +1,7 @@
+---
+title: Target Types API
+parent: Management API
+weight: -100
+---
+
+
\ No newline at end of file
diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java
index 5f082142d..9840f4dab 100644
--- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java
+++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java
@@ -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;
diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetTypeFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetTypeFields.java
new file mode 100644
index 000000000..54cf9c454
--- /dev/null
+++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetTypeFields.java
@@ -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;
+ }
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java
index 65fe29c69..b5fb5c390 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java
@@ -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
*/
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java
index 378801d02..ce965854d 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/QuotaManagement.java
@@ -106,4 +106,9 @@ public interface QuotaManagement {
*/
long getMaxArtifactStorage();
+ /**
+ * @return the maximum number of distribution set types per target type
+ */
+ int getMaxDistributionSetTypesPerTargetType();
+
}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java
index 7194fab9e..c78a44a03 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java
@@ -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}.
*
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java
new file mode 100644
index 000000000..8bab106f4
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java
@@ -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 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 create(@NotEmpty @Valid Collection 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 findAll(@NotNull Pageable pageable);
+
+ /**
+ * @param pageable
+ * Page
+ * @param rsqlParam
+ * query param
+ * @return Target type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ Page findByRsql(@NotNull Pageable pageable, @NotEmpty String rsqlParam);
+
+ /**
+ * @param id
+ * Target type ID
+ * @return Target Type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ Optional get(long id);
+
+ /**
+ * @param targetId
+ * Target ID
+ * @return Target Type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ Optional findByTargetId(long targetId);
+
+ /**
+ * @param targetIds
+ * List of Target ID
+ * @return Target Type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ List findByTargetIds(Collection targetIds);
+
+ /**
+ * @param controllerId
+ * Target controller ID
+ * @return Target Type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ Optional findByTargetControllerId(String controllerId);
+
+ /**
+ * @param controllerIds
+ * List of Target controller ID
+ * @return Target Type
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ List findByTargetControllerIds(Collection controllerIds);
+
+
+ /**
+ * @param ids
+ * List of Target type ID
+ * @return Target type list
+ */
+ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_TARGET)
+ List get(@NotEmpty Collection 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 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);
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java
index b787f97da..9af746391 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java
@@ -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
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeBuilder.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeBuilder.java
new file mode 100644
index 000000000..fa17a67e8
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeBuilder.java
@@ -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();
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeCreate.java
new file mode 100644
index 000000000..c6770c655
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeCreate.java
@@ -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 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();
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeUpdate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeUpdate.java
new file mode 100644
index 000000000..dca751247
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetTypeUpdate.java
@@ -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);
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java
index 76154b2ca..f7fc03c5f 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java
@@ -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()}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetTypeDeletedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetTypeDeletedEvent.java
new file mode 100644
index 000000000..d411773bd
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/TargetTypeDeletedEvent.java
@@ -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);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java
new file mode 100644
index 000000000..12c917b5c
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java
@@ -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
+ 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);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java
new file mode 100644
index 000000000..f001cb25a
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java
@@ -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
+ 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);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java
new file mode 100644
index 000000000..d44376900
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/DistributionSetTypeNotInTargetTypeException.java
@@ -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 + "}.");
+ }
+}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/TargetTypeInUseException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/TargetTypeInUseException.java
new file mode 100644
index 000000000..e27ac3735
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/TargetTypeInUseException.java
@@ -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);
+ }
+}
+
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
index 952c432e7..bd9353be8 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
@@ -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
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetType.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetType.java
new file mode 100644
index 000000000..a705d2459
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetType.java
@@ -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 getCompatibleDistributionSetTypes();
+
+ /**
+ * @return immutable set of optional {@link Target}s
+ */
+ Set getTargets();
+
+ /**
+ * Checks if the given {@link DistributionSetType} is in
+ * {@link #getCompatibleDistributionSetTypes()}.
+ *
+ * @param distributionSetType
+ * search for
+ * @return true 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 true 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();
+}
diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/PropertiesQuotaManagement.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/PropertiesQuotaManagement.java
index ff05dac18..4ab876288 100644
--- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/PropertiesQuotaManagement.java
+++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/PropertiesQuotaManagement.java
@@ -108,4 +108,8 @@ public class PropertiesQuotaManagement implements QuotaManagement {
return securityProperties.getDos().getMaxArtifactStorage();
}
+ @Override
+ public int getMaxDistributionSetTypesPerTargetType() {
+ return securityProperties.getDos().getMaxDistributionSetTypesPerTargetType();
+ }
}
diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetTypeUpdateCreate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetTypeUpdateCreate.java
new file mode 100644
index 000000000..2a30d732f
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetTypeUpdateCreate.java
@@ -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
+ * update or create builder interface
+ */
+public abstract class AbstractTargetTypeUpdateCreate extends AbstractNamedEntityBuilder {
+ @ValidString
+ protected String colour;
+
+ protected Collection compatible;
+
+ /**
+ * @param compatible
+ * list of ID
+ * @return generic type
+ */
+ public T compatible(final Collection compatible) {
+ this.compatible = compatible;
+ return (T) this;
+ }
+
+ /**
+ * @return List of ID
+ */
+ public Optional> 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 getColour() {
+ return Optional.ofNullable(colour);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java
index 405402339..4ff522bc8 100644
--- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java
+++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/AbstractTargetUpdateCreate.java
@@ -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 extends AbstractNamedEntityBuilder
protected Boolean requestAttributes;
+ protected Long targetTypeId;
+
protected AbstractTargetUpdateCreate(final String controllerId) {
this.controllerId = StringUtils.trimWhitespace(controllerId);
}
@@ -79,6 +84,11 @@ public class AbstractTargetUpdateCreate extends AbstractNamedEntityBuilder
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 extends AbstractNamedEntityBuilder
return Optional.ofNullable(status);
}
+ public Long getTargetTypeId() {
+ return targetTypeId;
+ }
+
}
diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/GenericTargetTypeUpdate.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/GenericTargetTypeUpdate.java
new file mode 100644
index 000000000..b60d0e41f
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/builder/GenericTargetTypeUpdate.java
@@ -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
+ implements TargetTypeUpdate {
+
+ /**
+ * @param id
+ * Target type ID
+ */
+ public GenericTargetTypeUpdate(final Long id) {
+ super.id = id;
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java
index a7675bf93..755c933aa 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java
@@ -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 targetTypesByDsType = targetTypeRepository.findByDsType(typeId);
+ targetTypesByDsType.forEach(targetType -> {
+ targetType.removeDistributionSetType(typeId);
+ targetTypeRepository.save(targetType);
+ });
+ }
+
@Override
@Transactional
@Retryable(include = {
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java
index b620571e8..ac1644708 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java
@@ -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
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java
index 859832b78..74c6855c7 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java
@@ -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);
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java
index 178cf9d72..e9176fb30 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java
@@ -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 getByControllerID(final Collection 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 findByFilterOrderByLinkedDistributionSet(final Pageable pageable,
final long orderByDistributionId, final FilterParams filterParams) {
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java
new file mode 100644
index 000000000..34a9a0997
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java
@@ -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 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 create(Collection 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 findAll(Pageable pageable) {
+ return convertPage(targetTypeRepository.findAll(pageable), pageable);
+ }
+
+ @Override
+ public Page findByRsql(Pageable pageable, String rsqlParam) {
+ final Specification spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetTypeFields.class,
+ virtualPropertyReplacer, database);
+
+ return convertPage(targetTypeRepository.findAll(spec, pageable), pageable);
+ }
+
+ @Override
+ public Optional get(long id) {
+ return targetTypeRepository.findById(id).map(targetType -> targetType);
+ }
+
+ @Override
+ public Optional findByTargetId(long targetId) {
+ return targetTypeRepository
+ .findOne(TargetTypeSpecification.hasTarget(targetId)).map(TargetType.class::cast);
+ }
+
+ @Override
+ public List findByTargetIds(Collection targetIds) {
+ return targetTypeRepository.findAll(TargetTypeSpecification.hasTarget(targetIds))
+ .stream().map(TargetType.class::cast).collect(Collectors.toList());
+ }
+
+ @Override
+ public Optional findByTargetControllerId(String controllerId) {
+ return targetTypeRepository
+ .findOne(TargetTypeSpecification.hasTargetControllerId(controllerId)).map(TargetType.class::cast);
+ }
+
+ @Override
+ public List findByTargetControllerIds(Collection controllerIds) {
+ return targetTypeRepository.findAll(TargetTypeSpecification.hasTargetControllerIdIn(controllerIds))
+ .stream().map(TargetType.class::cast).collect(Collectors.toList());
+ }
+
+ @Override
+ public List get(Collection 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 distributionSetTypeIds) {
+ final Collection 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 convertPage(final Page findAll, final Pageable pageable) {
+ return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
+ }
+
+ private static Slice convertPage(final Slice findAll, final Pageable pageable) {
+ return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
index db37513bb..b9d8e66e4 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
@@ -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());
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
index 0746d509f..59d7501a5 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
@@ -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,
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
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java
new file mode 100644
index 000000000..c23936155
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java
@@ -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, JpaSpecificationExecutor {
+
+ /**
+ * 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 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 findAllById(Iterable ids) {
+ final List 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 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 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 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 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 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 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 findByName(String name){
+ return this.findOne(Specification.where(TargetTypeSpecification.hasName(name)));
+ };
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetBuilder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetBuilder.java
index b9c07b7db..48f7109e2 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetBuilder.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetBuilder.java
@@ -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);
}
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java
index b76394985..d1701431a 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java
@@ -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 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 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));
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeBuilder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeBuilder.java
new file mode 100644
index 000000000..2ab280248
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeBuilder.java
@@ -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);
+ }
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeCreate.java
new file mode 100644
index 000000000..67a0d7b55
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetTypeCreate.java
@@ -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
+ 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 findDistributionSetTypeWithExceptionIfNotFound(
+ final Collection distributionSetTypeId) {
+ if (CollectionUtils.isEmpty(distributionSetTypeId)) {
+ return Collections.emptyList();
+ }
+
+ final Collection type = distributionSetTypeManagement.get(distributionSetTypeId);
+ if (type.size() < distributionSetTypeId.size()) {
+ throw new EntityNotFoundException(SoftwareModuleType.class, distributionSetTypeId);
+ }
+
+ return type;
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java
index c81f66b44..b55ac92a5 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java
@@ -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 compatibleToTargetTypes;
+
public JpaDistributionSetType() {
// default public constructor for JPA
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java
index 92ecb7c40..a3181d05e 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java
@@ -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 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 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 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 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 getControllerAttributes() {
return controllerAttributes;
}
@@ -361,6 +427,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return requestControllerAttributes;
}
+ /**
+ * @return target metadata
+ */
public List 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;
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetType.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetType.java
new file mode 100644
index 000000000..cbfe0e725
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetType.java
@@ -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 distributionSetTypes;
+
+ @OneToMany(targetEntity = JpaTarget.class, mappedBy = "targetType", fetch = FetchType.LAZY)
+ private Set 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 getCompatibleDistributionSetTypes() {
+
+ if (distributionSetTypes == null) {
+ return Collections.emptySet();
+ }
+
+ return Collections.unmodifiableSet(distributionSetTypes);
+ }
+
+ @Override
+ public Set 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()));
+ }
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetTypeSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetTypeSpecification.java
new file mode 100644
index 000000000..734e7e986
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetTypeSpecification.java
@@ -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 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 hasTarget(final long id) {
+ return (targetRoot, query, cb) -> {
+ final SetJoin 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 hasTarget(final Collection ids) {
+ return (targetRoot, query, cb) -> {
+ final SetJoin 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 hasTargetControllerId(final String controllerId) {
+ return (targetRoot, query, cb) -> {
+ final SetJoin 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 hasTargetControllerIdIn(final Collection controllerIds) {
+ return (targetRoot, query, cb) -> {
+ final SetJoin 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 hasIdIn(final Collection 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 hasDsSetType(final Long dsTypeId) {
+ return (targetRoot, query, cb) -> {
+ final SetJoin 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 hasName(final String name) {
+ return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaTargetType_.name), name);
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_18__add_target_type___DB2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_18__add_target_type___DB2.sql
new file mode 100644
index 000000000..fed3796ff
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_18__add_target_type___DB2.sql
@@ -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;
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_18__add_target_type___H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_18__add_target_type___H2.sql
new file mode 100644
index 000000000..023b8fd96
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_18__add_target_type___H2.sql
@@ -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;
+
+
+
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_18__add_target_type___MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_18__add_target_type___MYSQL.sql
new file mode 100644
index 000000000..c6435a63d
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_18__add_target_type___MYSQL.sql
@@ -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;
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_18__add_target_type___POSTGRESQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_18__add_target_type___POSTGRESQL.sql
new file mode 100644
index 000000000..8acd8038d
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_18__add_target_type___POSTGRESQL.sql
@@ -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;
\ No newline at end of file
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_18__add_target_type___SQL_SERVER.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_18__add_target_type___SQL_SERVER.sql
new file mode 100644
index 000000000..924e0681c
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_18__add_target_type___SQL_SERVER.sql
@@ -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;
+
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java
index 4b4cf1c05..d3c45fa1f 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java
@@ -76,6 +76,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected TargetTagRepository targetTagRepository;
+ @Autowired
+ protected TargetTypeRepository targetTypeRepository;
+
@Autowired
protected DistributionSetTagRepository distributionSetTagRepository;
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java
index ae5549eea..de19b5575 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java
@@ -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 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 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 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 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 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 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() {
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java
new file mode 100644
index 000000000..1e9b7a740
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java
@@ -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 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 targetTypeWithDsTypes = targetTypeRepository.findById(targetType.getId());
+ assertThat(targetTypeWithDsTypes).isPresent();
+ assertThat(targetTypeWithDsTypes.get().getCompatibleDistributionSetTypes()).extracting("key").contains("testDst1");
+ targetTypeManagement.unassignDistributionSetType(targetType.getId(),distributionSetType.getId());
+ Optional 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")));
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java
index ced4021d2..f049acc48 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java
@@ -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 {
diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java
index 9937a9c28..860d14e66 100644
--- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java
+++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java
@@ -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;
diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JpaTestRepositoryManagement.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JpaTestRepositoryManagement.java
index 894e1d302..80309c8f8 100644
--- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JpaTestRepositoryManagement.java
+++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/JpaTestRepositoryManagement.java
@@ -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);
}
});
}
diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java
index 54fbaab89..4f356a336 100644
--- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java
+++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java
@@ -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 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 createTargetTypes(final String targetTypePrefix, int count) {
+ final List 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);
+ }
+
}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionsettype/MgmtDistributionSetTypeAssignment.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionsettype/MgmtDistributionSetTypeAssignment.java
new file mode 100644
index 000000000..c838c9849
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/distributionsettype/MgmtDistributionSetTypeAssignment.java
@@ -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 {
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java
index 7553cb947..49fd2ca11 100644
--- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTarget.java
@@ -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;
}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java
index e526ad44e..2c7541f0f 100644
--- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtTargetRequestBody.java
@@ -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;
}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetType.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetType.java
new file mode 100644
index 000000000..9c3f432ad
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetType.java
@@ -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;
+ }
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPost.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPost.java
new file mode 100644
index 000000000..8adc15c72
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPost.java
@@ -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 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 getCompatibleDsTypes() {
+ return compatibledistributionsettypes;
+ }
+
+ /**
+ * @param compatibleDsTypes
+ * the compatible ds types to set
+ *
+ * @return updated body
+ */
+ public MgmtTargetTypeRequestBodyPost setCompatibleDsTypes(
+ final List compatibleDsTypes) {
+ this.compatibledistributionsettypes = compatibleDsTypes;
+ return this;
+ }
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPut.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPut.java
new file mode 100644
index 000000000..139116653
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targettype/MgmtTargetTypeRequestBodyPut.java
@@ -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;
+ }
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java
index cbb7bf8ed..bd742ccf7 100644
--- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java
@@ -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.
*
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
index e51cfd19e..dd1c107ba 100644
--- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
@@ -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 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 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 assignTargetType(@PathVariable("targetId") String targetId, MgmtId targetTypeId);
+
/**
* Handles the GET request of retrieving the attributes of a specific
* target.
diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTypeRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTypeRestApi.java
new file mode 100644
index 000000000..f221e5248
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetTypeRestApi.java
@@ -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> 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 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 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 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> createTargetTypes(List 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> 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 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 addCompatibleDistributionSets(@PathVariable("targetTypeId") final Long targetTypeId,
+ final List distributionSetTypeIds);
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeMapper.java
index dfd3b2896..7f438eaae 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeMapper.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetTypeMapper.java
@@ -67,7 +67,7 @@ final class MgmtDistributionSetTypeMapper {
.orElse(Collections.emptyList());
}
- static List toListResponse(final List types) {
+ static List toListResponse(final Collection types) {
if (types == null) {
return Collections.emptyList();
}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java
index bc6162a14..e2c1b9fec 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java
@@ -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 fromRequestTargetMetadata(final List metadata,
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
index cc750fe09..f7b1e4f04 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
@@ -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 unassignTargetType(@PathVariable("targetId") final String targetId) {
+ this.targetManagement.unAssignType(targetId);
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ public ResponseEntity assignTargetType(@PathVariable("targetId") final String targetId, @RequestBody final MgmtId targetTypeId) {
+ this.targetManagement.assignType(targetId, targetTypeId.getId());
+ return ResponseEntity.ok().build();
+ }
+
@Override
public ResponseEntity getAttributes(@PathVariable("targetId") final String targetId) {
final Map controllerAttributes = targetManagement.getControllerAttributes(targetId);
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeMapper.java
new file mode 100644
index 000000000..ab5dda58e
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeMapper.java
@@ -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 targetFromRequest(final EntityFactory entityFactory,
+ final Collection 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 getDistributionSets(final MgmtTargetTypeRequestBodyPost targetTypesRest) {
+ return Optional.ofNullable(targetTypesRest.getCompatibleDsTypes())
+ .map(ds -> ds.stream().map(MgmtDistributionSetTypeAssignment::getId).collect(Collectors.toList()))
+ .orElse(Collections.emptyList());
+ }
+
+ static List toListResponse(final List 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));
+ }
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java
new file mode 100644
index 000000000..029b0023b
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResource.java
@@ -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> 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 findTargetTypesAll;
+ long countTargetTypesAll;
+ if (rsqlParam != null) {
+ findTargetTypesAll= targetTypeManagement.findByRsql(pageable, rsqlParam);
+ } else {
+ findTargetTypesAll = targetTypeManagement.findAll(pageable);
+ }
+ countTargetTypesAll = findTargetTypesAll.getTotalElements();
+
+ final List rest = MgmtTargetTypeMapper.toListResponse(findTargetTypesAll.getContent());
+ return ResponseEntity.ok(new PagedList<>(rest, countTargetTypesAll));
+ }
+
+ @Override
+ public ResponseEntity 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 deleteTargetType(@PathVariable("targetTypeId") final Long targetTypeId) {
+ LOG.debug("Delete {} target type", targetTypeId);
+ targetTypeManagement.delete(targetTypeId);
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ public ResponseEntity 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> createTargetTypes(
+ @RequestBody final List targetTypes) {
+
+ final List createdTargetTypes = targetTypeManagement
+ .create(MgmtTargetTypeMapper.targetFromRequest(entityFactory, targetTypes));
+ return ResponseEntity.status(HttpStatus.CREATED).body(MgmtTargetTypeMapper.toListResponse(createdTargetTypes));
+ }
+
+ @Override
+ public ResponseEntity> getCompatibleDistributionSets(
+ @PathVariable("targetTypeId") final Long targetTypeId) {
+
+ final TargetType foundType = findTargetTypeWithExceptionIfNotFound(targetTypeId);
+ return ResponseEntity
+ .ok(MgmtDistributionSetTypeMapper.toListResponse(foundType.getCompatibleDistributionSetTypes()));
+ }
+
+ @Override
+ public ResponseEntity removeCompatibleDistributionSet(@PathVariable("targetTypeId") final Long targetTypeId,
+ @PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
+
+ targetTypeManagement.unassignDistributionSetType(targetTypeId, distributionSetTypeId);
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ public ResponseEntity addCompatibleDistributionSets(@PathVariable("targetTypeId") final Long targetTypeId,
+ @RequestBody final List 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));
+ }
+
+}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java
index b9fd6df59..305995c44 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java
@@ -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
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
index d3aad4cae..7c60aa6b9 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
@@ -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 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 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 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 = 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 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();
+ }
+
}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java
new file mode 100644
index 000000000..7fb7cbc93
--- /dev/null
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java
@@ -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 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 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 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 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 dsTypes) {
+ TargetType targetType = testdataFactory.createTargetType(name, dsTypes);
+ assertThat(targetType.getOptLockRevision()).isEqualTo(1);
+ return targetType;
+ }
+
+ @Step
+ private List buildTestTargetTypesWithoutDsTypes(String namePrefix, int count) {
+ final List types = new ArrayList<>();
+ for (int index = 0; index < count; index++) {
+ types.add(buildTestTargetTypeBody(namePrefix + index));
+ }
+ return types;
+ }
+
+ @Step
+ private void runPostTargetTypeAndVerify(final List 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);
+ }
+
+}
diff --git a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java
index 283c7f59a..2480a2118 100644
--- a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java
+++ b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java
@@ -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) {
diff --git a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java
index 2c537525d..1046a7346 100644
--- a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java
+++ b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/util/JsonBuilder.java
@@ -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 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 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 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,
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/targettypes-api-guide.adoc b/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/targettypes-api-guide.adoc
new file mode 100644
index 000000000..9534c8fde
--- /dev/null
+++ b/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/targettypes-api-guide.adoc
@@ -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 <>
+include::../errors/405.adoc[]
+include::../errors/406.adoc[]
+| `409 Conflict`
+| Target type already exists
+| See <>
+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 <>
+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 <>
+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 <>
+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 <>
+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 <>
+include::../errors/405.adoc[]
+include::../errors/406.adoc[]
+| `409 Conflict`
+| Distribution set type already exists
+| See <>
+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 <>
+include::../errors/405.adoc[]
+include::../errors/406.adoc[]
+include::../errors/429.adoc[]
+|===
+
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/ApiModelPropertiesGeneric.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/ApiModelPropertiesGeneric.java
index 1dfefb9d6..869f4af96 100644
--- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/ApiModelPropertiesGeneric.java
+++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/ApiModelPropertiesGeneric.java
@@ -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
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java
index 43e1a2abf..188cd4b84 100644
--- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java
+++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/documentation/MgmtApiModelProperties.java
@@ -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";
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetTypesDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetTypesDocumentationTest.java
new file mode 100644
index 000000000..053ed29b3
--- /dev/null
+++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/TargetTypesDocumentationTest.java
@@ -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 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))));
+
+ }
+}
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java
index 420f1e828..222bbf36b 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java
@@ -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