Move Mgmt artifacts into hawkbit-mgmt (#2003)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-11 15:57:56 +02:00
committed by GitHub
parent 05d8d6cc7e
commit baab2fcf95
200 changed files with 167 additions and 114 deletions

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2022 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MgmtActionMapper {
/**
* Create a response for actions.
*
* @param actions list of actions
* @param repMode the representation mode
* @return the response
*/
public static List<MgmtAction> toResponse(final Collection<Action> actions, final MgmtRepresentationMode repMode) {
if (actions == null) {
return Collections.emptyList();
}
return new ResponseList<>(actions.stream()
.map(action -> toResponse(action, repMode))
.collect(Collectors.toList()));
}
static MgmtAction toResponse(final Action action, final MgmtRepresentationMode repMode) {
final String controllerId = action.getTarget().getControllerId();
if (repMode == MgmtRepresentationMode.COMPACT) {
return MgmtTargetMapper.toResponse(controllerId, action);
}
return MgmtTargetMapper.toResponseWithLinks(controllerId, action);
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2022 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtActionRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@ConditionalOnProperty(name = "hawkbit.rest.MgmtActionResource.enabled", matchIfMissing = true)
public class MgmtActionResource implements MgmtActionRestApi {
private final DeploymentManagement deploymentManagement;
MgmtActionResource(final DeploymentManagement deploymentManagement) {
this.deploymentManagement = deploymentManagement;
}
@Override
public ResponseEntity<PagedList<MgmtAction>> getActions(final int pagingOffsetParam, final int pagingLimitParam,
final String sortParam, final String rsqlParam, final String representationModeParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeActionSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<Action> actions;
final Long totalActionCount;
if (rsqlParam != null) {
actions = this.deploymentManagement.findActions(rsqlParam, pageable);
totalActionCount = this.deploymentManagement.countActions(rsqlParam);
} else {
actions = this.deploymentManagement.findActionsAll(pageable);
totalActionCount = this.deploymentManagement.countActionsAll();
}
final MgmtRepresentationMode repMode = getRepresentationModeFromString(representationModeParam);
return ResponseEntity
.ok(new PagedList<>(MgmtActionMapper.toResponse(actions.getContent(), repMode), totalActionCount));
}
@Override
public ResponseEntity<MgmtAction> getAction(final Long actionId) {
final Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
return ResponseEntity.ok(MgmtActionMapper.toResponse(action, MgmtRepresentationMode.FULL));
}
private MgmtRepresentationMode getRepresentationModeFromString(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam)
.orElseGet(() -> {
// no need for a 400, just apply a safe fallback
log.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import org.eclipse.hawkbit.rest.OpenApiConfiguration;
import org.eclipse.hawkbit.rest.RestConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Controller;
/**
* Enable {@link ComponentScan} in the resource package to setup all
* {@link Controller} annotated classes and setup the REST-Resources for the
* Management API.
*/
@Configuration
@ComponentScan
@Import({ RestConfiguration.class, OpenApiConfiguration.class })
@PropertySource("classpath:/hawkbit-mgmt-api-defaults.properties")
public class MgmtApiConfiguration {
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import org.eclipse.hawkbit.mgmt.json.model.auth.MgmtUserInfo;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtBasicAuthRestApi;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling basic auth validation.
*/
@RestController
public class MgmtBasicAuthResource implements MgmtBasicAuthRestApi {
private final TenantAware tenantAware;
/**
* Default constructor
*
* @param tenantAware tenantAware
*/
public MgmtBasicAuthResource(TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
@Override
public ResponseEntity<MgmtUserInfo> validateBasicAuth() {
MgmtUserInfo userInfo = new MgmtUserInfo();
userInfo.setUsername(tenantAware.getCurrentUsername());
userInfo.setTenant(tenantAware.getCurrentTenant());
return ResponseEntity.ok(userInfo);
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindowRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignment;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
/**
* A mapper for assignment requests
*/
public final class MgmtDeploymentRequestMapper {
private MgmtDeploymentRequestMapper() {
// Utility class
}
/**
* Convert assignment information to an {@link DeploymentRequestBuilder}
*
* @param dsAssignment DS assignment information
* @param targetId target to assign the DS to
* @return resulting {@link DeploymentRequestBuilder}
*/
public static DeploymentRequestBuilder createAssignmentRequestBuilder(
final MgmtDistributionSetAssignment dsAssignment, final String targetId) {
return createAssignmentRequestBuilder(targetId, dsAssignment.getId(), dsAssignment.getType(),
dsAssignment.getForcetime(), dsAssignment.getWeight(), dsAssignment.getMaintenanceWindow());
}
/**
* Convert assignment information to an {@link DeploymentRequestBuilder}
*
* @param targetAssignment target assignment information
* @param dsId DS to assign the target to
* @return resulting {@link DeploymentRequestBuilder}
*/
public static DeploymentRequestBuilder createAssignmentRequestBuilder(
final MgmtTargetAssignmentRequestBody targetAssignment, final Long dsId) {
return createAssignmentRequestBuilder(targetAssignment.getId(), dsId, targetAssignment.getType(),
targetAssignment.getForcetime(), targetAssignment.getWeight(), targetAssignment.getMaintenanceWindow());
}
private static DeploymentRequestBuilder createAssignmentRequestBuilder(final String targetId, final Long dsId,
final MgmtActionType type, final long forcetime, final Integer weight,
final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
final DeploymentRequestBuilder request = DeploymentManagement.deploymentRequest(targetId, dsId)
.setActionType(MgmtRestModelMapper.convertActionType(type)).setForceTime(forcetime).setWeight(weight);
if (maintenanceWindow != null) {
final String cronSchedule = maintenanceWindow.getSchedule();
final String duration = maintenanceWindow.getDuration();
final String timezone = maintenanceWindow.getTimezone();
MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone);
request.setMaintenance(cronSchedule, duration, timezone);
}
return request;
}
}

View File

@@ -0,0 +1,189 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionId;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MgmtDistributionSetMapper {
/**
* {@link MgmtDistributionSetRequestBodyPost}s to {@link DistributionSet}s.
*
* @param sets to convert
* @return converted list of {@link DistributionSet}s
*/
static List<DistributionSetCreate> dsFromRequest(final Collection<MgmtDistributionSetRequestBodyPost> sets,
final EntityFactory entityFactory) {
return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).collect(Collectors.toList());
}
static List<MetaData> fromRequestDsMetadata(final List<MgmtMetadata> metadata, final EntityFactory entityFactory) {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.stream()
.map(metadataRest -> entityFactory.generateDsMetadata(metadataRest.getKey(), metadataRest.getValue()))
.collect(Collectors.toList());
}
static MgmtDistributionSet toResponse(final DistributionSet distributionSet) {
if (distributionSet == null) {
return null;
}
final MgmtDistributionSet response = new MgmtDistributionSet();
MgmtRestModelMapper.mapNamedToNamed(response, distributionSet);
response.setDsId(distributionSet.getId());
response.setVersion(distributionSet.getVersion());
response.setComplete(distributionSet.isComplete());
response.setType(distributionSet.getType().getKey());
response.setTypeName(distributionSet.getType().getName());
response.setLocked(distributionSet.isLocked());
response.setDeleted(distributionSet.isDeleted());
response.setValid(distributionSet.isValid());
distributionSet.getModules()
.forEach(module -> response.getModules().add(MgmtSoftwareModuleMapper.toResponse(module)));
response.setRequiredMigrationStep(distributionSet.isRequiredMigrationStep());
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(response.getDsId()))
.withSelfRel().expand());
return response;
}
static void addLinks(final DistributionSet distributionSet, final MgmtDistributionSet response) {
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getAssignedSoftwareModules(response.getDsId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null))
.withRel(MgmtRestConstants.DISTRIBUTIONSET_V1_MODULE).expand());
response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class)
.getDistributionSetType(distributionSet.getType().getId())).withRel("type").expand());
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getDsId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata")
.expand());
}
static MgmtTargetAssignmentResponseBody toResponse(final DistributionSetAssignmentResult dsAssignmentResult) {
final MgmtTargetAssignmentResponseBody result = new MgmtTargetAssignmentResponseBody();
result.setAlreadyAssigned(dsAssignmentResult.getAlreadyAssigned());
result.setAssignedActions(dsAssignmentResult.getAssignedEntity().stream()
.map(a -> new MgmtActionId(a.getTarget().getControllerId(), a.getId())).collect(Collectors.toList()));
return result;
}
static MgmtTargetAssignmentResponseBody toResponse(
final List<DistributionSetAssignmentResult> dsAssignmentResults) {
final MgmtTargetAssignmentResponseBody result = new MgmtTargetAssignmentResponseBody();
final int alreadyAssigned = dsAssignmentResults.stream()
.mapToInt(DistributionSetAssignmentResult::getAlreadyAssigned).sum();
final List<MgmtActionId> assignedActions = dsAssignmentResults.stream()
.flatMap(assignmentResult -> assignmentResult.getAssignedEntity().stream())
.map(action -> new MgmtActionId(action.getTarget().getControllerId(), action.getId()))
.collect(Collectors.toList());
result.setAlreadyAssigned(alreadyAssigned);
result.setAssignedActions(assignedActions);
return result;
}
static List<MgmtDistributionSet> toResponseDistributionSets(final Collection<DistributionSet> sets) {
if (sets == null) {
return Collections.emptyList();
}
return new ResponseList<>(
sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList()));
}
static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) {
final MgmtMetadata metadataRest = new MgmtMetadata();
metadataRest.setKey(metadata.getKey());
metadataRest.setValue(metadata.getValue());
return metadataRest;
}
static List<MgmtMetadata> toResponseDsMetadata(final List<DistributionSetMetadata> metadata) {
final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size());
for (final DistributionSetMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseDsMetadata(distributionSetMetadata));
}
return mappedList;
}
static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) {
if (sets == null) {
return Collections.emptyList();
}
return sets.stream().map(MgmtDistributionSetMapper::toResponse).collect(Collectors.toList());
}
/**
* {@link MgmtDistributionSetRequestBodyPost} to {@link DistributionSet}.
*
* @param dsRest to convert
* @return converted {@link DistributionSet}
*/
private static DistributionSetCreate fromRequest(final MgmtDistributionSetRequestBodyPost dsRest,
final EntityFactory entityFactory) {
final List<Long> modules = new ArrayList<>();
if (dsRest.getOs() != null) {
modules.add(dsRest.getOs().getId());
}
if (dsRest.getApplication() != null) {
modules.add(dsRest.getApplication().getId());
}
if (dsRest.getRuntime() != null) {
modules.add(dsRest.getRuntime().getId());
}
if (dsRest.getModules() != null) {
dsRest.getModules().forEach(module -> modules.add(module.getId()));
}
return entityFactory.distributionSet().create().name(dsRest.getName()).version(dsRest.getVersion())
.description(dsRest.getDescription()).type(dsRest.getType()).modules(modules)
.requiredMigrationStep(dsRest.getRequiredMigrationStep());
}
}

View File

