Commit 35e0e68c authored by Christos Tranoris's avatar Christos Tranoris
Browse files

fix for #35

parent 0a16f2d3
Loading
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.etsi.osl.tmf.common.model.AttachmentRefOrValue;
import org.etsi.osl.tmf.common.model.BaseEntity;
import org.etsi.osl.tmf.prm669.model.RelatedParty;
@@ -53,6 +56,20 @@ import jakarta.validation.Valid;
@Schema(description = "Resources are physical or non-physical components (or some combination of these) within an enterprise's infrastructure or inventory. They are typically consumed or used by services (for example a physical port assigned to a service) or contribute to the realization of a Product (for example, a SIM card). They can be drawn from the Application, Computing and Network domains, and include, for example, Network Elements, software, IT systems, content and information, and technology components. A ResourceSpecification is an abstract base class for representing a generic means for implementing a particular type of Resource. In essence, a ResourceSpecification defines the common attributes and relationships of a set of related Resources, while Resource defines a specific instance that is based on a particular ResourceSpecification.")
@Validated
@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2021-05-29T22:34:44.143740800+03:00[Europe/Athens]")
@JsonIgnoreProperties(
		value = "@type", // ignore manually set @type, it will be automatically generated by Jackson during serialization
		allowSetters = true // allows the @type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type",
		visible = true, defaultImpl = LogicalResourceSpecification.class)
@JsonSubTypes({
		@JsonSubTypes.Type(value = LogicalResourceSpecification.class, name = "LogicalResourceSpecification"),
		@JsonSubTypes.Type(value = PhysicalResourceSpecification.class, name = "PhysicalResourceSpecification"),
		// Extends LogicalResourceSpecification and adds its own fields, so it must be listed too:
		// without it a ResourceFunctionSpecification payload falls back to the defaultImpl and then
		// fails on connectionPointSpecification / connectivitySpecification.
		@JsonSubTypes.Type(value = ResourceFunctionSpecification.class, name = "ResourceFunctionSpecification")
})
@Entity(name = "ResSpec")
public abstract class ResourceSpecification extends BaseEntity {
	@JsonProperty("id")
+13 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ import java.util.Objects;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import org.etsi.osl.tmf.common.model.AttachmentRefOrValue;
import org.etsi.osl.tmf.common.model.BaseRootNamedEntity;
import org.etsi.osl.tmf.common.model.service.Note;
@@ -30,6 +32,17 @@ import jakarta.validation.constraints.NotNull;
@Schema(description = "Resource is an abstract entity that describes the common set of attributes shared by all concrete resources (e.g. TPE, EQUIPMENT) in the inventory.")
@Validated
@jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2021-07-08T09:52:18.013684600+03:00[Europe/Athens]")
/*
 * Lets a caller deserialize to Resource and get the right concrete subclass, rather than having to
 * guess it. "@type" here is free-form text (see ResourceTypeIdResolver), so the id is resolved by the
 * inventory's own rule instead of a @JsonSubTypes name match, and a null "@type" means logical.
 *
 * include=EXISTING_PROPERTY: the inherited "@type" field already serializes itself, so Jackson writes
 * no id of its own and the JSON a Resource produces is unchanged by this annotation.
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.EXISTING_PROPERTY,
		property = "@type", visible = true, defaultImpl = LogicalResource.class)
@JsonTypeIdResolver(ResourceTypeIdResolver.class)
@Entity(name = "RIResource")
public class Resource extends BaseRootNamedEntity {
	@JsonProperty("id")
+87 −0
Original line number Diff line number Diff line
package org.etsi.osl.tmf.ri639.model;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;

/**
 * Resolves a {@link Resource} payload to {@link PhysicalResource} or {@link LogicalResource} from its
 * {@code "@type"} value.
 *
 * <p>A plain {@code @JsonSubTypes} name match cannot be used here, because {@code "@type"} on the wire
 * is <b>not</b> guaranteed to be a class name. The resource inventory picks the concrete class with a
 * case-insensitive substring test but then stores the caller's {@code @type} text verbatim
 * (ResourceRepoService.addResource):
 *
 * <pre>
 *   if (resource.getAtType() != null
 *           &amp;&amp; resource.getAtType().toLowerCase().contains("physicalresource")) {
 *       s = new PhysicalResource();
 *   } else {
 *       s = new LogicalResource();
 *   }
 *   if (resource.getAtType() != null) {
 *       s.setType(resource.getAtType());   // free-form; not normalised to the class name
 *   }
 * </pre>
 *
 * <p>So a physical resource can legitimately arrive as {@code "PhysicalResource"},
 * {@code "physicalresource"} or {@code "urn:x:PhysicalResource"}. This resolver applies the producer's
 * rule verbatim so that reader and writer agree on every value the producer can emit. Keep the two in
 * step: if the inventory's rule changes, this must change with it.
 *
 * <p>Unlike {@code org.etsi.osl.tmf.rcm634.model.ResourceSpecification}, whose subclasses set
 * {@code @type} to their own class name in their constructors, nothing populates {@code @type} for a
 * Resource, so a null value is normal and means "logical" — matching the producer's else-branch and
 * the {@code defaultImpl} declared on {@link Resource}.
 */
public class ResourceTypeIdResolver extends TypeIdResolverBase {

    /** Matches the resource inventory's own test; lower-cased before comparison. */
    private static final String PHYSICAL_MARKER = "physicalresource";

    private JavaType baseType;

    @Override
    public void init(JavaType baseType) {
        this.baseType = baseType;
    }

    @Override
    public JsonTypeInfo.Id getMechanism() {
        return JsonTypeInfo.Id.CUSTOM;
    }

    @Override
    public String idFromValue(Object value) {
        return idFromValueAndType(value, value == null ? null : value.getClass());
    }

    @Override
    public String idFromValueAndType(Object value, Class<?> suggestedType) {
        // Only consulted for serialization. Resource declares include=EXISTING_PROPERTY, so the
        // inherited "@type" field writes itself and this id is never emitted — meaning a resource
        // serializes byte-identically to before, keeping any non-canonical @type the caller supplied.
        return suggestedType == null ? null : suggestedType.getSimpleName();
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) {
        Class<?> target = (id != null && id.toLowerCase().contains(PHYSICAL_MARKER))
                ? PhysicalResource.class
                : LogicalResource.class;

        // Honour a declared type that is already concrete (e.g. readValue(json, LogicalResource.class)):
        // narrowing to a sibling would fail, so leave such reads exactly as they behaved before.
        if (baseType != null && !baseType.getRawClass().isAssignableFrom(target)) {
            return baseType;
        }
        return context.constructSpecializedType(baseType, target);
    }

    @Override
    public String getDescForKnownTypeIds() {
        return "LogicalResource, PhysicalResource (by \"@type\" containing \"" + PHYSICAL_MARKER + "\")";
    }
}