Skip to content

Commit

Permalink
Added /description endpoint and StandardizedLiteralEnum (#94)
Browse files Browse the repository at this point in the history
* Added /description endpoint and StandardizedLiteralEnum

* Added /description endpoint and StandardizedLiteralEnum

* Fixed enum serialization of Profile

Explicit serialization for aas4j enums fixed issue with other serializations
  • Loading branch information
alexgordtop authored Oct 10, 2023
1 parent 0852f9e commit 284548b
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.eclipse.digitaltwin.basyx.aasrepository.http;

import org.eclipse.digitaltwin.basyx.http.description.Profile;
import org.eclipse.digitaltwin.basyx.http.description.ProfileDeclaration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.TreeSet;

@Configuration
public class AasRepositoryServiceDescriptionConfiguration {
@Bean
public ProfileDeclaration aasRepositoryProfiles() {
return () -> new TreeSet<>(List.of(Profile.ASSETADMINISTRATIONSHELLREPOSITORYSERVICESPECIFICATION_SSP_001,
Profile.ASSETADMINISTRATIONSHELLREPOSITORYSERVICESPECIFICATION_SSP_002));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (C) 2021 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;


/**
* Enums with this interface hold a custom string literal that is used during e.g. XML serialization. You may use the
* {@link org.eclipse.digitaltwin.basyx.http.StandardizedLiteralEnumHelper} to map a custom string literal to an enum.
*
* @author alexgordtop
*/
public interface StandardizedLiteralEnum {

/**
* Custom string for use in case sensitive environments or during serialization.
*
* @return Case sensitive string
*/
String getValue();
}
Original file line number Diff line number Diff line change
@@ -1,86 +1,89 @@
/*******************************************************************************
* Copyright (C) 2023 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.http;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.ReflectionAnnotationIntrospector;
import org.eclipse.digitaltwin.basyx.core.StandardizedLiteralEnum;
import org.eclipse.digitaltwin.basyx.http.description.Profile;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
import com.fasterxml.jackson.databind.module.SimpleModule;

/**
* SerializationExtension integrating the AAS4J serialization in BaSyx
*
* @author schnicke
*
* @author schnicke
*/
@Component
public class Aas4JHTTPSerializationExtension implements SerializationExtension {

protected JsonMapper mapper;
protected SimpleAbstractTypeResolver typeResolver;
protected JsonMapper mapper;
protected SimpleAbstractTypeResolver typeResolver;

public Aas4JHTTPSerializationExtension() {
initTypeResolver();
}
public Aas4JHTTPSerializationExtension() {
initTypeResolver();
}

@Override
public void extend(Jackson2ObjectMapperBuilder builder) {
builder.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.annotationIntrospector(new ReflectionAnnotationIntrospector()).modulesToInstall(buildEnumModule(), buildImplementationModule());
ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> builder.mixIn(x.getKey(), x.getValue()));
}
@Override
public void extend(Jackson2ObjectMapperBuilder builder) {
builder.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.annotationIntrospector(new ReflectionAnnotationIntrospector())
.modulesToInstall(buildEnumModule(), buildImplementationModule());
ReflectionHelper.JSON_MIXINS.entrySet().forEach(x -> builder.mixIn(x.getKey(), x.getValue()));
}

@SuppressWarnings("unchecked")
private void initTypeResolver() {
typeResolver = new SimpleAbstractTypeResolver();
ReflectionHelper.DEFAULT_IMPLEMENTATIONS.stream()
.forEach(x -> typeResolver.addMapping(x.getInterfaceType(), x.getImplementationType()));
}
@SuppressWarnings("unchecked")
private void initTypeResolver() {
typeResolver = new SimpleAbstractTypeResolver();
ReflectionHelper.DEFAULT_IMPLEMENTATIONS.stream()
.forEach(x -> typeResolver.addMapping(x.getInterfaceType(), x.getImplementationType()));
}

protected SimpleModule buildEnumModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(Enum.class, new EnumSerializer());
ReflectionHelper.ENUMS.forEach(x -> module.addDeserializer(x, new EnumDeserializer<>(x)));
return module;
}
protected SimpleModule buildEnumModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(StandardizedLiteralEnum.class, new StandardizedLiteralEnumSerializer<>());
module.addDeserializer(Profile.class, new StandardizedLiteralEnumDeserializer<>(Profile.class));
ReflectionHelper.ENUMS.forEach(x -> module.addSerializer(x, new EnumSerializer()));
ReflectionHelper.ENUMS.forEach(x -> module.addDeserializer(x, new EnumDeserializer<>(x)));
return module;
}

protected SimpleModule buildImplementationModule() {
SimpleModule module = new SimpleModule();
module.setAbstractTypes(typeResolver);
return module;
}
protected SimpleModule buildImplementationModule() {
SimpleModule module = new SimpleModule();
module.setAbstractTypes(typeResolver);
return module;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.eclipse.digitaltwin.basyx.http;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.eclipse.digitaltwin.basyx.core.StandardizedLiteralEnum;

import java.io.IOException;

public class StandardizedLiteralEnumDeserializer<T extends StandardizedLiteralEnum> extends JsonDeserializer<T> {

private Class<T> clazz;

public StandardizedLiteralEnumDeserializer(Class<T> t) {
clazz = t;
}

@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String valueAsString = p.getValueAsString();
return StandardizedLiteralEnumHelper.fromLiteral(clazz, valueAsString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*******************************************************************************
* Copyright (C) 2021 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.http;

import com.google.common.base.Strings;
import org.eclipse.digitaltwin.basyx.core.StandardizedLiteralEnum;

/**
* Helper class to map custom string literals to StandardizedLiteralEnums.
*
* @author alexgordtop
*/
public class StandardizedLiteralEnumHelper {

/**
* Maps string literals of {@link StandardizedLiteralEnum}s to enum constants. The string literals read via
* getStandardizedLiteral() from the enum constants.
*
* @param <T> Enum class implementing StandardizedLiteralEnum
* @param clazz Target enum with matching custom string literal
* @param literal The literal as contained in e.g. XML schema
* @return Enum constant
* @throws IllegalArgumentException when string literal is not found in enum.
*/
public static <T extends StandardizedLiteralEnum> T fromLiteral(Class<T> clazz, String literal) {
if (Strings.isNullOrEmpty(literal)) {
return null;
}

T[] enumConstants = clazz.getEnumConstants();
for (T constant : enumConstants) {
if (constant.getValue().equals(literal)) {
return constant;
}
}
throw new IllegalArgumentException("The literal '" + literal + "' is not contained in enum " + clazz.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.eclipse.digitaltwin.basyx.http;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.eclipse.digitaltwin.basyx.core.StandardizedLiteralEnum;

import java.io.IOException;

public class StandardizedLiteralEnumSerializer<T extends StandardizedLiteralEnum> extends JsonSerializer<T> {

@Override
public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(value.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.eclipse.digitaltwin.basyx.http.description;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.eclipse.digitaltwin.basyx.http.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

@RestController
@Tag(name = "Registry and Discovery Interface", description = "the Registry and Discovery Interface API")
public class DescriptionController {

private final SortedSet<Profile> profiles;

@Autowired
public DescriptionController(List<ProfileDeclaration> declarations) {
profiles = new TreeSet<>();
for (ProfileDeclaration declaration : declarations) {
SortedSet<Profile> profilesOfDeclaration = declaration.getProfiles();
profiles.addAll(profilesOfDeclaration);
}
}

@Operation(operationId = "getDescription",
summary = "Returns the self-describing information of a network resource (ServiceDescription)",
tags = {"Registry and Discovery Interface"}, responses = {
@ApiResponse(responseCode = "200", description = "Requested Description", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ServiceDescription.class))}),
@ApiResponse(responseCode = "403", description = "Forbidden",
content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))}),
@ApiResponse(responseCode = "default", description = "Default error handling for unmentioned status codes",
content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))})})
@RequestMapping(method = RequestMethod.GET, value = "/description", produces = {"application/json"})
public ResponseEntity<ServiceDescription> getDescription() {
ServiceDescription serviceDescription = new ServiceDescription();
serviceDescription.profiles(new ArrayList<>(profiles));
return new ResponseEntity<>(serviceDescription, HttpStatus.OK);
}
}
Loading

0 comments on commit 284548b

Please sign in to comment.