@@ -0,0 +1,463 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.text.MessageFormat;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetStatistics;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtInvalidateDistributionSetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
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.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
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 DistributionSet} CRUD operations.
*/
@Slf4j
@RestController
public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
private final SoftwareModuleManagement softwareModuleManagement;
private final TargetManagement targetManagement;
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final DeploymentManagement deployManagament;
private final SystemManagement systemManagement;
private final EntityFactory entityFactory;
private final DistributionSetManagement distributionSetManagement;
private final DistributionSetTypeManagement distributionSetTypeManagement;
private final SystemSecurityContext systemSecurityContext;
private final DistributionSetInvalidationManagement distributionSetInvalidationManagement;
private final TenantConfigHelper tenantConfigHelper;
MgmtDistributionSetResource(final SoftwareModuleManagement softwareModuleManagement,
final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement,
final DeploymentManagement deployManagament, final SystemManagement systemManagement,
final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final SystemSecurityContext systemSecurityContext,
final DistributionSetInvalidationManagement distributionSetInvalidationManagement,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.softwareModuleManagement = softwareModuleManagement;
this.targetManagement = targetManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.deployManagament = deployManagament;
this.systemManagement = systemManagement;
this.entityFactory = entityFactory;
this.distributionSetManagement = distributionSetManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement;
this.systemSecurityContext = systemSecurityContext;
this.distributionSetInvalidationManagement = distributionSetInvalidationManagement;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
}
@Override
public ResponseEntity<PagedList<MgmtDistributionSet>> getDistributionSets(
@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.sanitizeDistributionSetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSet> findDsPage;
final long countModulesAll;
if (rsqlParam != null) {
findDsPage = distributionSetManagement.findByRsql(pageable, rsqlParam);
countModulesAll = ((Page<DistributionSet>) findDsPage).getTotalElements();
} else {
findDsPage = distributionSetManagement.findAll(pageable);
countModulesAll = distributionSetManagement.count();
}
final List<MgmtDistributionSet> rest = MgmtDistributionSetMapper.toResponseFromDsList(findDsPage.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
}
@Override
public ResponseEntity<MgmtDistributionSet> getDistributionSet(
@PathVariable("distributionSetId") final Long distributionSetId) {
final DistributionSet foundDs = distributionSetManagement.getOrElseThrowException(distributionSetId);
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(foundDs);
MgmtDistributionSetMapper.addLinks(foundDs, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtDistributionSet>> createDistributionSets(
@RequestBody final List<MgmtDistributionSetRequestBodyPost> sets) {
log.debug("creating {} distribution sets", sets.size());
// set default Ds type if ds type is null
final String defaultDsKey = systemSecurityContext
.runAsSystem(systemManagement.getTenantMetadata().getDefaultDsType()::getKey);
sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(defaultDsKey));
//check if there is already deleted DS Type
for (MgmtDistributionSetRequestBodyPost ds : sets) {
final Optional<DistributionSetType> opt = distributionSetTypeManagement.getByKey(ds.getType());
opt.ifPresent(dsType -> {
if (dsType.isDeleted()) {
final String text = "Cannot create Distribution Set from type with key {0}. Distribution Set Type already deleted!";
final String message = MessageFormat.format(text, dsType.getKey());
throw new ValidationException(message);
}
});
}
final Collection<DistributionSet> createdDSets = distributionSetManagement
.create(MgmtDistributionSetMapper.dsFromRequest(sets, entityFactory));
log.debug("{} distribution sets created, return status {}", sets.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtDistributionSetMapper.toResponseDistributionSets(createdDSets),
HttpStatus.CREATED);
}
@Override
public ResponseEntity<Void> deleteDistributionSet(@PathVariable("distributionSetId") final Long distributionSetId) {
distributionSetManagement.delete(distributionSetId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtDistributionSet> updateDistributionSet(
@PathVariable("distributionSetId") final Long distributionSetId,
@RequestBody final MgmtDistributionSetRequestBodyPut toUpdate) {
final DistributionSet updated = distributionSetManagement.update(entityFactory.distributionSet()
.update(distributionSetId).name(toUpdate.getName()).description(toUpdate.getDescription())
.version(toUpdate.getVersion()).locked(toUpdate.getLocked())
.requiredMigrationStep(toUpdate.getRequiredMigrationStep()));
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(updated);
MgmtDistributionSetMapper.addLinks(updated, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<PagedList<MgmtTarget>> getAssignedTargets(
@PathVariable("distributionSetId") final Long distributionSetId,
@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.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> targetsAssignedDS;
if (rsqlParam != null) {
targetsAssignedDS = this.targetManagement.findByAssignedDistributionSetAndRsql(pageable, distributionSetId,
rsqlParam);
} else {
targetsAssignedDS = this.targetManagement.findByAssignedDistributionSet(pageable, distributionSetId);
}
return ResponseEntity
.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsAssignedDS.getContent(), tenantConfigHelper),
targetsAssignedDS.getTotalElements()));
}
@Override
public ResponseEntity<PagedList<MgmtTarget>> getInstalledTargets(
@PathVariable("distributionSetId") final Long distributionSetId,
@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) {
// check if distribution set exists otherwise throw exception
// immediately
distributionSetManagement.getOrElseThrowException(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> targetsInstalledDS;
if (rsqlParam != null) {
targetsInstalledDS = this.targetManagement.findByInstalledDistributionSetAndRsql(pageable,
distributionSetId, rsqlParam);
} else {
targetsInstalledDS = this.targetManagement.findByInstalledDistributionSet(pageable, distributionSetId);
}
return ResponseEntity
.ok(new PagedList<>(MgmtTargetMapper.toResponse(targetsInstalledDS.getContent(), tenantConfigHelper),
targetsInstalledDS.getTotalElements()));
}
@Override
public ResponseEntity<PagedList<MgmtTargetFilterQuery>> getAutoAssignTargetFilterQueries(
@PathVariable("distributionSetId") final Long distributionSetId,
@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.sanitizeTargetFilterQuerySortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<TargetFilterQuery> targetFilterQueries = targetFilterQueryManagement
.findByAutoAssignDSAndRsql(pageable, distributionSetId, rsqlParam);
return ResponseEntity
.ok(new PagedList<>(MgmtTargetFilterQueryMapper.toResponse(targetFilterQueries.getContent(),
tenantConfigHelper.isConfirmationFlowEnabled(), false), targetFilterQueries.getTotalElements()));
}
@Override
public ResponseEntity<MgmtTargetAssignmentResponseBody> createAssignedTarget(
@PathVariable("distributionSetId") final Long distributionSetId,
@RequestBody final List<MgmtTargetAssignmentRequestBody> assignments,
@RequestParam(value = "offline", required = false) final Boolean offline) {
if (offline != null && offline) {
final List<Entry<String, Long>> offlineAssignments = assignments.stream()
.map(assignment -> new SimpleEntry<>(assignment.getId(), distributionSetId))
.collect(Collectors.toList());
return ResponseEntity.ok(MgmtDistributionSetMapper
.toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
}
final List<DeploymentRequest> deploymentRequests = assignments.stream().map(dsAssignment -> {
final boolean isConfirmationRequired = dsAssignment.getConfirmationRequired() == null
? tenantConfigHelper.isConfirmationFlowEnabled()
: dsAssignment.getConfirmationRequired();
return MgmtDeploymentRequestMapper.createAssignmentRequestBuilder(dsAssignment, distributionSetId)
.setConfirmationRequired(isConfirmationRequired).build();
}).collect(Collectors.toList());
final List<DistributionSetAssignmentResult> assignmentResults = deployManagament
.assignDistributionSets(deploymentRequests);
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignmentResults));
}
@Override
public ResponseEntity<PagedList<MgmtMetadata>> getMetadata(
@PathVariable("distributionSetId") final Long distributionSetId,
@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.sanitizeDistributionSetMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<DistributionSetMetadata> metaDataPage;
if (rsqlParam != null) {
metaDataPage = distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(pageable, distributionSetId,
rsqlParam);
} else {
metaDataPage = distributionSetManagement.findMetaDataByDistributionSetId(pageable, distributionSetId);
}
return ResponseEntity
.ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metaDataPage.getContent()),
metaDataPage.getTotalElements()));
}
@Override
public ResponseEntity<MgmtMetadata> getMetadataValue(
@PathVariable("distributionSetId") final Long distributionSetId,
@PathVariable("metadataKey") final String metadataKey) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSetMetadata findOne = distributionSetManagement
.getMetaDataByDistributionSetId(distributionSetId, metadataKey)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, distributionSetId,
metadataKey));
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(findOne));
}
@Override
public ResponseEntity<MgmtMetadata> updateMetadata(@PathVariable("distributionSetId") final Long distributionSetId,
@PathVariable("metadataKey") final String metadataKey, @RequestBody final MgmtMetadataBodyPut metadata) {
// check if distribution set exists otherwise throw exception
// immediately
final DistributionSetMetadata updated = distributionSetManagement.updateMetaData(distributionSetId,
entityFactory.generateDsMetadata(metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(updated));
}
@Override
public ResponseEntity<Void> deleteMetadata(@PathVariable("distributionSetId") final Long distributionSetId,
@PathVariable("metadataKey") final String metadataKey) {
// check if distribution set exists otherwise throw exception
// immediately
distributionSetManagement.deleteMetaData(distributionSetId, metadataKey);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<List<MgmtMetadata>> createMetadata(
@PathVariable("distributionSetId") final Long distributionSetId,
@RequestBody final List<MgmtMetadata> metadataRest) {
// check if distribution set exists otherwise throw exception
// immediately
final List<DistributionSetMetadata> created = distributionSetManagement.createMetaData(distributionSetId,
MgmtDistributionSetMapper.fromRequestDsMetadata(metadataRest, entityFactory));
return new ResponseEntity<>(MgmtDistributionSetMapper.toResponseDsMetadata(created), HttpStatus.CREATED);
}
@Override
public ResponseEntity<Void> assignSoftwareModules(@PathVariable("distributionSetId") final Long distributionSetId,
@RequestBody final List<MgmtSoftwareModuleAssigment> softwareModuleIDs) {
distributionSetManagement.assignSoftwareModules(distributionSetId,
softwareModuleIDs.stream().map(MgmtSoftwareModuleAssigment::getId).collect(Collectors.toList()));
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> deleteAssignSoftwareModules(
@PathVariable("distributionSetId") final Long distributionSetId,
@PathVariable("softwareModuleId") final Long softwareModuleId) {
distributionSetManagement.unassignSoftwareModule(distributionSetId, softwareModuleId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtSoftwareModule>> getAssignedSoftwareModules(
@PathVariable("distributionSetId") final Long distributionSetId,
@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) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<SoftwareModule> softwaremodules = softwareModuleManagement.findByAssignedTo(pageable,
distributionSetId);
return ResponseEntity.ok(new PagedList<>(MgmtSoftwareModuleMapper.toResponse(softwaremodules.getContent()),
softwaremodules.getTotalElements()));
}
@Override
public ResponseEntity<MgmtDistributionSetStatistics> getRolloutsCountByStatusForDistributionSet(Long distributionSetId) {
MgmtDistributionSetStatistics.Builder statistics = new MgmtDistributionSetStatistics.Builder(false);
distributionSetManagement.countRolloutsByStatusForDistributionSet(distributionSetId).forEach(statistic ->
statistics.addTotalRolloutPerStatus(String.valueOf(statistic.getName()), Long.parseLong(statistic.getData().toString())));
return ResponseEntity.ok(statistics.build());
}
@Override
public ResponseEntity<MgmtDistributionSetStatistics> getActionsCountByStatusForDistributionSet(Long distributionSetId) {
MgmtDistributionSetStatistics.Builder statistics = new MgmtDistributionSetStatistics.Builder(false);
distributionSetManagement.countActionsByStatusForDistributionSet(distributionSetId).forEach(statistic ->
statistics.addTotalActionPerStatus(String.valueOf(statistic.getName()), Long.parseLong(statistic.getData().toString())));
return ResponseEntity.ok(statistics.build());
}
@Override
public ResponseEntity<MgmtDistributionSetStatistics> getAutoAssignmentsCountForDistributionSet(Long distributionSetId) {
MgmtDistributionSetStatistics.Builder statistics = new MgmtDistributionSetStatistics.Builder(false);
statistics.addTotalAutoAssignments(distributionSetManagement.countAutoAssignmentsForDistributionSet(distributionSetId));
return ResponseEntity.ok(statistics.build());
}
@Override
public ResponseEntity<MgmtDistributionSetStatistics> getStatisticsForDistributionSet(Long distributionSetId) {
MgmtDistributionSetStatistics.Builder statistics = new MgmtDistributionSetStatistics.Builder(true);
distributionSetManagement.countRolloutsByStatusForDistributionSet(distributionSetId).forEach(statistic ->
statistics.addTotalRolloutPerStatus(String.valueOf(statistic.getName()), Long.parseLong(statistic.getData().toString())));
distributionSetManagement.countActionsByStatusForDistributionSet(distributionSetId).forEach(statistic ->
statistics.addTotalActionPerStatus(String.valueOf(statistic.getName()), Long.parseLong(statistic.getData().toString())));
statistics.addTotalAutoAssignments(distributionSetManagement.countAutoAssignmentsForDistributionSet(distributionSetId));
return ResponseEntity.ok(statistics.build());
}
@Override
public ResponseEntity<Void> invalidateDistributionSet(
@PathVariable("distributionSetId") final Long distributionSetId,
@Valid @RequestBody final MgmtInvalidateDistributionSetRequestBody invalidateRequestBody) {
distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(Arrays.asList(distributionSetId),
MgmtRestModelMapper.convertCancelationType(invalidateRequestBody.getActionCancelationType()),
invalidateRequestBody.isCancelRollouts()));
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,251 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtAssignedDistributionSetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtDistributionSetTagAssigmentResult;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTagRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
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 DistributionSetTag} CRUD operations.
*/
@Slf4j
@RestController
public class MgmtDistributionSetTagResource implements MgmtDistributionSetTagRestApi {
private final DistributionSetTagManagement distributionSetTagManagement;
private final DistributionSetManagement distributionSetManagement;
private final EntityFactory entityFactory;
MgmtDistributionSetTagResource(final DistributionSetTagManagement distributionSetTagManagement,
final DistributionSetManagement distributionSetManagement, final EntityFactory entityFactory) {
this.distributionSetTagManagement = distributionSetTagManagement;
this.distributionSetManagement = distributionSetManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<PagedList<MgmtTag>> getDistributionSetTags(
@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.sanitizeTagSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSetTag> distributionSetTags;
final long count;
if (rsqlParam == null) {
distributionSetTags = distributionSetTagManagement.findAll(pageable);
count = distributionSetTagManagement.count();
} else {
final Page<DistributionSetTag> page = distributionSetTagManagement.findByRsql(pageable, rsqlParam);
distributionSetTags = page;
count = page.getTotalElements();
}
final List<MgmtTag> rest = MgmtTagMapper.toResponseDistributionSetTag(distributionSetTags.getContent());
return ResponseEntity.ok(new PagedList<>(rest, count));
}
@Override
public ResponseEntity<MgmtTag> getDistributionSetTag(
@PathVariable("distributionsetTagId") final Long distributionsetTagId) {
final DistributionSetTag distributionSetTag = findDistributionTagById(distributionsetTagId);
final MgmtTag response = MgmtTagMapper.toResponse(distributionSetTag);
MgmtTagMapper.addLinks(distributionSetTag, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtTag>> createDistributionSetTags(
@RequestBody final List<MgmtTagRequestBodyPut> tags) {
log.debug("creating {} ds tags", tags.size());
final List<DistributionSetTag> createdTags = distributionSetTagManagement
.create(MgmtTagMapper.mapTagFromRequest(entityFactory, tags));
return new ResponseEntity<>(MgmtTagMapper.toResponseDistributionSetTag(createdTags), HttpStatus.CREATED);
}
@Override
public ResponseEntity<MgmtTag> updateDistributionSetTag(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@RequestBody final MgmtTagRequestBodyPut restDSTagRest) {
final DistributionSetTag distributionSetTag = distributionSetTagManagement
.update(entityFactory.tag().update(distributionsetTagId).name(restDSTagRest.getName())
.description(restDSTagRest.getDescription()).colour(restDSTagRest.getColour()));
final MgmtTag response = MgmtTagMapper.toResponse(distributionSetTag);
MgmtTagMapper.addLinks(distributionSetTag, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteDistributionSetTag(
@PathVariable("distributionsetTagId") final Long distributionsetTagId) {
log.debug("Delete {} distribution set tag", distributionsetTagId);
final DistributionSetTag tag = findDistributionTagById(distributionsetTagId);
distributionSetTagManagement.delete(tag.getName());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtDistributionSet>> getAssignedDistributionSets(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@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.sanitizeTagSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
Page<DistributionSet> findDistrAll;
if (rsqlParam == null) {
findDistrAll = distributionSetManagement.findByTag(pageable, distributionsetTagId);
} else {
findDistrAll = distributionSetManagement.findByRsqlAndTag(pageable, rsqlParam, distributionsetTagId);
}
final List<MgmtDistributionSet> rest = MgmtDistributionSetMapper
.toResponseFromDsList(findDistrAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, findDistrAll.getTotalElements()));
}
@Override
public ResponseEntity<Void> assignDistributionSet(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@PathVariable("distributionsetId") final Long distributionsetId) {
log.debug("Assign ds {} for ds tag {}", distributionsetId, distributionsetTagId);
this.distributionSetManagement.assignTag(List.of(distributionsetId), distributionsetTagId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> assignDistributionSets(
final Long distributionsetTagId,
final List<Long> distributionsetIds) {
log.debug("Assign DistributionSet {} for ds tag {}", distributionsetIds.size(), distributionsetTagId);
final List<DistributionSet> assignedDs = this.distributionSetManagement
.assignTag(distributionsetIds, distributionsetTagId);
log.debug("Assigned DistributionSet {}", assignedDs.size());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignDistributionSet(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@PathVariable("distributionsetId") final Long distributionsetId) {
log.debug("Unassign ds {} for ds tag {}", distributionsetId, distributionsetTagId);
this.distributionSetManagement.unassignTag(List.of(distributionsetId), distributionsetTagId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignDistributionSets(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@RequestBody final List<Long> distributionsetIds) {
log.debug("Unassign DistributionSet {} for ds tag {}", distributionsetIds.size(), distributionsetTagId);
final List<DistributionSet> assignedDs = this.distributionSetManagement
.unassignTag(distributionsetIds, distributionsetTagId);
log.debug("Unassigned DistributionSet {}", assignedDs.size());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtDistributionSetTagAssigmentResult> toggleTagAssignment(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@RequestBody final List<MgmtAssignedDistributionSetRequestBody> assignedDSRequestBodies) {
log.debug("Toggle distribution set assignment {} for ds tag {}", assignedDSRequestBodies.size(),
distributionsetTagId);
final DistributionSetTag tag = findDistributionTagById(distributionsetTagId);
final DistributionSetTagAssignmentResult assigmentResult = this.distributionSetManagement
.toggleTagAssignment(findDistributionSetIds(assignedDSRequestBodies), tag.getName());
final MgmtDistributionSetTagAssigmentResult tagAssigmentResultRest = new MgmtDistributionSetTagAssigmentResult();
tagAssigmentResultRest.setAssignedDistributionSets(
MgmtDistributionSetMapper.toResponseDistributionSets(assigmentResult.getAssignedEntity()));
tagAssigmentResultRest.setUnassignedDistributionSets(
MgmtDistributionSetMapper.toResponseDistributionSets(assigmentResult.getUnassignedEntity()));
log.debug("Toggled assignedDS {} and unassignedDS{}", assigmentResult.getAssigned(),
assigmentResult.getUnassigned());
return ResponseEntity.ok(tagAssigmentResultRest);
}
@Override
public ResponseEntity<List<MgmtDistributionSet>> assignDistributionSetsByRequestBody(
@PathVariable("distributionsetTagId") final Long distributionsetTagId,
@RequestBody final List<MgmtAssignedDistributionSetRequestBody> assignedDSRequestBodies) {
log.debug("Assign DistributionSet {} for ds tag {}", assignedDSRequestBodies.size(), distributionsetTagId);
final List<DistributionSet> assignedDs = this.distributionSetManagement
.assignTag(findDistributionSetIds(assignedDSRequestBodies), distributionsetTagId);
log.debug("Assigned DistributionSet {}", assignedDs.size());
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDistributionSets(assignedDs));
}
private static List<Long> findDistributionSetIds(
final List<MgmtAssignedDistributionSetRequestBody> assignedDistributionSetRequestBodies) {
return assignedDistributionSetRequestBodies.stream()
.map(MgmtAssignedDistributionSetRequestBody::getDistributionSetId).collect(Collectors.toList());
}
private DistributionSetTag findDistributionTagById(final Long distributionsetTagId) {
return distributionSetTagManagement.get(distributionsetTagId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, distributionsetTagId));
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
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.MgmtDistributionSetType;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeAssigment;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
final class MgmtDistributionSetTypeMapper {
// private constructor, utility class
private MgmtDistributionSetTypeMapper() {
}
static List<DistributionSetTypeCreate> smFromRequest(final EntityFactory entityFactory,
final Collection<MgmtDistributionSetTypeRequestBodyPost> smTypesRest) {
if (smTypesRest == null) {
return Collections.emptyList();
}
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, smRest)).collect(Collectors.toList());
}
static List<MgmtDistributionSetType> toListResponse(final Collection<DistributionSetType> types) {
if (types == null) {
return Collections.emptyList();
}
return new ResponseList<>(
types.stream().map(MgmtDistributionSetTypeMapper::toResponse).collect(Collectors.toList()));
}
static MgmtDistributionSetType toResponse(final DistributionSetType type) {
final MgmtDistributionSetType result = new MgmtDistributionSetType();
MgmtRestModelMapper.mapTypeToType(result, type);
result.setModuleId(type.getId());
result.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class).getDistributionSetType(result.getModuleId()))
.withSelfRel().expand());
return result;
}
static void addLinks(final MgmtDistributionSetType result) {
result.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class).getMandatoryModules(result.getModuleId()))
.withRel(MgmtRestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULES).expand());
result.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class).getOptionalModules(result.getModuleId()))
.withRel(MgmtRestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULES).expand());
}
private static DistributionSetTypeCreate fromRequest(final EntityFactory entityFactory,
final MgmtDistributionSetTypeRequestBodyPost smsRest) {
return entityFactory.distributionSetType().create().key(smsRest.getKey()).name(smsRest.getName())
.description(smsRest.getDescription()).colour(smsRest.getColour())
.mandatory(getMandatoryModules(smsRest)).optional(getOptionalmodules(smsRest));
}
private static Collection<Long> getMandatoryModules(final MgmtDistributionSetTypeRequestBodyPost smsRest) {
return Optional.ofNullable(smsRest.getMandatorymodules()).map(
modules -> modules.stream().map(MgmtSoftwareModuleTypeAssigment::getId).collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
private static Collection<Long> getOptionalmodules(final MgmtDistributionSetTypeRequestBodyPost smsRest) {
return Optional.ofNullable(smsRest.getOptionalmodules()).map(
modules -> modules.stream().map(MgmtSoftwareModuleTypeAssigment::getId).collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Arrays;
import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
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.MgmtDistributionSetTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionsettype.MgmtDistributionSetTypeRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.SoftwareModuleTypeNotInDistributionSetTypeException;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
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 DistributionSetType} CRUD operations.
*/
@RestController
public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeRestApi {
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
private final DistributionSetTypeManagement distributionSetTypeManagement;
private final EntityFactory entityFactory;
MgmtDistributionSetTypeResource(final SoftwareModuleTypeManagement softwareModuleTypeManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final EntityFactory entityFactory) {
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<PagedList<MgmtDistributionSetType>> getDistributionSetTypes(
@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.sanitizeDistributionSetTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<DistributionSetType> findModuleTypessAll;
long countModulesAll;
if (rsqlParam != null) {
findModuleTypessAll = distributionSetTypeManagement.findByRsql(pageable, rsqlParam);
countModulesAll = ((Page<DistributionSetType>) findModuleTypessAll).getTotalElements();
} else {
findModuleTypessAll = distributionSetTypeManagement.findAll(pageable);
countModulesAll = distributionSetTypeManagement.count();
}
final List<MgmtDistributionSetType> rest = MgmtDistributionSetTypeMapper
.toListResponse(findModuleTypessAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
}
@Override
public ResponseEntity<MgmtDistributionSetType> getDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final MgmtDistributionSetType reponse = MgmtDistributionSetTypeMapper.toResponse(foundType);
MgmtDistributionSetTypeMapper.addLinks(reponse);
return ResponseEntity.ok(reponse);
}
@Override
public ResponseEntity<Void> deleteDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
distributionSetTypeManagement.delete(distributionSetTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtDistributionSetType> updateDistributionSetType(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@RequestBody final MgmtDistributionSetTypeRequestBodyPut restDistributionSetType) {
final DistributionSetType updated = distributionSetTypeManagement.update(entityFactory.distributionSetType()
.update(distributionSetTypeId).description(restDistributionSetType.getDescription())
.colour(restDistributionSetType.getColour()));
final MgmtDistributionSetType reponse = MgmtDistributionSetTypeMapper.toResponse(updated);
MgmtDistributionSetTypeMapper.addLinks(reponse);
return ResponseEntity.ok(reponse);
}
@Override
public ResponseEntity<List<MgmtDistributionSetType>> createDistributionSetTypes(
@RequestBody final List<MgmtDistributionSetTypeRequestBodyPost> distributionSetTypes) {
final List<DistributionSetType> createdSoftwareModules = distributionSetTypeManagement
.create(MgmtDistributionSetTypeMapper.smFromRequest(entityFactory, distributionSetTypes));
return ResponseEntity.status(HttpStatus.CREATED)
.body(MgmtDistributionSetTypeMapper.toListResponse(createdSoftwareModules));
}
@Override
public ResponseEntity<List<MgmtSoftwareModuleType>> getMandatoryModules(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toTypesResponse(foundType.getMandatoryModuleTypes()));
}
@Override
public ResponseEntity<MgmtSoftwareModuleType> getMandatoryModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsMandatoryModuleType(foundSmType)) {
throw new SoftwareModuleTypeNotInDistributionSetTypeException(softwareModuleTypeId, distributionSetTypeId);
}
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType));
}
@Override
public ResponseEntity<MgmtSoftwareModuleType> getOptionalModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
final SoftwareModuleType foundSmType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
if (!foundType.containsOptionalModuleType(foundSmType)) {
throw new SoftwareModuleTypeNotInDistributionSetTypeException(softwareModuleTypeId, distributionSetTypeId);
}
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toResponse(foundSmType));
}
@Override
public ResponseEntity<List<MgmtSoftwareModuleType>> getOptionalModules(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId);
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toTypesResponse(foundType.getOptionalModuleTypes()));
}
@Override
public ResponseEntity<Void> removeMandatoryModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
distributionSetTypeManagement.unassignSoftwareModuleType(distributionSetTypeId, softwareModuleTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> removeOptionalModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId,
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
return removeMandatoryModule(distributionSetTypeId, softwareModuleTypeId);
}
@Override
public ResponseEntity<Void> addMandatoryModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(distributionSetTypeId,
Arrays.asList(smtId.getId()));
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> addOptionalModule(
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final MgmtId smtId) {
distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(distributionSetTypeId,
Arrays.asList(smtId.getId()));
return ResponseEntity.ok().build();
}
private DistributionSetType findDistributionSetTypeWithExceptionIfNotFound(final Long distributionSetTypeId) {
return distributionSetTypeManagement.get(distributionSetTypeId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, distributionSetTypeId));
}
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
return softwareModuleTypeManagement.get(softwareModuleTypeId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, softwareModuleTypeId));
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.InputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadArtifactRestApi;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNoLongerExistsException;
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.rest.util.FileStreamingUtil;
import org.eclipse.hawkbit.rest.util.HttpUtil;
import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
/**
*
*/
@RestController
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi {
@Autowired
private SoftwareModuleManagement softwareModuleManagement;
@Autowired
private ArtifactManagement artifactManagement;
/**
* Handles the GET request for downloading an artifact.
*
* @param softwareModuleId of the parent SoftwareModule
* @param artifactId of the related Artifact
* @return responseEntity with status ok if successful
*/
@Override
public ResponseEntity<InputStream> downloadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) {
final SoftwareModule module = softwareModuleManagement.get(softwareModuleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId));
if (module.isDeleted()) {
throw new ArtifactBinaryNoLongerExistsException();
}
final Artifact artifact = module.getArtifact(artifactId)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId));
final DbArtifact file = artifactManagement
.loadArtifactBinary(artifact.getSha1Hash(), module.getId(), module.isEncrypted())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
final HttpServletRequest request = RequestResponseContextHolder.getHttpServletRequest();
final String ifMatch = request.getHeader(HttpHeaders.IF_MATCH);
if (ifMatch != null && !HttpUtil.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
return FileStreamingUtil.writeFileResponse(file, artifact.getFilename(), artifact.getCreatedAt(),
RequestResponseContextHolder.getHttpServletResponse(), request, null);
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.eclipse.hawkbit.rest.OpenApiConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(
value = "hawkbit.server.swagger.enabled",
havingValue = "true",
matchIfMissing = true)
public class MgmtOpenApiConfiguration {
private static final String BASIC_AUTH_SEC_SCHEME_NAME = "Basic Authentication";
private static final String BEARER_AUTH_SEC_SCHEME_NAME = "Bearer Authentication";
@Bean
@ConditionalOnProperty(
value = OpenApiConfiguration.HAWKBIT_SERVER_SWAGGER_ENABLED,
havingValue = "true",
matchIfMissing = true)
public GroupedOpenApi mgmtApi() {
return GroupedOpenApi
.builder()
.group("Management API")
.pathsToMatch("/rest/v1/**")
.addOpenApiCustomizer(openApi ->
openApi
.addSecurityItem(new SecurityRequirement()
.addList(BASIC_AUTH_SEC_SCHEME_NAME)
.addList(BEARER_AUTH_SEC_SCHEME_NAME))
.components(
openApi
.getComponents()
.addSecuritySchemes(BASIC_AUTH_SEC_SCHEME_NAME,
new SecurityScheme()
.name(BASIC_AUTH_SEC_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.in(SecurityScheme.In.HEADER)
.scheme("basic"))
.addSecuritySchemes(BEARER_AUTH_SEC_SCHEME_NAME,
new SecurityScheme()
.name(BEARER_AUTH_SEC_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.in(SecurityScheme.In.HEADER)
.bearerFormat("JWT")
.scheme("bearer"))))
.build();
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity;
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
import org.eclipse.hawkbit.mgmt.json.model.MgmtTypeEntity;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtCancelationType;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.Type;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
public final class MgmtRestModelMapper {
// private constructor, utility class
private MgmtRestModelMapper() {
}
/**
* Convert the given {@link MgmtActionType} into a corresponding repository
* {@link ActionType}.
*
* @param actionTypeRest the REST representation of the action type
* @return <null> or the repository action type
*/
public static ActionType convertActionType(final MgmtActionType actionTypeRest) {
if (actionTypeRest == null) {
return null;
}
switch (actionTypeRest) {
case SOFT:
return ActionType.SOFT;
case FORCED:
return ActionType.FORCED;
case TIMEFORCED:
return ActionType.TIMEFORCED;
case DOWNLOAD_ONLY:
return ActionType.DOWNLOAD_ONLY;
default:
throw new IllegalStateException("Action Type is not supported");
}
}
/**
* Converts the given repository {@link ActionType} into a corresponding
* {@link MgmtActionType}.
*
* @param actionType the repository representation of the action type
* @return <null> or the REST action type
*/
public static MgmtActionType convertActionType(final ActionType actionType) {
if (actionType == null) {
return null;
}
switch (actionType) {
case SOFT:
return MgmtActionType.SOFT;
case FORCED:
return MgmtActionType.FORCED;
case TIMEFORCED:
return MgmtActionType.TIMEFORCED;
case DOWNLOAD_ONLY:
return MgmtActionType.DOWNLOAD_ONLY;
default:
throw new IllegalStateException("Action Type is not supported");
}
}
/**
* Converts the given repository {@link CancelationType} into a
* corresponding {@link MgmtCancelationType}.
*
* @param cancelationType the repository representation of the cancellation type
* @return <null> or the REST cancellation type
*/
public static CancelationType convertCancelationType(final MgmtCancelationType cancelationType) {
if (cancelationType == null) {
return null;
}
switch (cancelationType) {
case SOFT:
return CancelationType.SOFT;
case FORCE:
return CancelationType.FORCE;
case NONE:
return CancelationType.NONE;
default:
throw new IllegalStateException("Action Cancelation Type is not supported");
}
}
static void mapBaseToBase(final MgmtBaseEntity response, final TenantAwareBaseEntity base) {
response.setCreatedBy(base.getCreatedBy());
response.setLastModifiedBy(base.getLastModifiedBy());
if (base.getCreatedAt() > 0) {
response.setCreatedAt(base.getCreatedAt());
}
if (base.getLastModifiedAt() > 0) {
response.setLastModifiedAt(base.getLastModifiedAt());
}
}
static void mapNamedToNamed(final MgmtNamedEntity response, final NamedEntity base) {
mapBaseToBase(response, base);
response.setName(base.getName());
response.setDescription(base.getDescription());
}
static void mapTypeToType(final MgmtTypeEntity response, final Type base) {
mapNamedToNamed(response, base);
response.setKey(base.getKey());
response.setColour(base.getColour());
response.setDeleted(base.isDeleted());
}
}

View File

@@ -0,0 +1,317 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.rollout.AbstractMgmtRolloutConditionsEntity;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition.Condition;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction.ErrorAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction.SuccessAction;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtDynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
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.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
final class MgmtRolloutMapper {
private static final String NOT_SUPPORTED = " is not supported";
private MgmtRolloutMapper() {
// Utility class
}
static List<MgmtRolloutResponseBody> toResponseRollout(final List<Rollout> rollouts) {
return toResponseRollout(rollouts, false);
}
static List<MgmtRolloutResponseBody> toResponseRollout(final List<Rollout> rollouts, final boolean withDetails) {
if (rollouts == null) {
return Collections.emptyList();
}
return rollouts.stream().map(rollout -> toResponseRollout(rollout, withDetails)).collect(Collectors.toList());
}
static MgmtRolloutResponseBody toResponseRollout(final Rollout rollout, final boolean withDetails) {
final MgmtRolloutResponseBody body = new MgmtRolloutResponseBody();
body.setCreatedAt(rollout.getCreatedAt());
body.setCreatedBy(rollout.getCreatedBy());
body.setDescription(rollout.getDescription());
body.setLastModifiedAt(rollout.getLastModifiedAt());
body.setLastModifiedBy(rollout.getLastModifiedBy());
body.setName(rollout.getName());
body.setRolloutId(rollout.getId());
body.setDynamic(rollout.isDynamic());
body.setTargetFilterQuery(rollout.getTargetFilterQuery());
body.setDistributionSetId(rollout.getDistributionSet().getId());
body.setStatus(rollout.getStatus().toString().toLowerCase());
body.setTotalTargets(rollout.getTotalTargets());
body.setDeleted(rollout.isDeleted());
body.setType(MgmtRestModelMapper.convertActionType(rollout.getActionType()));
body.setForcetime(rollout.getForcedTime());
rollout.getWeight().ifPresent(body::setWeight);
if (withDetails) {
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
body.addTotalTargetsPerStatus(status.name().toLowerCase(),
rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
}
body.setTotalGroups(rollout.getRolloutGroupsCreated());
body.setStartAt(rollout.getStartAt());
body.setApproveDecidedBy(rollout.getApprovalDecidedBy());
body.setApprovalRemark(rollout.getApprovalRemark());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start").expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause").expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).resume(rollout.getId())).withRel("resume").expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).triggerNextGroup(rollout.getId()))
.withRel("triggerNextGroup").expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).approve(rollout.getId(), null)).withRel("approve")
.expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).deny(rollout.getId(), null)).withRel("deny").expand());
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null, null)).withRel("groups")
.expand());
final DistributionSet distributionSet = rollout.getDistributionSet();
body.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(distributionSet.getId()))
.withRel("distributionset").withName(distributionSet.getName() + ":" + distributionSet.getVersion())
.expand());
}
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRollout(rollout.getId())).withSelfRel().expand());
return body;
}
static RolloutCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutRestRequestBodyPost restRequest,
final DistributionSet distributionSet) {
return entityFactory.rollout().create()
.name(restRequest.getName())
.description(restRequest.getDescription())
.distributionSetId(distributionSet)
.targetFilterQuery(restRequest.getTargetFilterQuery())
.actionType(MgmtRestModelMapper.convertActionType(restRequest.getType()))
.forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt())
.weight(restRequest.getWeight())
.dynamic(restRequest.isDynamic());
}
static RolloutUpdate fromRequest(final EntityFactory entityFactory, final MgmtRolloutRestRequestBodyPut restRequest, final long rolloutId) {
return entityFactory.rollout().update(rolloutId)
.name(restRequest.getName())
.description(restRequest.getDescription());
}
static RolloutCreate fromRetriedRollout(final EntityFactory entityFactory, final Rollout rollout) {
return entityFactory.rollout().create()
.name(rollout.getName().concat("_retry"))
.description(rollout.getDescription())
.distributionSetId(rollout.getDistributionSet())
.targetFilterQuery("failedrollout==".concat(String.valueOf(rollout.getId())))
.actionType(rollout.getActionType())
.forcedTime(rollout.getForcedTime())
.startAt(rollout.getStartAt())
.weight(null);
}
static RolloutGroupCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
return entityFactory.rolloutGroup().create().name(restRequest.getName())
.description(restRequest.getDescription()).targetFilterQuery(restRequest.getTargetFilterQuery())
.targetPercentage(restRequest.getTargetPercentage()).conditions(fromRequest(restRequest, false));
}
static DynamicRolloutGroupTemplate fromRequest(final MgmtDynamicRolloutGroupTemplate restRequest) {
if (restRequest == null) {
return null;
}
return DynamicRolloutGroupTemplate.builder()
.nameSuffix(Optional.ofNullable(restRequest.getNameSuffix()).orElse(""))
.targetCount(restRequest.getTargetCount())
.build();
}
static RolloutGroupConditions fromRequest(final AbstractMgmtRolloutConditionsEntity restRequest,
final boolean withDefaults) {
final RolloutGroupConditionBuilder conditions = new RolloutGroupConditionBuilder();
if (withDefaults) {
conditions.withDefaults();
}
if (restRequest.getSuccessCondition() != null) {
conditions.successCondition(mapFinishCondition(restRequest.getSuccessCondition().getCondition()),
restRequest.getSuccessCondition().getExpression());
}
if (restRequest.getSuccessAction() != null) {
conditions.successAction(map(restRequest.getSuccessAction().getAction()),
restRequest.getSuccessAction().getExpression());
}
if (restRequest.getErrorCondition() != null) {
conditions.errorCondition(mapErrorCondition(restRequest.getErrorCondition().getCondition()),
restRequest.getErrorCondition().getExpression());
}
if (restRequest.getErrorAction() != null) {
conditions.errorAction(map(restRequest.getErrorAction().getAction()),
restRequest.getErrorAction().getExpression());
}
return conditions.build();
}
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts,
final boolean confirmationFlowEnabled, final boolean withDetails) {
if (rollouts == null) {
return Collections.emptyList();
}
return rollouts.stream().map(group -> toResponseRolloutGroup(group, withDetails, confirmationFlowEnabled))
.collect(Collectors.toList());
}
static MgmtRolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup,
final boolean withDetailedStatus, final boolean confirmationFlowEnabled) {
final MgmtRolloutGroupResponseBody body = new MgmtRolloutGroupResponseBody();
body.setCreatedAt(rolloutGroup.getCreatedAt());
body.setCreatedBy(rolloutGroup.getCreatedBy());
body.setDescription(rolloutGroup.getDescription());
body.setLastModifiedAt(rolloutGroup.getLastModifiedAt());
body.setLastModifiedBy(rolloutGroup.getLastModifiedBy());
body.setName(rolloutGroup.getName());
body.setRolloutGroupId(rolloutGroup.getId());
body.setStatus(rolloutGroup.getStatus().toString().toLowerCase());
body.setTargetPercentage(rolloutGroup.getTargetPercentage());
body.setTargetFilterQuery(rolloutGroup.getTargetFilterQuery());
body.setTotalTargets(rolloutGroup.getTotalTargets());
if (confirmationFlowEnabled) {
body.setConfirmationRequired(rolloutGroup.isConfirmationRequired());
}
body.setDynamic(rolloutGroup.isDynamic());
body.setSuccessCondition(new MgmtRolloutCondition(map(rolloutGroup.getSuccessCondition()),
rolloutGroup.getSuccessConditionExp()));
body.setSuccessAction(
new MgmtRolloutSuccessAction(map(rolloutGroup.getSuccessAction()), rolloutGroup.getSuccessActionExp()));
body.setErrorCondition(
new MgmtRolloutCondition(map(rolloutGroup.getErrorCondition()), rolloutGroup.getErrorConditionExp()));
body.setErrorAction(
new MgmtRolloutErrorAction(map(rolloutGroup.getErrorAction()), rolloutGroup.getErrorActionExp()));
if (withDetailedStatus) {
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
body.addTotalTargetsPerStatus(status.name().toLowerCase(),
rolloutGroup.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
}
}
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroup(rolloutGroup.getRollout().getId(),
rolloutGroup.getId())).withSelfRel());
return body;
}
private static RolloutGroupErrorCondition mapErrorCondition(final Condition condition) {
if (Condition.THRESHOLD == condition) {
return RolloutGroupErrorCondition.THRESHOLD;
}
throw new IllegalArgumentException(createIllegalArgumentLiteral(condition));
}
private static RolloutGroupSuccessCondition mapFinishCondition(final Condition condition) {
if (Condition.THRESHOLD == condition) {
return RolloutGroupSuccessCondition.THRESHOLD;
}
throw new IllegalArgumentException(createIllegalArgumentLiteral(condition));
}
private static Condition map(final RolloutGroupSuccessCondition rolloutCondition) {
if (RolloutGroupSuccessCondition.THRESHOLD == rolloutCondition) {
return Condition.THRESHOLD;
}
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
}
private static Condition map(final RolloutGroupErrorCondition rolloutCondition) {
if (RolloutGroupErrorCondition.THRESHOLD == rolloutCondition) {
return Condition.THRESHOLD;
}
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
}
private static RolloutGroupErrorAction map(final ErrorAction action) {
if (ErrorAction.PAUSE == action) {
return RolloutGroupErrorAction.PAUSE;
}
throw new IllegalArgumentException("Error Action " + action + NOT_SUPPORTED);
}
private static RolloutGroupSuccessAction map(final SuccessAction action) {
if (SuccessAction.NEXTGROUP == action) {
return RolloutGroupSuccessAction.NEXTGROUP;
}
throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED);
}
private static SuccessAction map(final RolloutGroupSuccessAction successAction) {
if (RolloutGroupSuccessAction.NEXTGROUP == successAction) {
return SuccessAction.NEXTGROUP;
}
throw new IllegalArgumentException("Rollout group success action " + successAction + NOT_SUPPORTED);
}
private static ErrorAction map(final RolloutGroupErrorAction errorAction) {
if (RolloutGroupErrorAction.PAUSE == errorAction) {
return ErrorAction.PAUSE;
}
throw new IllegalArgumentException("Rollout group error action " + errorAction + NOT_SUPPORTED);
}
private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED;
}
}

