blob: 6c6a37c7fdf3a8082d5ffd9ee7320576e732ff9b [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.test.integration;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.time.Instant;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.JAXBException;
import org.opengis.metadata.*;
import org.opengis.metadata.citation.*;
import org.opengis.metadata.constraint.*;
import org.opengis.metadata.identification.*;
import org.opengis.metadata.maintenance.*;
import org.opengis.metadata.spatial.GeometricObjectType;
import org.opengis.referencing.cs.AxisDirection;
import org.apache.sis.measure.Units;
import org.apache.sis.metadata.iso.*;
import org.apache.sis.metadata.iso.citation.*;
import org.apache.sis.metadata.iso.constraint.*;
import org.apache.sis.metadata.iso.content.*;
import org.apache.sis.metadata.iso.distribution.*;
import org.apache.sis.metadata.iso.extent.*;
import org.apache.sis.metadata.iso.identification.*;
import org.apache.sis.metadata.iso.spatial.*;
import org.apache.sis.referencing.NamedIdentifier;
import org.apache.sis.referencing.ImmutableIdentifier;
import org.apache.sis.referencing.datum.DefaultVerticalDatum;
import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
import org.apache.sis.referencing.cs.DefaultVerticalCS;
import org.apache.sis.referencing.crs.DefaultVerticalCRS;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.xml.MarshallerPool;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.xml.NilReason;
import org.apache.sis.xml.XML;
import org.apache.sis.xml.bind.metadata.replace.ReferenceSystemMetadata;
import org.apache.sis.xml.privy.LegacyNamespaces;
import org.apache.sis.xml.bind.gcx.Anchor;
import org.apache.sis.system.Loggers;
import org.apache.sis.util.SimpleInternationalString;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.privy.Constants;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.xml.test.DocumentComparator;
import org.apache.sis.xml.test.TestCase;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.referencing.ObjectDomain;
import org.opengis.referencing.datum.RealizationMethod;
/**
* Tests XML (un)marshalling of a metadata object containing various elements
* in addition to Coordinate Reference System (CRS) elements.
*
* @author Guilhem Legal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
* @see org.apache.sis.metadata.iso.DefaultMetadataTest
*/
public final class MetadataTest extends TestCase.WithLogs {
/**
* The resource file which contains an XML representation of a {@link Metadata} object.
*/
private static final String XML_FILE = "Metadata.xml";
/**
* Creates a new test case.
*/
public MetadataTest() {
super(Loggers.XML);
}
/**
* Programmatically creates the metadata to marshal, or to compare against the unmarshalled metadata.
*
* @return the hard-coded representation of {@value #XML_FILE} content.
*/
private DefaultMetadata createHardCoded() {
final var metadata = new DefaultMetadata();
metadata.setMetadataIdentifier(new DefaultIdentifier("test/Metadata.xml"));
metadata.setLocalesAndCharsets(Map.of(Locale.ENGLISH, StandardCharsets.UTF_8));
metadata.setMetadataScopes(Set.of(new DefaultMetadataScope(ScopeCode.DATASET, "Pseudo Common Data Index record")));
metadata.setDateInfo(Set.of(new DefaultCitationDate(TestUtilities.date("2009-01-01 04:00:00"), DateType.CREATION)));
/*
* Contact information for the author. The same party will be used for custodian and distributor,
* with only the role changed. Note that we need to create an instance of the deprecated class,
* because this is what will be unmarshalled from the XML document.
*/
@SuppressWarnings("deprecation")
final var author = new DefaultResponsibleParty(Role.AUTHOR);
final var country = new Anchor(URI.create("SDN:C320:2:FR"), "France"); // Non-public SIS class.
{
final var online = new DefaultOnlineResource(URI.create("http://www.ifremer.fr/sismer/"));
online.setProtocol(Constants.HTTP);
final var contact = new DefaultContact(online);
contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "IFREMER");
contact.setPhones(List.of(
new DefaultTelephone("+33 (0)2 xx.xx.xx.x6", TelephoneType.VOICE),
new DefaultTelephone("+33 (0)2 xx.xx.xx.x4", TelephoneType.FACSIMILE)
));
final var address = new DefaultAddress();
address.setDeliveryPoints(Set.of(new SimpleInternationalString("Brest institute")));
address.setCity(new SimpleInternationalString("Plouzane"));
address.setPostalCode("29280");
address.setCountry(country);
address.setElectronicMailAddresses(Set.of("xx@xx.fr"));
contact.setAddresses(Set.of(address));
author.setParties(Set.of(new DefaultOrganisation("Some marine institute", null, null, contact)));
metadata.setContacts(Set.of(author));
}
/*
* Data indentification.
*/
{
final var citation = new DefaultCitation("Some set of points");
citation.setAlternateTitles(Set.of(new SimpleInternationalString("Code XYZ")));
citation.setDates(List.of(
new DefaultCitationDate(TestUtilities.date("1990-06-04 22:00:00"), DateType.REVISION),
new DefaultCitationDate(TestUtilities.date("1979-08-02 22:00:00"), DateType.CREATION)));
{
@SuppressWarnings("deprecation")
final var originator = new DefaultResponsibleParty(Role.ORIGINATOR);
final var online = new DefaultOnlineResource(URI.create("http://www.com.univ-mrs.fr/LOB/"));
online.setProtocol(Constants.HTTP);
final var contact = new DefaultContact(online);
contact.setPhones(List.of(
new DefaultTelephone("+33 (0)4 xx.xx.xx.x5", TelephoneType.VOICE),
new DefaultTelephone("+33 (0)4 xx.xx.xx.x8", TelephoneType.FACSIMILE)
));
final var address = new DefaultAddress();
address.setDeliveryPoints(Set.of(new SimpleInternationalString("Oceanology institute")));
address.setCity(new SimpleInternationalString("Marseille"));
address.setPostalCode("13288");
address.setCountry(country);
contact.setAddresses(Set.of(address));
originator.setParties(Set.of(new DefaultOrganisation("Oceanology laboratory", null, null, contact)));
citation.setCitedResponsibleParties(Set.of(originator));
}
final var identification = new DefaultDataIdentification(
citation, // Citation
"Description of pseudo data for testing purpose only.", // Abstract
Locale.ENGLISH, // Language,
TopicCategory.OCEANS); // Topic category
{
@SuppressWarnings("deprecation")
final var custodian = new DefaultResponsibleParty(author);
custodian.setRole(Role.CUSTODIAN);
identification.setPointOfContacts(Set.of(custodian));
}
/*
* Data indentification / Keywords.
*/
{
final var keyword = new DefaultKeywords(
new Anchor(URI.create("SDN:P021:35:ATTN"), "Transmittance and attenuance of the water column"));
keyword.setType(KeywordType.THEME);
final var thesaurus = new DefaultCitation("BODC Parameter Discovery Vocabulary");
thesaurus.setAlternateTitles(Set.of(new SimpleInternationalString("P021")));
thesaurus.setDates(Set.of(new DefaultCitationDate(TestUtilities.date("2008-11-25 23:00:00"), DateType.REVISION)));
thesaurus.setEdition(new Anchor(URI.create("SDN:C371:1:35"), "35"));
thesaurus.setIdentifiers(Set.of(new ImmutableIdentifier(null, null, "http://www.seadatanet.org/urnurl/")));
keyword.setThesaurusName(thesaurus);
identification.setDescriptiveKeywords(Set.of(keyword));
}
/*
* Data indentification / Browse graphic.
*/
{
final var g = new DefaultBrowseGraphic(URI.create("file:///thumbnail.png"));
g.setFileDescription(new SimpleInternationalString("Arbitrary thumbnail for this test only."));
identification.setGraphicOverviews(Set.of(g));
}
/*
* Data indentification / Resource constraint.
*/
{
final var constraint = new DefaultLegalConstraints();
constraint.setAccessConstraints(Set.of(Restriction.LICENCE));
identification.setResourceConstraints(Set.of(constraint));
}
/*
* Data indentification / Aggregate information.
*/
{
@SuppressWarnings("deprecation")
final var aggregateInfo = new DefaultAggregateInformation();
final var name = new DefaultCitation("Some oceanographic campaign");
name.setAlternateTitles(Set.of(new SimpleInternationalString("Pseudo group of data")));
name.setDates(Set.of(new DefaultCitationDate(TestUtilities.date("1990-06-04 22:00:00"), DateType.REVISION)));
aggregateInfo.setName(name);
aggregateInfo.setInitiativeType(InitiativeType.CAMPAIGN);
aggregateInfo.setAssociationType(AssociationType.LARGER_WORK_CITATION);
identification.setAssociatedResources(Set.of(aggregateInfo));
}
/*
* Data indentification / Extent.
*/
{
final var axis = new DefaultCoordinateSystemAxis(
nameAndIdentifier("depth", "Depth", null), "D", AxisDirection.DOWN, Units.METRE);
final var cs = new DefaultVerticalCS(
nameAndIdentifier("depth", "Depth", null), axis);
final var datum = new DefaultVerticalDatum(
nameAndIdentifier("D28", "Depth below D28", "For testing purpose"), (RealizationMethod) null);
final var vcrs = new DefaultVerticalCRS(
nameAndIdentifier("D28", "Depth below D28", "CRS for testing purpose"), datum, cs);
final var temporal = new DefaultTemporalExtent();
temporal.setBounds(Instant.parse("1990-06-05T00:00:00Z"),
Instant.parse("1990-07-02T00:00:00Z"));
identification.setExtents(Set.of(new DefaultExtent(
null,
new DefaultGeographicBoundingBox(1.1666, 1.1667, 36.4, 36.6),
new DefaultVerticalExtent(10, 25, vcrs),
temporal)));
}
/*
* Data identification / Environmental description and Supplemental information.
*/
{
identification.setEnvironmentDescription (new SimpleInternationalString("Possibly cloudy."));
identification.setSupplementalInformation(new SimpleInternationalString("This metadata has been modified with dummy values."));
}
metadata.setIdentificationInfo(Set.of(identification));
}
/*
* Information about spatial representation.
*/
{
final var rep = new DefaultVectorSpatialRepresentation();
final var geoObj = new DefaultGeometricObjects(GeometricObjectType.POINT);
rep.setGeometricObjects(Set.of(geoObj));
metadata.setSpatialRepresentationInfo(Set.of(rep));
}
/*
* Information about Coordinate Reference System.
*/
{
final var citation = new DefaultCitation("World Geodetic System 84");
citation.setAlternateTitles(Set.of(new SimpleInternationalString("L101")));
citation.setIdentifiers(Set.of(new ImmutableIdentifier(null, null, "SDN:L101:2:4326")));
citation.setEdition(new Anchor(URI.create("SDN:C371:1:2"), "2"));
metadata.setReferenceSystemInfo(Set.of(
new ReferenceSystemMetadata(new ImmutableIdentifier(citation, "L101", "4326"))));
}
/*
* Information about content.
*/
{
final var contentInfo = new DefaultImageDescription();
contentInfo.setCloudCoverPercentage(50.0);
metadata.setContentInfo(Set.of(contentInfo));
}
/*
* Extension to metadata.
*/
{
final var extensionInfo = new DefaultMetadataExtensionInformation();
extensionInfo.setExtendedElementInformation(Set.of(new DefaultExtendedElementInformation(
"SDN:EDMO", // Name
"European Directory of Marine Organisations", // Definition
null, // Condition
Datatype.CODE_LIST, // Data type
"SeaDataNet", // Parent entity
"For testing only", // Rule
NilReason.MISSING.createNilObject(Responsibility.class)))); // Source
metadata.setMetadataExtensionInfo(Set.of(extensionInfo));
}
/*
* Distribution information.
*/
{
@SuppressWarnings("deprecation")
final var distributor = new DefaultResponsibleParty(author);
final var distributionInfo = new DefaultDistribution();
distributor.setRole(Role.DISTRIBUTOR);
distributionInfo.setDistributors(Set.of(new DefaultDistributor(distributor)));
final var format = new DefaultFormat();
final var specification = new DefaultCitation();
specification.setAlternateTitles(Set.of(new Anchor(URI.create("SDN:L241:1:MEDATLAS"), "MEDATLAS ASCII")));
specification.setEdition(new SimpleInternationalString("1.0"));
format.setFormatSpecificationCitation(specification);
distributionInfo.setDistributionFormats(Set.of(format));
final var transfer = new DefaultDigitalTransferOptions();
transfer.setTransferSize(2.431640625);
final var onlines = new DefaultOnlineResource(URI.create("ftp://www.ifremer.fr/data/something"));
onlines.setDescription(new SimpleInternationalString("Dummy download link"));
onlines.setFunction(OnLineFunction.DOWNLOAD);
onlines.setProtocol("ftp");
transfer.setOnLines(Set.of(onlines));
distributionInfo.setTransferOptions(Set.of(transfer));
metadata.setDistributionInfo(Set.of(distributionInfo));
}
return metadata;
}
/**
* Returns a property map with a name and identifier. This is used for creating CRS components.
*/
private static Map<String,?> nameAndIdentifier(final String identifier, final String name, final String scope) {
final var properties = new HashMap<String,Object>(4);
properties.put(DefaultVerticalDatum.NAME_KEY, new NamedIdentifier(null, name));
properties.put(DefaultVerticalDatum.IDENTIFIERS_KEY, new NamedIdentifier(null, "test", identifier, null, null));
if (scope != null) {
properties.put(ObjectDomain.SCOPE_KEY, scope);
}
return properties;
}
/**
* Returns the URL to the {@value #XML_FILE} file to use for this test.
*
* @return the URL to {@value #XML_FILE} test file.
*/
private URL getResource() {
return MetadataTest.class.getResource(XML_FILE);
}
/**
* Tests marshalling of a XML document.
*
* @throws Exception if an error occurred during marshalling.
*/
@Test
public void testMarshalling() throws Exception {
final MarshallerPool pool = getMarshallerPool();
final Marshaller ms = pool.acquireMarshaller();
final StringWriter writer = new StringWriter(25000);
ms.setProperty(XML.METADATA_VERSION, VERSION_2007);
ms.marshal(createHardCoded(), writer);
pool.recycle(ms);
/*
* Apache SIS can marshal CharSequence as Anchor only if the property type is InternationalString.
* But the 'Metadata.hierarchyLevelName' and 'Identifier.code' properties are String, which we can
* not subclass. Consequently, SIS currently marshals them as plain string. Replace those strings
* by the anchor version so we can compare the XML with the "Metadata.xml" file content.
*/
final StringBuffer xml = writer.getBuffer();
replace(xml, "<gcol:CharacterString>Pseudo Common Data Index record</gcol:CharacterString>",
"<gmx:Anchor xlink:href=\"SDN:L231:3:CDI\">Pseudo Common Data Index record</gmx:Anchor>");
replace(xml, "<gcol:CharacterString>4326</gcol:CharacterString>",
"<gmx:Anchor xlink:href=\"SDN:L101:2:4326\">4326</gmx:Anchor>");
/*
* The <gmd:EX_TemporalExtent> block cannot be marshalled es expected yet (need a "sis-temporal" module).
* We need to instruct the XML comparator to ignore this block during the comparison. We also ignore for
* now the "gml:id" attribute since SIS generates different values than the ones in our test XML file,
* and those values may change in future SIS version.
*/
final var comparator = new DocumentComparator(getResource(), xml.toString());
comparator.ignoredNodes.add(LegacyNamespaces.GMD + ":temporalElement");
comparator.ignoredAttributes.add("http://www.w3.org/2000/xmlns:*");
comparator.ignoredAttributes.add(Namespaces.XSI + ":schemaLocation");
comparator.ignoredAttributes.add(Namespaces.GML + ":id");
comparator.ignoreComments = true;
comparator.compare();
loggings.assertNoUnexpectedLog();
}
/**
* Replaces the first occurrence of the given string by another one.
*
* @param buffer the buffer in which to perform the replacement.
* @param toSearch the string to search.
* @param replaceBy the value to use as a replacement.
*/
private static void replace(final StringBuffer buffer, final String toSearch, final String replaceBy) {
final int i = buffer.indexOf(toSearch);
assertTrue(i >= 0, "String to replace not found.");
buffer.replace(i, i + toSearch.length(), replaceBy);
}
/**
* Tests unmarshalling of a XML document.
*
* @throws JAXBException if an error occurred during unmarshalling.
*/
@Test
public void testUnmarshalling() throws JAXBException {
/*
* Note: if this MetadataTest class is made final, then all following lines
* until pool.recycle(…) can be replaced by a call to unmarshallFile(XML_FILE).
*/
final MarshallerPool pool = getMarshallerPool();
final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
final DefaultMetadata metadata = (DefaultMetadata) unmarshaller.unmarshal(getResource());
pool.recycle(unmarshaller);
final DefaultMetadata expected = createHardCoded();
assertTrue(metadata.equals(expected, ComparisonMode.DEBUG));
loggings.assertNoUnexpectedLog();
}
}