580 lines
18 KiB
ReStructuredText
580 lines
18 KiB
ReStructuredText
|
|
||
|
.. _update_type_definition:
|
||
|
|
||
|
How to update a type definition
|
||
|
================================
|
||
|
|
||
|
This guide provides a concrete example of how to update a type definition while respecting backwards compatibility.
|
||
|
|
||
|
Imagine you have defined the following facet:
|
||
|
|
||
|
|
||
|
.. code:: java
|
||
|
|
||
|
import java.util.Date;
|
||
|
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonFormat;
|
||
|
import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||
|
import org.gcube.informationsystem.base.reference.Element;
|
||
|
import org.gcube.informationsystem.model.reference.entities.Facet;
|
||
|
import org.gcube.informationsystem.types.annotations.ISProperty;
|
||
|
import org.gcube.informationsystem.types.reference.Change;
|
||
|
import org.gcube.informationsystem.types.reference.TypeMetadata;
|
||
|
import org.gcube.informationsystem.utils.Version;
|
||
|
import org.gcube.resourcemanagement.model.impl.entities.facets.EventFacetImpl;
|
||
|
|
||
|
/**
|
||
|
* EventFacet captures information on a certain event/happening characterising the life cycle of the resource.
|
||
|
*
|
||
|
* Examples of an event are the start time of a virtual machine or the activation time of an electronic service.
|
||
|
*
|
||
|
* https://wiki.gcube-system.org/gcube/GCube_Model#Event_Facet
|
||
|
*
|
||
|
* @author Luca Frosini (ISTI - CNR)
|
||
|
*/
|
||
|
@JsonDeserialize(as=EventFacetImpl.class)
|
||
|
@TypeMetadata(
|
||
|
name = EventFacet.NAME,
|
||
|
description = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
|
||
|
+ "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
|
||
|
version = Version.MINIMAL_VERSION_STRING
|
||
|
)
|
||
|
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
|
||
|
public interface EventFacet extends Facet {
|
||
|
|
||
|
public static final String NAME = "EventFacet"; // EventFacet.class.getSimpleName();
|
||
|
|
||
|
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern = Element.DATETIME_PATTERN)
|
||
|
@ISProperty(description = "The time the event took place/occurred", mandatory=true, nullable=false)
|
||
|
public Date getDate();
|
||
|
|
||
|
public void setDate(Date date);
|
||
|
|
||
|
@ISProperty(description = "The typology of event", mandatory=true, nullable=false)
|
||
|
public String getEvent();
|
||
|
|
||
|
public void setEvent(String event);
|
||
|
|
||
|
}
|
||
|
|
||
|
which produces the following type definition:
|
||
|
|
||
|
.. code:: javascript
|
||
|
|
||
|
{
|
||
|
"type": "FacetType",
|
||
|
"id": "b76de0ac-048a-43a0-ae4f-e3fdb1a7d27c",
|
||
|
"name": "EventFacet",
|
||
|
"description": "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
|
||
|
"properties":
|
||
|
[
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "date",
|
||
|
"description": "The time the event took place/occurred",
|
||
|
"mandatory": true,
|
||
|
"readonly": false,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "Date"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "event",
|
||
|
"description": "The typology of event",
|
||
|
"mandatory": true,
|
||
|
"readonly": false,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
}
|
||
|
],
|
||
|
"version": "1.0.0",
|
||
|
"changelog": { "1.0.0": "First Version" },
|
||
|
"abstract": false,
|
||
|
"final": false,
|
||
|
"extendedTypes": [ "Facet" ]
|
||
|
}
|
||
|
|
||
|
At a certain point, the information-system-model introduces the following Property type, which models an event.
|
||
|
|
||
|
.. code:: java
|
||
|
|
||
|
import java.util.Date;
|
||
|
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonFormat;
|
||
|
import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||
|
import org.gcube.informationsystem.base.reference.Element;
|
||
|
import org.gcube.informationsystem.model.impl.properties.EventImpl;
|
||
|
import org.gcube.informationsystem.types.annotations.Final;
|
||
|
import org.gcube.informationsystem.types.annotations.ISProperty;
|
||
|
import org.gcube.informationsystem.types.reference.Change;
|
||
|
import org.gcube.informationsystem.types.reference.TypeMetadata;
|
||
|
import org.gcube.informationsystem.utils.Version;
|
||
|
|
||
|
/**
|
||
|
* @author Luca Frosini (ISTI - CNR)
|
||
|
*/
|
||
|
@JsonDeserialize(as=EventImpl.class)
|
||
|
@TypeMetadata(name = Event.NAME, description = "This type provides information regarding an event using the Five Ws (checklist) https://en.wikipedia.org/wiki/Five_Ws", version = Version.MINIMAL_VERSION_STRING)
|
||
|
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
|
||
|
@Final
|
||
|
public interface Event extends Property {
|
||
|
|
||
|
public static final String NAME = "Event"; // Event.class.getSimpleName();
|
||
|
|
||
|
public static final String WHAT_PROPERTY = "what";
|
||
|
public static final String WHEN_PROPERTY = "when";
|
||
|
public static final String WHO_PROPERTY = "who";
|
||
|
public static final String WHERE_PROPERTY = "where";
|
||
|
public static final String WHY_PROPERTY = "why";
|
||
|
public static final String HOW_PROPERTY = "how";
|
||
|
|
||
|
@ISProperty(name = WHAT_PROPERTY, description = "WHAT happened.", readonly = true, mandatory = true, nullable = false)
|
||
|
public String getWhat();
|
||
|
|
||
|
public void setWhat(String what);
|
||
|
|
||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Element.DATETIME_PATTERN)
|
||
|
@ISProperty(name = WHEN_PROPERTY, description = "WHEN the event occured. It is represented in the format " + Element.DATETIME_PATTERN + ".", readonly = true, mandatory = true, nullable = false)
|
||
|
public Date getWhen();
|
||
|
|
||
|
public void setWhen(Date when);
|
||
|
|
||
|
@ISProperty(name = WHO_PROPERTY, description = "WHO triggered the event.", readonly = true, mandatory = false, nullable = false)
|
||
|
public String getWho();
|
||
|
|
||
|
public void setWho(String who);
|
||
|
|
||
|
@ISProperty(name = WHERE_PROPERTY, description = "The location (can be virtual) WHERE the event occurred.", readonly = true, mandatory = false, nullable = false)
|
||
|
public String getWhere();
|
||
|
|
||
|
public void setWhere(String where);
|
||
|
|
||
|
@ISProperty(name = WHY_PROPERTY, description = "The reason WHY the event occurred.", readonly = true, mandatory = false, nullable = false)
|
||
|
public String getWhy();
|
||
|
|
||
|
public void setWhy(String why);
|
||
|
|
||
|
@ISProperty(name = HOW_PROPERTY, description = "How the event occurred.", readonly = true, mandatory = false, nullable = false)
|
||
|
public String getHow();
|
||
|
|
||
|
public void setHow(String how);
|
||
|
|
||
|
}
|
||
|
|
||
|
which produces the following type definition:
|
||
|
|
||
|
.. code:: javascript
|
||
|
|
||
|
{
|
||
|
"type": "PropertyType",
|
||
|
"id" : "",
|
||
|
"name": "Event",
|
||
|
"description": "This type provides information regarding an event using the Five Ws (checklist) https://en.wikipedia.org/wiki/Five_Ws",
|
||
|
"properties":
|
||
|
[
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "how",
|
||
|
"description": "How the event occurred.",
|
||
|
"mandatory": false,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "what",
|
||
|
"description": "WHAT happened.",
|
||
|
"mandatory": true,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "when",
|
||
|
"description": "WHEN the event occured. It is represented in the format yyyy-MM-dd HH:mm:ss.SSS Z.",
|
||
|
"mandatory": true,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "Date"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "where",
|
||
|
"description": "The location (can be virtual) WHERE the event occurred.",
|
||
|
"mandatory": false,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "who",
|
||
|
"description": "WHO triggered the event.",
|
||
|
"mandatory": false,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
},
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "why",
|
||
|
"description": "The reason WHY the event occurred.",
|
||
|
"mandatory": false,
|
||
|
"readonly": true,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "String"
|
||
|
}
|
||
|
],
|
||
|
"version": "1.0.0",
|
||
|
"changelog": { "1.0.0": "First Version" },
|
||
|
"abstract": false,
|
||
|
"final": true,
|
||
|
"extendedTypes": [ "Property" ]
|
||
|
}
|
||
|
|
||
|
|
||
|
So imagine you want to update the 'EventFacet' type definition to use this property type instead of the 'date' and 'event' properties.
|
||
|
|
||
|
To update the 'EventFacet' there are different strategies:
|
||
|
|
||
|
|
||
|
Java Backwards Compatibility
|
||
|
----------------------------
|
||
|
|
||
|
This solution is Java compliant from a code perspective.
|
||
|
Moreover, instances using the old format are properly deserialised.
|
||
|
|
||
|
.. code:: java
|
||
|
|
||
|
/**
|
||
|
* EventFacet captures information on a certain event/happening characterising the life cycle of the resource.
|
||
|
*
|
||
|
* Examples of an event are the start time of a virtual machine or the activation time of an electronic service.
|
||
|
*
|
||
|
* https://wiki.gcube-system.org/gcube/GCube_Model#Event_Facet
|
||
|
*
|
||
|
* @author Luca Frosini (ISTI - CNR)
|
||
|
*/
|
||
|
@JsonDeserialize(as=EventFacetImpl.class)
|
||
|
@TypeMetadata(
|
||
|
name = EventFacet.NAME,
|
||
|
description = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
|
||
|
+ "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
|
||
|
version = EventFacet.VERSION
|
||
|
)
|
||
|
@Changelog ({
|
||
|
@Change(version = EventFacet.VERSION, description = "Switching to {@link Event} Property added in the information-system-model"),
|
||
|
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
|
||
|
})
|
||
|
public interface EventFacet extends Facet {
|
||
|
|
||
|
public static final String NAME = "EventFacet"; // EventFacet.class.getSimpleName();
|
||
|
public static final String VERSION = "2.0.0";
|
||
|
|
||
|
public static final String EVENT_PROPERTY = "event";
|
||
|
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* This method will be removed soon.
|
||
|
* Use {@link EventFacet#getTheEvent()} method which return {@link Event} instance.
|
||
|
* The date can be retrieved by using {@link Event#getWhen()} method.
|
||
|
*
|
||
|
* @return the date of the occurred event.
|
||
|
*/
|
||
|
public Date getDate();
|
||
|
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* This method will be removed soon.
|
||
|
* Use {@link EventFacet#setEvent(Event event)}.
|
||
|
* The date can be set in {@link Event} instance by using
|
||
|
* {@link Event#setWhen(Date date)} method.
|
||
|
*
|
||
|
* @param date the date of the occurred event.
|
||
|
* which is the same of the 'when' Date of the {@link Event} instance.
|
||
|
*/
|
||
|
public void setDate(Date date);
|
||
|
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* This method will be removed soon.
|
||
|
* Use {@link EventFacet#getTheEvent()} method which return {@link Event} instance.
|
||
|
* The deprecated 'event' String can be retrieved by using {@link Event#getWhat()} method.
|
||
|
*
|
||
|
* @return the string of event, e.g. active
|
||
|
*/
|
||
|
public String getEvent();
|
||
|
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* This method will be removed soon.
|
||
|
* Use {@link EventFacet#setEvent(Event event)}.
|
||
|
* The deprecated 'event' String can be set in {@link Event} instance by using
|
||
|
* {@link Event#setWhat(String what)} method.
|
||
|
*
|
||
|
* @param event the string of the event to set, e.g. failed
|
||
|
* which is the same of the 'what' String of the {@link Event} instance.
|
||
|
*/
|
||
|
public void setEvent(String event);
|
||
|
|
||
|
/**
|
||
|
* Actually is not mandatory for backward compatibility.
|
||
|
* In the next version this method will be deprecated to and it will be replaced
|
||
|
* by a method with the following signature
|
||
|
*
|
||
|
* public Event getTheEvent()
|
||
|
*
|
||
|
* This is not actually possible because we need to maintain
|
||
|
* {@link EventFacet#getEvent()} for backward compatibility
|
||
|
*
|
||
|
* @return the event
|
||
|
*/
|
||
|
@ISProperty(name=EVENT_PROPERTY, type=Object.class, description = "The event eventually described by the five W (What, When, Who, Where, Why)", mandatory=false, nullable=false)
|
||
|
public Event getTheEvent();
|
||
|
|
||
|
/**
|
||
|
* Set the event
|
||
|
* @param event the event to set
|
||
|
*/
|
||
|
@JsonSetter(EVENT_PROPERTY)
|
||
|
public void setEvent(Event event);
|
||
|
|
||
|
}
|
||
|
|
||
|
which produces the following type definition:
|
||
|
|
||
|
.. code:: javascript
|
||
|
|
||
|
{
|
||
|
"type": "FacetType",
|
||
|
"id": "b76de0ac-048a-43a0-ae4f-e3fdb1a7d27c",
|
||
|
"name": "EventFacet",
|
||
|
"description": "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
|
||
|
"properties": [
|
||
|
{
|
||
|
"type": "PropertyDefinition",
|
||
|
"name": "event",
|
||
|
"description": "The event eventually described by the five W (What, When, Who, Where, Why)",
|
||
|
"mandatory": false,
|
||
|
"readonly": false,
|
||
|
"notnull": true,
|
||
|
"max": null,
|
||
|
"min": null,
|
||
|
"regexp": null,
|
||
|
"defaultValue": null,
|
||
|
"propertyType": "Any"
|
||
|
}
|
||
|
],
|
||
|
"version": "2.0.0",
|
||
|
"changelog": {
|
||
|
"2.0.0": "Switching to {@link Event} Property added in the information-system-model",
|
||
|
"1.0.0": "First Version"
|
||
|
},
|
||
|
"abstract": false,
|
||
|
"final": false,
|
||
|
"extendedTypes": [ "Facet" ]
|
||
|
}
|
||
|
|
||
|
|
||
|
The implementation should be something like this:
|
||
|
|
||
|
.. code:: java
|
||
|
|
||
|
import java.util.Date;
|
||
|
import java.util.LinkedHashMap;
|
||
|
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonGetter;
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore;
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonSetter;
|
||
|
import org.gcube.com.fasterxml.jackson.annotation.JsonTypeName;
|
||
|
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
import org.gcube.informationsystem.model.impl.entities.FacetImpl;
|
||
|
import org.gcube.informationsystem.model.impl.properties.EventImpl;
|
||
|
import org.gcube.informationsystem.model.reference.properties.Event;
|
||
|
import org.gcube.informationsystem.serialization.ElementMapper;
|
||
|
import org.gcube.resourcemanagement.model.reference.entities.facets.EventFacet;
|
||
|
|
||
|
/**
|
||
|
* @author Luca Frosini (ISTI - CNR)
|
||
|
*/
|
||
|
@JsonTypeName(value = EventFacet.NAME)
|
||
|
public class EventFacetImpl extends FacetImpl implements EventFacet {
|
||
|
|
||
|
/**
|
||
|
* Generated Serial version UID
|
||
|
*/
|
||
|
private static final long serialVersionUID = -4130548762073254058L;
|
||
|
|
||
|
protected Event event;
|
||
|
|
||
|
@Override
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*
|
||
|
* This function is actually a shortcut to
|
||
|
* {@link EventFacet#getTheEvent()}.{@link Event#getWhen()}
|
||
|
*/
|
||
|
@JsonIgnore
|
||
|
public Date getDate() {
|
||
|
if(event==null) {
|
||
|
return null;
|
||
|
}
|
||
|
return event.getWhen();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*
|
||
|
* This function is actually a shortcut to
|
||
|
* {@link EventFacet#getTheEvent()}.{@link Event#setWhen(Date date)}
|
||
|
*
|
||
|
* It eventually instantiate {@link Event} if null.
|
||
|
*/
|
||
|
@JsonSetter("date")
|
||
|
public void setDate(Date date) {
|
||
|
if(event == null) {
|
||
|
event = new EventImpl();
|
||
|
}
|
||
|
event.setWhen(date);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*
|
||
|
* This function is actually a shortcut to
|
||
|
* {@link EventFacet#getTheEvent()}.{@link Event#getWhat()}
|
||
|
*/
|
||
|
@JsonIgnore
|
||
|
public String getEvent() {
|
||
|
if(event==null) {
|
||
|
return null;
|
||
|
}
|
||
|
return event.getWhat();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Deprecated
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*
|
||
|
* This function is actually a shortcut to
|
||
|
* {@link EventFacet#getTheEvent()}.{@link Event#setWhat(String what)}.
|
||
|
*
|
||
|
* It eventually instantiate {@link Event} if null.
|
||
|
*/
|
||
|
@JsonIgnore
|
||
|
public void setEvent(String event) {
|
||
|
if(event == null) {
|
||
|
this.event = new EventImpl();
|
||
|
}
|
||
|
this.event.setWhat(event);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
@JsonGetter(EventFacet.EVENT_PROPERTY)
|
||
|
public Event getTheEvent() {
|
||
|
return event;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
@JsonIgnore
|
||
|
public void setEvent(Event event) {
|
||
|
this.event = event;
|
||
|
}
|
||
|
|
||
|
@JsonSetter(EventFacet.EVENT_PROPERTY)
|
||
|
protected void setGenericEvent(Object event) {
|
||
|
if(event instanceof String) {
|
||
|
setEvent((String) event);
|
||
|
}
|
||
|
|
||
|
if(event instanceof LinkedHashMap<?, ?>) {
|
||
|
ObjectMapper objectMapper = ElementMapper.getObjectMapper();
|
||
|
Event theEvent = objectMapper.convertValue(event, Event.class);
|
||
|
setEvent(theEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
With this strategy, any Java client reading the event facet instantiated with the old
|
||
|
model will remap the 'date' and 'event' properties to an instance of 'Event' named 'event'.
|
||
|
|
||
|
We receive
|
||
|
|
||
|
.. code:: javascript
|
||
|
|
||
|
{
|
||
|
"type": "EventFacet",
|
||
|
"id": "d9f91175-8c33-4f68-8619-48e6feca4e47",
|
||
|
"date": "2024-01-08 12:52:59.907 +0100",
|
||
|
"event": "certified"
|
||
|
}
|
||
|
|
||
|
which will be serialized in turn as:
|
||
|
|
||
|
.. code:: javascript
|
||
|
{
|
||
|
"type": "EventFacet",
|
||
|
"id": "d9f91175-8c33-4f68-8619-48e6feca4e47",
|
||
|
"event": {
|
||
|
"type": "Event",
|
||
|
"what": "certified",
|
||
|
"when": "2024-01-08 11:52:59.907 +0000",
|
||
|
"who": null,
|
||
|
"where": null,
|
||
|
"why": null,
|
||
|
"how": null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
The problem of this solution is that if the facet is updated to the new format in the server and
|
||
|
there are old clients they will not be able to deserialise the new version.
|
||
|
|
||
|
This
|