View File

@@ -0,0 +1,354 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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 rollout CRUD operations.
*/
@Slf4j
@RestController
public class MgmtRolloutResource implements MgmtRolloutRestApi {
private final RolloutManagement rolloutManagement;
private final RolloutGroupManagement rolloutGroupManagement;
private final DistributionSetManagement distributionSetManagement;
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final EntityFactory entityFactory;
private final TenantConfigHelper tenantConfigHelper;
MgmtRolloutResource(final RolloutManagement rolloutManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.rolloutManagement = rolloutManagement;
this.rolloutGroupManagement = rolloutGroupManagement;
this.distributionSetManagement = distributionSetManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.entityFactory = entityFactory;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
}
@Override
public ResponseEntity<PagedList<MgmtRolloutResponseBody>> getRollouts(
@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 String representationModeParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeRolloutSortParam(sortParam);
final boolean isFullMode = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Rollout> rollouts;
if (rsqlParam != null) {
rollouts = this.rolloutManagement.findByRsql(pageable, rsqlParam, false);
} else {
rollouts = this.rolloutManagement.findAll(pageable, false);
}
final long totalElements = rollouts.getTotalElements();
if (isFullMode) {
this.rolloutManagement.setRolloutStatusDetails(rollouts);
}
final List<MgmtRolloutResponseBody> rest = MgmtRolloutMapper.toResponseRollout(rollouts.getContent(),
isFullMode);
return ResponseEntity.ok(new PagedList<>(rest, totalElements));
}
@Override
public ResponseEntity<MgmtRolloutResponseBody> getRollout(@PathVariable("rolloutId") final Long rolloutId) {
final Rollout findRolloutById = rolloutManagement.getWithDetailedStatus(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
return ResponseEntity.ok(MgmtRolloutMapper.toResponseRollout(findRolloutById, true));
}
@Override
public ResponseEntity<MgmtRolloutResponseBody> create(
@RequestBody final MgmtRolloutRestRequestBodyPost rolloutRequestBody) {
// first check the given RSQL query if it's well-formed, otherwise and
// exception is thrown
final String targetFilterQuery = rolloutRequestBody.getTargetFilterQuery();
if (targetFilterQuery == null) {
// Use RSQLParameterSyntaxException due to backwards compatibility
throw new RSQLParameterSyntaxException("Cannot create a Rollout with an empty target query filter!");
}
targetFilterQueryManagement.verifyTargetFilterQuerySyntax(targetFilterQuery);
final DistributionSet distributionSet = distributionSetManagement
.getValidAndComplete(rolloutRequestBody.getDistributionSetId());
final RolloutGroupConditions rolloutGroupConditions = MgmtRolloutMapper.fromRequest(rolloutRequestBody, true);
final RolloutCreate create = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
final boolean confirmationFlowActive = tenantConfigHelper.isConfirmationFlowEnabled();
final Rollout rollout;
if (rolloutRequestBody.getGroups() != null) {
if (rolloutRequestBody.isDynamic()) {
throw new ValidationException("Dynamic rollouts are not supported with groups");
}
if (rolloutRequestBody.getAmountGroups() != null) {
throw new ValidationException("Either 'amountGroups' or 'groups' must be defined in the request");
}
final List<RolloutGroupCreate> rolloutGroups = rolloutRequestBody.getGroups().stream()
.map(mgmtRolloutGroup -> {
final boolean confirmationRequired = isConfirmationRequiredForGroup(mgmtRolloutGroup,
rolloutRequestBody).orElse(confirmationFlowActive);
return MgmtRolloutMapper.fromRequest(entityFactory, mgmtRolloutGroup)
.confirmationRequired(confirmationRequired);
}).collect(Collectors.toList());
rollout = rolloutManagement.create(create, rolloutGroups, rolloutGroupConditions);
} else if (rolloutRequestBody.getAmountGroups() != null) {
final boolean confirmationRequired = rolloutRequestBody.getConfirmationRequired() == null
? confirmationFlowActive
: rolloutRequestBody.getConfirmationRequired();
rollout = rolloutManagement.create(create, rolloutRequestBody.getAmountGroups(), confirmationRequired,
rolloutGroupConditions, MgmtRolloutMapper.fromRequest(rolloutRequestBody.getDynamicGroupTemplate()));
} else {
throw new ValidationException("Either 'amountGroups' or 'groups' must be defined in the request");
}
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout, true));
}
@Override
public ResponseEntity<MgmtRolloutResponseBody> update(
@PathVariable("rolloutId") final Long rolloutId,
@RequestBody final MgmtRolloutRestRequestBodyPut rolloutUpdateBody) {
final Rollout updated =
rolloutManagement.update(MgmtRolloutMapper.fromRequest(entityFactory, rolloutUpdateBody, rolloutId));
return ResponseEntity.ok(MgmtRolloutMapper.toResponseRollout(updated, true));
}
@Override
public ResponseEntity<Void> approve(@PathVariable("rolloutId") final Long rolloutId, final String remark) {
rolloutManagement.approveOrDeny(rolloutId, Rollout.ApprovalDecision.APPROVED, remark);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> deny(@PathVariable("rolloutId") final Long rolloutId, final String remark) {
rolloutManagement.approveOrDeny(rolloutId, Rollout.ApprovalDecision.DENIED, remark);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.start(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> pause(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.pauseRollout(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> delete(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.delete(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> resume(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.resumeRollout(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtRolloutGroupResponseBody>> getRolloutGroups(
@PathVariable("rolloutId") final Long rolloutId,
@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 String representationModeParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeRolloutGroupSortParam(sortParam);
final boolean isFullMode = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<RolloutGroup> rolloutGroups;
if (rsqlParam != null) {
if (isFullMode) {
rolloutGroups = this.rolloutGroupManagement.findByRolloutAndRsqlWithDetailedStatus(pageable,
rolloutId, rsqlParam);
} else {
rolloutGroups = this.rolloutGroupManagement.findByRolloutAndRsql(pageable, rolloutId, rsqlParam);
}
} else {
if (isFullMode) {
rolloutGroups = this.rolloutGroupManagement.findByRolloutWithDetailedStatus(pageable, rolloutId);
} else {
rolloutGroups = this.rolloutGroupManagement.findByRollout(pageable, rolloutId);
}
}
final List<MgmtRolloutGroupResponseBody> rest = MgmtRolloutMapper.toResponseRolloutGroup(
rolloutGroups.getContent(), tenantConfigHelper.isConfirmationFlowEnabled(), isFullMode);
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroups.getTotalElements()));
}
@Override
public ResponseEntity<MgmtRolloutGroupResponseBody> getRolloutGroup(@PathVariable("rolloutId") final Long rolloutId,
@PathVariable("groupId") final Long groupId) {
findRolloutOrThrowException(rolloutId);
final RolloutGroup rolloutGroup = rolloutGroupManagement.getWithDetailedStatus(groupId)
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, rolloutId));
if (!Objects.equals(rolloutId, rolloutGroup.getRollout().getId())) {
throw new EntityNotFoundException(RolloutGroup.class, groupId);
}
return ResponseEntity.ok(MgmtRolloutMapper.toResponseRolloutGroup(rolloutGroup, true,
tenantConfigHelper.isConfirmationFlowEnabled()));
}
@Override
public ResponseEntity<PagedList<MgmtTarget>> getRolloutGroupTargets(@PathVariable("rolloutId") final Long rolloutId,
@PathVariable("groupId") final Long groupId,
@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) {
findRolloutOrThrowException(rolloutId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> rolloutGroupTargets;
if (rsqlParam != null) {
rolloutGroupTargets = this.rolloutGroupManagement.findTargetsOfRolloutGroupByRsql(pageable, groupId,
rsqlParam);
} else {
final Page<Target> pageTargets = this.rolloutGroupManagement.findTargetsOfRolloutGroup(pageable, groupId);
rolloutGroupTargets = pageTargets;
}
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent(), tenantConfigHelper);
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements()));
}
@Override
public ResponseEntity<Void> triggerNextGroup(@PathVariable("rolloutId") final Long rolloutId) {
this.rolloutManagement.triggerNextGroup(rolloutId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtRolloutResponseBody> retryRollout(final Long rolloutId) {
final Rollout rolloutForRetry = this.rolloutManagement.get(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
if (rolloutForRetry.isDeleted()) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
if (!rolloutForRetry.getStatus().equals(Rollout.RolloutStatus.FINISHED)) {
throw new ValidationException("Rollout must be finished in order to be retried!");
}
final RolloutCreate create = MgmtRolloutMapper.fromRetriedRollout(entityFactory, rolloutForRetry);
final RolloutGroupConditions groupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
final Rollout retriedRollout = rolloutManagement.create(create, 1, false, groupConditions, null);
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(retriedRollout, true));
}
private static MgmtRepresentationMode parseRepresentationMode(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam).orElseGet(() -> {
// no need for a 400, just apply a safe fallback
log.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
}
private Optional<Boolean> isConfirmationRequiredForGroup(final MgmtRolloutGroup group,
final MgmtRolloutRestRequestBodyPost request) {
if (group.getConfirmationRequired() != null) {
return Optional.of(group.getConfirmationRequired());
} else if (request.getConfirmationRequired() != null) {
return Optional.of(request.getConfirmationRequired());
}
return Optional.empty();
}
private void findRolloutOrThrowException(final Long rolloutId) {
if (!rolloutManagement.exists(rolloutId)) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.artifact.repository.urlhandler.ApiType;
import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrl;
import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler;
import org.eclipse.hawkbit.artifact.repository.urlhandler.URLPlaceholder;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifactHash;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMetadata;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
import org.springframework.hateoas.Link;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MgmtSoftwareModuleMapper {
static List<SoftwareModuleMetadataCreate> fromRequestSwMetadata(final EntityFactory entityFactory,
final Long softwareModuleId, final Collection<MgmtSoftwareModuleMetadata> metadata) {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.stream()
.map(metadataRest -> entityFactory.softwareModuleMetadata().create(softwareModuleId)
.key(metadataRest.getKey()).value(metadataRest.getValue())
.targetVisible(metadataRest.isTargetVisible()))
.collect(Collectors.toList());
}
static List<SoftwareModuleCreate> smFromRequest(final EntityFactory entityFactory,
final Collection<MgmtSoftwareModuleRequestBodyPost> smsRest) {
if (smsRest == null) {
return Collections.emptyList();
}
return smsRest.stream().map(smRest -> fromRequest(entityFactory, smRest)).collect(Collectors.toList());
}
static List<MgmtSoftwareModule> toResponse(final Collection<SoftwareModule> softwareModules) {
if (softwareModules == null) {
return Collections.emptyList();
}
return new ResponseList<>(
softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList()));
}
static List<MgmtSoftwareModuleMetadata> toResponseSwMetadata(final Collection<SoftwareModuleMetadata> metadata) {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.stream().map(MgmtSoftwareModuleMapper::toResponseSwMetadata).collect(Collectors.toList());
}
static MgmtSoftwareModuleMetadata toResponseSwMetadata(final SoftwareModuleMetadata metadata) {
final MgmtSoftwareModuleMetadata metadataRest = new MgmtSoftwareModuleMetadata();
metadataRest.setKey(metadata.getKey());
metadataRest.setValue(metadata.getValue());
metadataRest.setTargetVisible(metadata.isTargetVisible());
return metadataRest;
}
static MgmtSoftwareModule toResponse(final SoftwareModule softwareModule) {
if (softwareModule == null) {
return null;
}
final MgmtSoftwareModule response = new MgmtSoftwareModule();
MgmtRestModelMapper.mapNamedToNamed(response, softwareModule);
response.setModuleId(softwareModule.getId());
response.setVersion(softwareModule.getVersion());
response.setType(softwareModule.getType().getKey());
response.setTypeName(softwareModule.getType().getName());
response.setVendor(softwareModule.getVendor());
response.setLocked(softwareModule.isLocked());
response.setDeleted(softwareModule.isDeleted());
response.setEncrypted(softwareModule.isEncrypted());
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getModuleId()))
.withSelfRel().expand());
return response;
}
static void addLinks(final SoftwareModule softwareModule, final MgmtSoftwareModule response) {
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getArtifacts(response.getModuleId(), null, null))
.withRel(MgmtRestConstants.SOFTWAREMODULE_V1_ARTIFACT).expand());
response.add(linkTo(
methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(softwareModule.getType().getId()))
.withRel(MgmtRestConstants.SOFTWAREMODULE_V1_TYPE).expand());
response.add(linkTo(methodOn(MgmtSoftwareModuleResource.class).getMetadata(response.getModuleId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata")
.expand().expand());
}
static MgmtArtifact toResponse(final Artifact artifact) {
final MgmtArtifact artifactRest = new MgmtArtifact();
artifactRest.setArtifactId(artifact.getId());
artifactRest.setSize(artifact.getSize());
artifactRest.setHashes(
new MgmtArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash()));
artifactRest.setProvidedFilename(artifact.getFilename());
MgmtRestModelMapper.mapBaseToBase(artifactRest, artifact);
artifactRest.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class)
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null)).withSelfRel().expand());
return artifactRest;
}
static void addLinks(final Artifact artifact, final MgmtArtifact response) {
response.add(linkTo(methodOn(MgmtDownloadArtifactResource.class)
.downloadArtifact(artifact.getSoftwareModule().getId(), artifact.getId())).withRel("download")
.expand());
}
static void addLinks(final Artifact artifact, final MgmtArtifact response,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
final List<ArtifactUrl> urls = artifactUrlHandler.getUrls(
new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), null, null,
new URLPlaceholder.SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(),
artifact.getId(), artifact.getSha1Hash())), ApiType.MGMT, null);
urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand()));
}
private static SoftwareModuleCreate fromRequest(final EntityFactory entityFactory,
final MgmtSoftwareModuleRequestBodyPost smsRest) {
return entityFactory.softwareModule().create().type(smsRest.getType()).name(smsRest.getName())
.version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor())
.encrypted(smsRest.isEncrypted());
}
}

