blob: 6d9d652e73573e7b2eaee0dd65fec77446ad537d [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.internal.jaxb.metadata.replace;
import java.util.Set;
import java.util.Objects;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.measure.Unit;
import org.opengis.util.TypeName;
import org.opengis.util.MemberName;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.referencing.ReferenceIdentifier;
import org.apache.sis.internal.simple.SimpleIdentifiedObject;
import org.apache.sis.internal.jaxb.FilterByVersion;
import org.apache.sis.internal.xml.LegacyNamespaces;
import org.apache.sis.internal.jaxb.gco.GO_GenericName;
import org.apache.sis.internal.metadata.ReferencingServices;
import org.apache.sis.internal.metadata.NameToIdentifier;
import org.apache.sis.util.iso.DefaultMemberName;
import org.apache.sis.util.iso.Names;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.resources.Errors;
import static org.apache.sis.internal.util.CollectionsExt.nonNull;
/**
* Parameter information conform to the ISO 19115:2014 specification.
* GeoAPI tries to provides a single API for the parameter classes defined in various specifications
* (ISO 19111, ISO 19115, Web Processing Service). But we still need separated representations at XML
* (un)marshalling time. This class is for the ISO 19115:2014 case.
*
* <p>Note that this implementation is simple and serves no other purpose than being a container for XML
* parsing and formatting. For real parameter framework, consider using {@link org.apache.sis.parameter}
* package instead.</p>
*
* <h2>Note about raw-type usage</h2>
* We use raw type (i.e. we implement {@code ParameterDescriptor} instead of {@code ParameterDescriptor<T>})
* because there is no way we can know {@code <T>} for sure at unmarshalling time. This is not a recommended
* practice, so <strong>this class shall not be in public API</strong>. However it should be okay to create
* {@code ServiceMetadata} instances in Apache SIS internal code if all methods creating such instances declare
* {@code ParameterDescriptor<?>} as their return type.
*
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.5
* @module
*/
@SuppressWarnings("rawtypes") // For the omission of <T> in ParameterDescriptor<T> - see javadoc.
@XmlType(name = "SV_Parameter_Type", namespace = Namespaces.SRV, propOrder = {
"memberName", // The ISO 19115-3:2016 way to marshal name.
"legacyName", // Legacy ISO 19139:2007 way to marshal name.
"description",
"optionality",
"optionalityLabel", // Legacy ISO 19139:2007 way to marshal optionality.
"repeatability",
"valueType"
})
@XmlRootElement(name = "SV_Parameter", namespace = Namespaces.SRV)
public final class ServiceParameter extends SimpleIdentifiedObject implements ParameterDescriptor {
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = -5335736212313243889L;
/**
* The name, as used by the service for this parameter. Note that in ISO 19115-3:2016, this element is
* inside a {@code <gco:MemberName>} element (i.e. ISO inserts the same kind of {@code Property_Type}
* element as it does for all other attributes) while in ISO 19139:2007 it was not (i.e. name attributes
* like {@code <gco:aName>} were marshalled directly, without wrapper). Example:
*
* {@preformat xml
* <srv:name>
* <gco:MemberName>
* <gco:aName>
* <gco:CharacterString>A parameter name</gco:CharacterString>
* </gco:aName>
* </gco:MemberName>
* </srv:name>
* }
*
* @see #getLegacyName()
* @see #getValueType()
*/
@XmlElement(required=true, name="name")
@XmlJavaTypeAdapter(GO_GenericName.Since2014.class)
MemberName memberName;
/**
* A narrative explanation of the role of the parameter.
*/
@XmlElement
InternationalString description;
/**
* Indication if the parameter is required.
*
* <ul>
* <li>In ISO 19115-3:2016, this is represented by "{@code true}" or "{@code false}".</li>
* <li>In ISO 19139:2007, this was marshalled as "{@code Optional}" or "{@code Mandatory}".</li>
* </ul>
*
* @see #getOptionality()
* @see #setOptionality(Boolean)
*/
public boolean optionality;
/**
* Indication if more than one value of the parameter may be provided.
*/
@XmlElement(required = true)
public boolean repeatability;
/**
* A copy of {@code this} as a fully-implemented parameter descriptor.
* This is created when first needed for implementation of {@link #createValue()}.
*/
private transient ParameterDescriptor descriptor;
/**
* Creates an initially empty parameter.
* This constructor is needed by JAXB.
*
* <p><strong>Consider this constructor as private</strong> except for testing purpose.
* See <cite>Note about raw-type usage</cite> in class javadoc.</p>
*/
ServiceParameter() {
}
/**
* Creates a parameter initialized to the values of the given one.
*/
private ServiceParameter(final ParameterDescriptor<?> parameter) {
super(parameter);
memberName = getMemberName(parameter);
optionality = parameter.getMinimumOccurs() > 0;
repeatability = parameter.getMaximumOccurs() > 1;
}
/**
* Returns the given parameter as an instance of {@code ServiceParameter}.
*
* @param parameter the parameter (may be {@code null}).
* @return the service parameter, or {@code null} if the given argument was null.
*/
public static ServiceParameter castOrCopy(final ParameterDescriptor<?> parameter) {
if (parameter == null || parameter instanceof ServiceParameter) {
return (ServiceParameter) parameter;
}
return new ServiceParameter(parameter);
}
/**
* Gets the parameter name as a {@code MemberName}. This method first checks if the primary name is an instance of
* {@code MemberName}. If not, this method searches for the first alias which is an instance of {@code MemberName}.
* If none is found, then this method tries to build a member name from the primary name and value class.
*
* @param parameter the parameter from which to get the name (may be {@code null}).
* @return the member name, or {@code null} if none.
*/
public static MemberName getMemberName(final ParameterDescriptor<?> parameter) {
if (parameter != null) {
final ReferenceIdentifier id = parameter.getName();
if (id instanceof MemberName) {
return (MemberName) id;
}
for (final GenericName alias : nonNull(parameter.getAlias())) {
if (alias instanceof MemberName) {
return (MemberName) alias;
}
}
if (id != null) {
final Class<?> valueClass = parameter.getValueClass();
if (valueClass != null) {
final String code = id.getCode();
if (code != null) {
return Names.createMemberName(id.getCodeSpace(), null, code, valueClass);
}
}
}
}
return null;
}
/**
* Returns the name to be marshalled in the ISO 19139:2007 way. Example:
*
* {@preformat xml
* <srv:name>
* <gco:aName>
* <gco:CharacterString>A parameter name</gco:CharacterString>
* </gco:aName>
* </srv:name>
* }
*
* @return the name if marshalling legacy ISO 19139:2007 format, or {@code null} otherwise.
*/
@XmlElement(name = "name", namespace = LegacyNamespaces.SRV)
private DefaultMemberName getLegacyName() {
return FilterByVersion.LEGACY_METADATA.accept() ? DefaultMemberName.castOrCopy(memberName) : null;
}
/**
* Sets the value from the {@code <gco:aName>} (legacy ISO 19139:2007 format).
* This method is called at unmarshalling-time by JAXB.
*
* @param value the new name.
* @throws IllegalStateException if a name is already defined.
*/
@SuppressWarnings("unused")
private void setLegacyName(final DefaultMemberName value) {
ensureUndefined();
memberName = value;
}
/**
* Ensures that the {@linkplain #memberName} is not already defined.
*
* @throws IllegalStateException if a name is already defined.
*/
private void ensureUndefined() throws IllegalStateException {
if (memberName != null) {
throw new IllegalStateException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, "name"));
}
}
/**
* Returns the name as an {@code Identifier}, which is the type requested by ISO 19111.
* Note that this is different than the type requested by ISO 19115, which is {@link MemberName}.
*
* This method is the converse of {@link #getMemberName(ParameterDescriptor)}.
*
* @return the parameter name as an identifier (the type specified by ISO 19111).
*/
@Override
public synchronized ReferenceIdentifier getName() {
if (name == null && memberName != null) {
if (memberName instanceof ReferenceIdentifier) {
name = (ReferenceIdentifier) memberName;
} else {
name = new NameToIdentifier(memberName);
}
}
return name;
}
/**
* Infers the value class from the attribute type.
* This method is the reason why we can not parameterize this {@code ServiceParameter} class
* (see <cite>Note about raw-type usage</cite> in class javadoc), since there is no way we
* can ensure that the returned class is really for type {@code <T>}.
*
* @return the value class inferred from the attribute type, or {@code null} if unknown.
*/
@Override
public Class<?> getValueClass() {
return (memberName != null) ? Names.toClass(memberName.getAttributeType()) : null;
}
/**
* For JAXB marshalling of ISO 19139:2007 document only.
* Note that there is not setter method, since we expect the same information
* to be provided in the {@link #name} attribute type.
*/
@XmlElement(name = "valueType", namespace = LegacyNamespaces.SRV)
@XmlJavaTypeAdapter(GO_GenericName.class) // Not in package-info because shall not be applied to getLegacyName().
final TypeName getValueType() {
if (memberName != null && FilterByVersion.LEGACY_METADATA.accept()) {
return memberName.getAttributeType();
}
return null;
}
/**
* Returns a narrative explanation of the role of the parameter.
*
* @return a narrative explanation of the role of the parameter, or {@code null} if none.
*/
public InternationalString getDescription() {
return description;
}
/**
* Returns the optionality as a boolean (ISO 19115-3:2016 way).
*/
@XmlElement(name = "optionality", required = true)
final Boolean getOptionality() {
return FilterByVersion.CURRENT_METADATA.accept() ? optionality : null;
}
/**
* Sets whether this parameter is optional.
*/
final void setOptionality(final Boolean optional) {
if (optional != null) optionality = optional;
}
/**
* Returns {@code "Optional"} if {@link #optionality} is {@code true} or {@code "Mandatory"} otherwise.
* This is the legacy ISO 19139:2007 way to marshal optionality.
*/
@XmlElement(name = "optionality", namespace = LegacyNamespaces.SRV)
final String getOptionalityLabel() {
return FilterByVersion.LEGACY_METADATA.accept() ? (optionality ? "Optional" : "Mandatory") : null;
}
/**
* Sets whether this parameter is optional.
*/
final void setOptionalityLabel(final String optional) {
if (optional != null) {
optionality = Boolean.parseBoolean(optional) || optional.equalsIgnoreCase("Optional");
}
}
/**
* The minimum number of times that values for this parameter group or parameter are required.
*
* @return the minimum occurrence.
*/
@Override
public int getMinimumOccurs() {
return optionality ? 0 : 1;
}
/**
* The maximum number of times that values for this parameter group or parameter can be included.
*
* @return the maximum occurrence.
*/
@Override
public int getMaximumOccurs() {
return repeatability ? Integer.MAX_VALUE : 1;
}
/**
* Optional properties.
* @return {@code null}.
*/
@Override public Set<?> getValidValues() {return null;} // Really null, not an empty set. See method contract.
@Override public Comparable<?> getMinimumValue() {return null;}
@Override public Comparable<?> getMaximumValue() {return null;}
@Override public Object getDefaultValue() {return null;}
@Override public Unit<?> getUnit() {return null;}
/**
* Creates a new instance of {@code ParameterValue}.
* This method delegates the work to {@link org.apache.sis.parameter.DefaultParameterDescriptor}
* since this {@code ServiceParameter} class is not a full-featured parameter descriptor implementation.
*
* @return a new instance of {@code ParameterValue}.
*/
@Override
public ParameterValue<?> createValue() {
ParameterDescriptor<?> desc;
synchronized (this) {
desc = descriptor;
if (desc == null) {
descriptor = desc = ReferencingServices.getInstance().toImplementation(this);
}
}
return desc.createValue();
}
/**
* Compares this object with the given one for equality.
*
* @param object the object to compare with this reference system.
* @param mode the strictness level of the comparison.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true;
}
if (super.equals(object, mode) && object instanceof ParameterDescriptor<?>) {
final ParameterDescriptor<?> that = (ParameterDescriptor<?>) object;
if (that.getUnit() == null &&
that.getDefaultValue() == null &&
that.getValueClass() == getValueClass())
{
if (mode.isIgnoringMetadata()) {
return Objects.equals(toString(getName()), toString(that.getName()));
// super.equals(…) already compared 'getName()' in others mode.
}
return that.getMinimumOccurs() == getMinimumOccurs() &&
that.getMaximumOccurs() == getMaximumOccurs() &&
that.getValidValues() == null &&
that.getMinimumValue() == null &&
that.getMaximumValue() == null;
}
}
return false;
}
/**
* Null-safe string representation of the given identifier, for comparison purpose.
* We ignore codespace because they can not be represented in ISO 19139 XML documents.
*/
private static String toString(final Identifier identifier) {
return (identifier != null) ? identifier.toString() : null;
}
}