Class TransformingWriter

java.lang.Object
org.apache.sis.xml.Transformer
org.apache.sis.xml.TransformingWriter
All Implemented Interfaces:
XMLEventConsumer, XMLEventWriter

final class TransformingWriter extends Transformer implements XMLEventWriter
A writer replacing the namespaces used by JAXB by other namespaces to be used in the XML document at marshalling time. This class forwards every method calls to the wrapped XMLEventWriter, with all namespaceURI arguments transformed before to be delegated. See Transformer for more information.
Since:
1.0
Version:
1.0
  • Field Details

    • FILENAME

      private static final String FILENAME
      Location of the file listing types and their properties contained in legacy namespaces. This is used for mapping new ISO 19115-3:2016 namespaces to legacy ISO 19139:2007 ones, where the same "http://standards.iso.org/iso/19115/-3/…" URI is used in places where legacy schema had two distinct URIs: "http://www.isotc211.org/2005/gmd" and "http://standards.iso.org/iso/19115/-2/gmi/1.0".
      See Also:
    • NAMESPACES

      private static final Map<String,Map<String,String>> NAMESPACES
      The mapping from (type, attribute) pairs to legacy namespaces.
      • Keys are XML names of types, ignoring "_TYPE" suffix (e.g. "MI_Georectified")
      • Values are maps where:
        • Keys are XML names of properties (e.g. "checkPoint")
        • Values are either:
          • Namespace URI if Transformer.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.
      This map is initialized only once and should not be modified after that point.
    • ELEMENTS_TO_REORDER

      private static final Map<QName,Set<QName>> ELEMENTS_TO_REORDER
      Elements that appear in different order in ISO 19139:2007 (or other legacy standards) compared to ISO 19115-3:2016 (or other newer standards). Key are names of elements to reorder. Values are the elements to skip before to write the element to reorder.
      Example: In SV_ServiceIdentification, <srv:couplingType> appears before <srv:coupledResource> according ISO 19115-3:2016. But in ISO 19139:2007, it was the reverse order. Since Apache SIS writes elements in the order defined by the newer standard, <couplingType> is encountered first. The set associated to that key tells us that, when writing legacy ISO 19139 document, we should skip <coupledResource> before to write <srv:couplingType>.
      While this map is used for reordering elements according legacy standards, the QName keys and values use the namespaces of newer standards. This is because newer standards like ISO 19115-3 uses many namespaces where legacy ISO 19139:2007 used only one namespace, so using the newer names reduce the risk of confusion.
      See Also:
    • out

      private final XMLEventWriter out
      Where events are sent.
    • uniqueNamespaces

      private final Map<String,Namespace> uniqueNamespaces
      Keep track of namespace URIs that have already been declared so they don't get duplicated. This map is recycled in two different contexts:
      • In a sequence of NAMESPACE events.
      • In the namespaces of a start element.
    • isDeferring

      private boolean isDeferring
      true if events should be sent to deferred instead of to out. This is set to true when we see a StartElement having one of the names contained in the ELEMENTS_TO_REORDER keys set.
    • deferred

      private final Queue<Object> deferred
      Events for which writing is deferred as long as there is elements to skip. The intent is to reorder elements that appear in a different order in legacy standards compared to newer standards. This is a FIFO (First-In-First-Out) queue. Namespaces are the exported ones (the ones after conversions from JAXB to the XML document to write).

      Elements are instance of XMLEvent or TransformingWriter.NewDeferred.

      See Also:
    • toSkip

      private Set<QName> toSkip
      If non-null, elements to skip before we can write the deferred events. Should be the ELEMENTS_TO_REORDER value associated to the element to defer. A null value means that events can be written immediately to out.
    • subtreeRootName

      private QName subtreeRootName
      Name of the the root element of a sub-tree to handle in a special way, or null if none. At first, this is the name of the StartElement of a sub-tree to defer (i.e. one of the keys in the ELEMENTS_TO_REORDER map). Later, it becomes the names of sub-trees to skip (i.e. the toSkip values).
    • subtreeNesting

      private int subtreeNesting
      Number of times that subtreeRootName has been found. A value of 1 means that we started receiving events for that subtree. A value of 0 means that we finished receiving events for that subtree. A value greater than 1 means that there is nested sub-trees (should not happen).
  • Constructor Details

  • Method Details

    • renamingMap

      final Map<String,Map<String,String>> renamingMap(String namespace)
      Specified by:
      renamingMap in class Transformer
      Parameters:
      namespace - the namespace URI for which to get the substitution map.
      Returns:
      the substitution map for the given namespace.
    • relocate

      final String relocate(String namespace)
      Returns the old 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.
      Specified by:
      relocate in class Transformer
    • prefixReplacement

      final String prefixReplacement(String previous, String namespace) throws XMLStreamException
      Returns the prefix to use for a name in a new namespace.
      Specified by:
      prefixReplacement in class Transformer
      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.
    • exportIfNew

      private Namespace exportIfNew(Namespace namespace)
      Returns the namespace to write in the XML document. This may imply a prefix change. If there is no namespace change, then this method returns the given instance as-is. To test if the returned namespace is a new one, callers should check if the size of uniqueNamespaces changed.
      Parameters:
      namespace - the namespace to export.
    • export

      private List<Namespace> export(Iterator<Namespace> namespaces, boolean changed)
      Returns the namespaces to write in the XML document, or null if there is no change. If non-null, the result may contain less namespaces because duplicated entries are omitted (duplication may occur as a result of replacing various ISO 19115-3 namespaces by the legacy ISO 19139:2007 "gmd" unique namespace).
      Parameters:
      namespaces - the namespaces to transform.
      changed - whether to unconditionally pretend that there is a change.
      Returns:
      the updated namespaces, or null if there is no change.
    • add

      public void add(XMLEvent event) throws XMLStreamException
      Converts an event from the namespaces used in JAXB annotations to the namespaces used in the XML document to write. This method may wrap the given event into another event for changing the namespace and prefix, or use the event as-is if no change is needed.
      Specified by:
      add in interface XMLEventConsumer
      Specified by:
      add in interface XMLEventWriter
      Parameters:
      event - the event using JAXB namespaces.
      Throws:
      XMLStreamException
    • writeDeferred

      private boolean writeDeferred(QName element) throws XMLStreamException
      Writes immediately all elements that were deferred. This happen because the next StartElement to write should be after the deferred element, or because we are about to exit the parent element that contains the deferred element, or because flush() has been invoked.
      Parameters:
      element - the StartElement element name, or null for other events.
      Returns:
      true if the given element starts a new subtree to skip.
      Throws:
      XMLStreamException
      See Also:
    • add

      public void add(XMLEventReader reader) throws XMLStreamException
      Adds an entire stream to an output stream.
      Specified by:
      add in interface XMLEventWriter
      Throws:
      XMLStreamException
    • getPrefix

      public String getPrefix(String uri) throws XMLStreamException
      Gets the prefix the URI is bound to. Since our (imported URI) ⟶ (exported URI) transformation is not bijective, implementing this method could potentially result in the same prefix for different URIs, which is illegal for a XML document and potentially dangerous. Thankfully JAXB seems to never invoke this method in our tests.
      Specified by:
      getPrefix in interface XMLEventWriter
      Throws:
      XMLStreamException
    • setPrefix

      public void setPrefix(String prefix, String uri) throws XMLStreamException
      Sets the prefix the URI is bound to. This method replaces the given URI if needed, then forwards the call. Note that it may result in the same URI to be bound to many prefixes. For example, ISO 19115-3:2016 has many URIs, each with a different prefix ("mdb", "cit", etc.). But all those URIs may be replaced by the unique URI used in legacy ISO 19139:2007. Since this method does not replace the prefix (it was "gmd" in ISO 19139:2007), the various ISO 19115-3:2016 prefixes are all bound to the same legacy ISO 19139:2007 URI. This is confusing, but not ambiguous for XML parsers.

      Implemented as a matter of principle, but JAXB did not invoked this method in our tests.

      Specified by:
      setPrefix in interface XMLEventWriter
      Throws:
      XMLStreamException
    • setDefaultNamespace

      public void setDefaultNamespace(String uri) throws XMLStreamException
      Binds a URI to the default namespace. Current implementation replaces the given URI (e.g. an ISO 19115-3:2016 one) by the exported URI (e.g. legacy ISO 19139:2007 one), then forwards the call.

      Implemented as a matter of principle, but JAXB did not invoked this method in our tests.

      Specified by:
      setDefaultNamespace in interface XMLEventWriter
      Throws:
      XMLStreamException
    • setNamespaceContext

      public void setNamespaceContext(NamespaceContext context) throws XMLStreamException
      Sets the current namespace context for prefix and URI bindings. This method unwraps the original context and forwards the call.

      Implemented as a matter of principle, but JAXB did not invoked this method in our tests.

      Specified by:
      setNamespaceContext in interface XMLEventWriter
      Throws:
      XMLStreamException
    • getNamespaceContext

      public NamespaceContext getNamespaceContext()
      Returns a naming context suitable for consumption by JAXB marshallers. The XMLEventWriter wrapped by this TransformingWriter has been created for writing in a file. Consequently, its naming context manages namespaces used in the XML document. But the JAXB marshaller using this TransformingWriter facade expects the namespaces declared in JAXB annotations. Consequently, this method returns an adapter that converts namespaces on the fly.
      Specified by:
      getNamespaceContext in interface XMLEventWriter
      See Also:
    • flush

      public void flush() throws XMLStreamException
      Writes any cached events to the underlying output mechanism.
      Specified by:
      flush in interface XMLEventWriter
      Throws:
      XMLStreamException
    • close

      public void close() throws XMLStreamException
      Frees any resources associated with this writer.
      Specified by:
      close in interface XMLEventWriter
      Overrides:
      close in class Transformer
      Throws:
      XMLStreamException