Class Transformer

java.lang.Object
org.apache.sis.xml.Transformer
Direct Known Subclasses:
TransformingReader, TransformingWriter

abstract class Transformer extends Object
Base class of XML reader or writer replacing the namespaces used in JAXB annotations by namespaces used in the XML document, or conversely (depending on the direction of the I/O operation). The Transform* classes in this package perform a work similar to XSLT transformers, but using a lighter implementation at the expense of less transformation capabilities. This transformer supports:
  • Renaming namespaces, which may depend on the parent element.
  • Renaming elements (classes or properties).
  • Moving elements provided that the old and new locations are in the same parent element.
If a more complex transformation is needed, it should be handled by JAXB methods. This Transformer is not expected to transform fully a XML document by itself. It is rather designed to complete JAXB: some transformations are better handled with Java methods annotated with JAXB (see for example the deprecated methods in org.apache.sis.metadata.iso packages for legacy ISO 19115:2003 properties), and some transformations, in particular namespace changes, are better handled by this Transformer.

Why using Transformer

When the XML schemas of an international standard is updated, the URL of the namespace is often modified. For example, when GML has been updated from version 3.1 to 3.2, the URL mandated by the international standard changed from "http://www.opengis.net/gml" to "http://www.opengis.net/gml/3.2" (XML namespaces usually have a version number or publication year - GML before 3.2 were an exception). The problem is that namespaces in JAXB annotations are static. The straightforward solution is to generate complete new set of classes for every GML version using the xjc compiler. But this approach has many inconvenient:
  • Massive code duplication (hundreds of classes, many of them strictly identical except for the namespace).
  • Handling of above-cited classes duplication requires either a bunch of if (x instanceof Y) in every SIS corners, or to modify the xjc output in order to give to generated classes a common parent class or interface. In the latter case, the auto-generated classes require significant work anyways.
  • The namespaces of all versions appear in the xmlns attributes of the root element (we cannot always create separated JAXB contexts), which is confusing and prevent usage of usual prefixes for all versions except one.
