Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@

package org.eclipse.digitaltwin.basyx.aasenvironment.http;

import java.io.IOException;
import java.util.List;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.model.Result;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -80,5 +83,5 @@ ResponseEntity<Boolean> uploadEnvironment(
@Parameter(description = "An environment file (XML, JSON, AASX)") @Valid @RequestParam("file") MultipartFile envFile,
@Parameter(description = "Flag to indicate if already existing Ids should be ignored when reuploading an environment (default: false)", schema = @Schema(defaultValue = "false"))
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates
);
) throws IOException, InvalidFormatException, DeserializationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,14 @@ public ResponseEntity<Resource> generateSerializationByIds(
@Override
public ResponseEntity<Boolean> uploadEnvironment(
@RequestParam(value = "file") MultipartFile envFile,
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates) {
try {
EnvironmentType envType = EnvironmentType.getFromMimeType(envFile.getContentType());

if (envType == null)
envType = EnvironmentType.AASX;

aasEnvironment.loadEnvironment(CompleteEnvironment.fromInputStream(envFile.getInputStream(), envType),
ignoreDuplicates);

} catch (InvalidFormatException e) {
return new ResponseEntity<>(false, HttpStatus.BAD_REQUEST);
} catch (DeserializationException | IOException e) {
return new ResponseEntity<>(false, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(true, HttpStatus.OK);
@RequestParam(value = "ignore-duplicates", required = false, defaultValue = "false") boolean ignoreDuplicates) throws IOException, InvalidFormatException, DeserializationException {
EnvironmentType envType = EnvironmentType.getFromMimeType(envFile.getContentType());

if (envType == null)
envType = EnvironmentType.AASX;
aasEnvironment.loadEnvironment(CompleteEnvironment.fromInputStream(envFile.getInputStream(), envType),
ignoreDuplicates);
return new ResponseEntity<>(true, HttpStatus.OK);
}

private List<String> getOriginalIds(List<String> ids) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.eclipse.digitaltwin.basyx.core;

public enum MessageType {
Error,
Warning,
Info,
Exception
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (C) 2025 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
******************************************************************************/

package org.eclipse.digitaltwin.basyx.core;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Creates a Message for HTTP Result in the standardized format.
* @author fried
*/
public class ResultMessage {

private final String message;
private final int code;
private final String correlationId;
private final MessageType messageType;

public ResultMessage(String message, int code, String correlationId, MessageType messageType){
this.message = message;
this.code = code;
this.correlationId = correlationId;
this.messageType = messageType;
}

public Object build() {
Map<String, Object> body = new HashMap<>();
String timestamp = DateTimeFormatter.ISO_INSTANT.format(Instant.now());

body.put("timestamp", timestamp);
body.put("messageType", this.messageType);
body.put("code", this.code);
body.put("text", this.message);
body.put("correlationId", this.correlationId);
return body;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (C) 2023 the Eclipse BaSyx Authors
* Copyright (C) 2025 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
Expand All @@ -26,102 +26,147 @@

package org.eclipse.digitaltwin.basyx.http;

import org.eclipse.digitaltwin.basyx.core.exceptions.AssetLinkDoesNotExistException;
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingAssetLinkException;
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException;
import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotSupportedException;
import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException;
import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException;
import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException;
import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException;
import org.eclipse.digitaltwin.basyx.core.exceptions.NullSubjectException;
import org.eclipse.digitaltwin.basyx.core.exceptions.OperationDelegationException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.apache.commons.lang3.SerializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.basyx.core.MessageType;
import org.eclipse.digitaltwin.basyx.core.ResultMessage;
import org.eclipse.digitaltwin.basyx.core.exceptions.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.List;

/**
* Configures overall Exception to HTTP status code mapping
*
* @author schnicke
* @author schnicke, fried
*
*/
@ControllerAdvice
public class BaSyxExceptionHandler extends ResponseEntityExceptionHandler {
public class BaSyxExceptionHandler {

private ResponseEntity<Object> buildResponse(String message, HttpStatus status, Object object) {
var body = new ResultMessage(message, status.value(), object.getClass().getSimpleName() + "-" + status.value(), MessageType.Error).build();
return new ResponseEntity<>(List.of(body), status);
}

@ExceptionHandler(ElementDoesNotExistException.class)
public <T> ResponseEntity<T> handleElementNotFoundException(ElementDoesNotExistException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
public ResponseEntity<Object> handleElementNotFoundException(ElementDoesNotExistException exception) {
return buildResponse(exception.getMessage(), HttpStatus.NOT_FOUND, exception);
}

@ExceptionHandler(AssetLinkDoesNotExistException.class)
public <T> ResponseEntity<T> handleElementNotFoundException(AssetLinkDoesNotExistException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
public ResponseEntity<Object> handleElementNotFoundException(AssetLinkDoesNotExistException exception) {
return buildResponse(exception.getMessage(), HttpStatus.NOT_FOUND, exception);
}

@ExceptionHandler(FileDoesNotExistException.class)
public <T> ResponseEntity<T> handleElementNotFoundException(FileDoesNotExistException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
public ResponseEntity<Object> handleElementNotFoundException(FileDoesNotExistException exception) {
return buildResponse(exception.getMessage(), HttpStatus.NOT_FOUND, exception);
}

@ExceptionHandler(CollidingIdentifierException.class)
public <T> ResponseEntity<T> handleCollidingIdentifierException(CollidingIdentifierException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
public ResponseEntity<Object> handleCollidingIdentifierException(CollidingIdentifierException exception) {
return buildResponse(exception.getMessage(), HttpStatus.CONFLICT, exception);
}

@ExceptionHandler(MissingIdentifierException.class)
public <T> ResponseEntity<T> handleMissingIdentifierException(MissingIdentifierException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
public ResponseEntity<Object> handleMissingIdentifierException(MissingIdentifierException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(CollidingAssetLinkException.class)
public <T> ResponseEntity<T> handleCollidingIdentifierException(CollidingAssetLinkException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
public ResponseEntity<Object> handleCollidingIdentifierException(CollidingAssetLinkException exception) {
return buildResponse(exception.getMessage(), HttpStatus.CONFLICT, exception);
}

@ExceptionHandler(IllegalArgumentException.class)
public <T> ResponseEntity<T> handleIllegalArgumentException(IllegalArgumentException exception) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(IdentificationMismatchException.class)
public <T> ResponseEntity<T> handleIdMismatchException(IdentificationMismatchException exception) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
public ResponseEntity<Object> handleIdMismatchException(IdentificationMismatchException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(FeatureNotSupportedException.class)
public <T> ResponseEntity<T> handleFeatureNotSupportedException(FeatureNotSupportedException exception) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
public ResponseEntity<Object> handleFeatureNotSupportedException(FeatureNotSupportedException exception) {
return buildResponse(exception.getMessage(), HttpStatus.NOT_IMPLEMENTED, exception);
}

@ExceptionHandler(NotInvokableException.class)
public <T> ResponseEntity<T> handleNotInvokableException(NotInvokableException exception) {
return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED);
public ResponseEntity<Object> handleNotInvokableException(NotInvokableException exception) {
return buildResponse(exception.getMessage(), HttpStatus.METHOD_NOT_ALLOWED, exception);
}

@ExceptionHandler(ElementNotAFileException.class)
public <T> ResponseEntity<T> handleElementNotAFileException(ElementNotAFileException exception) {
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
public ResponseEntity<Object> handleElementNotAFileException(ElementNotAFileException exception) {
return buildResponse(exception.getMessage(), HttpStatus.PRECONDITION_FAILED, exception);
}

@ExceptionHandler(InsufficientPermissionException.class)
public <T> ResponseEntity<T> handleInsufficientPermissionException(InsufficientPermissionException exception, WebRequest request) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
public ResponseEntity<Object> handleInsufficientPermissionException(InsufficientPermissionException exception) {
return buildResponse(exception.getMessage(), HttpStatus.FORBIDDEN, exception);
}

@ExceptionHandler(NullSubjectException.class)
public <T> ResponseEntity<T> handleNullSubjectException(NullSubjectException exception) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
public ResponseEntity<Object> handleNullSubjectException(NullSubjectException exception) {
return buildResponse(exception.getMessage(), HttpStatus.UNAUTHORIZED, exception);
}

@ExceptionHandler(OperationDelegationException.class)
public <T> ResponseEntity<T> handleNullSubjectException(OperationDelegationException exception) {
return new ResponseEntity<>(HttpStatus.FAILED_DEPENDENCY);
public ResponseEntity<Object> handleNullSubjectException(OperationDelegationException exception) {
return buildResponse(exception.getMessage(), HttpStatus.FAILED_DEPENDENCY, exception);
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(RepositoryRegistryLinkException.class)
public ResponseEntity<Object> handleRepositoryRegistryLinkException(RepositoryRegistryLinkException exception) {
return buildResponse(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, exception);
}

@ExceptionHandler(RepositoryRegistryUnlinkException.class)
public ResponseEntity<Object> handleRepositoryRegistryUnlinkException(RepositoryRegistryUnlinkException exception) {
return buildResponse(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, exception);
}

@ExceptionHandler(MissingKeyTypeException.class)
public ResponseEntity<Object> handleMissingKeyTypeException(MissingKeyTypeException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(MissingAuthorizationConfigurationException.class)
public ResponseEntity<Object> handleMissingAuthorizationConfigurationException(MissingAuthorizationConfigurationException exception) {
return buildResponse(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, exception);
}

@ExceptionHandler(InvalidTargetInformationException.class)
public ResponseEntity<Object> handleInvalidTargetInformationException(InvalidTargetInformationException exception) {
return buildResponse(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, exception);
}

@ExceptionHandler(SerializationException.class)
public ResponseEntity<Object> handleSerializationException(SerializationException exception) {
return buildResponse(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, exception);
}

@ExceptionHandler(DeserializationException.class)
public ResponseEntity<Object> handleDeserializationException(DeserializationException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

@ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<Object> handleInvalidFormatException(InvalidFormatException exception) {
return buildResponse(exception.getMessage(), HttpStatus.BAD_REQUEST, exception);
}

}