View File

@@ -0,0 +1,355 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandler;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMetadata;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
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.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
*/
@Slf4j
@RestController
public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
private final ArtifactManagement artifactManagement;
private final SoftwareModuleManagement softwareModuleManagement;
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
private final ArtifactUrlHandler artifactUrlHandler;
private final SystemManagement systemManagement;
private final EntityFactory entityFactory;
MgmtSoftwareModuleResource(final ArtifactManagement artifactManagement, final SoftwareModuleManagement softwareModuleManagement,
final SoftwareModuleTypeManagement softwareModuleTypeManagement,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement,
final EntityFactory entityFactory) {
this.artifactManagement = artifactManagement;
this.softwareModuleManagement = softwareModuleManagement;
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
this.artifactUrlHandler = artifactUrlHandler;
this.systemManagement = systemManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<MgmtArtifact> uploadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestPart("file") final MultipartFile file,
@RequestParam(value = "filename", required = false) final String optionalFileName,
@RequestParam(value = "md5sum", required = false) final String md5Sum,
@RequestParam(value = "sha1sum", required = false) final String sha1Sum,
@RequestParam(value = "sha256sum", required = false) final String sha256Sum) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().build();
}
String fileName = optionalFileName;
if (fileName == null) {
fileName = file.getOriginalFilename();
}
try (final InputStream in = file.getInputStream()) {
final Artifact result = artifactManagement.create(new ArtifactUpload(in, softwareModuleId, fileName,
md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(),
sha256Sum == null ? null : sha256Sum.toLowerCase(), false, file.getContentType(), file.getSize()));
final MgmtArtifact reponse = MgmtSoftwareModuleMapper.toResponse(result);
MgmtSoftwareModuleMapper.addLinks(result, reponse);
return ResponseEntity.status(HttpStatus.CREATED).body(reponse);
} catch (final IOException e) {
log.error("Failed to store artifact", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Override
public ResponseEntity<List<MgmtArtifact>> getArtifacts(
@PathVariable("softwareModuleId") final Long softwareModuleId, final String representationModeParam,
final Boolean useArtifactUrlHandler) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final boolean isFullMode = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
final List<MgmtArtifact> response = module.getArtifacts().stream().map(artifact -> {
final MgmtArtifact mgmtArtifact = MgmtSoftwareModuleMapper.toResponse(artifact);
if (isFullMode && !module.isDeleted() && Boolean.TRUE.equals(useArtifactUrlHandler)) {
MgmtSoftwareModuleMapper.addLinks(artifact, mgmtArtifact, artifactUrlHandler, systemManagement);
} else if (isFullMode && !module.isDeleted()) {
MgmtSoftwareModuleMapper.addLinks(artifact, mgmtArtifact);
}
return mgmtArtifact;
}).toList();
return ResponseEntity.ok(new ResponseList<>(response));
}
@Override
@ResponseBody
// Exception squid:S3655 - Optional access is checked in
// findSoftwareModuleWithExceptionIfNotFound
// subroutine
@SuppressWarnings("squid:S3655")
public ResponseEntity<MgmtArtifact> getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER, required = false) final Boolean useArtifactUrlHandler) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
final MgmtArtifact response = MgmtSoftwareModuleMapper.toResponse(module.getArtifact(artifactId).get());
if (!module.isDeleted()) {
if (Boolean.TRUE.equals(useArtifactUrlHandler)) {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response, artifactUrlHandler,
systemManagement);
} else {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response);
}
}
return ResponseEntity.ok(response);
}
@Override
@ResponseBody
public ResponseEntity<Void> deleteArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("artifactId") final Long artifactId) {
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId);
artifactManagement.delete(artifactId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtSoftwareModule>> getSoftwareModules(
@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.sanitizeSoftwareModuleSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<SoftwareModule> findModulesAll;
final long countModulesAll;
if (rsqlParam != null) {
findModulesAll = softwareModuleManagement.findByRsql(pageable, rsqlParam);
countModulesAll = ((Page<SoftwareModule>) findModulesAll).getTotalElements();
} else {
findModulesAll = softwareModuleManagement.findAll(pageable);
countModulesAll = softwareModuleManagement.count();
}
final List<MgmtSoftwareModule> rest = MgmtSoftwareModuleMapper.toResponse(findModulesAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
}
@Override
public ResponseEntity<MgmtSoftwareModule> getSoftwareModule(
@PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final MgmtSoftwareModule response = MgmtSoftwareModuleMapper.toResponse(module);
MgmtSoftwareModuleMapper.addLinks(module, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtSoftwareModule>> createSoftwareModules(
@RequestBody final List<MgmtSoftwareModuleRequestBodyPost> softwareModules) {
log.debug("creating {} softwareModules", softwareModules.size());
for (final MgmtSoftwareModuleRequestBodyPost sm : softwareModules) {
final Optional<SoftwareModuleType> opt = softwareModuleTypeManagement.getByKey(sm.getType());
opt.ifPresent(smType -> {
if (smType.isDeleted()) {
final String text = "Cannot create Software Module from type with key {0}. Software Module Type already deleted!";
final String message = MessageFormat.format(text, smType.getKey());
throw new ValidationException(message);
}
});
}
final Collection<SoftwareModule> createdSoftwareModules = softwareModuleManagement
.create(MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules));
log.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED);
return ResponseEntity.status(HttpStatus.CREATED)
.body(MgmtSoftwareModuleMapper.toResponse(createdSoftwareModules));
}
@Override
public ResponseEntity<MgmtSoftwareModule> updateSoftwareModule(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final MgmtSoftwareModuleRequestBodyPut restSoftwareModule) {
final SoftwareModule module = softwareModuleManagement
.update(entityFactory.softwareModule().update(softwareModuleId)
.description(restSoftwareModule.getDescription())
.vendor(restSoftwareModule.getVendor())
.locked(restSoftwareModule.getLocked()));
final MgmtSoftwareModule response = MgmtSoftwareModuleMapper.toResponse(module);
MgmtSoftwareModuleMapper.addLinks(module, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteSoftwareModule(@PathVariable("softwareModuleId") final Long softwareModuleId) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
softwareModuleManagement.delete(module.getId());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtSoftwareModuleMetadata>> getMetadata(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@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) {
// check if software module exists otherwise throw exception immediately
findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeSoftwareModuleMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<SoftwareModuleMetadata> metaDataPage;
if (rsqlParam != null) {
metaDataPage = softwareModuleManagement.findMetaDataByRsql(pageable, softwareModuleId, rsqlParam);
} else {
metaDataPage = softwareModuleManagement.findMetaDataBySoftwareModuleId(pageable, softwareModuleId);
}
return ResponseEntity
.ok(new PagedList<>(MgmtSoftwareModuleMapper.toResponseSwMetadata(metaDataPage.getContent()),
metaDataPage.getTotalElements()));
}
@Override
public ResponseEntity<MgmtSoftwareModuleMetadata> getMetadataValue(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) {
final SoftwareModuleMetadata findOne = softwareModuleManagement
.getMetaDataBySoftwareModuleId(softwareModuleId, metadataKey).orElseThrow(
() -> new EntityNotFoundException(SoftwareModuleMetadata.class, softwareModuleId, metadataKey));
return ResponseEntity.ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(findOne));
}
@Override
public ResponseEntity<MgmtSoftwareModuleMetadata> updateMetadata(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey,
@RequestBody final MgmtSoftwareModuleMetadataBodyPut metadata) {
final SoftwareModuleMetadata updated = softwareModuleManagement
.updateMetaData(entityFactory.softwareModuleMetadata().update(softwareModuleId, metadataKey)
.value(metadata.getValue()).targetVisible(metadata.getTargetVisible()));
return ResponseEntity.ok(MgmtSoftwareModuleMapper.toResponseSwMetadata(updated));
}
@Override
public ResponseEntity<Void> deleteMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId,
@PathVariable("metadataKey") final String metadataKey) {
softwareModuleManagement.deleteMetaData(softwareModuleId, metadataKey);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<List<MgmtSoftwareModuleMetadata>> createMetadata(
@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestBody final List<MgmtSoftwareModuleMetadata> metadataRest) {
final List<SoftwareModuleMetadata> created = softwareModuleManagement.createMetaData(
MgmtSoftwareModuleMapper.fromRequestSwMetadata(entityFactory, softwareModuleId, metadataRest));
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtSoftwareModuleMapper.toResponseSwMetadata(created));
}
private static MgmtRepresentationMode parseRepresentationMode(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam).orElseGet(() -> {
// no need for a 400, just apply a safe fallback
log.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
}
private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId,
final Long artifactId) {
final SoftwareModule module = softwareModuleManagement.get(softwareModuleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId));
if (artifactId != null && !module.getArtifact(artifactId).isPresent()) {
throw new EntityNotFoundException(Artifact.class, artifactId);
}
return module;
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
final class MgmtSoftwareModuleTypeMapper {
// private constructor, utility class
private MgmtSoftwareModuleTypeMapper() {
}
static List<SoftwareModuleTypeCreate> smFromRequest(final EntityFactory entityFactory,
final Collection<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
if (smTypesRest == null) {
return Collections.emptyList();
}
return smTypesRest.stream().map(smRest -> fromRequest(entityFactory, smRest)).collect(Collectors.toList());
}
static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<SoftwareModuleType> types) {
if (types == null) {
return Collections.emptyList();
}
return new ResponseList<>(
types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).collect(Collectors.toList()));
}
static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {
final MgmtSoftwareModuleType result = new MgmtSoftwareModuleType();
MgmtRestModelMapper.mapTypeToType(result, type);
result.setMaxAssignments(type.getMaxAssignments());
result.setModuleId(type.getId());
result.add(linkTo(methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getModuleId()))
.withSelfRel().expand());
return result;
}
private static SoftwareModuleTypeCreate fromRequest(final EntityFactory entityFactory,
final MgmtSoftwareModuleTypeRequestBodyPost smsRest) {
return entityFactory.softwareModuleType().create().key(smsRest.getKey()).name(smsRest.getName())
.description(smsRest.getDescription()).colour(smsRest.getColour())
.maxAssignments(smsRest.getMaxAssignments());
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
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 SoftwareModuleType} CRUD operations.
*/
@RestController
public class MgmtSoftwareModuleTypeResource implements MgmtSoftwareModuleTypeRestApi {
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
private final EntityFactory entityFactory;
MgmtSoftwareModuleTypeResource(final SoftwareModuleTypeManagement softwareModuleTypeManagement,
final EntityFactory entityFactory) {
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<PagedList<MgmtSoftwareModuleType>> getTypes(
@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.sanitizeSoftwareModuleTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<SoftwareModuleType> findModuleTypessAll;
Long countModulesAll;
if (rsqlParam != null) {
findModuleTypessAll = softwareModuleTypeManagement.findByRsql(pageable, rsqlParam);
countModulesAll = ((Page<SoftwareModuleType>) findModuleTypessAll).getTotalElements();
} else {
findModuleTypessAll = softwareModuleTypeManagement.findAll(pageable);
countModulesAll = softwareModuleTypeManagement.count();
}
final List<MgmtSoftwareModuleType> rest = MgmtSoftwareModuleTypeMapper
.toTypesResponse(findModuleTypessAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
}
@Override
public ResponseEntity<MgmtSoftwareModuleType> getSoftwareModuleType(
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
final SoftwareModuleType foundType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId);
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toResponse(foundType));
}
@Override
public ResponseEntity<Void> deleteSoftwareModuleType(
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId) {
softwareModuleTypeManagement.delete(softwareModuleTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtSoftwareModuleType> updateSoftwareModuleType(
@PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId,
@RequestBody final MgmtSoftwareModuleTypeRequestBodyPut restSoftwareModuleType) {
final SoftwareModuleType updatedSoftwareModuleType = softwareModuleTypeManagement.update(entityFactory
.softwareModuleType().update(softwareModuleTypeId).description(restSoftwareModuleType.getDescription())
.colour(restSoftwareModuleType.getColour()));
return ResponseEntity.ok(MgmtSoftwareModuleTypeMapper.toResponse(updatedSoftwareModuleType));
}
@Override
public ResponseEntity<List<MgmtSoftwareModuleType>> createSoftwareModuleTypes(
@RequestBody final List<MgmtSoftwareModuleTypeRequestBodyPost> softwareModuleTypes) {
final List<SoftwareModuleType> createdSoftwareModules = softwareModuleTypeManagement
.create(MgmtSoftwareModuleTypeMapper.smFromRequest(entityFactory, softwareModuleTypes));
return new ResponseEntity<>(MgmtSoftwareModuleTypeMapper.toTypesResponse(createdSoftwareModules),
HttpStatus.CREATED);
}
private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) {
return softwareModuleTypeManagement.get(softwareModuleTypeId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, softwareModuleTypeId));
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.mgmt.json.model.systemmanagement.MgmtSystemCache;
import org.eclipse.hawkbit.mgmt.json.model.systemmanagement.MgmtSystemStatisticsRest;
import org.eclipse.hawkbit.mgmt.json.model.systemmanagement.MgmtSystemTenantServiceUsage;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSystemManagementRestApi;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.report.model.SystemUsageReportWithTenants;
import org.eclipse.hawkbit.repository.report.model.TenantUsage;
import org.springframework.cache.CacheManager;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* {@link SystemManagement} capabilities by REST.
*/
@Slf4j
@RestController
public class MgmtSystemManagementResource implements MgmtSystemManagementRestApi {
private final SystemManagement systemManagement;
private final CacheManager cacheManager;
MgmtSystemManagementResource(final SystemManagement systemManagement, final CacheManager cacheManager) {
this.systemManagement = systemManagement;
this.cacheManager = cacheManager;
}
/**
* Deletes the tenant data of a given tenant. USE WITH CARE!
*
* @param tenant to delete
* @return HttpStatus.OK
*/
@Override
public ResponseEntity<Void> deleteTenant(@PathVariable("tenant") final String tenant) {
systemManagement.deleteTenant(tenant);
return ResponseEntity.ok().build();
}
/**
* Collects and returns system usage statistics. It provides a system wide
* overview and tenant based stats.
*
* @return system usage statistics
*/
@Override
public ResponseEntity<MgmtSystemStatisticsRest> getSystemUsageStats() {
final SystemUsageReportWithTenants report = systemManagement.getSystemUsageStatisticsWithTenants();
final MgmtSystemStatisticsRest result = new MgmtSystemStatisticsRest()
.setOverallActions(report.getOverallActions()).setOverallArtifacts(report.getOverallArtifacts())
.setOverallArtifactVolumeInBytes(report.getOverallArtifactVolumeInBytes())
.setOverallTargets(report.getOverallTargets()).setOverallTenants(report.getTenants().size());
result.setTenantStats(report.getTenants().stream().map(MgmtSystemManagementResource::convertTenant)
.collect(Collectors.toList()));
return ResponseEntity.ok(result);
}
/**
* Returns a list of all caches.
*
* @return a list of caches for all tenants
*/
@Override
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
public ResponseEntity<Collection<MgmtSystemCache>> getCaches() {
final Collection<String> cacheNames = cacheManager.getCacheNames();
return ResponseEntity
.ok(cacheNames.stream().map(cacheManager::getCache)
.filter(Objects::nonNull)
.map(cache -> new MgmtSystemCache(cache.getName(), Collections.emptyList()))
.collect(Collectors.toList()));
}
/**
* Invalidates all caches for all tenants.
*
* @return a list of cache names which has been invalidated
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
@Override
public ResponseEntity<Collection<String>> invalidateCaches() {
final Collection<String> cacheNames = cacheManager.getCacheNames();
log.info("Invalidating caches {}", cacheNames);
cacheNames.forEach(cacheName -> cacheManager.getCache(cacheName).clear());
return ResponseEntity.ok(cacheNames);
}
private static MgmtSystemTenantServiceUsage convertTenant(final TenantUsage tenant) {
final MgmtSystemTenantServiceUsage result = new MgmtSystemTenantServiceUsage();
result.setTenantName(tenant.getTenantName());
result.setActions(tenant.getActions());
result.setArtifacts(tenant.getArtifacts());
result.setOverallArtifactVolumeInBytes(tenant.getOverallArtifactVolumeInBytes());
result.setTargets(tenant.getTargets());
if (!tenant.getUsageData().isEmpty()) {
result.setUsageData(tenant.getUsageData());
}
return result;
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTagRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTagRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.TagCreate;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and back.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
final class MgmtTagMapper {
static List<MgmtTag> toResponse(final List<TargetTag> targetTags) {
final List<MgmtTag> tagsRest = new ArrayList<>();
if (targetTags == null) {
return tagsRest;
}
for (final TargetTag target : targetTags) {
final MgmtTag response = toResponse(target);
tagsRest.add(response);
}
return new ResponseList<>(tagsRest);
}
static MgmtTag toResponse(final TargetTag targetTag) {
final MgmtTag response = new MgmtTag();
if (targetTag == null) {
return response;
}
mapTag(response, targetTag);
response.add(
linkTo(methodOn(MgmtTargetTagRestApi.class).getTargetTag(targetTag.getId())).withSelfRel().expand());
return response;
}
static void addLinks(final TargetTag targetTag, final MgmtTag response) {
response.add(linkTo(methodOn(MgmtTargetTagRestApi.class).getAssignedTargets(targetTag.getId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("assignedTargets")
.expand());
}
static List<MgmtTag> toResponseDistributionSetTag(final List<DistributionSetTag> distributionSetTags) {
final List<MgmtTag> tagsRest = new ArrayList<>();
if (distributionSetTags == null) {
return tagsRest;
}
for (final DistributionSetTag distributionSetTag : distributionSetTags) {
final MgmtTag response = toResponse(distributionSetTag);
tagsRest.add(response);
}
return new ResponseList<>(tagsRest);
}
static MgmtTag toResponse(final DistributionSetTag distributionSetTag) {
final MgmtTag response = new MgmtTag();
if (distributionSetTag == null) {
return null;
}
mapTag(response, distributionSetTag);
response.add(
linkTo(methodOn(MgmtDistributionSetTagRestApi.class).getDistributionSetTag(distributionSetTag.getId()))
.withSelfRel().expand());
return response;
}
static void addLinks(final DistributionSetTag distributionSetTag, final MgmtTag response) {
response.add(linkTo(methodOn(MgmtDistributionSetTagRestApi.class).getAssignedDistributionSets(
distributionSetTag.getId(), MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null))
.withRel("assignedDistributionSets").expand());
}
static List<TagCreate> mapTagFromRequest(final EntityFactory entityFactory,
final Collection<MgmtTagRequestBodyPut> tags) {
return tags.stream()
.map(tagRest -> entityFactory.tag().create().name(tagRest.getName())
.description(tagRest.getDescription()).colour(tagRest.getColour()))
.collect(Collectors.toList());
}
private static void mapTag(final MgmtTag response, final Tag tag) {
MgmtRestModelMapper.mapNamedToNamed(response, tag);
response.setTagId(tag.getId());
response.setColour(tag.getColour());
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.util.CollectionUtils;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
public final class MgmtTargetFilterQueryMapper {
private MgmtTargetFilterQueryMapper() {
// Utility class
}
static List<MgmtTargetFilterQuery> toResponse(final List<TargetFilterQuery> filters,
final boolean confirmationFlowEnabled, final boolean isRepresentationFull) {
if (CollectionUtils.isEmpty(filters)) {
return Collections.emptyList();
}
return filters.stream().map(filter -> toResponse(filter, confirmationFlowEnabled, isRepresentationFull)).collect(Collectors.toList());
}
static MgmtTargetFilterQuery toResponse(final TargetFilterQuery filter, final boolean confirmationFlowEnabled,
final boolean isReprentationFull) {
final MgmtTargetFilterQuery targetRest = new MgmtTargetFilterQuery();
targetRest.setFilterId(filter.getId());
targetRest.setName(filter.getName());
targetRest.setQuery(filter.getQuery());
targetRest.setCreatedBy(filter.getCreatedBy());
targetRest.setLastModifiedBy(filter.getLastModifiedBy());
targetRest.setCreatedAt(filter.getCreatedAt());
targetRest.setLastModifiedAt(filter.getLastModifiedAt());
final DistributionSet distributionSet = filter.getAutoAssignDistributionSet();
if (distributionSet != null) {
targetRest.setAutoAssignDistributionSet(distributionSet.getId());
targetRest.setAutoAssignActionType(MgmtRestModelMapper.convertActionType(filter.getAutoAssignActionType()));
filter.getAutoAssignWeight().ifPresent(targetRest::setAutoAssignWeight);
if (confirmationFlowEnabled) {
targetRest.setConfirmationRequired(filter.isConfirmationRequired());
}
}
targetRest.add(
linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withSelfRel().expand());
if (isReprentationFull && distributionSet != null) {
targetRest.add(
linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSets(
Integer.parseInt(MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET),
Integer.parseInt(MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null,
"name==" + distributionSet.getName() + ";version==" + distributionSet.getVersion())).withRel("DS").expand());
}
return targetRest;
}
static void addLinks(final MgmtTargetFilterQuery targetRest) {
targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class)
.postAssignedDistributionSet(targetRest.getFilterId(), null)).withRel("autoAssignDS").expand());
}
static TargetFilterQueryCreate fromRequest(final EntityFactory entityFactory,
final MgmtTargetFilterQueryRequestBody filterRest) {
return entityFactory.targetFilterQuery().create().name(filterRest.getName()).query(filterRest.getQuery());
}
static AutoAssignDistributionSetUpdate fromRequest(final EntityFactory entityFactory, final long filterId,
final MgmtDistributionSetAutoAssignment assignRest) {
final ActionType type = MgmtRestModelMapper.convertActionType(assignRest.getType());
return entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(assignRest.getId()).actionType(type)
.weight(assignRest.getWeight());
}
}

View File

@@ -0,0 +1,201 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
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 target CRUD operations.
*/
@Slf4j
@RestController
public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestApi {
private final TargetFilterQueryManagement filterManagement;
private final EntityFactory entityFactory;
private final TenantConfigHelper tenantConfigHelper;
MgmtTargetFilterQueryResource(final TargetFilterQueryManagement filterManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.filterManagement = filterManagement;
this.entityFactory = entityFactory;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
}
@Override
public ResponseEntity<MgmtTargetFilterQuery> getFilter(@PathVariable("filterId") final Long filterId) {
final TargetFilterQuery findTarget = findFilterWithExceptionIfNotFound(filterId);
// to single response include poll status
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(findTarget,
tenantConfigHelper.isConfirmationFlowEnabled(), true);
MgmtTargetFilterQueryMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<PagedList<MgmtTargetFilterQuery>> getFilters(
@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,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE_DEFAULT) String representationModeParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetFilterQuerySortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<TargetFilterQuery> findTargetFiltersAll;
final Long countTargetsAll;
if (rsqlParam != null) {
final Page<TargetFilterQuery> findFilterPage = filterManagement.findByRsql(pageable, rsqlParam);
countTargetsAll = findFilterPage.getTotalElements();
findTargetFiltersAll = findFilterPage;
} else {
findTargetFiltersAll = filterManagement.findAll(pageable);
countTargetsAll = filterManagement.count();
}
final boolean isRepresentationFull = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
final List<MgmtTargetFilterQuery> rest = MgmtTargetFilterQueryMapper
.toResponse(findTargetFiltersAll.getContent(), tenantConfigHelper.isConfirmationFlowEnabled(), isRepresentationFull);
return ResponseEntity.ok(new PagedList<>(rest, countTargetsAll));
}
@Override
public ResponseEntity<MgmtTargetFilterQuery> createFilter(
@RequestBody final MgmtTargetFilterQueryRequestBody filter) {
final TargetFilterQuery createdTarget = filterManagement
.create(MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filter));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(createdTarget,
tenantConfigHelper.isConfirmationFlowEnabled(), false);
MgmtTargetFilterQueryMapper.addLinks(response);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
@Override
public ResponseEntity<MgmtTargetFilterQuery> updateFilter(@PathVariable("filterId") final Long filterId,
@RequestBody final MgmtTargetFilterQueryRequestBody targetFilterRest) {
log.debug("updating target filter query {}", filterId);
final TargetFilterQuery updateFilter = filterManagement
.update(entityFactory.targetFilterQuery().update(filterId).name(targetFilterRest.getName())
.query(targetFilterRest.getQuery()));
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter,
tenantConfigHelper.isConfirmationFlowEnabled(), false);
MgmtTargetFilterQueryMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteFilter(@PathVariable("filterId") final Long filterId) {
filterManagement.delete(filterId);
log.debug("{} target filter query deleted, return status {}", filterId, HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtDistributionSet> getAssignedDistributionSet(
@PathVariable("filterId") final Long filterId) {
final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId);
final DistributionSet autoAssignDistributionSet = filter.getAutoAssignDistributionSet();
if (autoAssignDistributionSet == null) {
return ResponseEntity.noContent().build();
}
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(autoAssignDistributionSet);
MgmtDistributionSetMapper.addLinks(autoAssignDistributionSet, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<MgmtTargetFilterQuery> postAssignedDistributionSet(
@PathVariable("filterId") final Long filterId,
@RequestBody final MgmtDistributionSetAutoAssignment autoAssignRequest) {
final boolean confirmationRequired = autoAssignRequest.getConfirmationRequired() == null
? tenantConfigHelper.isConfirmationFlowEnabled()
: autoAssignRequest.getConfirmationRequired();
final AutoAssignDistributionSetUpdate update = MgmtTargetFilterQueryMapper
.fromRequest(entityFactory, filterId, autoAssignRequest).confirmationRequired(confirmationRequired);
final TargetFilterQuery updateFilter = filterManagement.updateAutoAssignDS(update);
final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(updateFilter,
tenantConfigHelper.isConfirmationFlowEnabled(), false);
MgmtTargetFilterQueryMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteAssignedDistributionSet(@PathVariable("filterId") final Long filterId) {
filterManagement.updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(null));
return ResponseEntity.noContent().build();
}
private static MgmtRepresentationMode parseRepresentationMode(final String representationModeParam) {
return MgmtRepresentationMode.fromValue(representationModeParam).orElseGet(() -> {
// no need for a 400, just apply a safe fallback
log.warn("Received an invalid representation mode: {}", representationModeParam);
return MgmtRepresentationMode.COMPACT;
});
}
private TargetFilterQuery findFilterWithExceptionIfNotFound(final Long filterId) {
return filterManagement.get(filterId)
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, filterId));
}
}

View File

@@ -0,0 +1,368 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.net.URI;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirm;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
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.mgmt.rest.api.SortDirection;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
import org.eclipse.hawkbit.util.IpUtil;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.ObjectUtils;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
public final class MgmtTargetMapper {
private MgmtTargetMapper() {
// Utility class
}
/**
* Add links to a target response.
*
* @param response the target response
*/
public static void addTargetLinks(final MgmtTarget response) {
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getAssignedDistributionSet(response.getControllerId()))
.withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_DISTRIBUTION_SET).expand());
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getInstalledDistributionSet(response.getControllerId()))
.withRel(MgmtRestConstants.TARGET_V1_INSTALLED_DISTRIBUTION_SET).expand());
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getAttributes(response.getControllerId()))
.withRel(MgmtRestConstants.TARGET_V1_ATTRIBUTES).expand());
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getActionHistory(response.getControllerId(), 0,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionFields.ID.getJpaEntityFieldName() + ":" + SortDirection.DESC, null))
.withRel(MgmtRestConstants.TARGET_V1_ACTIONS).expand());
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")
.expand());
if (response.getTargetType() != null) {
response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(response.getTargetType()))
.withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE).expand());
}
if (response.getAutoConfirmActive() != null) {
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getAutoConfirmStatus(response.getControllerId()))
.withRel(MgmtRestConstants.TARGET_V1_AUTO_CONFIRM).expand());
}
}
public static MgmtTargetAutoConfirm getTargetAutoConfirmResponse(final Target target) {
final AutoConfirmationStatus status = target.getAutoConfirmationStatus();
final MgmtTargetAutoConfirm response;
if (status != null) {
response = MgmtTargetAutoConfirm.active(status.getActivatedAt());
response.setInitiator(status.getInitiator());
response.setRemark(status.getRemark());
response.add(linkTo(methodOn(MgmtTargetRestApi.class).deactivateAutoConfirm(target.getControllerId()))
.withRel(MgmtRestConstants.TARGET_V1_DEACTIVATE_AUTO_CONFIRM).expand());
} else {
response = MgmtTargetAutoConfirm.disabled();
response.add(linkTo(methodOn(MgmtTargetRestApi.class).activateAutoConfirm(target.getControllerId(), null))
.withRel(MgmtRestConstants.TARGET_V1_ACTIVATE_AUTO_CONFIRM).expand());
}
return response;
}
/**
* Create a response for targets.
*
* @param targets list of targets
* @return the response
*/
public static List<MgmtTarget> toResponse(final Collection<Target> targets, final TenantConfigHelper configHelper) {
if (targets == null) {
return Collections.emptyList();
}
final Function<Target, PollStatus> pollStatusResolver = configHelper.pollStatusResolver();
return new ResponseList<>(
targets.stream().map(target -> toResponse(target, configHelper, pollStatusResolver)).collect(Collectors.toList()));
}
/**
* Create a response for target.
*
* @param target the target
* @return the response
*/
public static MgmtTarget toResponse(final Target target, final TenantConfigHelper configHelper,
final Function<Target, PollStatus> pollStatusResolver) {
if (target == null) {
return null;
}
final MgmtTarget targetRest = new MgmtTarget();
targetRest.setControllerId(target.getControllerId());
targetRest.setDescription(target.getDescription());
targetRest.setName(target.getName());
targetRest.setUpdateStatus(target.getUpdateStatus().name().toLowerCase());
final URI address = target.getAddress();
if (address != null) {
if (IpUtil.isIpAddresKnown(address)) {
targetRest.setIpAddress(address.getHost());
}
targetRest.setAddress(address.toString());
}
targetRest.setCreatedBy(target.getCreatedBy());
targetRest.setLastModifiedBy(target.getLastModifiedBy());
targetRest.setCreatedAt(target.getCreatedAt());
targetRest.setLastModifiedAt(target.getLastModifiedAt());
targetRest.setSecurityToken(target.getSecurityToken());
targetRest.setRequestAttributes(target.isRequestControllerAttributes());
// last target query is the last controller request date
final Long lastTargetQuery = target.getLastTargetQuery();
final Long installationDate = target.getInstallationDate();
if (lastTargetQuery != null) {
targetRest.setLastControllerRequestAt(lastTargetQuery);
}
if (installationDate != null) {
targetRest.setInstalledAt(installationDate);
}
if (target.getTargetType() != null) {
targetRest.setTargetType(target.getTargetType().getId());
targetRest.setTargetTypeName(target.getTargetType().getName());
}
if (configHelper.isConfirmationFlowEnabled()) {
targetRest.setAutoConfirmActive(target.getAutoConfirmationStatus() != null);
}
targetRest.add(
linkTo(methodOn(MgmtTargetRestApi.class).getTarget(target.getControllerId())).withSelfRel().expand());
addPollStatus(target, targetRest, pollStatusResolver == null ? configHelper.pollStatusResolver() : pollStatusResolver);
return targetRest;
}
static List<TargetCreate> fromRequest(final EntityFactory entityFactory,
final Collection<MgmtTargetRequestBody> targetsRest) {
if (targetsRest == null) {
return Collections.emptyList();
}
return targetsRest.stream().map(targetRest -> fromRequest(entityFactory, targetRest))
.collect(Collectors.toList());
}
static List<MetaData> fromRequestTargetMetadata(final List<MgmtMetadata> metadata,
final EntityFactory entityFactory) {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.stream().map(
metadataRest -> entityFactory.generateTargetMetadata(metadataRest.getKey(), metadataRest.getValue()))
.collect(Collectors.toList());
}
static List<MgmtActionStatus> toActionStatusRestResponse(final Collection<ActionStatus> actionStatus,
final DeploymentManagement deploymentManagement) {
if (actionStatus == null) {
return Collections.emptyList();
}
return actionStatus.stream()
.map(status -> toResponse(status,
deploymentManagement.findMessagesByActionStatusId(
PageRequest.of(0, MgmtRestConstants.REQUEST_PARAMETER_PAGING_MAX_LIMIT), status.getId())
.getContent()))
.collect(Collectors.toList());
}
static MgmtAction toResponse(final String targetId, final Action action) {
final MgmtAction result = new MgmtAction();
result.setActionId(action.getId());
result.setType(getType(action));
if (ActionType.TIMEFORCED == action.getActionType()) {
result.setForceTime(action.getForcedTime());
}
action.getWeight().ifPresent(result::setWeight);
result.setActionType(MgmtRestModelMapper.convertActionType(action.getActionType()));
if (action.isActive()) {
result.setStatus(MgmtAction.ACTION_PENDING);
} else {
result.setStatus(MgmtAction.ACTION_FINISHED);
}
result.setDetailStatus(action.getStatus().toString().toLowerCase());
action.getLastActionStatusCode().ifPresent(statusCode -> {
result.setLastStatusCode(statusCode);
});
final Rollout rollout = action.getRollout();
if (rollout != null) {
result.setRollout(rollout.getId());
result.setRolloutName(rollout.getName());
}
if (action.hasMaintenanceSchedule()) {
final MgmtMaintenanceWindow maintenanceWindow = new MgmtMaintenanceWindow();
maintenanceWindow.setSchedule(action.getMaintenanceWindowSchedule());
maintenanceWindow.setDuration(action.getMaintenanceWindowDuration());
maintenanceWindow.setTimezone(action.getMaintenanceWindowTimeZone());
action.getMaintenanceWindowStartTime()
.ifPresent(nextStart -> maintenanceWindow.setNextStartAt(nextStart.toInstant().toEpochMilli()));
result.setMaintenanceWindow(maintenanceWindow);
}
final String externalRef = action.getExternalRef();
if (!ObjectUtils.isEmpty(externalRef)) {
result.setExternalRef(externalRef);
}
MgmtRestModelMapper.mapBaseToBase(result, action);
result.add(
linkTo(methodOn(MgmtTargetRestApi.class).getAction(targetId, action.getId())).withSelfRel().expand());
return result;
}
static MgmtAction toResponseWithLinks(final String controllerId, final Action action) {
final MgmtAction result = toResponse(controllerId, action);
if (action.isCancelingOrCanceled()) {
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getAction(controllerId, action.getId()))
.withRel(MgmtRestConstants.TARGET_V1_CANCELED_ACTION).expand());
}
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getTarget(controllerId)).withRel("target")
.withName(action.getTarget().getName()).expand());
final DistributionSet distributionSet = action.getDistributionSet();
result.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(distributionSet.getId()))
.withRel("distributionset").withName(distributionSet.getName() + ":" + distributionSet.getVersion())
.expand());
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getActionStatusList(controllerId, action.getId(), 0,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionStatusFields.ID.getJpaEntityFieldName() + ":" + SortDirection.DESC))
.withRel(MgmtRestConstants.TARGET_V1_ACTION_STATUS).expand());
final Rollout rollout = action.getRollout();
if (rollout != null) {
result.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRollout(rollout.getId()))
.withRel(MgmtRestConstants.TARGET_V1_ROLLOUT).withName(rollout.getName()).expand());
}
return result;
}
static List<MgmtAction> toResponse(final String targetId, final Collection<Action> actions) {
if (actions == null) {
return Collections.emptyList();
}
return actions.stream().map(action -> toResponse(targetId, action)).collect(Collectors.toList());
}
static MgmtMetadata toResponseTargetMetadata(final TargetMetadata metadata) {
final MgmtMetadata metadataRest = new MgmtMetadata();
metadataRest.setKey(metadata.getKey());
metadataRest.setValue(metadata.getValue());
return metadataRest;
}
static List<MgmtMetadata> toResponseTargetMetadata(final List<TargetMetadata> metadata) {
return metadata.stream().map(MgmtTargetMapper::toResponseTargetMetadata).collect(Collectors.toList());
}
private static void addPollStatus(final Target target, final MgmtTarget targetRest, final Function<Target, PollStatus> pollStatusResolver) {
final PollStatus pollStatus = pollStatusResolver == null ? target.getPollStatus() : pollStatusResolver.apply(target);
if (pollStatus != null) {
final MgmtPollStatus pollStatusRest = new MgmtPollStatus();
pollStatusRest.setLastRequestAt(
Date.from(pollStatus.getLastPollDate().atZone(ZoneId.systemDefault()).toInstant()).getTime());
pollStatusRest.setNextExpectedRequestAt(
Date.from(pollStatus.getNextPollDate().atZone(ZoneId.systemDefault()).toInstant()).getTime());
pollStatusRest.setOverdue(pollStatus.isOverdue());
targetRest.setPollStatus(pollStatusRest);
}
}
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()).targetType(targetRest.getTargetType());
}
private static String getType(final Action action) {
if (!action.isCancelingOrCanceled()) {
return MgmtAction.ACTION_UPDATE;
} else if (action.isCancelingOrCanceled()) {
return MgmtAction.ACTION_CANCEL;
}
return null;
}
private static MgmtActionStatus toResponse(final ActionStatus actionStatus, final List<String> messages) {
final MgmtActionStatus result = new MgmtActionStatus();
result.setMessages(messages);
result.setReportedAt(actionStatus.getCreatedAt());
result.setStatusId(actionStatus.getId());
result.setType(actionStatus.getStatus().name().toLowerCase());
actionStatus.getCode().ifPresent(result::setCode);
return result;
}
}

