content/developer-guide/metadata/NavigateMetadata.html (245 lines of code) (raw):

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html> <!-- 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. --> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>NavigateMetadata</title> <meta charset="UTF-8"/> <link rel="stylesheet" type="text/css" href="../book.css"/> </head> <body> <!-- Content below this point is copied in "/asf-staging/book/en/developer-guide.html" file by the `org.apache.sis.buildtools.book` class in `buildSrc`. --> <section> <header> <h2 id="NavigateMetadata">Navigating in metadata elements</h2> </header> <p> The metadata modules provide support methods for handling the metadata objects through Java Reflection. This is an approach similar to <cite>Java Beans</cite>, in that users are encouraged to use directly the API of Plain Old Java objects every time their type is known at compile time, and fallback on the reflection technic when the type is known only at runtime. When using Java reflection, a metadata can be viewed in different ways: </p> <ul> <li>As key-value pairs in a <code>Map</code> (from <code>java.util</code>).</li> <li>As a <code class="SIS">TreeTable</code> (from <code>org.apache.sis.util.collection</code>).</li> <li>As a table record in a database (using <code>org.apache.sis.metadata.sql</code>).</li> <li>As an <abbr>XML</abbr> document conforms to <abbr>ISO</abbr> standard schema.</li> </ul> <p> The use of reflection is described <a href="#MetadataAsMap">below</a>. The <abbr>XML</abbr> representation is described in a <a href="#XML-ISO-19115">separated chapter</a>. </p> <h3 id="GetMetadataElement">Direct access via getter methods</h3> <p> All metadata classes provide getter, and sometime setter, methods for their properties. The following example prints all ranges of latitudes found in a metadata. It may be, for example, a metadata describing the extent of a raster file. Fetching that information requires navigating through the following steps: </p><p style="text-align:center"> <code class="API">Metadata</code> ⟶ <code class="OGC">identificationInfo</code> ⟶ <code class="OGC">extent</code> ⟶ <code class="OGC">geographicElement</code> ⟶ (<code class="OGC">southBoundLatitude</code>, <code class="OGC">northBoundLatitude</code>) </p><p> Some properties accept many values, in which case the code must iterate over the elements of a collection. Furthermore, some properties are available only in a sub-interface, in which case <code>instanceof</code> checks are required. The resulting code is as below: </p> <pre><code>import org.opengis.metadata.metadata; import org.opengis.metadata.extent.Extent; import org.opengis.metadata.extent.GeographicExtent; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.metadata.identification.Identification; import org.opengis.metadata.identification.DataIdentification; void main() { Metadata metadata = ...; // For example, metadata read from a data store. for (Identification identification : metadata.getIdentificationInfo()) { if (identification instanceof DataIdentification data) { for (Extent extent : data.getExtents()) { // Extents may have horizontal, vertical and temporal components. for (GeographicExtent horizontal : extent.getGeographicElements()) { if (horizontal instanceof GeographicBoundingBox bbox) { double south = bbox.getSouthBoundLatitude(); double north = bbox.getNorthBoundLatitude(); System.out.println("Latitude range: " + south + " to " + north); } } } } } }</code></pre> <p> Because of <abbr>ISO</abbr> 19115 richness, interesting information may be buried deeply in the metadata tree, as in above example. For a few frequently-used elements, some convenience methods are provided. Those conveniences are generally defined as static methods in classes having a name in plural form. For example the <code>Extents</code> class defines static methods for fetching more easily some information from <code>Extent</code> metadata elements. For example the following method navigates through different branches where North, South, East and West data bounds may be found: </p> <pre><code>import org.opengis.metadata.metadata; import org.opengis.metadata.extent.GeographicBoundingBox; import org.apache.sis.metadata.iso.extent.Extents; void main() { Metadata metadata = ...; // For example, metadata read from a data store. GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(extent); if (box != null) { // Same as in the previous example. } }</code></pre> <p> Those conveniences are defined as static methods in order to allow their use with different metadata implementations. Some other classes providing static methods for specific interfaces are <code>Citations</code>, <code>Envelopes</code>, <code>Matrices</code> and <code>MathTransforms</code>. </p> <h3 id="SetMetadataElement">Direct construction via setter methods</h3> <p> For every getter method, there is a corresponding setter method. However the setter methods are not defined in the GeoAPI interfaces, which are read-only. For invoking setter methods, it is necessary to use the Apache <abbr>SIS</abbr> implementation classes directly. It is straightforward when the metadata objects are created and populated in the same code, as there is nothing special to do. The following example creates a citation using three different ways to specify a value: at construction time using convenience constructors, using setter methods, and by adding elements directly in the collections, which are modifiable. </p> <pre><code>import org.apache.sis.metadata.iso.DefaultIdentifier; import org.apache.sis.metadata.iso.citation.DefaultCitation; import org.opengis.metadata.citation.PresentationForm; // A code list. void main() { // Following constructor is a convenience for setting the `title` property at construction time. var citation = new DefaultCitation("Climate Change 2022: Impacts, Adaptation, and Vulnerability"); citation.getPresentationForms().add(PresentationForm.DOCUMENT_DIGITAL); var identifier = new DefaultIdentifier(); identifier.setCode("10.1017/9781009325844"); identifier.setCodeSpace("DOI"); citation.getIdentifiers().add(identifier); // Do not allow the citation to be modified after this point (optional). citation.transitionTo(DefaultCitation.State.FINAL); System.out.println(citation); }</code></pre> <p> Output is as below: </p> <pre><samp>Citation……………………………………… Climate Change 2022: Impacts, Adaptation, and Vulnerability   ├─Identifier……………………… 10.1017/9781009325844   │   └─Code space…………… DOI   └─Presentation form…… Document digital</samp></pre> <p> Often, the newly created metadata will be reused many times. It may be for example a static final constant, or a cached value. In such case, it may be desirable to declare the metadata as unmodifiable after its construction. This is the purpose of the call to <code class="SIS">transitionTo(State.FINAL)</code> in above example. After that call, any attempt to modify the metadata will cause an <code class="SIS">UnmodifiableMetadataException</code> to be thrown. </p><p> Setting the values of a metadata object may be more tricky when the implementation class is unknown. Since there is no setter methods in the GeoAPI interfaces, the metadata may need to be converted to an Apache <abbr>SIS</abbr> implementation class first, and that instance needs to be modifiable. This is a two steps process. The first step can be done by invoking the static <code class="SIS">castOrCopy(…)</code> method which is defined in every Apache <abbr>SIS</abbr> implementation classes. For example there is a <code class="SIS">DefaultCitation.castOrCopy(<code class="OGC">Citation</code>)</code> method, a <code class="SIS">DefaultExtent.castOrCopy(<code class="OGC">Extent</code>)</code> method, <i>etc.</i> Those methods will either return the given instance directly when possible, or otherwise create a <em>shallow</em> copy of that instance (i.e., without recursive copy of children). The second step can be done in different ways. An easy way (while not the most efficient) is to make an unconditional <em>deep</em> copy of the metadata, in case that metadata was unmodifiable (<code class="SIS">State.FINAL</code>), and also (if desired) for avoiding to change the original metadata. Example: </p> <pre><code>import org.opengis.metadata.citation.Citation; import org.apache.sis.metadata.iso.citation.DefaultCitation; import org.apache.sis.util.SimpleInternationalString; void main() { Citation original = ...; // Some pre-existing citation. // Get a modifiable copy and change its title. DefaultCitation edit = DefaultCitation.castOrCopy(original); edit = (DefaultCitation) copy.deepCopy(DefaultCitation.State.EDITABLE); edit.setTitle(new SimpleInternationalString("A new title")); }</code></pre> <h3 id="MetadataAsMap">View as key-value pairs</h3> <p> Above static methods explore fragments of metadata tree in search for requested information, but the searches are still targeted to elements whose types and at least part of their paths are known at compile-time. Sometimes the element to search is known only at runtime, or sometimes there is a need to iterate over all elements. In such cases, one can view the metadata as a <code>java.util.Map</code> like below: </p> <pre><code>import java.util.Map; import org.apache.sis.metadata.MetadataStandard; import org.apache.sis.metadata.KeyNamePolicy; import org.apache.sis.metadata.ValueExistencePolicy; void main() { Object metadata = ...; Map&lt;String,Object&gt; elements = MetadataStandard.ISO_19115.asValueMap( metadata, // Any instance from the org.opengis.metadata package or a sub-package. null, // Used for resolving ambiguities. We can ignore for this example. KeyNamePolicy.JAVABEANS_PROPERTY, // Keys in the map will be getter method names without "get" prefix. ValueExistencePolicy.NON_EMPTY); // Entries with null or empty values will be omitted. // Print the names of all root metadata elements having a value. for (String name : elements.keySet()) { System.out.println(name); } }</code></pre> <p> The <code>Map</code> object returned by <code class="SIS">asValueMap(…)</code> is live: any change in the <code>metadata</code> instance will be immediately reflected in the view. Actually, each <code>map.get("foo")</code> call is forwarded to the corresponding <code>metadata.getFoo()</code> method. Conversely, any <code>map.put("foo", …)</code> or <code>map.remove("foo")</code> operation applied on the view will be forwarded to the corresponding <code>metadata.setFoo(…)</code> method, if that method exists. The view is lenient regarding keys given in arguments to <code>Map</code> methods: keys may be property names (<code>"foo"</code>), method names (<code>"getFoo"</code>), or names used in <abbr>ISO</abbr> 19115 standard <abbr>UML</abbr> diagrams (similar to property names but not always identical). Differences in upper cases and lower cases are ignored when this tolerance does not introduce ambiguities. For more information on metadata views, see <a href="../../apidocs/org.apache.sis.metadata/org/apache/sis/metadata/package-summary.html#package.description"><code>org.apache.sis.metadata</code></a> package javadoc. </p> <h3 id="MetadataAsTreeTable">View as tree table</h3> <p> A richer alternative to the view as a map is the view as a tree table. With this view, the metadata structure is visible as a tree, and each tree node is a table row with the following columns: </p> <table> <caption>Columns of a tree table view of a metadata</caption> <tr><th>Column</th> <th>Description</th> <th>Writable</th></tr> <tr><td><code class="SIS">IDENTIFIER</code></td> <td>The UML identifier if any, or otherwise the Java Beans name, of the metadata property.</td><td></td></tr> <tr><td><code class="SIS">INDEX</code></td> <td>If the property is a collection, then the zero-based index of the element in that collection.</td><td></td></tr> <tr><td><code class="SIS">NAME</code></td> <td>A human-readable name for the node, derived from above identifier and index.</td><td></td></tr> <tr><td><code class="SIS">TYPE</code></td> <td>The base type of the value (usually a GeoAPI interface).</td><td></td></tr> <!-- SIS 1.5 <tr><td><code class="SIS">OBLIGATION</code></td> <td>Whether the property is mandatory, optional or conditional.</td><td></td></tr> --> <tr><td><code class="SIS">VALUE</code></td> <td>The metadata value for the node. This column may be writable.</td><td>✓</td></tr> <!-- SIS 1.5 <tr><td><code class="SIS">NIL_REASON</code></td> <td>If the value is mandatory and nevertheless absent, the reason why.</td><td>✓</td></tr> --> <tr><td><code class="SIS">REMARKS</code></td> <td>Remarks or warning on the property value.</td><td></td></tr> </table> <p> Tree table views are obtained in a way similar to <a href="#MetadataAsMap">map views</a>, but using the <code class="SIS">asTreeTable(…)</code> method instead of <code class="SIS">asValueMap(…)</code>. Values can be obtained for each column, and can be set for the <code class="SIS">VALUE</code> column if the underlying metadata object is modifiable. The following example prints the values of the <code class="SIS">NAME</code> and <code class="SIS">TYPE</code> columns for a given metadata and all its children: </p> <pre><code>import org.apache.sis.metadata.MetadataStandard; import org.apache.sis.metadata.ValueExistencePolicy; import org.apache.sis.util.collection.TreeTable; import org.apache.sis.util.collection.TableColumn; class Explorer { void main() { Object metadata = ...; TreeTable table = MetadataStandard.ISO_19115.asTreeTable( metadata, // Any instance from the org.opengis.metadata package or a sub-package. null, // Used for resolving ambiguities. We can ignore for this example. ValueExistencePolicy.NON_EMPTY); // Entries with null or empty values will be omitted. // Print the names and types of all metadata elements having a value. printRecursively(table.getRoot()); } private static void printRecursively(TreeTable.Node node) { String name = node.getValue(TableColumn.NAME); Class&lt;?&gt; type = node.getValue(TableColumn.TYPE); System.out.println(name + " : " + type.getSimpleName()); node.getChildren().forEach(Explorer::printRecursively); } }</code></pre> </section> </body> </html>