blob: 667c0ded61b7a20968f7423426fe3e4e5cb3cdb0 [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.citation;
import java.net.URI;
import java.util.Set;
import java.util.List;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.io.InputStream;
import jakarta.xml.bind.JAXBException;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.citation.CitationDate;
import org.opengis.metadata.citation.Contact;
import org.opengis.metadata.citation.DateType;
import org.opengis.metadata.citation.Role;
import org.opengis.metadata.citation.OnLineFunction;
import org.opengis.metadata.citation.OnlineResource;
import org.opengis.metadata.citation.PresentationForm;
import org.apache.sis.util.SimpleInternationalString;
import org.apache.sis.util.DefaultInternationalString;
import org.apache.sis.util.privy.CollectionsExt;
import org.apache.sis.xml.IdentifierMap;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.metadata.MetadataCopier;
import org.apache.sis.metadata.MetadataStandard;
import org.apache.sis.metadata.UnmodifiableMetadataException;
import org.apache.sis.metadata.iso.DefaultIdentifier;
import org.apache.sis.metadata.iso.extent.Extents;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.metadata.xml.TestUsingFile;
import org.apache.sis.test.TestUtilities;
import static org.apache.sis.test.TestUtilities.getSingleton;
import static org.apache.sis.metadata.Assertions.assertTitleEquals;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.metadata.citation.Party;
import org.opengis.metadata.citation.Responsibility;
/**
* Tests {@link DefaultCitation}.
*
* @author Martin Desruisseaux (Geomatys)
* @author Cullen Rombach (Image Matters)
*/
public final class DefaultCitationTest extends TestUsingFile {
/**
* Creates a new test case.
*/
public DefaultCitationTest() {
}
/**
* Opens the stream to the XML file containing a citation.
*
* @param format whether to use the 2007 or 2016 version of ISO 19115.
* @return stream opened on the XML document to use for testing purpose.
*/
private static InputStream openTestFile(final Format format) {
return format.openTestFile("Citation.xml");
}
/**
* Creates a citation with an arbitrary title, presentation form and other properties.
*
* @return an arbitrary citation.
*/
public static DefaultCitation create() {
final var citation = new DefaultCitation();
final var title = new DefaultInternationalString();
title.add(Locale.JAPANESE, "アンダーカレント");
title.add(Locale.ENGLISH, "Undercurrent");
citation.setTitle(title);
citation.setISBN("9782505004509");
citation.setPresentationForms(List.of(
PresentationForm.DOCUMENT_HARDCOPY,
PresentationForm.DOCUMENT_DIGITAL));
citation.setAlternateTitles(Set.of(
new SimpleInternationalString("Andākarento"))); // Actually a different script of the Japanese title.
citation.setCitedResponsibleParties(List.of(
new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)),
new DefaultResponsibility(Role.EDITOR, Extents.WORLD, new DefaultOrganisation("Kōdansha", null, null, null))));
return citation;
}
/**
* Tests the identifier map, which handles ISBN and ISSN codes in a special way.
*/
@Test
public void testIdentifierMap() {
final var citation = new DefaultCitation();
final Collection<Identifier> identifiers = citation.getIdentifiers();
final IdentifierMap identifierMap = citation.getIdentifierMap();
assertTrue(identifiers.isEmpty(), "Expected an initially empty set of identifiers.");
/*
* Set the ISBN code, and ensure that the ISBN is reflected in the identifier map.
*/
citation.setISBN("MyISBN");
assertEquals("MyISBN", citation.getISBN());
assertEquals(1, identifiers.size(), "ISBN code shall be included in the set of identifiers.");
assertEquals("{ISBN=“MyISBN”}", identifierMap.toString());
/*
* Set the identifiers with a list containing ISBN and ISSN codes.
* The ISBN code shall be ignored because and ISBN property was already set.
* The ISSN code shall be retained because it is a new code.
*/
assertNull(citation.getISSN(), "ISSN shall be initially null.");
citation.setIdentifiers(List.of(
new DefaultIdentifier(Citations.NETCDF, "MyNetCDF"),
new DefaultIdentifier(Citations.EPSG, "MyEPSG"),
new DefaultIdentifier(Citations.ISBN, "NewISBN"),
new DefaultIdentifier(Citations.ISSN, "MyISSN")));
assertEquals("NewISBN", citation.getISBN(), "The ISBN value shall have been overwritten.");
assertEquals("MyISSN", citation.getISSN(), "The ISSN value shall have been added, because new.");
assertEquals("{NetCDF=“MyNetCDF”, EPSG=“MyEPSG”, ISBN=“NewISBN”, ISSN=“MyISSN”}", identifierMap.toString());
}
/**
* Tests {@link DefaultCitation#transitionTo(DefaultCitation.State)} to the final state.
*/
@Test
public void testTransitionToFinal() {
final DefaultCitation original = create();
final DefaultCitation clone = create();
clone.transitionTo(DefaultCitation.State.FINAL);
assertEquals(DefaultCitation.State.EDITABLE, original.state());
assertEquals(DefaultCitation.State.FINAL, clone.state());
assertEquals(original, clone);
SimpleInternationalString title = new SimpleInternationalString("Undercurrent");
original.setTitle(title);
var e = assertThrows(UnmodifiableMetadataException.class, () -> clone.setTitle(title),
"Frozen metadata shall not be modifiable.");
assertNotNull(e);
}
/**
* Tests {@link MetadataCopier} on a citation.
*/
public void testCopy() {
final DefaultCitation original = create();
final var clone = assertInstanceOf(DefaultCitation.class,
new MetadataCopier(MetadataStandard.ISO_19115).copy(original));
assertCopy(original, clone);
}
/**
* Verifies that {@code clone} is a copy of {@code original}, sharing same instance of values when possible.
*/
private static void assertCopy(final DefaultCitation original, final DefaultCitation clone) {
assertNotSame(original, clone);
assertSame(original.getISBN(), clone.getISBN());
assertSame(original.getTitle(), clone.getTitle());
assertSame(getSingleton(original.getAlternateTitles()),
getSingleton(clone.getAlternateTitles()));
assertCopy(original.getIdentifiers(), clone.getIdentifiers());
assertCopy(original.getCitedResponsibleParties(), clone.getCitedResponsibleParties());
assertCopy(original.getPresentationForms(), clone.getPresentationForms());
/*
* Verify the unique identifier, which is the ISBN code. ISBN and ISSN codes are handled
* in a special way by DefaultCitation (they are instances of SpecializedIdentifier), but
* the should nevertheless be cloned.
*/
final Identifier ide = getSingleton(original.getIdentifiers());
final Identifier ida = getSingleton( clone.getIdentifiers());
assertNotSame(ide, ida);
assertSame(ide.getCode(), ida.getCode());
assertSame(ide.getAuthority(), ida.getAuthority());
/*
* Verify the author metadata.
*/
final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties());
final Responsibility ra = CollectionsExt.first(clone .getCitedResponsibleParties());
assertNotSame(re, ra);
assertSame(re.getRole(), ra.getRole());
assertSame(getSingleton(re.getParties()).getName(),
getSingleton(ra.getParties()).getName());
}
/**
* Verifies that {@code actual} is an unmodifiable copy of {@code expected}.
*/
private static <T> void assertCopy(final Collection<T> expected, final Collection<T> actual) {
assertNotSame(expected, actual, "ModifiableMetadata.transitionTo(FINAL) shall have copied the collection.");
assertEquals(expected, actual, "The copied collection shall have the same content as the original.");
var e = assertThrows(UnsupportedOperationException.class, () -> actual.add(null),
"The copied collection shall be unmodifiable.");
assertNotNull(e);
}
/**
* Tests XML marshalling using the format derived form ISO 19115:2014 model.
* This method also tests usage of {@code gml:id} and {@code xlink:href}.
*
* @throws JAXBException if an error occurred during marshalling.
*/
@Test
public void testMarshalling() throws JAXBException {
testMarshalling(Format.XML2016);
}
/**
* Tests XML marshalling using the format derived form ISO 19115:2003 model.
* This method also tests usage of {@code gml:id} and {@code xlink:href}.
*
* @throws JAXBException if an error occurred during marshalling.
*/
@Test
public void testMarshallingLegacy() throws JAXBException {
testMarshalling(Format.XML2007);
}
/**
* Tests XML marshalling for the given metadata version.
*
* @param format whether to use the 2007 or 2016 version of ISO 19115.
*/
private void testMarshalling(final Format format) throws JAXBException {
final var rs = new DefaultOnlineResource(URI.create("https://tools.ietf.org/html/rfc1149"));
rs.setName(new SimpleInternationalString("IP over Avian Carriers"));
rs.setDescription(new SimpleInternationalString("High delay, low throughput, and low altitude service."));
rs.setFunction(OnLineFunction.OFFLINE_ACCESS);
final var contact = new DefaultContact(rs);
contact.setContactInstructions(new SimpleInternationalString("Send carrier pigeon."));
contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "ip-protocol");
final DefaultCitation c = new DefaultCitation("Fight against poverty");
c.setCitedResponsibleParties(List.of(
new DefaultResponsibility(Role.ORIGINATOR, null, new DefaultIndividual("Maid Marian", null, contact)),
new DefaultResponsibility(Role.FUNDER, null, new DefaultIndividual("Robin Hood", null, contact))
));
c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.ADOPTED));
c.getPresentationForms().add(PresentationForm.PHYSICAL_OBJECT);
/*
* Check that XML file built by the marshaller is the same as the example file.
*/
assertMarshalEqualsFile(openTestFile(format), c, format.schemaVersion, "xmlns:*", "xsi:schemaLocation");
}
/**
* Tests XML unmarshalling using the format derived form ISO 19115:2014 model.
* This method also tests usage of {@code gml:id} and {@code xlink:href}.
*
* @throws JAXBException if an error occurred during unmarshalling.
*/
@Test
public void testUnmarshalling() throws JAXBException {
testUnmarshalling(Format.XML2016);
}
/**
* Tests XML unmarshalling using the format derived form ISO 19115:2003 model.
* This method also tests usage of {@code gml:id} and {@code xlink:href}.
*
* @throws JAXBException if an error occurred during unmarshalling.
*/
@Test
public void testUnmarshallingLegacy() throws JAXBException {
testUnmarshalling(Format.XML2007);
}
/**
* Tests XML unmarshalling for a metadata version.
* The version is not specified since it should be detected automatically.
*
* @param format whether to use the 2007 or 2016 version of ISO 19115.
*/
private void testUnmarshalling(final Format format) throws JAXBException {
verifyUnmarshalledCitation(unmarshalFile(DefaultCitation.class, openTestFile(format)));
}
/**
* Verifies the citation unmarshalled from the XML file.
*
* @param c the citation.
*/
public static void verifyUnmarshalledCitation(final Citation c) {
assertTitleEquals("Fight against poverty", c, "citation");
final CitationDate date = getSingleton(c.getDates());
assertEquals(date.getDate(), TestUtilities.date("2015-10-17 00:00:00"));
assertEquals(DateType.ADOPTED, date.getDateType());
assertEquals(PresentationForm.PHYSICAL_OBJECT, getSingleton(c.getPresentationForms()));
final Iterator<? extends Responsibility> it = c.getCitedResponsibleParties().iterator();
final Contact contact = assertResponsibilityEquals(Role.ORIGINATOR, "Maid Marian", it.next());
assertEquals("Send carrier pigeon.", String.valueOf(contact.getContactInstructions()));
final OnlineResource resource = TestUtilities.getSingleton(contact.getOnlineResources());
assertEquals("IP over Avian Carriers", String.valueOf(resource.getName()));
assertEquals("High delay, low throughput, and low altitude service.", String.valueOf(resource.getDescription()));
assertEquals("https://tools.ietf.org/html/rfc1149", String.valueOf(resource.getLinkage()));
assertEquals(OnLineFunction.OFFLINE_ACCESS, resource.getFunction());
// Thanks to xlink:href, the Contact shall be the same instance as above.
assertSame(contact, assertResponsibilityEquals(Role.FUNDER, "Robin Hood", it.next()));
assertFalse(it.hasNext());
}
/**
* Asserts that the given responsibility has the expected properties, then returns its contact info.
*/
private static Contact assertResponsibilityEquals(final Role role, final String name, final Responsibility actual) {
assertEquals(role, actual.getRole());
final Party p = getSingleton(actual.getParties());
assertEquals(name, String.valueOf(p.getName()));
return getSingleton(p.getContactInfo());
}
}