View File

@@ -0,0 +1,488 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
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;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignments;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirm;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
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.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
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 target CRUD operations.
*/
@Slf4j
@RestController
public class MgmtTargetResource implements MgmtTargetRestApi {
private static final String ACTION_TARGET_MISSING_ASSIGN_WARN = "given action ({}) is not assigned to given target ({}).";
private final TargetManagement targetManagement;
private final ConfirmationManagement confirmationManagement;
private final DeploymentManagement deploymentManagement;
private final EntityFactory entityFactory;
private final TenantConfigHelper tenantConfigHelper;
MgmtTargetResource(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final ConfirmationManagement confirmationManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement;
this.confirmationManagement = confirmationManagement;
this.entityFactory = entityFactory;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
}
@Override
public ResponseEntity<MgmtTarget> getTarget(@PathVariable("targetId") final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
// to single response include poll status
final MgmtTarget response = MgmtTargetMapper.toResponse(findTarget, tenantConfigHelper, null);
MgmtTargetMapper.addTargetLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<PagedList<MgmtTarget>> getTargets(
@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.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<Target> findTargetsAll;
final long countTargetsAll;
if (rsqlParam != null) {
findTargetsAll = targetManagement.findByRsql(pageable, rsqlParam);
countTargetsAll = targetManagement.countByRsql(rsqlParam);
} else {
findTargetsAll = targetManagement.findAll(pageable);
countTargetsAll = targetManagement.count();
}
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent(), tenantConfigHelper);
return ResponseEntity.ok(new PagedList<>(rest, countTargetsAll));
}
@Override
public ResponseEntity<List<MgmtTarget>> createTargets(@RequestBody final List<MgmtTargetRequestBody> targets) {
log.debug("creating {} targets", targets.size());
final Collection<Target> createdTargets = this.targetManagement
.create(MgmtTargetMapper.fromRequest(entityFactory, targets));
log.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtTargetMapper.toResponse(createdTargets, tenantConfigHelper),
HttpStatus.CREATED);
}
@Override
public ResponseEntity<MgmtTarget> updateTarget(@PathVariable("targetId") final String targetId,
@RequestBody final MgmtTargetRequestBody targetRest) {
if (targetRest.getRequestAttributes() != null) {
if (targetRest.getRequestAttributes()) {
targetManagement.requestControllerAttributes(targetId);
} else {
return ResponseEntity.badRequest().build();
}
}
Target updateTarget;
if (targetRest.getTargetType() != null && targetRest.getTargetType() == -1L) {
// if targetType in request is -1 - unassign targetType from target
this.targetManagement.unassignType(targetId);
// update target without targetType here ...
updateTarget = this.targetManagement.update(entityFactory.target().update(targetId)
.name(targetRest.getName()).description(targetRest.getDescription()).address(targetRest.getAddress())
.securityToken(targetRest.getSecurityToken()).requestAttributes(targetRest.getRequestAttributes()));
} else {
updateTarget = this.targetManagement.update(
entityFactory.target().update(targetId).name(targetRest.getName()).description(targetRest.getDescription())
.address(targetRest.getAddress()).targetType(targetRest.getTargetType())
.securityToken(targetRest.getSecurityToken())
.requestAttributes(targetRest.getRequestAttributes()));
}
final MgmtTarget response = MgmtTargetMapper.toResponse(updateTarget, tenantConfigHelper, null);
MgmtTargetMapper.addTargetLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteTarget(@PathVariable("targetId") final String targetId) {
this.targetManagement.deleteByControllerID(targetId);
log.debug("{} target deleted, return status {}", targetId, HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignTargetType(@PathVariable("targetId") final String targetId) {
this.targetManagement.unassignType(targetId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> assignTargetType(@PathVariable("targetId") final String targetId,
@RequestBody final MgmtId targetTypeId) {
this.targetManagement.assignType(targetId, targetTypeId.getId());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtTargetAttributes> getAttributes(@PathVariable("targetId") final String targetId) {
final Map<String, String> controllerAttributes = targetManagement.getControllerAttributes(targetId);
if (controllerAttributes.isEmpty()) {
return ResponseEntity.noContent().build();
}
final MgmtTargetAttributes result = new MgmtTargetAttributes();
result.putAll(controllerAttributes);
return ResponseEntity.ok(result);
}
@Override
public ResponseEntity<PagedList<MgmtAction>> getActionHistory(@PathVariable("targetId") final String targetId,
@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) {
findTargetWithExceptionIfNotFound(targetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeActionSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<Action> activeActions;
final Long totalActionCount;
if (rsqlParam != null) {
activeActions = this.deploymentManagement.findActionsByTarget(rsqlParam, targetId, pageable);
totalActionCount = this.deploymentManagement.countActionsByTarget(rsqlParam, targetId);
} else {
activeActions = this.deploymentManagement.findActionsByTarget(targetId, pageable);
totalActionCount = this.deploymentManagement.countActionsByTarget(targetId);
}
return ResponseEntity.ok(
new PagedList<>(MgmtTargetMapper.toResponse(targetId, activeActions.getContent()), totalActionCount));
}
@Override
public ResponseEntity<MgmtAction> getAction(@PathVariable("targetId") final String targetId,
@PathVariable("actionId") final Long actionId) {
final Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.getTarget().getControllerId().equals(targetId)) {
log.warn(ACTION_TARGET_MISSING_ASSIGN_WARN, action.getId(), targetId);
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(MgmtTargetMapper.toResponseWithLinks(targetId, action));
}
@Override
public ResponseEntity<Void> cancelAction(@PathVariable("targetId") final String targetId,
@PathVariable("actionId") final Long actionId,
@RequestParam(value = "force", required = false, defaultValue = "false") final boolean force) {
final Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.getTarget().getControllerId().equals(targetId)) {
log.warn(ACTION_TARGET_MISSING_ASSIGN_WARN, actionId, targetId);
return ResponseEntity.notFound().build();
}
if (force) {
this.deploymentManagement.forceQuitAction(actionId);
} else {
this.deploymentManagement.cancelAction(actionId);
}
// both functions will throw an exception, when action is in wrong
// state, which is mapped by MgmtResponseExceptionHandler.
return ResponseEntity.noContent().build();
}
@Override
public ResponseEntity<MgmtAction> updateAction(@PathVariable("targetId") final String targetId,
@PathVariable("actionId") final Long actionId, @RequestBody final MgmtActionRequestBodyPut actionUpdate) {
Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.getTarget().getControllerId().equals(targetId)) {
log.warn(ACTION_TARGET_MISSING_ASSIGN_WARN, action.getId(), targetId);
return ResponseEntity.notFound().build();
}
if (MgmtActionType.FORCED != actionUpdate.getActionType()) {
throw new ValidationException("Resource supports only switch to FORCED.");
}
action = deploymentManagement.forceTargetAction(actionId);
return ResponseEntity.ok(MgmtTargetMapper.toResponseWithLinks(targetId, action));
}
@Override
public ResponseEntity<PagedList<MgmtActionStatus>> getActionStatusList(
@PathVariable("targetId") final String targetId, @PathVariable("actionId") final Long actionId,
@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) {
final Target target = findTargetWithExceptionIfNotFound(targetId);
final Action action = deploymentManagement.findAction(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.getTarget().getId().equals(target.getId())) {
log.warn(ACTION_TARGET_MISSING_ASSIGN_WARN, action.getId(), target.getId());
return ResponseEntity.notFound().build();
}
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeActionStatusSortParam(sortParam);
final Page<ActionStatus> statusList = this.deploymentManagement.findActionStatusByAction(
new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting), action.getId());
return ResponseEntity.ok(new PagedList<>(
MgmtTargetMapper.toActionStatusRestResponse(statusList.getContent(), deploymentManagement),
statusList.getTotalElements()));
}
@Override
public ResponseEntity<MgmtDistributionSet> getAssignedDistributionSet(
@PathVariable("targetId") final String targetId) {
final MgmtDistributionSet distributionSetRest = deploymentManagement.getAssignedDistributionSet(targetId)
.map(ds -> {
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(ds);
MgmtDistributionSetMapper.addLinks(ds, response);
return response;
}).orElse(null);
if (distributionSetRest == null) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.ok(distributionSetRest);
}
@Override
public ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
@PathVariable("targetId") final String targetId,
@RequestBody final MgmtDistributionSetAssignments dsAssignments,
@RequestParam(value = "offline", required = false) final Boolean offline) {
if (offline != null && offline) {
final List<Entry<String, Long>> offlineAssignments = dsAssignments.stream()
.map(dsAssignment -> new SimpleEntry<String, Long>(targetId, dsAssignment.getId()))
.collect(Collectors.toList());
return ResponseEntity.ok(MgmtDistributionSetMapper
.toResponse(deploymentManagement.offlineAssignedDistributionSets(offlineAssignments)));
}
findTargetWithExceptionIfNotFound(targetId);
final List<DeploymentRequest> deploymentRequests = dsAssignments.stream().map(dsAssignment -> {
final boolean isConfirmationRequired = dsAssignment.getConfirmationRequired() == null
? tenantConfigHelper.isConfirmationFlowEnabled()
: dsAssignment.getConfirmationRequired();
return MgmtDeploymentRequestMapper.createAssignmentRequestBuilder(dsAssignment, targetId)
.setConfirmationRequired(isConfirmationRequired).build();
}).collect(Collectors.toList());
final List<DistributionSetAssignmentResult> assignmentResults = deploymentManagement
.assignDistributionSets(deploymentRequests);
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignmentResults));
}
@Override
public ResponseEntity<MgmtDistributionSet> getInstalledDistributionSet(
@PathVariable("targetId") final String targetId) {
final MgmtDistributionSet distributionSetRest = deploymentManagement.getInstalledDistributionSet(targetId)
.map(set -> {
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(set);
MgmtDistributionSetMapper.addLinks(set, response);
return response;
}).orElse(null);
if (distributionSetRest == null) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.ok(distributionSetRest);
}
@Override
public ResponseEntity<List<MgmtTag>> getTags(@PathVariable("targetId") String targetId) {
final Set<TargetTag> tags = targetManagement.getTagsByControllerId(targetId);
return ResponseEntity.ok(
MgmtTagMapper.toResponse(tags == null ? Collections.emptyList() : tags.stream().toList()));
}
@Override
public ResponseEntity<PagedList<MgmtMetadata>> getMetadata(@PathVariable("targetId") final String targetId,
@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.sanitizeDistributionSetMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<TargetMetadata> metaDataPage;
if (rsqlParam != null) {
metaDataPage = targetManagement.findMetaDataByControllerIdAndRsql(pageable, targetId, rsqlParam);
} else {
metaDataPage = targetManagement.findMetaDataByControllerId(pageable, targetId);
}
return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponseTargetMetadata(metaDataPage.getContent()),
metaDataPage.getTotalElements()));
}
@Override
public ResponseEntity<MgmtMetadata> getMetadataValue(@PathVariable("targetId") final String targetId,
@PathVariable("metadataKey") final String metadataKey) {
final TargetMetadata findOne = targetManagement.getMetaDataByControllerId(targetId, metadataKey)
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, targetId, metadataKey));
return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(findOne));
}
@Override
public ResponseEntity<MgmtMetadata> updateMetadata(@PathVariable("targetId") final String targetId,
@PathVariable("metadataKey") final String metadataKey, @RequestBody final MgmtMetadataBodyPut metadata) {
final TargetMetadata updated = targetManagement.updateMetadata(targetId,
entityFactory.generateTargetMetadata(metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(updated));
}
@Override
public ResponseEntity<Void> deleteMetadata(@PathVariable("targetId") final String targetId,
@PathVariable("metadataKey") final String metadataKey) {
targetManagement.deleteMetaData(targetId, metadataKey);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<List<MgmtMetadata>> createMetadata(@PathVariable("targetId") final String targetId,
@RequestBody final List<MgmtMetadata> metadataRest) {
final List<TargetMetadata> created = targetManagement.createMetaData(targetId,
MgmtTargetMapper.fromRequestTargetMetadata(metadataRest, entityFactory));
return new ResponseEntity<>(MgmtTargetMapper.toResponseTargetMetadata(created), HttpStatus.CREATED);
}
@Override
public ResponseEntity<MgmtTargetAutoConfirm> getAutoConfirmStatus(@PathVariable("targetId") final String targetId) {
final Target findTarget = findTargetWithExceptionIfNotFound(targetId);
final MgmtTargetAutoConfirm state = MgmtTargetMapper.getTargetAutoConfirmResponse(findTarget);
return ResponseEntity.ok(state);
}
@Override
public ResponseEntity<Void> activateAutoConfirm(@PathVariable("targetId") final String targetId,
@RequestBody(required = false) final MgmtTargetAutoConfirmUpdate update) {
final String initiator = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getInitiator);
final String remark = getNullIfEmpty(update, MgmtTargetAutoConfirmUpdate::getRemark);
confirmationManagement.activateAutoConfirmation(targetId, initiator, remark);
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
public ResponseEntity<Void> deactivateAutoConfirm(@PathVariable("targetId") final String targetId) {
confirmationManagement.deactivateAutoConfirmation(targetId);
return new ResponseEntity<>(HttpStatus.OK);
}
private Target findTargetWithExceptionIfNotFound(final String targetId) {
return targetManagement.getByControllerID(targetId)
.orElseThrow(() -> new EntityNotFoundException(Target.class, targetId));
}
private <T, R> R getNullIfEmpty(final T object, final Function<T, R> extractMethod) {
return object == null ? null : extractMethod.apply(object);
}
}

View File

@@ -0,0 +1,255 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtAssignedTargetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTargetTagAssigmentResult;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetTagRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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 tag CRUD operations.
*/
@Slf4j
@RestController
public class MgmtTargetTagResource implements MgmtTargetTagRestApi {
private final TargetTagManagement tagManagement;
private final TargetManagement targetManagement;
private final EntityFactory entityFactory;
private final TenantConfigHelper tenantConfigHelper;
MgmtTargetTagResource(final TargetTagManagement tagManagement, final TargetManagement targetManagement,
final EntityFactory entityFactory, final SystemSecurityContext securityContext,
final TenantConfigurationManagement configurationManagement) {
this.tagManagement = tagManagement;
this.targetManagement = targetManagement;
this.entityFactory = entityFactory;
this.tenantConfigHelper = TenantConfigHelper.usingContext(securityContext, configurationManagement);
}
@Override
public ResponseEntity<PagedList<MgmtTag>> getTargetTags(
@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.sanitizeTagSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
Page<TargetTag> findTargetsAll;
if (rsqlParam == null) {
findTargetsAll = this.tagManagement.findAll(pageable);
} else {
findTargetsAll = this.tagManagement.findByRsql(pageable, rsqlParam);
}
final List<MgmtTag> rest = MgmtTagMapper.toResponse(findTargetsAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, findTargetsAll.getTotalElements()));
}
@Override
public ResponseEntity<MgmtTag> getTargetTag(@PathVariable("targetTagId") final Long targetTagId) {
final TargetTag tag = findTargetTagById(targetTagId);
final MgmtTag response = MgmtTagMapper.toResponse(tag);
MgmtTagMapper.addLinks(tag, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtTag>> createTargetTags(@RequestBody final List<MgmtTagRequestBodyPut> tags) {
log.debug("creating {} target tags", tags.size());
final List<TargetTag> createdTargetTags = this.tagManagement
.create(MgmtTagMapper.mapTagFromRequest(entityFactory, tags));
return new ResponseEntity<>(MgmtTagMapper.toResponse(createdTargetTags), HttpStatus.CREATED);
}
@Override
public ResponseEntity<MgmtTag> updateTargetTag(@PathVariable("targetTagId") final Long targetTagId,
@RequestBody final MgmtTagRequestBodyPut restTargetTagRest) {
log.debug("update {} target tag", restTargetTagRest);
final TargetTag updateTargetTag = tagManagement
.update(entityFactory.tag().update(targetTagId).name(restTargetTagRest.getName())
.description(restTargetTagRest.getDescription()).colour(restTargetTagRest.getColour()));
log.debug("target tag updated");
final MgmtTag response = MgmtTagMapper.toResponse(updateTargetTag);
MgmtTagMapper.addLinks(updateTargetTag, response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteTargetTag(@PathVariable("targetTagId") final Long targetTagId) {
log.debug("Delete {} target tag", targetTagId);
final TargetTag targetTag = findTargetTagById(targetTagId);
this.tagManagement.delete(targetTag.getName());
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<PagedList<MgmtTarget>> getAssignedTargets(@PathVariable("targetTagId") final Long targetTagId,
@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.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> findTargetsAll;
if (rsqlParam == null) {
findTargetsAll = targetManagement.findByTag(pageable, targetTagId);
} else {
findTargetsAll = targetManagement.findByRsqlAndTag(pageable, rsqlParam, targetTagId);
}
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(findTargetsAll.getContent(), tenantConfigHelper);
return ResponseEntity.ok(new PagedList<>(rest, findTargetsAll.getTotalElements()));
}
@Override
public ResponseEntity<Void> assignTarget(final Long targetTagId, final String controllerId) {
log.debug("Assign target {} for target tag {}", controllerId, targetTagId);
this.targetManagement.assignTag(List.of(controllerId), targetTagId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> assignTargets(final Long targetTagId, final OnNotFoundPolicy onNotFoundPolicy,
final List<String> controllerIds) {
log.debug("Assign {} targets for target tag {}", controllerIds.size(), targetTagId);
if (onNotFoundPolicy == OnNotFoundPolicy.FAIL) {
this.targetManagement.assignTag(controllerIds, targetTagId);
} else {
final AtomicReference<Collection<String>> notFound = new AtomicReference<>();
this.targetManagement.assignTag(controllerIds, targetTagId, notFound::set);
if (notFound.get() != null) {
// has not found
if (onNotFoundPolicy == OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL) {
throw new EntityNotFoundException(Target.class, notFound.get());
} // else - success
}
}
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignTarget(final Long targetTagId,
@PathVariable("controllerId") final String controllerId) {
log.debug("Unassign target {} for target tag {}", controllerId, targetTagId);
this.targetManagement.unassignTag(controllerId, targetTagId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> unassignTargets(final Long targetTagId, final OnNotFoundPolicy onNotFoundPolicy,
final List<String> controllerIds) {
log.debug("Unassign {} targets for target tag {}", controllerIds.size(), targetTagId);
if (onNotFoundPolicy == OnNotFoundPolicy.FAIL) {
this.targetManagement.unassignTag(controllerIds, targetTagId);
} else {
final AtomicReference<Collection<String>> notFound = new AtomicReference<>();
this.targetManagement.unassignTag(controllerIds, targetTagId, notFound::set);
if (notFound.get() != null) {
// has not found
if (onNotFoundPolicy == OnNotFoundPolicy.ON_WHAT_FOUND_AND_FAIL) {
throw new EntityNotFoundException(Target.class, notFound.get());
} // else - success
}
}
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtTargetTagAssigmentResult> toggleTagAssignment(
@PathVariable("targetTagId") final Long targetTagId,
@RequestBody final List<MgmtAssignedTargetRequestBody> assignedTargetRequestBodies) {
log.debug("Toggle Target assignment {} for target tag {}", assignedTargetRequestBodies.size(), targetTagId);
final TargetTag targetTag = findTargetTagById(targetTagId);
final TargetTagAssignmentResult assigmentResult = this.targetManagement
.toggleTagAssignment(findTargetControllerIds(assignedTargetRequestBodies), targetTag.getName());
final MgmtTargetTagAssigmentResult tagAssigmentResultRest = new MgmtTargetTagAssigmentResult();
tagAssigmentResultRest.setAssignedTargets(
MgmtTargetMapper.toResponse(assigmentResult.getAssignedEntity(), tenantConfigHelper));
tagAssigmentResultRest.setUnassignedTargets(
MgmtTargetMapper.toResponse(assigmentResult.getUnassignedEntity(), tenantConfigHelper));
return ResponseEntity.ok(tagAssigmentResultRest);
}
@Override
public ResponseEntity<List<MgmtTarget>> assignTargetsByRequestBody(@PathVariable("targetTagId") final Long targetTagId,
@RequestBody final List<MgmtAssignedTargetRequestBody> assignedTargetRequestBodies) {
log.debug("Assign targets {} for target tag {}", assignedTargetRequestBodies, targetTagId);
final List<Target> assignedTarget = this.targetManagement
.assignTag(findTargetControllerIds(assignedTargetRequestBodies), targetTagId);
return ResponseEntity.ok(MgmtTargetMapper.toResponse(assignedTarget, tenantConfigHelper));
}
private TargetTag findTargetTagById(final Long targetTagId) {
return tagManagement.get(targetTagId)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId));
}
private List<String> findTargetControllerIds(
final List<MgmtAssignedTargetRequestBody> assignedTargetRequestBodies) {
return assignedTargetRequestBodies.stream().map(MgmtAssignedTargetRequestBody::getControllerId)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
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.json.model.ResponseList;
/**
* A mapper which maps repository model to RESTful model representation and back.
*/
public final class MgmtTargetTypeMapper {
// private constructor, utility class
private MgmtTargetTypeMapper() {
}
static List<TargetTypeCreate> targetFromRequest(final EntityFactory entityFactory,
final Collection<MgmtTargetTypeRequestBodyPost> targetTypesRest) {
if (targetTypesRest == null) {
return Collections.emptyList();
}
return targetTypesRest.stream().map(targetRest -> fromRequest(entityFactory, targetRest))
.collect(Collectors.toList());
}
static List<MgmtTargetType> toListResponse(final List<TargetType> types) {
if (types == null) {
return Collections.emptyList();
}
return new ResponseList<>(types.stream().map(MgmtTargetTypeMapper::toResponse).collect(Collectors.toList()));
}
static MgmtTargetType toResponse(final TargetType type) {
final MgmtTargetType result = new MgmtTargetType();
MgmtRestModelMapper.mapTypeToType(result, type);
result.setTypeId(type.getId());
result.add(
linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(result.getTypeId())).withSelfRel().expand());
return result;
}
static void addLinks(final MgmtTargetType result) {
result.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getCompatibleDistributionSets(result.getTypeId()))
.withRel(MgmtRestConstants.TARGETTYPE_V1_DS_TYPES).expand());
}
private static TargetTypeCreate fromRequest(final EntityFactory entityFactory,
final MgmtTargetTypeRequestBodyPost targetTypesRest) {
return entityFactory.targetType().create()
.name(targetTypesRest.getName()).description(targetTypesRest.getDescription())
.key(targetTypesRest.getKey()).colour(targetTypesRest.getColour())
.compatible(getDistributionSets(targetTypesRest));
}
private static Collection<Long> getDistributionSets(final MgmtTargetTypeRequestBodyPost targetTypesRest) {
return Optional.ofNullable(targetTypesRest.getCompatibledistributionsettypes())
.map(ds -> ds.stream().map(MgmtDistributionSetTypeAssignment::getId).collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
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.mgmt.rest.resource.util.PagingUtility;
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.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.
*/
@Slf4j
@RestController
public class MgmtTargetTypeResource implements MgmtTargetTypeRestApi {
private final TargetTypeManagement targetTypeManagement;
private final EntityFactory entityFactory;
public MgmtTargetTypeResource(final TargetTypeManagement targetTypeManagement, final EntityFactory entityFactory) {
this.targetTypeManagement = targetTypeManagement;
this.entityFactory = entityFactory;
}
@Override
public ResponseEntity<PagedList<MgmtTargetType>> getTargetTypes(
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetTypeSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<TargetType> findTargetTypesAll;
long countTargetTypesAll;
if (rsqlParam != null) {
findTargetTypesAll = targetTypeManagement.findByRsql(pageable, rsqlParam);
countTargetTypesAll = ((Page<TargetType>) findTargetTypesAll).getTotalElements();
} else {
findTargetTypesAll = targetTypeManagement.findAll(pageable);
countTargetTypesAll = targetTypeManagement.count();
}
final List<MgmtTargetType> rest = MgmtTargetTypeMapper.toListResponse(findTargetTypesAll.getContent());
return ResponseEntity.ok(new PagedList<>(rest, countTargetTypesAll));
}
@Override
public ResponseEntity<MgmtTargetType> getTargetType(@PathVariable("targetTypeId") final Long targetTypeId) {
final TargetType foundType = findTargetTypeWithExceptionIfNotFound(targetTypeId);
final MgmtTargetType response = MgmtTargetTypeMapper.toResponse(foundType);
MgmtTargetTypeMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<Void> deleteTargetType(@PathVariable("targetTypeId") final Long targetTypeId) {
log.debug("Delete {} target type", targetTypeId);
targetTypeManagement.delete(targetTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtTargetType> updateTargetType(@PathVariable("targetTypeId") final Long targetTypeId,
@RequestBody final MgmtTargetTypeRequestBodyPut restTargetType) {
final TargetType updated = targetTypeManagement
.update(entityFactory.targetType().update(targetTypeId).name(restTargetType.getName())
.description(restTargetType.getDescription()).colour(restTargetType.getColour()));
final MgmtTargetType response = MgmtTargetTypeMapper.toResponse(updated);
MgmtTargetTypeMapper.addLinks(response);
return ResponseEntity.ok(response);
}
@Override
public ResponseEntity<List<MgmtTargetType>> createTargetTypes(
@RequestBody final List<MgmtTargetTypeRequestBodyPost> targetTypes) {
final List<TargetType> createdTargetTypes = targetTypeManagement
.create(MgmtTargetTypeMapper.targetFromRequest(entityFactory, targetTypes));
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtTargetTypeMapper.toListResponse(createdTargetTypes));
}
@Override
public ResponseEntity<List<MgmtDistributionSetType>> getCompatibleDistributionSets(
@PathVariable("targetTypeId") final Long targetTypeId) {
final TargetType foundType = findTargetTypeWithExceptionIfNotFound(targetTypeId);
return ResponseEntity
.ok(MgmtDistributionSetTypeMapper.toListResponse(foundType.getCompatibleDistributionSetTypes()));
}
@Override
public ResponseEntity<Void> removeCompatibleDistributionSet(@PathVariable("targetTypeId") final Long targetTypeId,
@PathVariable("distributionSetTypeId") final Long distributionSetTypeId) {
targetTypeManagement.unassignDistributionSetType(targetTypeId, distributionSetTypeId);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<Void> addCompatibleDistributionSets(@PathVariable("targetTypeId") final Long targetTypeId,
@RequestBody final List<MgmtDistributionSetTypeAssignment> distributionSetTypeIds) {
targetTypeManagement.assignCompatibleDistributionSetTypes(targetTypeId, distributionSetTypeIds.stream()
.map(MgmtDistributionSetTypeAssignment::getId).collect(Collectors.toList()));
return ResponseEntity.ok().build();
}
private TargetType findTargetTypeWithExceptionIfNotFound(final Long targetTypeId) {
return targetTypeManagement.get(targetTypeId)
.orElseThrow(() -> new EntityNotFoundException(TargetType.class, targetTypeId));
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValue;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*/
public final class MgmtTenantManagementMapper {
public static String DEFAULT_DISTRIBUTION_SET_TYPE_KEY = "default.ds.type";
private MgmtTenantManagementMapper() {
// Utility class
}
public static MgmtSystemTenantConfigurationValue toResponseTenantConfigurationValue(String key, TenantConfigurationValue<?> repoConfValue) {
final MgmtSystemTenantConfigurationValue restConfValue = new MgmtSystemTenantConfigurationValue();
restConfValue.setValue(repoConfValue.getValue());
restConfValue.setGlobal(repoConfValue.isGlobal());
restConfValue.setCreatedAt(repoConfValue.getCreatedAt());
restConfValue.setCreatedBy(repoConfValue.getCreatedBy());
restConfValue.setLastModifiedAt(repoConfValue.getLastModifiedAt());
restConfValue.setLastModifiedBy(repoConfValue.getLastModifiedBy());
restConfValue.add(linkTo(methodOn(MgmtTenantManagementResource.class).getTenantConfigurationValue(key))
.withSelfRel().expand());
return restConfValue;
}
public static MgmtSystemTenantConfigurationValue toResponseDefaultDsType(Long defaultDistributionSetType) {
final MgmtSystemTenantConfigurationValue restConfValue = new MgmtSystemTenantConfigurationValue();
restConfValue.setValue(defaultDistributionSetType);
restConfValue.setGlobal(Boolean.FALSE);
restConfValue.add(linkTo(methodOn(MgmtTenantManagementResource.class).getTenantConfigurationValue(DEFAULT_DISTRIBUTION_SET_TYPE_KEY))
.withSelfRel().expand());
return restConfValue;
}
}

View File

@@ -0,0 +1,183 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValue;
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTenantManagementRestApi;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
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.RestController;
/**
* REST Resource for handling tenant specific configuration operations.
*/
@Slf4j
@RestController
public class MgmtTenantManagementResource implements MgmtTenantManagementRestApi {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final TenantConfigurationProperties tenantConfigurationProperties;
private final SystemManagement systemManagement;
MgmtTenantManagementResource(final TenantConfigurationManagement tenantConfigurationManagement,
final TenantConfigurationProperties tenantConfigurationProperties,
final SystemManagement systemManagement) {
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.tenantConfigurationProperties = tenantConfigurationProperties;
this.systemManagement = systemManagement;
}
@Override
public ResponseEntity<Map<String, MgmtSystemTenantConfigurationValue>> getTenantConfiguration() {
// Load and Construct default Tenant Configuration
final Map<String, MgmtSystemTenantConfigurationValue> tenantConfigurationValueMap = new HashMap<>();
tenantConfigurationProperties.getConfigurationKeys().forEach(key -> {
try {
tenantConfigurationValueMap.put(key.getKeyName(), loadTenantConfigurationValueBy(key.getKeyName()));
} catch (final InsufficientPermissionException e) {
// some values as gateway token may not be accessibly for the caller - just skip them
}
});
// Load and Add Default DistributionSetType
final MgmtSystemTenantConfigurationValue defaultDsTypeId = loadTenantConfigurationValueBy(
MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY);
tenantConfigurationValueMap.put(MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY, defaultDsTypeId);
// return combined TenantConfiguration and TenantMetadata
log.debug("getTenantConfiguration, return status {}", HttpStatus.OK);
return ResponseEntity.ok(tenantConfigurationValueMap);
}
@Override
public ResponseEntity<Void> deleteTenantConfigurationValue(@PathVariable("keyName") final String keyName) {
//Default DistributionSet Type cannot be deleted as is part of TenantMetadata
if (isDefaultDistributionSetTypeKey(keyName)) {
return ResponseEntity.badRequest().build();
}
tenantConfigurationManagement.deleteConfiguration(keyName);
log.debug("{} config value deleted, return status {}", keyName, HttpStatus.OK);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<MgmtSystemTenantConfigurationValue> getTenantConfigurationValue(
@PathVariable("keyName") final String keyName) {
return ResponseEntity.ok(loadTenantConfigurationValueBy(keyName));
}
@Override
public ResponseEntity<MgmtSystemTenantConfigurationValue> updateTenantConfigurationValue(
@PathVariable("keyName") final String keyName,
@RequestBody final MgmtSystemTenantConfigurationValueRequest configurationValueRest) {
Serializable configurationValue = configurationValueRest.getValue();
final MgmtSystemTenantConfigurationValue responseUpdatedValue;
if (isDefaultDistributionSetTypeKey(keyName)) {
responseUpdatedValue = updateDefaultDsType(configurationValue);
} else {
final TenantConfigurationValue<? extends Serializable> updatedTenantConfigurationValue = tenantConfigurationManagement
.addOrUpdateConfiguration(keyName, configurationValueRest.getValue());
responseUpdatedValue = MgmtTenantManagementMapper.toResponseTenantConfigurationValue(keyName, updatedTenantConfigurationValue);
}
return ResponseEntity.ok(responseUpdatedValue);
}
@Override
public ResponseEntity<List<MgmtSystemTenantConfigurationValue>> updateTenantConfiguration(
Map<String, Serializable> configurationValueMap) {
boolean containsNull = configurationValueMap.keySet().stream()
.anyMatch(Objects::isNull);
if (containsNull) {
return ResponseEntity.badRequest().build();
}
//Try update TenantMetadata first
Serializable defaultDsTypeValueUpdate = configurationValueMap.remove(MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY);
Long oldDefaultDsType = null;
MgmtSystemTenantConfigurationValue updatedDefaultDsType = null;
if (defaultDsTypeValueUpdate != null) {
oldDefaultDsType = systemManagement.getTenantMetadata().getDefaultDsType().getId();
updatedDefaultDsType = updateDefaultDsType(defaultDsTypeValueUpdate);
}
//try update TenantConfiguration, in case of Error -> rollback TenantMetadata
Map<String, TenantConfigurationValue<Serializable>> tenantConfigurationValues;
try {
tenantConfigurationValues = tenantConfigurationManagement.addOrUpdateConfiguration(configurationValueMap);
} catch (Exception ex) {
//if DefaultDsType was updated, rollback it in case of TenantConfiguration update.
if (updatedDefaultDsType != null) {
systemManagement.updateTenantMetadata(oldDefaultDsType);
}
throw ex;
}
List<MgmtSystemTenantConfigurationValue> tenantConfigurationListUpdated = new java.util.ArrayList<>(
tenantConfigurationValues.entrySet().stream()
.map(entry -> MgmtTenantManagementMapper.toResponseTenantConfigurationValue(entry.getKey(), entry.getValue()))
.toList());
if (updatedDefaultDsType != null) {
tenantConfigurationListUpdated.add(updatedDefaultDsType);
}
return ResponseEntity.ok(tenantConfigurationListUpdated);
}
private static boolean isDefaultDistributionSetTypeKey(String keyName) {
return MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY.equals(keyName);
}
private MgmtSystemTenantConfigurationValue loadTenantConfigurationValueBy(String keyName) {
//Check if requested key is TenantConfiguration or TenantMetadata, load it and return it as rest response
MgmtSystemTenantConfigurationValue response;
if (isDefaultDistributionSetTypeKey(keyName)) {
response = MgmtTenantManagementMapper.toResponseDefaultDsType(systemManagement.getTenantMetadata().getDefaultDsType().getId());
} else {
response = MgmtTenantManagementMapper.toResponseTenantConfigurationValue(keyName,
tenantConfigurationManagement.getConfigurationValue(keyName));
}
return response;
}
private MgmtSystemTenantConfigurationValue updateDefaultDsType(Serializable defaultDsType) {
long updateDefaultDsType;
try {
updateDefaultDsType = ((Number) defaultDsType).longValue();
} catch (ClassCastException cce) {
throw new TenantConfigurationValidatorException(String.format(
"Default DistributionSetType Value Type is incorrect. Expected Long, received %s", defaultDsType.getClass().getName()));
}
systemManagement.updateTenantMetadata(updateDefaultDsType);
return MgmtTenantManagementMapper.toResponseDefaultDsType(updateDefaultDsType);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource.exception;
import java.io.Serial;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception used by the REST API in case of invalid sort parameter syntax.
*/
public class SortParameterSyntaxErrorException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException with {@link SpServerError#SP_REST_SORT_PARAM_SYNTAX} error.
*/
public SortParameterSyntaxErrorException() {
super(SpServerError.SP_REST_SORT_PARAM_SYNTAX);
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource.exception;
import java.io.Serial;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception used by the REST API in case of invalid sort parameter direction name.
*/
public class SortParameterUnsupportedDirectionException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException with {@link SpServerError#SP_REST_SORT_PARAM_INVALID_DIRECTION} error.
*
* @param cause the cause (which is saved for later retrieval by the getCause() method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
*/
public SortParameterUnsupportedDirectionException(final Throwable cause) {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_DIRECTION, cause);
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource.exception;
import java.io.Serial;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception used by the REST API in case of invalid field name in the sort parameter.
*/
public class SortParameterUnsupportedFieldException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
/**
* Creates a new SortParameterSyntaxErrorException with {@link SpServerError#SP_REST_SORT_PARAM_INVALID_FIELD} error.
*
* @param cause the cause (which is saved for later retrieval by the getCause() method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
*/
public SortParameterUnsupportedFieldException(final Throwable cause) {
super(SpServerError.SP_REST_SORT_PARAM_INVALID_FIELD, cause);
}
}

View File

@@ -0,0 +1,170 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource.util;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
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.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
/**
* Utility class for for paged body generation.
*/
public final class PagingUtility {
/*
* utility constructor private.
*/
private PagingUtility() {
}
public static int sanitizeOffsetParam(final int offset) {
if (offset < 0) {
return MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE;
}
return offset;
}
public static int sanitizePageLimitParam(final int pageLimit) {
if (pageLimit < 1) {
return MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE;
} else if (pageLimit > MgmtRestConstants.REQUEST_PARAMETER_PAGING_MAX_LIMIT) {
return MgmtRestConstants.REQUEST_PARAMETER_PAGING_MAX_LIMIT;
}
return pageLimit;
}
public static Sort sanitizeTargetSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, TargetFields.CONTROLLERID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(TargetFields.class, sortParam));
}
public static Sort sanitizeTargetTypeSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, TargetTypeFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(TargetTypeFields.class, sortParam));
}
public static Sort sanitizeTagSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, TagFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(TagFields.class, sortParam));
}
public static Sort sanitizeTargetFilterQuerySortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, TargetFilterQueryFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(TargetFilterQueryFields.class, sortParam));
}
public static Sort sanitizeSoftwareModuleSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, SoftwareModuleFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(SoftwareModuleFields.class, sortParam));
}
public static Sort sanitizeSoftwareModuleTypeSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, SoftwareModuleTypeFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(SoftwareModuleTypeFields.class, sortParam));
}
public static Sort sanitizeDistributionSetSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, DistributionSetFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(DistributionSetFields.class, sortParam));
}
public static Sort sanitizeDistributionSetTypeSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, DistributionSetTypeFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(DistributionSetTypeFields.class, sortParam));
}
public static Sort sanitizeActionSortParam(final String sortParam) {
if (sortParam == null) {
// default sort is DESC in case of action to match behavior
// of management UI (last entry on top)
return Sort.by(Direction.DESC, ActionFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(ActionFields.class, sortParam));
}
public static Sort sanitizeActionStatusSortParam(final String sortParam) {
if (sortParam == null) {
// default sort is DESC in case of action status to match behavior
// of management UI (last entry on top)
return Sort.by(Direction.DESC, ActionStatusFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(ActionStatusFields.class, sortParam));
}
public static Sort sanitizeDistributionSetMetadataSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, DistributionSetMetadataFields.KEY.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(DistributionSetMetadataFields.class, sortParam));
}
public static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, SoftwareModuleMetadataFields.KEY.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam));
}
public static Sort sanitizeRolloutSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, RolloutFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(RolloutFields.class, sortParam));
}
public static Sort sanitizeRolloutGroupSortParam(final String sortParam) {
if (sortParam == null) {
// default
return Sort.by(Direction.ASC, RolloutGroupFields.ID.getJpaEntityFieldName());
}
return Sort.by(SortUtility.parse(RolloutGroupFields.class, sortParam));
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.mgmt.rest.resource.util;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.mgmt.rest.resource.exception.SortParameterSyntaxErrorException;
import org.eclipse.hawkbit.mgmt.rest.resource.exception.SortParameterUnsupportedDirectionException;
import org.eclipse.hawkbit.mgmt.rest.resource.exception.SortParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
/**
* A utility class for parsing query parameters which define the sorting of elements.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SortUtility {
/**
* the delimiter between the different field directions in the sort request.
*/
public static final String DELIMITER_SORT_TUPLE = ",";
/**
* the delimiter between the field and direction in the sort request.
*/
public static final String DELIMITER_FIELD_DIRECTION = ":";
/**
* Parses the sort string e.g. given in a REST call based on the definition
* of sorting: <code>http://localhost/entity?s=field1:ASC,field2:DESC</code> The fields
* will be split into the keys of the returned map. The direction of the
* sorting will be mapped into the {@link Direction} enum.
*
* @param enumType the class of the enum which the fields in the sort string should be related to.
* @param <T> the type of the enumeration which must be derived from {@link RsqlQueryField}
* @param sortString the string representation of the query parameters. Might be {@code null} or an empty string.
* @return a list which holds the {@link RsqlQueryField} and the specific {@link Direction} for them as a tuple. Never {@code null}.
* In case of no sorting parameters an empty map will be returned.
* @throws SortParameterSyntaxErrorException if the sorting query parameter is not well-formed
* @throws SortParameterUnsupportedFieldException if a field name cannot be mapped to the enum type
* @throws SortParameterUnsupportedDirectionException if the given direction is not "ASC" or "DESC"
*/
public static <T extends Enum<T> & RsqlQueryField> List<Order> parse(final Class<T> enumType, final String sortString)
throws SortParameterSyntaxErrorException {
final List<Order> orders = new ArrayList<>();
// scan the sort tuples e.g. field:direction
if (sortString != null) {
final StringTokenizer tupleTokenizer = new StringTokenizer(sortString, DELIMITER_SORT_TUPLE);
while (tupleTokenizer.hasMoreTokens()) {
final String sortTuple = tupleTokenizer.nextToken().trim();
final StringTokenizer fieldDirectionTokenizer = new StringTokenizer(sortTuple, DELIMITER_FIELD_DIRECTION);
if (fieldDirectionTokenizer.countTokens() == 2) {
final String fieldName = fieldDirectionTokenizer.nextToken().trim().toUpperCase();
final String sortDirectionStr = fieldDirectionTokenizer.nextToken().trim();
final T identifier;
try {
identifier = Enum.valueOf(enumType, fieldName.toUpperCase());
} catch (final IllegalArgumentException e) {
throw new SortParameterUnsupportedFieldException(e);
}
final Direction sortDirection;
try {
sortDirection = Direction.fromString(sortDirectionStr);
} catch (final IllegalArgumentException e) {
throw new SortParameterUnsupportedDirectionException(e);
}
orders.add(new Order(sortDirection, identifier.getJpaEntityFieldName()));
} else {
throw new SortParameterSyntaxErrorException();
}
}
}
return orders;
}
}

View File

@@ -0,0 +1,14 @@
#
# Copyright (c) 2018 Microsoft and others
#
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Upload of large files
spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=-1
spring.servlet.multipart.file-size-threshold=1MB