blob: 90d22a0e27766a035239645194c09ce5c2151044 [file] [log] [blame]
/*
* 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.metadata.iso;
import java.util.Set;
import java.util.List;
import java.util.Collection;
import java.util.Collections;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
import org.opengis.metadata.Identifier;
import org.apache.sis.xml.IdentifierMap;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.xml.IdentifiedObject;
import org.apache.sis.metadata.MetadataStandard;
import org.apache.sis.metadata.ModifiableMetadata;
import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
import org.apache.sis.internal.jaxb.ModifiableIdentifierMap;
import org.apache.sis.internal.jaxb.NonMarshalledAuthority;
import org.apache.sis.internal.metadata.MetadataUtilities;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.util.collection.Containers;
import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
/**
* The base class of ISO 19115 implementation classes. Each sub-classes implements one
* of the ISO Metadata interface provided by <a href="http://www.geoapi.org">GeoAPI</a>.
*
* <p><b>Limitations:</b></p>
* <ul>
* <li>Instances of this class are not synchronized for multi-threading.
* Synchronization, if needed, is caller's responsibility.</li>
* <li>Serialized objects of this class are not guaranteed to be compatible with future Apache SIS releases.
* Serialization support is appropriate for short term storage or RMI between applications running the
* same version of Apache SIS. For long term storage, use {@link org.apache.sis.xml.XML} instead.</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.3
* @module
*/
@XmlTransient
public class ISOMetadata extends ModifiableMetadata implements IdentifiedObject, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -4997239501383133209L;
/**
* All identifiers associated with this metadata, or {@code null} if none.
* This field is initialized to a non-null value when first needed.
*/
protected Collection<Identifier> identifiers;
/**
* Constructs an initially empty metadata.
*/
protected ISOMetadata() {
}
/**
* Constructs a new metadata initialized with the values from the specified object.
* If the given object is an instance of {@link IdentifiedObject}, then this constructor
* copies the {@linkplain #identifiers collection of identifiers}.
*
* @param object the metadata to copy values from, or {@code null} if none.
*/
protected ISOMetadata(final Object object) {
if (object instanceof IdentifiedObject) {
if (object instanceof ISOMetadata && Containers.isNullOrEmpty(((ISOMetadata) object).identifiers)) {
/*
* If the other object is an ISOMetadata instance, take a look at its 'identifiers' collection
* before to invoke object.getIdentifiers() in order to avoid unnecessary initialization of its
* backing collection. We do this optimization because the vast majority of metadata objects do
* not have 'identifiers' collection.
*
* Actually this optimization is a little bit dangerous, since users could override getIdentifiers()
* without invoking super.getIdentifiers(), in which case their identifiers will not be copied.
* For safety, we will do this optimization only if the implementation is an Apache SIS one.
*/
if (object.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX)) {
return;
}
}
identifiers = copyCollection(((IdentifiedObject) object).getIdentifiers(), Identifier.class);
}
}
/**
* Returns the metadata standard implemented by subclasses,
* which is {@linkplain MetadataStandard#ISO_19115 ISO 19115}.
*
* <h4>Note for implementers</h4>
* Subclasses shall not override this method in a way that depends on the object state,
* since this method may be indirectly invoked by copy constructors (i.e. is may be
* invoked before this metadata object is fully constructed).
*
* @return the metadata standard, which is {@linkplain MetadataStandard#ISO_19115 ISO 19115} by default.
*/
@Override
public MetadataStandard getStandard() {
return MetadataStandard.ISO_19115;
}
// --------------------------------------------------------------------------------------
// Identifier methods below also appear in other IdentifiedObject implementations.
// If this code is modified, consider revisiting also the following classes:
//
// * org.apache.sis.metadata.iso.identification.DefaultRepresentativeFraction
// --------------------------------------------------------------------------------------
/**
* Returns all identifiers associated to this object (from conceptual model and from XML document).
* This collection may contain identifiers from different sources:
*
* <ul class="verbose">
* <li>Identifiers specified in the ISO 19115-1 or 19115-2 abstract models,
* typically (but not necessarily) as an {@code identifier} property
* (may also be {@link DefaultMetadata#getMetadataIdentifier() metadataIdentifier},
* {@link org.apache.sis.metadata.iso.citation.DefaultCitation#getISBN() ISBN} or
* {@link org.apache.sis.metadata.iso.citation.DefaultCitation#getISSN() ISSN} properties).</li>
* <li>Identifiers specified in the ISO 19115-3 or 19115-4 XML schemas.
* Those identifiers are typically stored as a result of unmarshalling an XML document.
* Those identifiers can be recognized by an {@linkplain Identifier#getAuthority() authority}
* sets as one of the {@link IdentifierSpace} constants.</li>
* </ul>
*/
@Override
public Collection<Identifier> getIdentifiers() {
return identifiers = nonNullCollection(identifiers, Identifier.class);
}
/**
* {@inheritDoc}
*
* <p>The default implementation returns a wrapper around the {@link #identifiers} list.
* That map is <cite>live</cite>: changes in the identifiers list will be reflected in the map,
* and conversely.</p>
*/
@Override
public IdentifierMap getIdentifierMap() {
/*
* Do not invoke getIdentifiers(), because some subclasses like DefaultCitation and
* DefaultObjective override getIdentifiers() in order to return a filtered list.
*/
identifiers = nonNullCollection(identifiers, Identifier.class);
if (identifiers == null) {
return IdentifierMapAdapter.EMPTY;
}
/*
* We do not cache (for now) the IdentifierMap because it is cheap to create, and if we were
* caching it we would need anyway to check if 'identifiers' still references the same list.
*/
return (super.state() != State.FINAL) ? new ModifiableIdentifierMap(identifiers)
: new IdentifierMapAdapter(identifiers);
}
/**
* Returns the first identifier which is presumed to be defined by ISO 19115 conceptual model.
* This method checks the {@linkplain Identifier#getAuthority() authority} for filtering ignorable
* identifiers like ISBN/ISSN codes and XML attributes.
* This convenience method is provided for implementation of public {@code getIdentifier(Identifier)}
* methods in subclasses having an {@code identifier} property with [0 … 1] multiplicity.
*
* @return an identifier from ISO 19115-3 conceptual model (excluding XML identifiers),
* or {@code null} if none.
*
* @since 1.0
*/
protected Identifier getIdentifier() {
return NonMarshalledAuthority.getMarshallable(identifiers);
}
/**
* Sets the identifier for metadata objects that are expected to contain at most one ISO 19115 identifier.
* This convenience method is provided for implementation of public {@code setIdentifier(Identifier)} methods
* in subclasses having an {@code identifier} property with [0 … 1] multiplicity.
* The default implementation removes all identifiers that would be returned by {@link #getIdentifier()}
* before to add the given one in the {@link #identifiers} collection.
*
* @param newValue the new identifier value, or {@code null} for removing the identifier.
*
* @since 1.0
*/
protected void setIdentifier(final Identifier newValue) {
checkWritePermission(valueIfDefined(identifiers));
identifiers = nonNullCollection(identifiers, Identifier.class);
identifiers = writeCollection(NonMarshalledAuthority.setMarshallable(identifiers, newValue), identifiers, Identifier.class);
}
// --------------------------------------------------------------------------------------
// End of identifier methods.
// --------------------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public boolean transitionTo(final State target) {
final Collection<Identifier> p = identifiers;
final boolean changed = super.transitionTo(target);
if (changed) {
/*
* The 'identifiers' collection will have been replaced by an unmodifiable collection if
* subclass has an "identifiers" property. If this is not the case, then the collection
* is unchanged (or null) so we have to make it unmodifiable here.
*/
if (p != null && p == identifiers) {
if (p instanceof Set<?>) {
identifiers = CollectionsExt.unmodifiableOrCopy((Set<Identifier>) p);
} else if (p instanceof List<?>) {
identifiers = CollectionsExt.unmodifiableOrCopy((List<Identifier>) p);
} else {
identifiers = Collections.unmodifiableCollection(p);
}
}
}
return changed;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// XML support with JAXB ////////
//////// ////////
//////// The following methods are invoked by JAXB using reflection (even if ////////
//////// they are private) or are helpers for other methods invoked by JAXB. ////////
//////// Those methods can be safely removed if Geographic Markup Language ////////
//////// (GML) support is not needed. ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns an identifier unique for the XML document, or {@code null} if none.
* This method is invoked automatically by JAXB and should never be invoked explicitly.
*/
@XmlID
@XmlAttribute // Defined in "gco" as unqualified attribute.
@XmlSchemaType(name = "ID")
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
private String getID() {
return isNullOrEmpty(identifiers) ? null : MetadataUtilities.getObjectID(this);
}
/**
* Sets an identifier unique for the XML document.
* This method is invoked automatically by JAXB and should never be invoked explicitly.
*/
private void setID(final String id) {
MetadataUtilities.setObjectID(this, id);
}
/**
* Returns an unique identifier, or {@code null} if none.
* This method is invoked automatically by JAXB and should never be invoked explicitly.
*/
@XmlAttribute // Defined in "gco" as unqualified attribute.
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
private String getUUID() {
/*
* IdentifierMapAdapter will take care of converting UUID to String,
* or to return a previously stored String if it was an unparsable UUID.
*/
return isNullOrEmpty(identifiers) ? null : getIdentifierMap().get(IdentifierSpace.UUID);
}
/**
* Sets an unique identifier.
* This method is invoked automatically by JAXB and should never be invoked explicitly.
*/
private void setUUID(final String id) {
/*
* IdentifierMapAdapter will take care of converting the String to UUID if possible, or
* will store the value as a plain String if it can not be converted. In the later case,
* a warning will be emitted (logged or processed by listeners).
*/
getIdentifierMap().put(IdentifierSpace.UUID, id);
}
}