Author: desruisseaux Date: Wed Feb 14 21:42:41 2018 New Revision: 1824269 URL: http://svn.apache.org/viewvc?rev=1824269&view=rev Log: More stable mapping of namespace URIs to prefixes.
Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -20,7 +20,6 @@ import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Collections; -import javax.xml.namespace.QName; import org.apache.sis.util.Debug; import org.apache.sis.internal.jaxb.LegacyNamespaces; @@ -238,28 +237,6 @@ final class FilterVersion { } /** - * Returns the name (prefix, namespace and local part) to write in the XML document. - * This method may replace the namespace, and in some case the name local part too. - * If there is no name change, then this method returns the given instance as-is. - */ - final QName export(QName name) { - String uri = name.getNamespaceURI(); - if (uri != null && !uri.isEmpty()) { // Optimization for a common case. - final Replacement r = exports.get(uri); - if (r != null) { - uri = r.namespace; - name = new QName(uri, r.exportProperty(name.getLocalPart()), - Namespaces.getPreferredPrefix(uri, name.getPrefix())); - /* - * Note: the call for 'getPreferredPrefix' above is required: - * JAXB seems to need the prefixes for recognizing namespaces. - */ - } - } - return name; - } - - /** * Converts a namespace used in JAXB annotation to the namespace used in XML document. * Returns the same URI if there is no replacement. */ Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -246,24 +246,37 @@ abstract class FilteredEvent<E extends X } /** - * Gets a read-only namespace context. Default implementation is suitable for exports - * (i.e. write operation). Needs to be overridden for imports (read operation). + * Gets the URI used by JAXB annotations for the given prefix used in the XML document. + * Returns {@code null} if no unique URI can be provided for the given prefix. * - * @see FilteredWriter#getNamespaceContext() + * <div class="note"><b>Example:</b> + * the {@code "gmd"} prefix from legacy ISO 19139:2007 standard can map to the + * {@code "http://standards.iso.org/iso/19115/-3/mdb/1.0"}, {@code "…/cit/1.0"} + * and other namespaces in ISO 19115-3:2016. Because of this ambiguity, + * this method returns {@code null}.</div> + * + * <p>At unmarshalling time, events are created by an arbitrary {@link javax.xml.stream.XMLEventReader} + * with namespaces used in the XML document. {@link FilteredReader} wraps those events using this class + * for converting the XML namespaces to the namespaces used by JAXB annotations.</p> + * + * <p>At marshalling time, events are created by JAXB using namespaces used in JAXB annotations. + * {@link FilteredWriter} wraps those events for converting those namespaces to the ones used in + * the XML document. This is the opposite than the work performed by this default implementation + * and must be handled by a {@code Start} subclass.</p> */ @Override - public NamespaceContext getNamespaceContext() { - return FilteredNamespaces.exportNS(event.getNamespaceContext(), version); + public String getNamespaceURI(final String prefix) { + return version.importNS(event.getNamespaceURI(prefix)); } /** - * Gets the value that the prefix is bound to in the context of this element. - * Default implementation is suitable for export (i.e. write operation). - * Needs to be overridden for imports (read operation). + * Returns a context mapping prefixes used in XML document to namespaces used in JAXB annotations. + * The {@code FilteredNamespaces.Inverse.getNamespaceURI(String)} method in that context shall do + * the same work than {@link #getNamespaceURI(String)} in this event. */ @Override - public String getNamespaceURI(final String prefix) { - return version.exportNS(event.getNamespaceURI(prefix)); + public NamespaceContext getNamespaceContext() { + return FilteredNamespaces.asJAXB(event.getNamespaceContext(), version); } /** @@ -280,30 +293,4 @@ abstract class FilteredEvent<E extends X out.append('>'); } } - - /** - * Wrapper over an element emitted during the reading of an XML document. - */ - static final class Import extends Start { - /** Wraps the given event with potentially different name, namespaces and attributes. */ - Import(StartElement event, QName name, List<Namespace> namespaces, List<Attribute> attributes, FilterVersion version) { - super(event, name, namespaces, attributes, version); - } - - /** - * Gets a read-only namespace context. - */ - @Override - public NamespaceContext getNamespaceContext() { - return FilteredNamespaces.importNS(event.getNamespaceContext(), version); - } - - /** - * Gets the value that the prefix is bound to in the context of this element. - */ - @Override - public String getNamespaceURI(final String prefix) { - return version.importNS(event.getNamespaceURI(prefix)); - } - } } Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -26,8 +26,7 @@ import javax.xml.XMLConstants; /** * In the associations between prefixes and namespaces, substitutes the namespaces used in JAXB annotations by the * namespaces used in the XML document at marshalling time. This class is used internally by {@link FilteredReader} - * and {@link FilteredWriter} only. Current {@code FilteredNamespaces} implementation takes care of XML prefixes only; - * the stream reader and writer do the rest of the work. + * and {@link FilteredWriter} only. * * <div class="section">The problem</div> * When the XML schemas of an international standard is updated, the URL of the namespace is often modified. @@ -51,7 +50,7 @@ import javax.xml.XMLConstants; * * 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 {@link javax.xml.stream.XMLStreamReader} and {@link javax.xml.stream.XMLStreamWriter} + * A lighter approach is to use {@link javax.xml.stream.XMLEventReader} and {@link javax.xml.stream.XMLEventWriter} * as "micro-transformers". * * @author Martin Desruisseaux (Geomatys) @@ -66,23 +65,19 @@ class FilteredNamespaces implements Name /** * Given the context for namespaces used in our JAXB annotations, returns a context working with namespaces * used in XML document. The returned context converts namespace arguments from XML to JAXB namespaces, and - * converts returned namespaces from JAXB to XML. Prefixes are left unchanged since they can be arbitrary - * (even if not the standard match for a given namespace). + * converts returned namespaces from JAXB to XML. * * <div class="note"><b>Example:</b> * for a {@code "http://www.isotc211.org/2005/gmd"} namespace (legacy ISO 19139:2007) given in argument to - * {@link #getPrefixes(String)}, the context convert that namespace to all possible ISO 19115-3 namespaces + * {@link #getPrefixes(String)}, the context converts that namespace to all possible ISO 19115-3 namespaces * (there is many) and returns the associated prefixes: {@code "mdb"}, {@code "cit"}, <i>etc.</i> * Conversely given a {@code "mdb"}, {@code "cit"}, <i>etc.</i>, prefix, {@link #getNamespaceURI(String)} * method returns the above-cited legacy GMD namespace.</div> - * - * This can be used when a {@link javax.xml.stream.XMLStreamWriter} has been created for writing with JAXB - * annotations and we want to expose an {@code XMLStreamReader} for writing a legacy XML document. */ - static NamespaceContext exportNS(NamespaceContext context, final FilterVersion version) { + static NamespaceContext asXML(NamespaceContext context, final FilterVersion version) { if (context != null) { - if (context instanceof Import && ((Import) context).version == version) { - context = ((Import) context).context; + if (context instanceof Inverse && ((Inverse) context).version == version) { + context = ((Inverse) context).context; } else { context = new FilteredNamespaces(context, version); } @@ -93,16 +88,17 @@ class FilteredNamespaces implements Name /** * Given a context for namespaces used in XML document, returns a context working with the namespaces used * in our JAXB annotations. The returned context converts namespace arguments from JAXB to XML namespaces - * before to delegate to the wrapped context, and converts returned namespaces from XML to JAXB. This can - * be used when a {@link javax.xml.stream.XMLStreamReader} has been created for reading a legacy XML document - * and we want to expose an {@code XMLStreamReader} for JAXB. + * before to delegate to the wrapped context, and converts returned namespaces from XML to JAXB. + * + * <p>This can be used when a {@link javax.xml.stream.XMLEventWriter} has been created for writing a legacy + * XML document and we want to expose a {@link FilteredWriter} view to give to JAXB.</p> */ - static NamespaceContext importNS(NamespaceContext context, final FilterVersion version) { + static NamespaceContext asJAXB(NamespaceContext context, final FilterVersion version) { if (context != null) { if (context.getClass() == FilteredNamespaces.class && ((FilteredNamespaces) context).version == version) { context = ((FilteredNamespaces) context).context; } else { - context = new FilteredNamespaces.Import(context, version); + context = new FilteredNamespaces.Inverse(context, version); } } return context; @@ -111,8 +107,7 @@ class FilteredNamespaces implements Name /** * The context to wrap, given by {@link FilteredReader} or {@link FilteredWriter}. * - * @see javax.xml.stream.XMLStreamReader#getNamespaceContext() - * @see javax.xml.stream.XMLStreamWriter#getNamespaceContext() + * @see javax.xml.stream.XMLEventWriter#getNamespaceContext() */ final NamespaceContext context; @@ -131,13 +126,14 @@ class FilteredNamespaces implements Name /** * Substitutes the XML namespaces used in XML documents by namespaces used in JAXB annotations. - * This is used at unmarshalling time for importing legacy documents, performing the reverse of - * {@link FilteredNamespaces}. The <i>namespace → prefix</i> mapping is simpler because various - * ISO 19115-3 namespaces are mapped to the same legacy {@code "gmd"} prefix. + * This is used at marshalling time for exporting legacy documents, performing the reverse of + * {@link FilteredNamespaces}. The <i>namespace → prefix</i> mapping is simple because various + * ISO 19115-3 namespaces are mapped to the same legacy {@code "gmd"} prefix, but the reverse + * operation (<i>prefix → namespace</i> mapping) can often not be resolved. */ - private static final class Import extends FilteredNamespaces { + private static final class Inverse extends FilteredNamespaces { /** Creates a new namespaces filter for the given source version. */ - Import(final NamespaceContext context, final FilterVersion version) { + Inverse(final NamespaceContext context, final FilterVersion version) { super(context, version); } @@ -148,6 +144,8 @@ class FilteredNamespaces implements Name * * <p>Except for {@code NULL_NS_URI}, this is usually an <cite>injective</cite> function: * each namespace can be created from at most one prefix.</p> + * + * @see FilteredEvent.Start#getNamespaceURI(String) */ @Override public String getNamespaceURI(final String prefix) { return version.importNS(context.getNamespaceURI(prefix)); Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -225,12 +225,21 @@ final class FilteredReader extends Filte private final List<QName> outerElements; /** + * The prefixes for namespace URIs. Keys are URIs used in JAXB annotations and values are prefixes + * computed by {@link Namespaces#getPreferredPrefix(String, String)} or any other means. We store + * the prefix both for performance reasons and for improving the guarantees that the URI → prefix + * mapping is stable. + */ + private final Map<String,String> prefixes; + + /** * Creates a new filter for the given version of the standards. */ FilteredReader(final XMLEventReader in, final FilterVersion version) { super(version); this.in = in; outerElements = new ArrayList<>(); + prefixes = new HashMap<>(); } /** @@ -289,7 +298,7 @@ final class FilteredReader extends Filte break; } case NAMESPACE: { - event = importNS((Namespace) event); + event = importNS((Namespace) event, null, null); break; } case START_ELEMENT: { @@ -303,9 +312,10 @@ final class FilteredReader extends Filte changed |= (a != ae); renamedAttributes.add(ae); } - final List<Namespace> namespaces = importNS(e.getNamespaces(), changed); + final List<Namespace> namespaces = importNS(e.getNamespaces(), + originalName.getNamespaceURI(), name.getNamespaceURI(), changed); if (namespaces != null) { - event = new FilteredEvent.Import(e, name, namespaces, attributes(), version); + event = new FilteredEvent.Start(e, name, namespaces, attributes(), version); } else { renamedAttributes.clear(); } @@ -318,7 +328,8 @@ final class FilteredReader extends Filte final EndElement e = event.asEndElement(); final QName originalName = e.getName(); final QName name = convert(originalName); - final List<Namespace> namespaces = importNS(e.getNamespaces(), name != originalName); + final List<Namespace> namespaces = importNS(e.getNamespaces(), + originalName.getNamespaceURI(), name.getNamespaceURI(), name != originalName); if (namespaces != null) { event = new FilteredEvent.End(e, name, namespaces); } @@ -350,7 +361,7 @@ final class FilteredReader extends Filte * @return the namespace URI for the element or attribute in the current context (e.g. an ISO 19115-3 namespace), * or {@code null} if the given name is unknown. */ - private QName convert(QName name) { + private QName convert(final QName name) { String namespace = null; // In this method, null means no change to given name. String localPart = name.getLocalPart(); final Set<String> declaringTypes = DECLARING_TYPES.get(localPart); @@ -395,16 +406,17 @@ final class FilteredReader extends Filte if (namespace == null) { namespace = version.importNS(oldNS); } + if (namespace.equals(oldNS) && localPart.equals(name.getLocalPart())) { + return name; + } /* - * Build a new name if any component (URI or local part) changed. Do not specify the prefix - * because the same URI in the XML document could map to many different prefixes after we - * converted to the namespaces used by JAXB. Having the same prefix for those different URI - * may be a source of confusion. + * Build a new name if any component (URI or local part) changed. The prefix should have + * been specified (indirectly) by a previous call to 'importNS(Namespace)', for example + * as a result of a NAMESPACE event. If not, we compute it now using the same method. */ - if (!namespace.equals(oldNS) || !localPart.equals(name.getLocalPart())) { - name = new QName(namespace, localPart); - } - return name; + final String prefix = prefixes.computeIfAbsent(namespace, + (ns) -> Namespaces.getPreferredPrefix(ns, name.getPrefix())); + return new QName(namespace, localPart, prefix); } /** @@ -421,18 +433,28 @@ final class FilteredReader extends Filte } /** - * Imports a namespace read from the XML document. This may imply a prefix change. - * If there is no namespace change, then this method returns the given instance as-is. + * Converts a namespace read from the XML document to the namespace used by JAXB annotations. + * This methods can convert the namespace for which there is a bijective mapping, for example + * {@code "http://www.isotc211.org/2005/gco"} to {@code "http://standards.iso.org/iso/19115/-3/gco/1.0"}. + * However some namespaces like {@code "http://www.isotc211.org/2005/gmd"} may be left unchanged, + * because that namespace from legacy ISO 19139:2007 can be mapped to many different namespaces + * in newer ISO 19115-3:2016 standard. However in some cases the context allows us to determines + * which newer namespace is used. In such case, that mapping is specified by the + * ({@code oldURI}, {@code newURI}) pair. * * @param namespace the namespace to import. + * @param oldURI an old URI which has been renamed as {@code newURI}, or {@code null} if none. + * @param newURI the new URI for {@code oldURI}, or {@code null} if {@code newURI} is null. */ - private Namespace importNS(final Namespace namespace) { + private Namespace importNS(final Namespace namespace, final String oldURI, final String newURI) { String uri = namespace.getNamespaceURI(); if (uri != null && !uri.isEmpty()) { uri = FilteredXML.removeTrailingSlash(uri); - final String imported = version.importNS(uri); + final String imported = uri.equals(oldURI) ? newURI : version.importNS(uri); if (imported != uri) { - return new FilteredEvent.NS(namespace, Namespaces.getPreferredPrefix(imported, namespace.getPrefix()), imported); + final String prefix = prefixes.computeIfAbsent(imported, + (ns) -> Namespaces.getPreferredPrefix(ns, namespace.getPrefix())); + return new FilteredEvent.NS(namespace, prefix, imported); } } return namespace; @@ -442,17 +464,21 @@ final class FilteredReader extends Filte * Imports the namespaces read from the XML document. * * @param namespaces the namespaces to filter. + * @param oldURI an old URI which has been renamed as {@code newURI}, or {@code null} if none. + * @param newURI the new URI for {@code oldURI}, or {@code null} if {@code newURI} is null. * @param changed whether to unconditionally pretend that there is a change. * @return the updated namespaces, or {@code null} if there is no changes. */ - private List<Namespace> importNS(final Iterator<Namespace> namespaces, boolean changed) { + private List<Namespace> importNS(final Iterator<Namespace> namespaces, + final String oldURI, final String newURI, boolean changed) + { if (!namespaces.hasNext()) { return changed ? Collections.emptyList() : null; } final List<Namespace> modified = new ArrayList<>(); do { Namespace namespace = namespaces.next(); - changed |= (namespace != (namespace = importNS(namespace))); + changed |= (namespace != (namespace = importNS(namespace, oldURI, newURI))); modified.add(namespace); } while (namespaces.hasNext()); return changed ? modified : null; Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -65,7 +65,7 @@ final class FilteredWriter extends Filte * <li>In the namespaces of a start element.</li> * </ul> */ - private final Map<String, Namespace> uniqueNamespaces; + private final Map<String,Namespace> uniqueNamespaces; /** * Creates a new filter for the given version of the standards. @@ -77,12 +77,48 @@ final class FilteredWriter extends Filte } /** + * Returns the name (prefix, namespace and local part) to write in the XML document. + * This method may replace the namespace, and in some case the name local part too. + * If there is no name change, then this method returns the given instance as-is. + */ + private QName export(QName name) throws XMLStreamException { + String uri = name.getNamespaceURI(); + if (uri != null && !uri.isEmpty()) { // Optimization for a common case. + final FilterVersion.Replacement r = version.export(uri); + if (r != null) { + uri = r.namespace; + /* + * The wrapped XMLEventWriter maintains a mapping from prefixes to namespace URIs. + * Arguments are exported URIs (e.g. from legacy ISO 19139:2007) and return values + * are prefixes computed by 'Namespaces.getPreferredPrefix(…)' or any other means. + * We fetch those prefixes for performance reasons and for improving the guarantees + * that the URI → prefix mapping is stable, since JAXB seems to require them for + * writing namespaces in XML. + */ + String prefix = out.getPrefix(uri); + if (prefix == null) { + prefix = Namespaces.getPreferredPrefix(uri, name.getPrefix()); + out.setPrefix(prefix, uri); + /* + * The above call for 'getPreferredPrefix' above is required: JAXB seems to need the prefixes + * for recognizing namespaces. The prefix shall be computed in the same way than 'exportIfNew'. + * We enter in this block only for the root element, before to parse 'xmlns' attributes. For + * all other elements after the root elements, above call to 'out.getPrefix(uri)' should succeed. + */ + } + name = new QName(uri, r.exportProperty(name.getLocalPart()), prefix); + } + } + return name; + } + + /** * Returns the attribute to write in the XML document. * If there is no name change, then this method returns the given instance as-is. */ - private Attribute export(Attribute attribute) { + private Attribute export(Attribute attribute) throws XMLStreamException { final QName originalName = attribute.getName(); - final QName name = version.export(originalName); + final QName name = export(originalName); if (name != originalName) { attribute = new FilteredEvent.Attr(attribute, name); } @@ -104,6 +140,10 @@ final class FilteredWriter extends Filte if (exported != uri) { return uniqueNamespaces.computeIfAbsent(exported, (k) -> { return new FilteredEvent.NS(namespace, Namespaces.getPreferredPrefix(k, namespace.getPrefix()), k); + /* + * The new prefix selected by above line will be saved by out.add(Namespace) + * after this method has been invoked by the NAMESPACE case of add(XMLEvent). + */ }); } final Namespace c = uniqueNamespaces.put(uri, namespace); // No namespace change needed. Overwrite wrapper if any. @@ -165,7 +205,7 @@ final class FilteredWriter extends Filte uniqueNamespaces.clear(); // Discard entries created by NAMESPACE events. final StartElement e = event.asStartElement(); final QName originalName = e.getName(); - final QName name = version.export(originalName); + final QName name = export(originalName); boolean changed = name != originalName; for (final Iterator<Attribute> it = e.getAttributes(); it.hasNext();) { final Attribute a = it.next(); @@ -175,7 +215,7 @@ final class FilteredWriter extends Filte } final List<Namespace> namespaces = export(e.getNamespaces(), changed); if (namespaces != null) { - event = new FilteredEvent.Start(e, name, namespaces, attributes(), version); + event = new Event(e, name, namespaces, attributes(), version); } else { renamedAttributes.clear(); } @@ -184,7 +224,7 @@ final class FilteredWriter extends Filte case END_ELEMENT: { final EndElement e = event.asEndElement(); final QName originalName = e.getName(); - final QName name = version.export(originalName); + final QName name = export(originalName); final List<Namespace> namespaces = export(e.getNamespaces(), name != originalName); if (namespaces != null) { event = new FilteredEvent.End(e, name, namespaces); @@ -250,18 +290,60 @@ final class FilteredWriter extends Filte * <p>Implemented as a matter of principle, but JAXB did not invoked this method in our tests.</p> */ @Override - public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { - out.setNamespaceContext(FilteredNamespaces.importNS(context, version)); + public void setNamespaceContext(final NamespaceContext context) throws XMLStreamException { + out.setNamespaceContext(FilteredNamespaces.asXML(context, version)); } /** - * Returns the context of the underlying writer wrapped in a filter that convert the namespaces on the fly. + * Returns a naming context suitable for consumption by JAXB marshallers. + * The {@link XMLEventWriter} wrapped by this {@code FilteredWriter} 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 {@code FilteredWriter} facade expects the namespaces declared in JAXB annotations. Consequently this + * method returns an adapter that converts namespaces on the fly. * - * @see FilteredEvent.Start#getNamespaceContext() + * @see Event#getNamespaceContext() */ @Override public NamespaceContext getNamespaceContext() { - return FilteredNamespaces.exportNS(out.getNamespaceContext(), version); + return FilteredNamespaces.asJAXB(out.getNamespaceContext(), version); + } + + /** + * Wraps the {@link StartElement} produced by JAXB for using the namespaces used in the XML document. + */ + private static final class Event extends FilteredEvent.Start { + /** Wraps the given event with potentially different name, namespaces and attributes. */ + Event(StartElement event, QName name, List<Namespace> namespaces, List<Attribute> attributes, FilterVersion version) { + super(event, name, namespaces, attributes, version); + } + + /** + * Gets the URI used in the XML document for the given prefix used in JAXB annotations. + * At marshalling time, events are created by JAXB using namespaces used in JAXB annotations. + * {@link FilteredWriter} wraps those events for converting those namespaces to the ones used + * in the XML document. + * + * <div class="note"><b>Example:</b> the {@code "cit"} prefix from ISO 19115-3:2016 standard + * represents the {@code "http://standards.iso.org/iso/19115/-3/mdb/1.0"} namespace, which is + * mapped to {@code "http://www.isotc211.org/2005/gmd"} in the legacy ISO 19139:2007 standard. + * That later URI is returned.</div> + */ + @Override + public String getNamespaceURI(final String prefix) { + return version.exportNS(event.getNamespaceURI(prefix)); + } + + /** + * Returns a context mapping prefixes used in JAXB annotations to namespaces used in XML document. + * The {@link FilteredNamespaces#getNamespaceURI(String)} method in that context shall do the same + * work than {@link #getNamespaceURI(String)} in this event. + * + * @see FilteredNamespaces#getNamespaceURI(String) + */ + @Override + public NamespaceContext getNamespaceContext() { + return FilteredNamespaces.asXML(event.getNamespaceContext(), version); + } } /** Modified: sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java?rev=1824269&r1=1824268&r2=1824269&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java [UTF-8] Wed Feb 14 21:42:41 2018 @@ -50,8 +50,9 @@ public final strictfp class FilteredName * Tests {@link FilteredNamespaces#getPrefixes(String)}. */ @Test + @SuppressWarnings("unchecked") // TODO: remove with JDK9 public void testGetPrefixes() { - final NamespaceContext fns = FilteredNamespaces.exportNS(this, FilterVersion.ISO19139); + final NamespaceContext fns = FilteredNamespaces.asXML(this, FilterVersion.ISO19139); final Iterator<String> it = fns.getPrefixes(LegacyNamespaces.GMD); final Set<String> prefixes = new HashSet<>(); while (it.hasNext()) { @@ -91,7 +92,7 @@ public final strictfp class FilteredName */ @Test public void testGetPrefix() { - final NamespaceContext fns = FilteredNamespaces.exportNS(this, FilterVersion.ISO19139); + final NamespaceContext fns = FilteredNamespaces.asXML(this, FilterVersion.ISO19139); /* * Following tests are not really interesting since FilteredNamespaces, * after failing to find a mapping, just delegates to this.getPrefix(…).