Fix sonar findings on 21 style (#3020)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2026-04-15 16:57:10 +03:00
committed by GitHub
parent 643e96b7b1
commit 82ee1cc4e6
12 changed files with 284 additions and 273 deletions

View File

@@ -39,7 +39,7 @@ public class AmqpDeadletterProperties {
* @return map which holds the properties
*/
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
final Map<String, Object> args = new HashMap<>(1);
final Map<String, Object> args = HashMap.newHashMap(1);
args.put("x-dead-letter-exchange", exchange);
return args;
}
@@ -55,7 +55,7 @@ public class AmqpDeadletterProperties {
}
private Map<String, Object> getTTLArgs() {
final Map<String, Object> args = new HashMap<>(1);
final Map<String, Object> args = HashMap.newHashMap(1);
args.put("x-message-ttl", getTtl());
return args;
}

View File

@@ -142,7 +142,7 @@ public class DmfAmqpDeclarationConfiguration {
}
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
final Map<String, Object> args = new HashMap<>(2);
final Map<String, Object> args = HashMap.newHashMap(2);
args.put("x-message-ttl", Duration.ofSeconds(30).toMillis());
args.put("x-max-length", 1_000);
return args;

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.mcp.server.tools;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.mcp.server.config.HawkbitMcpProperties;
@@ -24,10 +27,18 @@ import org.eclipse.hawkbit.mcp.server.dto.TargetRequest;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
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.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.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
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.MgmtActionRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
@@ -39,9 +50,6 @@ import org.eclipse.hawkbit.sdk.Tenant;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springframework.http.ResponseEntity;
import java.util.Collections;
import java.util.List;
/**
* MCP tools for hawkBit using the SDK.
* <p>
@@ -94,8 +102,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_targets",
description = "Retrieves a paged list of targets (devices) with optional RSQL filtering. " +
"Targets represent devices that can receive software updates.")
description = "Retrieves a paged list of targets (devices) with optional RSQL filtering. " +
"Targets represent devices that can receive software updates.")
public PagedResponse<MgmtTarget> listTargets(final ListRequest request) {
log.debug("Listing targets with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -111,8 +119,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_rollouts",
description = "Retrieves a paged list of rollouts with optional RSQL filtering. " +
"Rollouts are used to deploy software to groups of targets.")
description = "Retrieves a paged list of rollouts with optional RSQL filtering. " +
"Rollouts are used to deploy software to groups of targets.")
public PagedResponse<MgmtRolloutResponseBody> listRollouts(final ListRequest request) {
log.debug("Listing rollouts with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -129,8 +137,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_distribution_sets",
description = "Retrieves a paged list of distribution sets with optional RSQL filtering. " +
"Distribution sets are software packages that can be deployed to targets.")
description = "Retrieves a paged list of distribution sets with optional RSQL filtering. " +
"Distribution sets are software packages that can be deployed to targets.")
public PagedResponse<MgmtDistributionSet> listDistributionSets(final ListRequest request) {
log.debug("Listing distribution sets with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -146,8 +154,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_actions",
description = "Retrieves a paged list of actions with optional RSQL filtering. " +
"Actions represent deployment operations assigned to targets.")
description = "Retrieves a paged list of actions with optional RSQL filtering. " +
"Actions represent deployment operations assigned to targets.")
public PagedResponse<MgmtAction> listActions(final ListRequest request) {
log.debug("Listing actions with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -164,8 +172,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_software_modules",
description = "Retrieves a paged list of software modules with optional RSQL filtering. " +
"Software modules are individual software components within distribution sets.")
description = "Retrieves a paged list of software modules with optional RSQL filtering. " +
"Software modules are individual software components within distribution sets.")
public PagedResponse<MgmtSoftwareModule> listSoftwareModules(final ListRequest request) {
log.debug("Listing software modules with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -181,8 +189,8 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "list_target_filters",
description = "Retrieves a paged list of target filter queries with optional RSQL filtering. " +
"Target filters define RSQL queries to group targets for rollouts or auto-assignment.")
description = "Retrieves a paged list of target filter queries with optional RSQL filtering. " +
"Target filters define RSQL queries to group targets for rollouts or auto-assignment.")
public PagedResponse<MgmtTargetFilterQuery> listTargetFilters(final ListRequest request) {
log.debug("Listing target filters with rsql={}, offset={}, limit={}",
request.rsql(), request.getOffsetOrDefault(), request.getLimitOrDefault());
@@ -199,305 +207,304 @@ public class HawkbitMcpToolProvider {
}
@McpTool(name = "manage_target",
description = "Create, update, or delete targets (devices). " +
"Operations: CREATE (new target with controllerId, name, description. When creating a target without a specific target type, set \"targetType\": null), " +
"UPDATE (modify existing target by controllerId), " +
"DELETE (remove target by controllerId). " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"controllerId\":\"id\",\"name\":\"name\"}}, " +
"{\"type\":\"Update\",\"controllerId\":\"id\",\"body\":{...}}, " +
"{\"type\":\"Delete\",\"controllerId\":\"id\"}")
description = "Create, update, or delete targets (devices). " +
"Operations: CREATE (new target with controllerId, name, description. When creating a target without a specific target type, set \"targetType\": null), " +
"UPDATE (modify existing target by controllerId), " +
"DELETE (remove target by controllerId). " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"controllerId\":\"id\",\"name\":\"name\"}}, " +
"{\"type\":\"Update\",\"controllerId\":\"id\",\"body\":{...}}, " +
"{\"type\":\"Delete\",\"controllerId\":\"id\"}")
@SuppressWarnings("java:S3776") // not too complex
public OperationResponse<Object> manageTarget(final TargetRequest request) {
log.debug("Managing target: request={}", request.getClass().getSimpleName());
final MgmtTargetRestApi api = hawkbitClient.mgmtService(MgmtTargetRestApi.class, dummyTenant);
if (request instanceof TargetRequest.Create r) {
if (request instanceof TargetRequest.Create(MgmtTargetRequestBody body)) {
validateOperation(OP_CREATE, TARGETS);
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_CREATE, "Request body is required for CREATE operation");
}
final ResponseEntity<List<MgmtTarget>> response = api.createTargets(List.of(r.body()));
final ResponseEntity<List<MgmtTarget>> response = api.createTargets(List.of(body));
final List<MgmtTarget> created = response.getBody();
return OperationResponse.success(OP_CREATE, "Target created successfully",
created != null && !created.isEmpty() ? created.get(0) : null);
} else if (request instanceof TargetRequest.Update r) {
} else if (request instanceof TargetRequest.Update(String controllerId, MgmtTargetRequestBody body)) {
validateOperation(OP_UPDATE, TARGETS);
if (r.controllerId() == null || r.controllerId().isBlank()) {
if (controllerId == null || controllerId.isBlank()) {
return OperationResponse.failure(OP_UPDATE, "controllerId is required for UPDATE operation");
}
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_UPDATE, "Request body is required for UPDATE operation");
}
final ResponseEntity<MgmtTarget> response = api.updateTarget(r.controllerId(), r.body());
final ResponseEntity<MgmtTarget> response = api.updateTarget(controllerId, body);
return OperationResponse.success(OP_UPDATE, "Target updated successfully", response.getBody());
} else if (request instanceof TargetRequest.Delete r) {
} else if (request instanceof TargetRequest.Delete(String controllerId)) {
validateOperation(OP_DELETE, TARGETS);
if (r.controllerId() == null || r.controllerId().isBlank()) {
if (controllerId == null || controllerId.isBlank()) {
return OperationResponse.failure(OP_DELETE, "controllerId is required for DELETE operation");
}
api.deleteTarget(r.controllerId());
api.deleteTarget(controllerId);
return OperationResponse.success(OP_DELETE, "Target deleted successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
@McpTool(name = "manage_rollout",
description = "Create, update, delete, and control rollouts for software deployment. " +
"Use 'type' field to select operation. " +
"Types: Create, Update, Delete, Start, Pause, Stop, Resume, Approve, Deny, Retry, TriggerNextGroup. " +
"For Create: use rollout 'type' values: 'soft', 'forced', 'timeforced', 'downloadonly' (lowercase). " +
"If 'groups' list is provided, omit 'amountGroups' (they are mutually exclusive). " +
"Examples: {\"type\":\"Create\",\"body\":{...}}, " +
"{\"type\":\"Start\",\"rolloutId\":123}, " +
"{\"type\":\"Approve\",\"rolloutId\":123,\"remark\":\"approved\"}")
description = "Create, update, delete, and control rollouts for software deployment. " +
"Use 'type' field to select operation. " +
"Types: Create, Update, Delete, Start, Pause, Stop, Resume, Approve, Deny, Retry, TriggerNextGroup. " +
"For Create: use rollout 'type' values: 'soft', 'forced', 'timeforced', 'downloadonly' (lowercase). " +
"If 'groups' list is provided, omit 'amountGroups' (they are mutually exclusive). " +
"Examples: {\"type\":\"Create\",\"body\":{...}}, " +
"{\"type\":\"Start\",\"rolloutId\":123}, " +
"{\"type\":\"Approve\",\"rolloutId\":123,\"remark\":\"approved\"}")
@SuppressWarnings("java:S3776") // not too complex, iterative logic
public OperationResponse<Object> manageRollout(final RolloutRequest request) {
log.debug("Managing rollout: request={}", request.getClass().getSimpleName());
final MgmtRolloutRestApi api = hawkbitClient.mgmtService(MgmtRolloutRestApi.class, dummyTenant);
if (request instanceof RolloutRequest.Create r) {
if (request instanceof RolloutRequest.Create(MgmtRolloutRestRequestBodyPost body)) {
validateRolloutOperation(OP_CREATE);
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
}
final ResponseEntity<MgmtRolloutResponseBody> response = api.create(r.body());
final ResponseEntity<MgmtRolloutResponseBody> response = api.create(body);
return OperationResponse.success(OP_CREATE, "Rollout created successfully", response.getBody());
} else if (request instanceof RolloutRequest.Update r) {
} else if (request instanceof RolloutRequest.Update(Long rolloutId, MgmtRolloutRestRequestBodyPut body)) {
validateRolloutOperation("update");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_UPDATE, "rolloutId is required for UPDATE operation");
}
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
}
final ResponseEntity<MgmtRolloutResponseBody> response = api.update(r.rolloutId(), r.body());
final ResponseEntity<MgmtRolloutResponseBody> response = api.update(rolloutId, body);
return OperationResponse.success(OP_UPDATE, "Rollout updated successfully", response.getBody());
} else if (request instanceof RolloutRequest.Delete r) {
} else if (request instanceof RolloutRequest.Delete(Long rolloutId)) {
validateRolloutOperation(OP_DELETE);
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_DELETE, "rolloutId is required for DELETE operation");
}
api.delete(r.rolloutId());
api.delete(rolloutId);
return OperationResponse.success(OP_DELETE, "Rollout deleted successfully");
} else if (request instanceof RolloutRequest.Start r) {
} else if (request instanceof RolloutRequest.Start(Long rolloutId)) {
validateRolloutOperation("start");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_START, "rolloutId is required for START operation");
}
api.start(r.rolloutId());
api.start(rolloutId);
return OperationResponse.success(OP_START, "Rollout started successfully");
} else if (request instanceof RolloutRequest.Pause r) {
} else if (request instanceof RolloutRequest.Pause(Long rolloutId)) {
validateRolloutOperation("pause");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_PAUSE, "rolloutId is required for PAUSE operation");
}
api.pause(r.rolloutId());
api.pause(rolloutId);
return OperationResponse.success(OP_PAUSE, "Rollout paused successfully");
} else if (request instanceof RolloutRequest.Stop r) {
} else if (request instanceof RolloutRequest.Stop(Long rolloutId)) {
validateRolloutOperation("stop");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_STOP, "rolloutId is required for STOP operation");
}
api.stop(r.rolloutId());
api.stop(rolloutId);
return OperationResponse.success(OP_STOP, "Rollout stopped successfully");
} else if (request instanceof RolloutRequest.Resume r) {
} else if (request instanceof RolloutRequest.Resume(Long rolloutId)) {
validateRolloutOperation("resume");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_RESUME, "rolloutId is required for RESUME operation");
}
api.resume(r.rolloutId());
api.resume(rolloutId);
return OperationResponse.success(OP_RESUME, "Rollout resumed successfully");
} else if (request instanceof RolloutRequest.Approve r) {
} else if (request instanceof RolloutRequest.Approve(Long rolloutId, String remark)) {
validateRolloutOperation("approve");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_APPROVE, "rolloutId is required for APPROVE operation");
}
api.approve(r.rolloutId(), r.remark());
api.approve(rolloutId, remark);
return OperationResponse.success(OP_APPROVE, "Rollout approved successfully");
} else if (request instanceof RolloutRequest.Deny r) {
} else if (request instanceof RolloutRequest.Deny(Long rolloutId, String remark)) {
validateRolloutOperation("deny");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_DENY, "rolloutId is required for DENY operation");
}
api.deny(r.rolloutId(), r.remark());
api.deny(rolloutId, remark);
return OperationResponse.success(OP_DENY, "Rollout denied successfully");
} else if (request instanceof RolloutRequest.Retry r) {
} else if (request instanceof RolloutRequest.Retry(Long rolloutId)) {
validateRolloutOperation("retry");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_RETRY, "rolloutId is required for RETRY operation");
}
final ResponseEntity<MgmtRolloutResponseBody> response = api.retryRollout(r.rolloutId());
final ResponseEntity<MgmtRolloutResponseBody> response = api.retryRollout(rolloutId);
return OperationResponse.success(OP_RETRY, "Rollout retry created successfully", response.getBody());
} else if (request instanceof RolloutRequest.TriggerNextGroup r) {
} else if (request instanceof RolloutRequest.TriggerNextGroup(Long rolloutId)) {
validateRolloutOperation("trigger-next-group");
if (r.rolloutId() == null) {
if (rolloutId == null) {
return OperationResponse.failure(OP_TRIGGER_NEXT_GROUP, "rolloutId is required for TRIGGER_NEXT_GROUP operation");
}
api.triggerNextGroup(r.rolloutId());
api.triggerNextGroup(rolloutId);
return OperationResponse.success(OP_TRIGGER_NEXT_GROUP, "Next rollout group triggered successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
@McpTool(name = "manage_distribution_set",
description = "Create, update, or delete distribution sets (software packages). " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"version\":\"v\",\"type\":\"t\"}}, " +
"{\"type\":\"Update\",\"distributionSetId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"distributionSetId\":123}")
description = "Create, update, or delete distribution sets (software packages). " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"version\":\"v\",\"type\":\"t\"}}, " +
"{\"type\":\"Update\",\"distributionSetId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"distributionSetId\":123}")
public OperationResponse<Object> manageDistributionSet(final DistributionSetRequest request) {
log.debug("Managing distribution set: request={}", request.getClass().getSimpleName());
final MgmtDistributionSetRestApi api = hawkbitClient.mgmtService(MgmtDistributionSetRestApi.class, dummyTenant);
if (request instanceof DistributionSetRequest.Create r) {
if (request instanceof DistributionSetRequest.Create(MgmtDistributionSetRequestBodyPost body)) {
validateOperation(OP_CREATE, DISTRIBUTION_SETS);
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
}
final ResponseEntity<List<MgmtDistributionSet>> response = api.createDistributionSets(List.of(r.body()));
final ResponseEntity<List<MgmtDistributionSet>> response = api.createDistributionSets(List.of(body));
final List<MgmtDistributionSet> created = response.getBody();
return OperationResponse.success(OP_CREATE, "Distribution set created successfully",
created != null && !created.isEmpty() ? created.get(0) : null);
} else if (request instanceof DistributionSetRequest.Update r) {
} else if (request instanceof DistributionSetRequest.Update(Long distributionSetId, MgmtDistributionSetRequestBodyPut body)) {
validateOperation(OP_UPDATE, DISTRIBUTION_SETS);
if (r.distributionSetId() == null) {
if (distributionSetId == null) {
return OperationResponse.failure(OP_UPDATE, "distributionSetId is required for UPDATE operation");
}
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
}
final ResponseEntity<MgmtDistributionSet> response = api.updateDistributionSet(r.distributionSetId(), r.body());
final ResponseEntity<MgmtDistributionSet> response = api.updateDistributionSet(distributionSetId, body);
return OperationResponse.success(OP_UPDATE, "Distribution set updated successfully", response.getBody());
} else if (request instanceof DistributionSetRequest.Delete r) {
} else if (request instanceof DistributionSetRequest.Delete(Long distributionSetId)) {
validateOperation(OP_DELETE, DISTRIBUTION_SETS);
if (r.distributionSetId() == null) {
if (distributionSetId == null) {
return OperationResponse.failure(OP_DELETE, "distributionSetId is required for DELETE operation");
}
api.deleteDistributionSet(r.distributionSetId());
api.deleteDistributionSet(distributionSetId);
return OperationResponse.success(OP_DELETE, "Distribution set deleted successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
@McpTool(name = "manage_action",
description = "Delete deployment actions. Actions are created indirectly via distribution set assignment. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Delete\",\"actionIds\":[123]}, " +
"{\"type\":\"DeleteBatch\",\"actionIds\":[1,2,3],\"rsql\":\"\"}")
description = "Delete deployment actions. Actions are created indirectly via distribution set assignment. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Delete\",\"actionIds\":[123]}, " +
"{\"type\":\"DeleteBatch\",\"actionIds\":[1,2,3],\"rsql\":\"\"}")
public OperationResponse<Object> manageAction(final ActionRequest request) {
log.debug("Managing action: request={}", request.getClass().getSimpleName());
final MgmtActionRestApi api = hawkbitClient.mgmtService(MgmtActionRestApi.class, dummyTenant);
if (request instanceof ActionRequest.Delete r) {
if (request instanceof ActionRequest.Delete(Long actionId)) {
validateActionOperation(OP_DELETE);
if (r.actionId() == null) {
if (actionId == null) {
return OperationResponse.failure(OP_DELETE, "actionId is required for DELETE operation");
}
api.deleteAction(r.actionId());
api.deleteAction(actionId);
return OperationResponse.success(OP_DELETE, "Action deleted successfully");
} else if (request instanceof ActionRequest.DeleteBatch r) {
} else if (request instanceof ActionRequest.DeleteBatch(List<Long> actionIds, String rsql)) {
validateActionOperation("delete-batch");
if ((r.actionIds() == null || r.actionIds().isEmpty()) &&
(r.rsql() == null || r.rsql().isBlank())) {
if ((actionIds == null || actionIds.isEmpty()) &&
(rsql == null || rsql.isBlank())) {
return OperationResponse.failure(OP_DELETE_BATCH, "Either actionIds or rsql is required for DELETE_BATCH operation");
}
api.deleteActions(r.rsql(), r.actionIds());
api.deleteActions(rsql, actionIds);
return OperationResponse.success(OP_DELETE_BATCH, "Actions deleted successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
@McpTool(name = "manage_software_module",
description = "Create, update, or delete software modules. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"version\":\"v\",\"type\":\"t\"}}, " +
"{\"type\":\"Update\",\"softwareModuleId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"softwareModuleId\":123}")
description = "Create, update, or delete software modules. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"version\":\"v\",\"type\":\"t\"}}, " +
"{\"type\":\"Update\",\"softwareModuleId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"softwareModuleId\":123}")
public OperationResponse<Object> manageSoftwareModule(final SoftwareModuleRequest request) {
log.debug("Managing software module: request={}", request.getClass().getSimpleName());
final MgmtSoftwareModuleRestApi api = hawkbitClient.mgmtService(MgmtSoftwareModuleRestApi.class, dummyTenant);
if (request instanceof SoftwareModuleRequest.Create r) {
if (request instanceof SoftwareModuleRequest.Create(MgmtSoftwareModuleRequestBodyPost body)) {
validateOperation(OP_CREATE, SOFTWARE_MODULES);
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
}
final ResponseEntity<List<MgmtSoftwareModule>> response = api.createSoftwareModules(List.of(r.body()));
final ResponseEntity<List<MgmtSoftwareModule>> response = api.createSoftwareModules(List.of(body));
final List<MgmtSoftwareModule> created = response.getBody();
return OperationResponse.success(OP_CREATE, "Software module created successfully",
created != null && !created.isEmpty() ? created.get(0) : null);
} else if (request instanceof SoftwareModuleRequest.Update r) {
} else if (request instanceof SoftwareModuleRequest.Update(Long softwareModuleId, MgmtSoftwareModuleRequestBodyPut body)) {
validateOperation(OP_UPDATE, SOFTWARE_MODULES);
if (r.softwareModuleId() == null) {
if (softwareModuleId == null) {
return OperationResponse.failure(OP_UPDATE, "softwareModuleId is required for UPDATE operation");
}
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
}
final ResponseEntity<MgmtSoftwareModule> response = api.updateSoftwareModule(r.softwareModuleId(), r.body());
final ResponseEntity<MgmtSoftwareModule> response = api.updateSoftwareModule(softwareModuleId, body);
return OperationResponse.success(OP_UPDATE, "Software module updated successfully", response.getBody());
} else if (request instanceof SoftwareModuleRequest.Delete r) {
} else if (request instanceof SoftwareModuleRequest.Delete(Long softwareModuleId)) {
validateOperation(OP_DELETE, SOFTWARE_MODULES);
if (r.softwareModuleId() == null) {
if (softwareModuleId == null) {
return OperationResponse.failure(OP_DELETE, "softwareModuleId is required for DELETE operation");
}
api.deleteSoftwareModule(r.softwareModuleId());
api.deleteSoftwareModule(softwareModuleId);
return OperationResponse.success(OP_DELETE, "Software module deleted successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
@McpTool(name = "manage_target_filter",
description = "Create, update, or delete target filter queries. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"query\":\"name==*\"}}, " +
"{\"type\":\"Update\",\"filterId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"filterId\":123}")
description = "Create, update, or delete target filter queries. " +
"Use 'type' field to select operation: " +
"{\"type\":\"Create\",\"body\":{\"name\":\"n\",\"query\":\"name==*\"}}, " +
"{\"type\":\"Update\",\"filterId\":123,\"body\":{...}}, " +
"{\"type\":\"Delete\",\"filterId\":123}")
public OperationResponse<Object> manageTargetFilter(final TargetFilterRequest request) {
log.debug("Managing target filter: request={}", request.getClass().getSimpleName());
final MgmtTargetFilterQueryRestApi api = hawkbitClient.mgmtService(MgmtTargetFilterQueryRestApi.class, dummyTenant);
if (request instanceof TargetFilterRequest.Create r) {
if (request instanceof TargetFilterRequest.Create(MgmtTargetFilterQueryRequestBody body)) {
validateOperation(OP_CREATE, TARGET_FILTERS);
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
}
final ResponseEntity<MgmtTargetFilterQuery> response = api.createFilter(r.body());
final ResponseEntity<MgmtTargetFilterQuery> response = api.createFilter(body);
return OperationResponse.success(OP_CREATE, "Target filter created successfully", response.getBody());
} else if (request instanceof TargetFilterRequest.Update r) {
} else if (request instanceof TargetFilterRequest.Update(Long filterId, MgmtTargetFilterQueryRequestBody body)) {
validateOperation(OP_UPDATE, TARGET_FILTERS);
if (r.filterId() == null) {
if (filterId == null) {
return OperationResponse.failure(OP_UPDATE, "filterId is required for UPDATE operation");
}
if (r.body() == null) {
if (body == null) {
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
}
final ResponseEntity<MgmtTargetFilterQuery> response = api.updateFilter(r.filterId(), r.body());
final ResponseEntity<MgmtTargetFilterQuery> response = api.updateFilter(filterId, body);
return OperationResponse.success(OP_UPDATE, "Target filter updated successfully", response.getBody());
} else if (request instanceof TargetFilterRequest.Delete r) {
} else if (request instanceof TargetFilterRequest.Delete(Long filterId)) {
validateOperation(OP_DELETE, TARGET_FILTERS);
if (r.filterId() == null) {
if (filterId == null) {
return OperationResponse.failure(OP_DELETE, "filterId is required for DELETE operation");
}
api.deleteFilter(r.filterId());
api.deleteFilter(filterId);
return OperationResponse.success(OP_DELETE, "Target filter deleted successfully");
}
throw new IllegalArgumentException("Unknown request type: " + request.getClass().getSimpleName());
}
private void validateOperation(final String operation, final String entity) {
if (!isOperationEnabled(operation, entity)) {
throw new IllegalArgumentException(
"Operation " + operation.toUpperCase() + " is not enabled for " + entity +
". Check hawkbit.mcp.operations configuration.");
". Check hawkbit.mcp.operations configuration.");
}
}
@@ -510,7 +517,7 @@ public class HawkbitMcpToolProvider {
if (!properties.getOperations().isGlobalOperationEnabled(operation)) {
throw new IllegalArgumentException(
"Operation " + operation.toUpperCase() + " is not enabled for rollouts. " +
"Check hawkbit.mcp.operations configuration.");
"Check hawkbit.mcp.operations configuration.");
}
return;
}
@@ -518,7 +525,7 @@ public class HawkbitMcpToolProvider {
if (!entitySetting) {
throw new IllegalArgumentException(
"Operation " + operation.toUpperCase() + " is not enabled for rollouts. " +
"Check hawkbit.mcp.operations configuration.");
"Check hawkbit.mcp.operations configuration.");
}
}
@@ -530,7 +537,7 @@ public class HawkbitMcpToolProvider {
if (operation.equals("delete") && !properties.getOperations().isGlobalOperationEnabled(OP_DELETE)) {
throw new IllegalArgumentException(
"Operation " + operation.toUpperCase() + " is not enabled for actions. " +
"Check hawkbit.mcp.operations configuration.");
"Check hawkbit.mcp.operations configuration.");
}
return;
}
@@ -538,7 +545,7 @@ public class HawkbitMcpToolProvider {
if (!entitySetting) {
throw new IllegalArgumentException(
"Operation " + operation.toUpperCase() + " is not enabled for actions. " +
"Check hawkbit.mcp.operations configuration.");
"Check hawkbit.mcp.operations configuration.");
}
}

View File

@@ -60,85 +60,87 @@ public class EntityMatcher {
@SuppressWarnings({ "java:S3776", "java:S3358", "java:S1125", "java:S6541" }) // better readable this way
private <T> boolean match(final T t, final Node node) {
if (node instanceof Node.Comparison comparison) {
final String[] split = comparison.getKey().split("\\.", 2);
try {
final Getter fieldGetter = getGetter(t.getClass(), split[0]);
final Object fieldValue = fieldGetter.get(t);
final Operator op = comparison.getOp();
if (Map.class.isAssignableFrom(getReturnType(fieldGetter))) {
if ((op == NE || op == NOT_IN || op == NOT_LIKE)
&& (fieldValue == null || !((Map<?, ?>) fieldValue).containsKey(split[1]))) {
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
return false;
}
return compareIgnoreCaseAware(
fieldValue == null ? null : ((Map<?, ?>) fieldValue).get(split[1]),
op,
map(
comparison.getValue(),
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[1]));
} else if (Collection.class.isAssignableFrom(getReturnType(fieldGetter))) { // Set / List
final Object value;
final BiPredicate<Object, Operator> compare;
if (split.length == 1) {
value = map(comparison.getValue(), getReturnType(fieldGetter));
compare = (e, operator) -> compareIgnoreCaseAware(e, operator, value);
} else {
final Getter valueGetter = getGetter(
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[0], split[1]);
value = map(comparison.getValue(), getReturnType(valueGetter));
compare = (e, operator) -> {
try {
return compareIgnoreCaseAware(
map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
} catch (final IllegalAccessException | InvocationTargetException ex) {
throw new IllegalArgumentException(ex);
}
};
}
final Collection<?> set = (Collection<?>) fieldValue;
return switch (op) {
case EQ, GT, GTE, LT, LTE, IN, LIKE -> set == null
? false
: set.stream().anyMatch(e -> compare.test(e, op));
case NE, NOT_IN, NOT_LIKE -> set == null
? true
: set.stream().noneMatch(e -> compare.test(e, op == NE ? EQ : op == NOT_IN ? IN : LIKE));
};
} else {
if (split.length == 1) {
return compareIgnoreCaseAware(fieldValue, op, map(comparison.getValue(), getReturnType(fieldGetter)));
} else {
if (split[1].contains(".")) {
// nested field access
final String[] nestedSplit = split[1].split("\\.", 2);
final Getter nestedFieldGetter = getGetter(getReturnType(fieldGetter), nestedSplit[0]);
final Getter valueGetter = getGetter(getReturnType(nestedFieldGetter), nestedSplit[1]);
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.get(fieldValue);
return compareIgnoreCaseAware(
nestedFieldValue == null ? null : valueGetter.get(nestedFieldValue),
op,
map(comparison.getValue(), getReturnType(valueGetter)));
switch (node) {
case Node.Comparison comparison -> {
final String[] split = comparison.getKey().split("\\.", 2);
try {
final Getter fieldGetter = getGetter(t.getClass(), split[0]);
final Object fieldValue = fieldGetter.get(t);
final Operator op = comparison.getOp();
if (Map.class.isAssignableFrom(getReturnType(fieldGetter))) {
if ((op == NE || op == NOT_IN || op == NOT_LIKE)
&& (fieldValue == null || !((Map<?, ?>) fieldValue).containsKey(split[1]))) {
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
return false;
}
return compareIgnoreCaseAware(
fieldValue == null ? null : ((Map<?, ?>) fieldValue).get(split[1]),
op,
map(
comparison.getValue(),
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[1]));
} else if (Collection.class.isAssignableFrom(getReturnType(fieldGetter))) { // Set / List
final Object value;
final BiPredicate<Object, Operator> compare;
if (split.length == 1) {
value = map(comparison.getValue(), getReturnType(fieldGetter));
compare = (e, operator) -> compareIgnoreCaseAware(e, operator, value);
} else {
final Getter valueGetter = getGetter(getReturnType(fieldGetter), split[1]);
return compareIgnoreCaseAware(
fieldValue == null ? null : valueGetter.get(fieldValue),
op,
map(comparison.getValue(), getReturnType(valueGetter)));
final Getter valueGetter = getGetter(
(Class<?>) ((ParameterizedType) fieldGetter.type()).getActualTypeArguments()[0], split[1]);
value = map(comparison.getValue(), getReturnType(valueGetter));
compare = (e, operator) -> {
try {
return compareIgnoreCaseAware(
map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
} catch (final IllegalAccessException | InvocationTargetException ex) {
throw new IllegalArgumentException(ex);
}
};
}
final Collection<?> set = (Collection<?>) fieldValue;
return switch (op) {
case EQ, GT, GTE, LT, LTE, IN, LIKE -> set == null
? false
: set.stream().anyMatch(e -> compare.test(e, op));
case NE, NOT_IN, NOT_LIKE -> set == null
? true
: set.stream().noneMatch(e -> compare.test(e, op == NE ? EQ : op == NOT_IN ? IN : LIKE));
};
} else {
if (split.length == 1) {
return compareIgnoreCaseAware(fieldValue, op, map(comparison.getValue(), getReturnType(fieldGetter)));
} else {
if (split[1].contains(".")) {
// nested field access
final String[] nestedSplit = split[1].split("\\.", 2);
final Getter nestedFieldGetter = getGetter(getReturnType(fieldGetter), nestedSplit[0]);
final Getter valueGetter = getGetter(getReturnType(nestedFieldGetter), nestedSplit[1]);
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.get(fieldValue);
return compareIgnoreCaseAware(
nestedFieldValue == null ? null : valueGetter.get(nestedFieldValue),
op,
map(comparison.getValue(), getReturnType(valueGetter)));
} else {
final Getter valueGetter = getGetter(getReturnType(fieldGetter), split[1]);
return compareIgnoreCaseAware(
fieldValue == null ? null : valueGetter.get(fieldValue),
op,
map(comparison.getValue(), getReturnType(valueGetter)));
}
}
}
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
} else if (node instanceof Node.Logical logical) {
return switch (logical.getOp()) {
case AND -> logical.getChildren().stream().allMatch(child -> match(t, child));
case OR -> logical.getChildren().stream().anyMatch(child -> match(t, child));
};
} else {
throw new IllegalArgumentException("Unsupported node type: " + node.getClass());
case Node.Logical logical -> {
return switch (logical.getOp()) {
case AND -> logical.getChildren().stream().allMatch(child -> match(t, child));
case OR -> logical.getChildren().stream().anyMatch(child -> match(t, child));
};
}
default -> throw new IllegalArgumentException("Unsupported node type: " + node.getClass());
}
}
@@ -151,13 +153,11 @@ public class EntityMatcher {
return o;
}
// if here - ignoreCase in true and we have non-null value
if (o instanceof String str) {
return str.toLowerCase();
} else if (o instanceof Collection<?> collection) {
return collection.stream().map(this::ignoreCase).toList();
} else {
return o;
}
return switch (o) {
case String str -> str.toLowerCase();
case Collection<?> collection -> collection.stream().map(this::ignoreCase).toList();
default -> o;
};
}
// java:S3011 uses reflection to private members anyway

View File

@@ -105,29 +105,33 @@ public class SpecificationBuilder<T> {
}
public Predicate build(final Node node) {
if (node instanceof Comparison comparison) {
return predicate(comparison);
} else if (node instanceof Logical logical) {
final Logical.Operator op = Objects.requireNonNull(logical.getOp());
if (op == Logical.Operator.AND) {
return cb.and(logical.getChildren().stream()
.map(this::build)
.toList()
.toArray(PREDICATES_ARRAY_0));
} else if (op == Logical.Operator.OR) {
final Map<String, Integer> state = pathResolver.getState();
return cb.or(logical.getChildren().stream()
.map(child -> {
pathResolver.reset(state);
return build(child); // for or path resolver joins could be reused
})
.toList()
.toArray(PREDICATES_ARRAY_0));
} else {
throw new IllegalArgumentException("Unsupported logical operator: " + op);
switch (node) {
case Comparison comparison -> {
return predicate(comparison);
}
} else {
throw new IllegalArgumentException("Unsupported node type: " + node.getClass());
case Logical logical -> {
final Logical.Operator op = Objects.requireNonNull(logical.getOp());
switch (op) {
case Logical.Operator.AND -> {
return cb.and(logical.getChildren().stream()
.map(this::build)
.toList()
.toArray(PREDICATES_ARRAY_0));
}
case Logical.Operator.OR -> {
final Map<String, Integer> state = pathResolver.getState();
return cb.or(logical.getChildren().stream()
.map(child -> {
pathResolver.reset(state);
return build(child); // for or path resolver joins could be reused
})
.toList()
.toArray(PREDICATES_ARRAY_0));
}
default -> throw new IllegalArgumentException("Unsupported logical operator: " + op);
}
}
default -> throw new IllegalArgumentException("Unsupported node type: " + node.getClass());
}
}

View File

@@ -36,7 +36,7 @@ import org.springframework.transaction.TransactionSystemException;
@Slf4j
public class ExceptionMapper {
private static final Map<String, String> EXCEPTION_MAPPING = new HashMap<>(4);
private static final Map<String, String> EXCEPTION_MAPPING = HashMap.newHashMap(4);
// this is required to enable a certain order of exception and to select the most specific mappable exception according to the type
// hierarchy of the exception

View File

@@ -81,7 +81,7 @@ public class JpaConfiguration extends JpaBaseConfiguration {
@Override
protected Map<String, Object> getVendorProperties(final DataSource dataSource) {
final Map<String, Object> properties = new HashMap<>(7);
final Map<String, Object> properties = HashMap.newHashMap(7);
// Turn off dynamic weaving to disable LTW lookup in static weaving mode
properties.put(PersistenceUnitProperties.WEAVING, "false");
// needed for reports

View File

@@ -34,6 +34,7 @@ import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
@@ -154,13 +155,13 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return false;
}
final BaseEntity other = (BaseEntity) obj;
final Long id = getId();
final Long thisId = getId();
final Long otherId = other.getId();
if (id == null) {
if (thisId == null) {
if (otherId != null) {
return false;
}
} else if (!id.equals(otherId)) {
} else if (!thisId.equals(otherId)) {
return false;
}
return getOptLockRevision() == other.getOptLockRevision();
@@ -198,9 +199,9 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
}
protected boolean isController() {
return SecurityContextHolder.getContext().getAuthentication() != null &&
SecurityContextHolder.getContext().getAuthentication().getDetails()
instanceof TenantAwareAuthenticationDetails tenantAwareDetails &&
tenantAwareDetails.controller();
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null
&& authentication.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails
&& tenantAwareDetails.controller();
}
}

View File

@@ -678,7 +678,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
log.debug("{} events in flushUpdateQueue.", size);
final Set<TargetPoll> events = new HashSet<>(queue.size());
final Set<TargetPoll> events = HashSet.newHashSet(queue.size());
final int drained = queue.drainTo(events);
if (drained <= 0) {

View File

@@ -68,12 +68,12 @@ public class StatisticsUtils {
final Meter.Id id = m.getId();
if (id.getName().startsWith(Statistics.METER_PREFIX)) {
final double value;
if (m instanceof Counter counter) {
value = counter.count();
} else if (m instanceof FunctionCounter functionCounter) {
value = functionCounter.count();
} else {
return;
switch (m) {
case Counter counter -> value = counter.count();
case FunctionCounter functionCounter -> value = functionCounter.count();
default -> {
return;
}
}
final StringBuilder key = new StringBuilder(id.getName());

View File

@@ -255,9 +255,8 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
}
private static List<MgmtTargetFilterQuery> listFilters(HawkbitMgmtClient hawkbitClient) {
return Optional.ofNullable(hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(null, 0, 30, null, null).getBody())
.map(PagedList<MgmtTargetFilterQuery>::getContent)
return Optional.ofNullable(hawkbitClient.getTargetFilterQueryRestApi().getFilters(null, 0, 30, null, null).getBody())
.map(PagedList::getContent)
.orElseGet(List::of);
}

View File

@@ -149,18 +149,18 @@ public final class Filter extends Div {
return null;
}
if (value instanceof Collection<?> coll) {
final StringBuilder sb = new StringBuilder();
coll.stream().forEach(next -> sb.append(key).append("==").append(next).append(','));
return sb.substring(0, sb.length() - 1);
} else if (value instanceof Optional<?> opt) {
if (opt.isEmpty()) {
return null;
} else {
return key + "==" + opt.get();
switch (value) {
case Collection<?> coll -> {
final StringBuilder sb = new StringBuilder();
coll.stream().forEach(next -> sb.append(key).append("==").append(next).append(','));
return sb.substring(0, sb.length() - 1);
}
case Optional<?> opt -> {
return opt.map(o -> key + "==" + o).orElse(null);
}
default -> {
return key + "==" + value;
}
} else {
return key + "==" + value;
}
}