An alternative is to support only one version of each standard, and transform XML documents before unmarshalling or after marshalling if they use different versions of standards. We could use XSLT for that, but this is heavy. A lighter approach is to use XMLEventReader and XMLEventWriter as "micro-transformers". One advantage is that they can transform on-the-fly (no need to load and transform the document in memory). It also avoid the need to detect in advance which schemas a XML document is using, and can handle the cases where mixed versions are used.
Since:
1.0
Version:
1.0
See Also:
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    private static final char
    Character used for separating a class name from the parent class name.
    private final Map<String,String>
    The namespaces associated to prefixes in the source.
    (package private) static final char
    A flag after type name in files loaded by load(boolean, String, Set, int), meaning that the type itself is in a different namespace than the properties listed below the type.
    private Map<String,String>
    Properties of the last outer elements, or null if not yet determined.
    private final List<QName>
    List of encountered XML tags, used for backtracking.
    private static final char
    Character used for separating an old name from the new name.
    (package private) final List<Attribute>
    Temporary list of attributes after their namespace change.
    private static final char
    Heading character for declaring a namespaces on which the remaining of the Rename.lst file applies.
    (package private) final TransformVersion
    The external XML format version to (un)marshal from.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates a new XML reader or writer.
  • Method Summary

    Modifier and Type
    Method
    Description
    (package private) final List<Attribute>
    Returns a snapshot of renamedAttributes list and clears the latter.
    void
    Frees any resources associated with this reader.
    (package private) final void
    close(QName name)
    Notifies that we are closing an element of the given name.
    (package private) final QName
    convert(QName name)
    Renames en element using the namespaces map given to the open(…) and close(…) methods.
    (package private) final Attribute
    convert(Attribute attribute)
    Imports or exports an attribute read or written from/to the XML document.
    (package private) static boolean
    isNamespace(String candidate)
    Returns true if the given string is a namespace URI, or false if it is a property name.
    private static boolean
    isTypeElement(String localPart)
    Returns true if an element with the given name is an OGC/ISO type (as opposed to property).
    (package private) static Map<String,Map<String,String>>
    load(boolean export, String filename, Set<String> targets, int capacity)
    Loads a file listing types and properties contained in namespaces.
    (package private) final void
    notify(Namespace namespace)
    Notifies that a new namespace is declared in the source.
    (package private) final void
    open(QName name)
    Notifies that we are opening an element of the given name.
    (package private) abstract String
    prefixReplacement(String previous, String namespace)
    Returns the prefix to use for a name in a new namespace.
    (package private) abstract String
    relocate(String namespace)
    Returns the new namespace for elements (types and properties) in the given namespace.
    (package private) static String
    Removes the trailing slash in given URI, if any.
    (package private) abstract Map<String,Map<String,String>>
    renamingMap(String namespace)
    Returns the map loaded by load(boolean, String, Set, int).

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • TARGET_PREFIX

      private static final char TARGET_PREFIX
      Heading character for declaring a namespaces on which the remaining of the Rename.lst file applies. Lines with this prefix specify legacy namespaces to be renamed (the target of the renaming process), while lines without this prefix specify new namespaces.
      See Also:
    • RENAME_SEPARATOR

      private static final char RENAME_SEPARATOR
      Character used for separating an old name from the new name. For example, in SV_OperationMetadata, "DCP" in ISO 19139:2007 has been renamed "distributedComputingPlatform" in ISO 19115-3. This is encoded in "RenameOnImport.lst" file as "DCP/distributedComputingPlatform".
      See Also:
    • EXTENDS

      private static final char EXTENDS
      Character used for separating a class name from the parent class name. When the Child : Parent syntax is used, the child inherits all properties defined in the parent. The parent class must be defined before the child class (no forward reference). We do not store the relationship between the two classes, so it is not necessary to extend a parent that define no property.
      See Also:
    • NO_NAMESPACE

      static final char NO_NAMESPACE
      A flag after type name in files loaded by load(boolean, String, Set, int), meaning that the type itself is in a different namespace than the properties listed below the type. For example in the following: SV_ServiceIdentification type is defined in the "http://standards.iso.org/iso/19115/-3/srv/2.0" namespace, but the citation and abstract properties inherited from Identification are defined in the http://standards.iso.org/iso/19115/-3/mri/1.0 namespace (note: using EXTENDS is a better way to achieve the same result for this particular example). If the '!' flag is not present, then the type is assumed in the same namespace than the properties (this is the most common case).
      See Also:
    • version

      final TransformVersion version
      The external XML format version to (un)marshal from.
    • outerElements

      private final List<QName> outerElements
      List of encountered XML tags, used for backtracking. Elements are removed from this list when they are closed. Names should be the ones we get after conversion from namespaces used in XML document to namespaces used in JAXB annotations. For example, given the following XML, this list should contain cit:CI_Citation, cit:date and cit:CI_Date (in that order) when the (un)marshalling reaches the "…" location.
    • outerElementProperties

      private Map<String,String> outerElementProperties
      Properties of the last outer elements, or null if not yet determined. If non-empty, this is one of the values got from the map given in argument to open(QName).
    • renamedAttributes

      final List<Attribute> renamedAttributes
      Temporary list of attributes after their namespace change. This list is recycled for each XML element to be read or written.
    • namespaces

      private final Map<String,String> namespaces
      The namespaces associated to prefixes in the source. When unmarshalling, this is for the namespaces in the source XML document (e.g. using legacy ISO 19139:2007 standard). When marshalling, this is for the namespaces in the JAXB annotations (e.g. using newer ISO 19115-3 standard). This is used for handling xsi:type attribute values.
  • Constructor Details

    • Transformer

      Transformer(TransformVersion version)
      Creates a new XML reader or writer.
  • Method Details

    • removeTrailingSlash

      static String removeTrailingSlash(String uri)
      Removes the trailing slash in given URI, if any. It is caller's responsibility to ensure that the URI is not null and not empty before to invoke this method.
    • isNamespace

      static boolean isNamespace(String candidate)
      Returns true if the given string is a namespace URI, or false if it is a property name. This method implements a very fast check based on the presence of ':' in "http://foo.bar". It assumes that all namespaces declared in files loaded by load(boolean, String, Set, int) use the "http" protocol and no property name use the ':' character.
    • load

      static Map<String,Map<String,String>> load(boolean export, String filename, Set<String> targets, int capacity)
      Loads a file listing types and properties contained in namespaces. The file location is relative to the Transformer class. The file format is a tree structured with indentation as below:
      • Lines with zero-space indentation are namespace URIs.
      • Lines with one-space indentation are classes within the last namespace URIs found so far.
      • Lines with two-spaces indentation are properties within the last class found so far.
      • All other indentations are illegal and cause an InvalidPropertiesFormatException to be thrown. This exception type is not really appropriate since the file format is not a .properties file, but it is the closest we could find in existing exceptions and we don't want to define a new exception type since this error should never happen.
      The returned map is structured as below:
      • Keys are XML names of types, ignoring "_TYPE" suffix (e.g. "CI_Citation")
      • Values are maps where:
        • Keys are XML names of properties (e.g. "title")
        • Values are either:
          • Namespace URI if isNamespace(String) returns true for that value.
          • New name of the element otherwise. In such case, the map must be queried again with that new name for obtaining the namespace.
      Parameters:
      export -   true for "RenameOnImport.lst", false for "RenameOnImport.lst".
      filename - name of the file to load. Shall be consistent with the export flag.
      targets - initially empty set where to add the namespaces on which the renaming will apply.
      capacity - initial capacity for the hash map to be returned. This is only a hint.
    • notify

      final void notify(Namespace namespace)
      Notifies that a new namespace is declared in the source. When unmarshalling, this is for a namespace in the source XML document (e.g. using legacy ISO 19139:2007 standard). When marshalling, this is for a namespaces in the JAXB annotations (e.g. using newer ISO 19115-3 standard).
    • attributes

      final List<Attribute> attributes()
      Returns a snapshot of renamedAttributes list and clears the latter.
    • convert

      final Attribute convert(Attribute attribute) throws XMLStreamException
      Imports or exports an attribute read or written from/to the XML document. If there is no name change, then this method returns the given instance as-is. This method performs a special check for the "xsi:type" attribute: its value is parsed as a name and converted.
      Throws:
      XMLStreamException
    • isTypeElement

      private static boolean isTypeElement(String localPart)
      Returns true if an element with the given name is an OGC/ISO type (as opposed to property). For example, given the following XML, this method returns true for cit:CI_Date but false for cit:date: This method is based on simple heuristic applicable to OGC/ISO conventions, and may change in any future SIS version depending on new formats to support.

      Other examples to keep in mind:

      • "AbstractCI_Party" (a type).
      • "ISBN" and "ISSN" (properties).
      • "MI_GCP" (a type).
    • open

      final void open(QName name)
      Notifies that we are opening an element of the given name.
      Parameters:
      name - element name as declared in JAXB annotations.
    • close

      final void close(QName name)
      Notifies that we are closing an element of the given name. This method closes the last start element with a matching name. It should be the last element on the list in a well-formed XML, but we loop in the list anyway as a safety.
      Parameters:
      name - element name as declared in JAXB annotations.
    • close

      public void close() throws XMLStreamException
      Frees any resources associated with this reader.
      Throws:
      XMLStreamException
    • convert

      final QName convert(QName name) throws XMLStreamException
      Renames en element using the namespaces map given to the open(…) and close(…) methods. When unmarshalling, this method converts a name read from the XML document to the name to give to JAXB. When marshalling, this method converts a name used in JAXB annotation to the name to use in XML document. The new namespace depends on both the old namespace and the element name. The prefix is computed by prefixReplacement(String, String).
      Parameters:
      name - the name of the element or attribute currently being read or written.
      Returns:
      a name with potentially the namespace and the local part replaced.
      Throws:
      XMLStreamException
    • renamingMap

      abstract Map<String,Map<String,String>> renamingMap(String namespace)
      Returns the map loaded by load(boolean, String, Set, int). This is a static field in the TransformingReader or TransformingWriter subclass.
      Parameters:
      namespace - the namespace URI for which to get the substitution map (never null).
      Returns:
      the substitution map for the given namespace, or an empty map if none.
    • relocate

      abstract String relocate(String namespace)
      Returns the new namespace for elements (types and properties) in the given namespace. This method is used only for default relocations, i.e. the fallback to apply when no explicit rule has been found.
    • prefixReplacement

      abstract String prefixReplacement(String previous, String namespace) throws XMLStreamException
      Returns the prefix to use for a name in a new namespace.
      Parameters:
      previous - the prefix associated to old namespace.
      namespace - the new namespace URI.
      Returns:
      prefix to use for the new namespace.
      Throws:
      XMLStreamException - if an error occurred while fetching the prefix.