18 KiB
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:
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(
= EventFacet.NAME,
name = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
description + "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
= Version.MINIMAL_VERSION_STRING
version )
@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:
{"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.
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:
{"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.
/**
* 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(
= EventFacet.NAME,
name = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
description + "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
= EventFacet.VERSION
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 getEvent()
*
* 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:
{"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:
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) {
= new EventImpl();
event }
.setWhen(date);
event}
@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<?, ?>) {
= ElementMapper.getObjectMapper();
ObjectMapper objectMapper 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
{"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:
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