| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.internal.jaxb.gco; |
| |
| import java.util.UUID; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import javax.xml.bind.annotation.XmlAttribute; |
| import javax.xml.bind.annotation.XmlSchemaType; |
| import javax.xml.bind.annotation.adapters.XmlAdapter; |
| import org.apache.sis.xml.XLink; |
| import org.apache.sis.xml.NilObject; |
| import org.apache.sis.xml.NilReason; |
| import org.apache.sis.xml.Namespaces; |
| import org.apache.sis.xml.IdentifierMap; |
| import org.apache.sis.xml.IdentifierSpace; |
| import org.apache.sis.xml.IdentifiedObject; |
| import org.apache.sis.xml.ReferenceResolver; |
| import org.apache.sis.internal.util.Strings; |
| import org.apache.sis.internal.jaxb.Context; |
| import org.apache.sis.internal.jaxb.FilterByVersion; |
| import org.apache.sis.internal.jaxb.PrimitiveTypeProperties; |
| import org.apache.sis.util.SimpleInternationalString; |
| import org.apache.sis.util.resources.Errors; |
| |
| |
| /** |
| * Base class for adapters from GeoAPI interfaces to their SIS implementations. |
| * Implementation subclasses are actually both JAXB adapters and wrappers around |
| * the value to be marshalled. Wrappers exist because ISO 19139 have the strange |
| * habit to wrap every properties in an extra level, for example: |
| * |
| * {@preformat xml |
| * <CI_ResponsibleParty> |
| * <contactInfo> |
| * <CI_Contact> |
| * ... |
| * </CI_Contact> |
| * </contactInfo> |
| * </CI_ResponsibleParty> |
| * } |
| * |
| * The {@code <CI_Contact>} level is not really necessary, and JAXB is not designed for inserting |
| * such level since it is not the usual way to write XML. In order to get this output with JAXB, |
| * we have to wrap metadata object in an additional object. So each {@code PropertyType} subclass |
| * is both a JAXB adapter and a wrapper. We have merged those functionalities in order to avoid |
| * doubling the amount of classes, which is already large. |
| * |
| * <p>In ISO 19139 terminology:</p> |
| * <ul> |
| * <li>the public classes defined in the {@code org.apache.sis.metadata.iso} packages are defined |
| * as {@code Foo_Type} in ISO 19139, where <var>Foo</var> is the ISO name of a class.</li> |
| * <li>the {@code PropertyType} subclasses are defined as {@code Foo_PropertyType} in |
| * ISO 19139 schemas.</li> |
| * </ul> |
| * |
| * <h2>Guidlines for subclasses</h2> |
| * Subclasses shall provide a method returning the SIS implementation class for the metadata value. |
| * This method will be systematically called at marshalling time by JAXB. Typical implementation |
| * ({@code BoundType} and {@code ValueType} need to be replaced by the concrete class): |
| * |
| * {@preformat java |
| * @XmlElementRef |
| * public BoundType getElement() { |
| * if (skip()) return null; |
| * final ValueType metadata = this.metadata; |
| * return (metadata instanceof BoundType) ? (BoundType) metadata : new BoundType(metadata); |
| * } |
| * |
| * public void getElement(final BoundType metadata) { |
| * this.metadata = metadata; |
| * } |
| * } |
| * |
| * The actual implementation may be slightly more complicated than the above if there is |
| * various subclasses to check. |
| * |
| * <div class="note"><b>Note:</b> |
| * A previous version provided an abstract {@code getElement()} method in this class |
| * for enforcing its definition in subclasses. But this has been removed for two reasons: |
| * <ul> |
| * <li>While the return value is usually {@code BoundType}, in some situations it is |
| * rather an other type like {@code String}. For this raison the return type must |
| * be declared as {@code Object}, and subclasses have to restrict it to a more |
| * specific type.</li> |
| * <li>The parameterized return type forces the compiler to generate bridge methods under |
| * the hood. In the particular case of typical {@code PropertyType} subclasses, |
| * this increases the size of {@code .class} files by approximately 4.5%. |
| * While quite small, this is a useless overhead since we never need to invoke the |
| * abstract {@code getElement()} from this class.</li> |
| * </ul></div> |
| * |
| * @author Cédric Briançon (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @author Cullen Rombach (Image Matters) |
| * @version 1.0 |
| * |
| * @param <ValueType> the adapter subclass. |
| * @param <BoundType> the interface being adapted. |
| * |
| * @see XmlAdapter |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public abstract class PropertyType<ValueType extends PropertyType<ValueType,BoundType>, BoundType> |
| extends XmlAdapter<ValueType,BoundType> |
| { |
| /** |
| * The wrapped GeoAPI metadata instance, or {@code null} if the metadata shall not be marshalled. |
| * Metadata are not marshalled when replaced by {@code xlink:href} or {@code uuidref} attributes. |
| */ |
| protected BoundType metadata; |
| |
| /** |
| * Either {@code null}, an {@link ObjectReference} or a {@link String}. |
| * |
| * <ul> |
| * <li>{@link ObjectReference} defines the {@code uuidref}, {@code xlink:href}, {@code xlink:role}, |
| * {@code xlink:arcrole}, {@code xlink:title}, {@code xlink:show} and {@code xlink:actuate} attributes.</li> |
| * <li>{@link String} defines the {@code nilReason} attribute.</li> |
| * </ul> |
| * |
| * Those two properties are exclusive (if the user defines an object reference, |
| * then the attribute is not nil). |
| */ |
| private Object reference; |
| |
| /** |
| * Empty constructor for subclasses only. |
| */ |
| protected PropertyType() { |
| } |
| |
| /** |
| * Builds a {@code PropertyType} wrapper for the given primitive type wrapper. |
| * This constructor checks for nil reasons only if {@code check} is {@code true}. |
| * |
| * @param value the primitive type wrapper. |
| * @param mayBeNil {@code true} if we should check for nil reasons. |
| */ |
| protected PropertyType(final BoundType value, final boolean mayBeNil) { |
| metadata = value; |
| if (mayBeNil) { |
| final Object property = PrimitiveTypeProperties.property(value); |
| if (property instanceof NilReason) { |
| reference = property.toString(); |
| metadata = null; |
| } |
| } |
| } |
| |
| /** |
| * Builds a wrapper for the given GeoAPI interface. This constructor checks if the given metadata |
| * implements the {@link NilObject} or {@link IdentifiedObject} interface. If the object implements |
| * both of them (should not happen, but we never know), then the identifiers will have precedence. |
| * |
| * @param value the interface to wrap. |
| */ |
| protected PropertyType(final BoundType value) { |
| /* |
| * Do not invoke NilReason.forObject(metadata) in order to avoid unnecessary synchronization. |
| * Subclasses will use the PropertyType(BoundType, boolean) constructor instead when a check |
| * for primitive type is required. |
| */ |
| if (value instanceof NilObject) { |
| final NilReason reason = ((NilObject) value).getNilReason(); |
| if (reason != null) { |
| reference = reason.toString(); |
| return; |
| } |
| } |
| /* |
| * Verifies if the object to marshal can be replaced by a xlink or uuidref. |
| * First, check if we can use a xlink:href="#foo" reference to a gml:id="foo". |
| * Only if no gml:id was found, check for user-defined xlink or uuidref. |
| */ |
| @SuppressWarnings("OverridableMethodCallInConstructor") |
| final Class<BoundType> type = getBoundType(); |
| final Context context = Context.current(); |
| final ReferenceResolver resolver = Context.resolver(context); |
| final String id = Context.getObjectID(context, value); |
| if (id != null && resolver.canSubstituteByReference(context, type, value, id)) try { |
| final XLink link = new XLink(); |
| link.setHRef(new URI(null, null, id)); |
| reference = new ObjectReference(null, link); |
| return; |
| } catch (URISyntaxException e) { |
| Context.warningOccured(context, getClass(), "<init>", e, true); |
| } |
| metadata = value; // Non-null only after we verified that not a NilObject or xlink:href="#foo". |
| if (value instanceof IdentifiedObject) { |
| /* |
| * Get the identifiers as full UUID or XLink objects. We do not use the more permissive methods |
| * working with arbitrary strings -- e.g. map.get(IdentifierSpace.HREF) -- because we are going |
| * to use those values for marshalling REFERENCES to an externally-defined metadata object, not |
| * for declaring the attributes to marshal together with the metadata. Since references REPLACE |
| * the original metadata object, we are better to ensure that they are well formed - in case of |
| * doubt, we are better to marshal the full object. We are not loosing information since in the |
| * later case, the identifiers will be marshalled as Strings by ISOMetadata. Example: |
| * |
| * <cit:CI_Citation> |
| * <cit:series uuidref="f8f5fcb1-d57b-4013-b3a4-4eaa40df6dcf"> ☚ marshalled by this |
| * <cit:CI_Series uuid="f8f5fcb1-d57b-4013-b3a4-4eaa40df6dcf"> ☚ marshalled by ISOMetadata |
| * ... |
| * </cit:CI_Series> |
| * </cit:series> |
| * </cit:CI_Citation> |
| * |
| * We do not try to parse UUID or XLink objects from String because it should be the job of |
| * org.apache.sis.internal.jaxb.ModifiableIdentifierMap.put(Citation, String). |
| */ |
| final IdentifierMap map = ((IdentifiedObject) value).getIdentifierMap(); |
| XLink link = map.getSpecialized(IdentifierSpace.XLINK); |
| UUID uuid = map.getSpecialized(IdentifierSpace.UUID); |
| if (uuid != null || link != null) { |
| /* |
| * Check if the user gives us the permission to use reference to those identifiers. |
| * If not, forget them in order to avoid marshalling the identifiers twice (see the |
| * example in the above comment). |
| */ |
| if (uuid != null) { |
| if (resolver.canSubstituteByReference(context, type, value, uuid)) { |
| metadata = null; |
| } else { |
| uuid = null; |
| } |
| } |
| /* |
| * There is no risk of duplication for 'xlink' because there is no such attribute in ISOMetadata. |
| * So if the user does not allow us to omit the metadata object, we will still keep the xlink for |
| * informative purpose. |
| */ |
| if (link != null && resolver.canSubstituteByReference(context, type, value, link)) { |
| metadata = null; |
| } |
| if (uuid != null || link != null) { |
| reference = new ObjectReference(uuid, link); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the object reference, or {@code null} if none and the {@code create} argument is {@code false}. |
| * If the {@code create} argument is {@code true}, then this method will create the object reference when |
| * first needed. In the latter case, any previous {@code gco:nilReason} will be overwritten since |
| * the object is not nil. |
| */ |
| private ObjectReference reference(final boolean create) { |
| final Object ref = reference; |
| if (ref instanceof ObjectReference) { |
| return (ObjectReference) ref; |
| } else if (create) { |
| final ObjectReference newRef = new ObjectReference(); |
| reference = newRef; |
| return newRef; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the {@code xlink}, or {@code null} if none and {@code create} is {@code false}. |
| * If the {@code create} argument is {@code true}, then this method will create the XLink |
| * when first needed. In the latter case, any previous {@code gco:nilReason} will be |
| * overwritten since the object is not nil. |
| */ |
| private XLink xlink(final boolean create) { |
| final ObjectReference ref = reference(create); |
| if (ref == null) { |
| return null; |
| } |
| XLink xlink = ref.xlink; |
| if (create && xlink == null) { |
| ref.xlink = xlink = new XLink(); |
| xlink.setType(XLink.Type.SIMPLE); // The "simple" type is fixed in the "gco" schema. |
| } |
| return xlink; |
| } |
| |
| /** |
| * The reason why a mandatory attribute if left unspecified. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category gco:PropertyType |
| */ |
| @XmlAttribute(name = "nilReason", namespace = Namespaces.GCO) |
| public final String getNilReason() { |
| final Object ref = reference; |
| return (ref instanceof String) ? (String) ref : null; |
| } |
| |
| /** |
| * Sets the {@code nilReason} attribute value. This method does nothing if a |
| * non-null {@linkplain #reference} exists, since in such case the object can |
| * not be nil. |
| * |
| * @param nilReason the new attribute value. |
| * @category gco:PropertyType |
| */ |
| public final void setNilReason(final String nilReason) { |
| if (!(reference instanceof ObjectReference)) { |
| reference = nilReason; |
| } |
| } |
| |
| /** |
| * A URN to an external resources, or to an other part of a XML document, or an identifier. |
| * The {@code uuidref} attribute is used to refer to an XML element that has a corresponding |
| * {@code uuid} attribute. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category gco:ObjectReference |
| */ |
| @XmlAttribute(name = "uuidref") // Defined in "gco" as unqualified attribute. |
| public final String getUUIDREF() { |
| final ObjectReference ref = reference(false); |
| return (ref != null) ? toString(ref.uuid) : null; |
| } |
| |
| /** |
| * Sets the {@code uuidref} attribute value. |
| * |
| * @param uuid the new attribute value. |
| * @throws IllegalArgumentException if the given UUID can not be parsed. |
| * @category gco:ObjectReference |
| */ |
| public final void setUUIDREF(final String uuid) throws IllegalArgumentException { |
| final Context context = Context.current(); |
| reference(true).uuid = Context.converter(context).toUUID(context, uuid); |
| } |
| |
| /** |
| * Returns the given URI as a string, or returns {@code null} if the given argument is null. |
| */ |
| private static String toString(final Object text) { |
| return (text != null) ? text.toString() : null; |
| } |
| |
| /** |
| * Parses the given URI, or returns {@code null} if the given argument is null or empty. |
| */ |
| private static URI toURI(final String uri) throws URISyntaxException { |
| final Context context = Context.current(); |
| return Context.converter(context).toURI(context, uri); |
| } |
| |
| /** |
| * A URN to an external resources, or to an other part of a XML document, or an identifier. |
| * The {@code xlink:href} attribute allows an XML element to refer to another XML element |
| * that has a corresponding {@code id} attribute. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlSchemaType(name = "anyURI") |
| @XmlAttribute(name = "href", namespace = Namespaces.XLINK) |
| public final String getHRef() { |
| final XLink link = xlink(false); |
| return (link != null) ? toString(link.getHRef()) : null; |
| } |
| |
| /** |
| * Sets the {@code href} attribute value. |
| * |
| * @param href the new attribute value. |
| * @throws URISyntaxException if the given string can not be parsed as a URI. |
| * @category xlink |
| */ |
| public final void setHRef(final String href) throws URISyntaxException { |
| xlink(true).setHRef(toURI(href)); |
| } |
| |
| /** |
| * A URI reference for some description of the arc role. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlSchemaType(name = "anyURI") |
| @XmlAttribute(name = "role", namespace = Namespaces.XLINK) |
| public final String getRole() { |
| final XLink link = xlink(false); |
| return (link != null) ? toString(link.getRole()) : null; |
| } |
| |
| /** |
| * Sets the {@code role} attribute value. |
| * |
| * @param role the new attribute value. |
| * @throws URISyntaxException if the given string can not be parsed as a URI. |
| * @category xlink |
| */ |
| public final void setRole(final String role) throws URISyntaxException { |
| xlink(true).setRole(toURI(role)); |
| } |
| |
| /** |
| * A URI reference for some description of the arc role. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlSchemaType(name = "anyURI") |
| @XmlAttribute(name = "arcrole", namespace = Namespaces.XLINK) |
| public final String getArcRole() { |
| final XLink link = xlink(false); |
| return (link != null) ? toString(link.getArcRole()) : null; |
| } |
| |
| /** |
| * Sets the {@code arcrole} attribute value. |
| * |
| * @param arcrole the new attribute value. |
| * @throws URISyntaxException if the given string can not be parsed as a URI. |
| * @category xlink |
| */ |
| public final void setArcRole(final String arcrole) throws URISyntaxException { |
| xlink(true).setArcRole(toURI(arcrole)); |
| } |
| |
| /** |
| * Just as with resources, this is simply a human-readable string with a short description |
| * for the arc. |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlAttribute(name = "title", namespace = Namespaces.XLINK) |
| public final String getTitle() { |
| final XLink link = xlink(false); |
| return (link != null) ? StringAdapter.toString(link.getTitle()) : null; |
| } |
| |
| /** |
| * Sets the {@code title} attribute value. |
| * |
| * @param title the new attribute value. |
| * @category xlink |
| */ |
| public final void setTitle(String title) { |
| title = Strings.trimOrNull(title); |
| if (title != null) { |
| xlink(true).setTitle(new SimpleInternationalString(title)); |
| } |
| } |
| |
| /** |
| * Communicates the desired presentation of the ending resource on traversal |
| * from the starting resource. It's value should be treated as follows: |
| * |
| * <ul> |
| * <li>new: load ending resource in a new window, frame, pane, or other presentation context</li> |
| * <li>replace: load the resource in the same window, frame, pane, or other presentation context</li> |
| * <li>embed: load ending resource in place of the presentation of the starting resource</li> |
| * <li>other: behavior is unconstrained; examine other markup in the link for hints</li> |
| * <li>none: behavior is unconstrained</li> |
| * </ul> |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlAttribute(name = "show", namespace = Namespaces.XLINK) |
| public final XLink.Show getShow() { |
| final XLink link = xlink(false); |
| return (link != null) ? link.getShow() : null; |
| } |
| |
| /** |
| * Sets the {@code show} attribute value. |
| * |
| * @param show the new attribute value. |
| * @category xlink |
| */ |
| public final void setShow(final XLink.Show show) { |
| xlink(true).setShow(show); |
| } |
| |
| /** |
| * Communicates the desired timing of traversal from the starting resource to the ending resource. |
| * It's value should be treated as follows: |
| * |
| * <ul> |
| * <li>onLoad: traverse to the ending resource immediately on loading the starting resource</li> |
| * <li>onRequest: traverse from the starting resource to the ending resource only on a post-loading event triggered for this purpose</li> |
| * <li>other: behavior is unconstrained; examine other markup in link for hints</li> |
| * <li>none: behavior is unconstrained</li> |
| * </ul> |
| * |
| * @return the current value, or {@code null} if none. |
| * @category xlink |
| */ |
| @XmlAttribute(name = "actuate", namespace = Namespaces.XLINK) |
| public final XLink.Actuate getActuate() { |
| final XLink link = xlink(false); |
| return (link != null) ? link.getActuate() : null; |
| } |
| |
| /** |
| * Sets the {@code actuate} attribute value. |
| * |
| * @param actuate the new attribute value. |
| * @category xlink |
| */ |
| public final void setActuate(final XLink.Actuate actuate) { |
| xlink(true).setActuate(actuate); |
| } |
| |
| // Do NOT declare attributes xlink:label, xlink:from and xlink:to, |
| // because they are not part of the xlink:simpleLink group. |
| |
| |
| // ======== XmlAdapter methods =============================================================== |
| |
| |
| /** |
| * Returns the bound type, which is typically the GeoAPI interface. |
| * Subclasses need to return a hard-coded value. They shall not compute |
| * a value from the object fields, because this method is invoked from |
| * the constructor. |
| * |
| * @return the bound type, which is typically the GeoAPI interface. |
| */ |
| protected abstract Class<BoundType> getBoundType(); |
| |
| /** |
| * Returns {@code true} if a {@code Since2014} subclasses should return a non-null value. |
| * This is a convenience method for {@code FilterByVersion.CURRENT_METADATA.accept()}. |
| * |
| * @return whether {@code Since2014} subclasses should return a non-null value. |
| */ |
| protected final boolean accept2014() { |
| return FilterByVersion.CURRENT_METADATA.accept(); |
| } |
| |
| /** |
| * Creates a new instance of this class wrapping the given metadata. |
| * This method is invoked by {@link #marshal} after making sure that |
| * {@code value} is not null. |
| * |
| * @param value the GeoAPI interface to wrap. |
| * @return the adapter. |
| */ |
| protected abstract ValueType wrap(final BoundType value); |
| |
| /** |
| * Converts a GeoAPI interface to the appropriate adapter for the way it will be |
| * marshalled into an XML file or stream. JAXB calls automatically this method at |
| * marshalling time. |
| * |
| * @param value the bound type value, here the interface. |
| * @return the adapter for the given value. |
| */ |
| @Override |
| public final ValueType marshal(final BoundType value) { |
| if (value == null) { |
| return null; |
| } |
| return wrap(value); |
| } |
| |
| /** |
| * Converts an adapter read from an XML stream to the GeoAPI interface which will |
| * contains this value. JAXB calls automatically this method at unmarshalling time. |
| * |
| * @param value the adapter for a metadata value. |
| * @return an instance of the GeoAPI interface which represents the metadata value. |
| * @throws URISyntaxException if a URI can not be parsed. |
| */ |
| @Override |
| public final BoundType unmarshal(final ValueType value) throws URISyntaxException { |
| return (value != null) ? value.resolve(Context.current()) : null; |
| } |
| |
| /** |
| * If the {@linkplain #metadata} is still null, tries to resolve it using UUID, XLink |
| * or NilReason information. This method is invoked at unmarshalling time. |
| * |
| * @throws URISyntaxException if a nil reason is present and can not be parsed. |
| */ |
| final BoundType resolve(final Context context) throws URISyntaxException { |
| final ObjectReference ref = reference(false); |
| if (ref != null) { |
| metadata = ref.resolve(context, getBoundType(), metadata); |
| } |
| if (metadata == null) { |
| final String value = getNilReason(); |
| if (value != null) { |
| final NilReason nilReason = Context.converter(context).toNilReason(context, value); |
| if (nilReason != null) { |
| metadata = nilReason.createNilObject(getBoundType()); |
| } |
| } |
| } |
| return metadata; |
| } |
| |
| /** |
| * Invoked by subclasses when the unmarshalled object is missing a component. |
| * This method is invoked when the missing component is essential to SIS working. |
| * This method is not invoked if the missing component is flagged as mandatory by GML, |
| * but is not mandatory for SIS working. |
| * |
| * @param missing the name of the missing XML component. |
| * @throws IllegalArgumentException always thrown. |
| * |
| * @since 0.7 |
| */ |
| protected final void incomplete(final String missing) throws IllegalArgumentException { |
| throw new IllegalArgumentException(Errors.format(Errors.Keys.MissingComponentInElement_2, getBoundType(), missing)); |
| } |
| |
| /* |
| * Do not provide the following method here: |
| * |
| * public abstract BountType getElement(); |
| * |
| * as it adds a small but unnecessary overhead. |
| * See class Javadoc for more information. |
| */ |
| } |