.. _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. The objective is to obtain a facet with ``event`` property as an ``Event``. 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 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 */ 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/deserialized 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 if there are old clients they will not be able to work with the new version. With this solution the ``event`` property in the server is of type ANY (an Object in Java) .. code:: java @ISProperty(name=EVENT_PROPERTY, type=Object.class, description = .... public Event getTheEvent(); The ``@ISProperty`` annotation specifies the type of the property ``type=Object.class`` instead of letting the system to get the type from the function return, i.e. ``Event``. The type definition in JSON has: .. code:: javascript "propertyType": "Any" PRO: * the name of the variable is already ``event`` as we want as final objective. CONS: * the server cannot check the validity of the variable received from an updating client. The future release of ``EventFacet`` will * remove ``type=Object.class`` from ``@ISProperty`` annotation; * remove deprecated methods from the Java interface and its implementation: * ``public Date getDate();`` * ``public void setDate(Date date);`` * ``public String getEvent();`` * ``public void setEvent(String event);`` * deprecate ``public Event getTheEvent();`` * add ``public Event getEvent();`` **To summarize**: This solution is capable of reading old format existing instances, but it always updates the instances to the new format, which cannot be used by an old client. Java and Javascript Backwards Compatibility ------------------------------------------- This solution is Java compliant from a code perspective and the old properties are keep for old clients. .. code:: java import java.util.Date; import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.gcube.informationsystem.model.reference.entities.Facet; import org.gcube.informationsystem.model.reference.properties.Event; import org.gcube.informationsystem.types.annotations.ISProperty; import org.gcube.informationsystem.types.reference.Change; import org.gcube.informationsystem.types.reference.Changelog; 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 = 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 = "theEvent"; @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, 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 */ public void setEvent(Event event); } With the following 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": "theEvent", "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": "Event" } ], "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 the following: .. code:: java import java.util.Date; 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.informationsystem.model.impl.entities.FacetImpl; import org.gcube.informationsystem.model.impl.properties.EventImpl; import org.gcube.informationsystem.model.reference.properties.Event; 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 theEvent; @Override @Deprecated /** * {@inheritDoc} * * This function is actually a shortcut to * {@link EventFacet#getTheEvent()}.{@link Event#getWhen()} */ public Date getDate() { if(theEvent==null) { return null; } return theEvent.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(theEvent == null) { theEvent = new EventImpl(); } theEvent.setWhen(date); } @Override @Deprecated /** * {@inheritDoc} * * This function is actually a shortcut to * {@link EventFacet#getTheEvent()}.{@link Event#getWhat()} */ public String getEvent() { if(theEvent==null) { return null; } return theEvent.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. */ @JsonSetter("event") public void setEvent(String event) { if(event == null) { this.theEvent = new EventImpl(); } this.theEvent.setWhat(event); } @Override /** * {@inheritDoc} */ @JsonGetter(EventFacet.EVENT_PROPERTY) public Event getTheEvent() { return theEvent; } @Override /** * {@inheritDoc} */ @JsonIgnore @JsonSetter(EventFacet.EVENT_PROPERTY) public void setEvent(Event event) { this.theEvent = event; } } 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 'theEvent'. The old properties 'date' and 'event' properties are still available for old clients. 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/deserialized in turn as: .. code:: javascript { "type": "EventFacet", "id": "d9f91175-8c33-4f68-8619-48e6feca4e47", "date": "2024-01-08 12:52:59.907 +0100" "event": "certified", "theEvent": { "type": "Event", "what": "certified", "when": "2024-01-08 11:52:59.907 +0000", "who": null, "where": null, "why": null, "how": null } } With this solution we use ``theEvent`` property in the server of type Event which validity can be checked by the server: PRO: * the server can check the validity of the variable received from an updating client. CONS: * the name of the variable is ``theEvent`` and only in a future step will be changed. The future release of ``EventFacet`` will * remove deprecated methods from the Java interface and its implementation: * ``public Date getDate();`` * ``public void setDate(Date date);`` * ``public String getEvent();`` * ``public void setEvent(String event);`` * deprecate ``public Event getTheEvent();`` * add ``public Event getEvent();`` Both ``event`` and ``theEvent`` will coexist until the next version. This solution has the following issue: ``date`` and ``event`` may not align with ``theEvent`` if updated by old clients. The proposed implementation involves ``setTheEvent()``, which could potentially overwrite ``date`` and ``event`` if invoked by Jackson beforehand. Therefore, implementing this correctly requires careful consideration. It's important to note that only old clients might disalign the date. As a result, the most advanced values always reside in the old properties. Conversely, a new client would have already corrected and aligned them. Of course, there is no resolution for bugged new clients causing misalignments, but that's a separate issue not covered in this guide. **To summarize**: This solution is capable of reading old format existing instances. New clients update the properties in both old and new formats. It can be used by old clients, but new clients must take into account that an update made by an old client causes misalignment of the date and event properties with the corresponding properties contained in the ``theEvent`` property.