/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.xml;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.geotoolkit.resources.Errors;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.sis.xml.XML;
import org.apache.sis.xml.XLink;
import org.apache.sis.xml.MarshalContext;
import org.apache.sis.xml.MarshallerPool;
import org.apache.sis.xml.ReferenceResolver;
import org.apache.sis.internal.jaxb.TypeRegistration;
/**
*
* @author Guilhem Legal (Geomatys)
*/
public class AnchoredMarshallerPool extends MarshallerPool {
public AnchoredMarshallerPool() throws JAXBException {
this(TypeRegistration.getSharedContext());
}
private final String schemaLocation;
/**
* Binds string labels with URNs or anchors.
*
* @see #addAnchor(String, URI)
*/
private final Map<String,URI> anchors;
/**
* Creates a new factory for the given JAXB context, with a default empty namespace.
*
* @param context The JAXB context.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final JAXBContext context) throws JAXBException {
this(null, context, null, new HashMap<String,URI>(), null);
}
public AnchoredMarshallerPool(final JAXBContext context, final Map<String, Object> properties) throws JAXBException {
this(null, context, null, new HashMap<String,URI>(), properties);
}
/**
* Creates a new factory for the given class to be bound, with a default empty namespace.
*
* @param classesToBeBound The classes to be bound, for example {@code DefaultMetadata.class}.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final Class<?>... classesToBeBound) throws JAXBException {
this(null, classesToBeBound);
}
/**
* Creates a new factory for the given class to be bound.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
* @param classesToBeBound The classes to be bound, for example {@code DefaultMetadata.class}.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final String rootNamespace, final Class<?>... classesToBeBound) throws JAXBException {
this(rootNamespace, null, classesToBeBound);
}
/**
* Creates a new factory for the given class to be bound.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
* @param classesToBeBound The classes to be bound, for example {@code DefaultMetadata.class}.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final String rootNamespace, final String schemaLocation, final Class<?>... classesToBeBound) throws JAXBException {
this(rootNamespace, JAXBContext.newInstance(classesToBeBound), schemaLocation, new HashMap<String,URI>(), null);
}
/**
* Creates a new factory for the given packages, with a default empty namespace.
* The separator character for the packages is the colon.
*
* @param packages The packages in which JAXB will search for annotated classes to be bound,
* for example {@code "org.apache.sis.metadata.iso:org.apache.sis.metadata.iso.citation"}.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final String packages) throws JAXBException {
this(null, packages);
}
/**
* Creates a new factory for the given packages. The separator character for the packages is the colon.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
* @param packages The packages in which JAXB will search for annotated classes to be bound,
* for example {@code "org.apache.sis.metadata.iso:org.apache.sis.metadata.iso.citation"}.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final String rootNamespace, final String packages) throws JAXBException {
this(rootNamespace, packages, (String) null);
}
/**
* Creates a new factory for the given packages. The separator character for the packages is the colon.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
* @param packages The packages in which JAXB will search for annotated classes to be bound,
* for example {@code "org.apache.sis.metadata.iso:org.apache.sis.metadata.iso.citation"}.
* @param schemaLocation The main xsd schema location for all the returned xml.
* @throws JAXBException If the JAXB context can not be created.
*/
public AnchoredMarshallerPool(final String rootNamespace, final String packages, final String schemaLocation) throws JAXBException {
this(rootNamespace, JAXBContext.newInstance(packages), schemaLocation, new HashMap<String,URI>(), null);
}
private AnchoredMarshallerPool(final String rootNamespace, final JAXBContext context, final String schemaLocation,
final Map<String,URI> anchors, final Map<String, Object> properties) throws JAXBException
{
super(context, getProperties(rootNamespace, anchors, properties));
this.schemaLocation = schemaLocation;
this.anchors = anchors;
}
/**
* Return a Map of Marshaller properties.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
*/
public static Map<String, String> getProperties(final String rootNamespace) {
final Map<String, String> properties = new HashMap<>();
properties.put(XML.DEFAULT_NAMESPACE, rootNamespace);
return properties;
}
/**
* Return a Map of Marshaller properties.
*
* @param rootNamespace The root namespace, for example {@code "http://www.isotc211.org/2005/gmd"}.
* @param anchors Map of anchors to be stored as the {@link #anchors} field.
*/
private static Map<String, Object> getProperties(final String rootNamespace, final Map<String,URI> anchors, final Map<String, Object> previousProperties) {
final Map<String, Object> properties;
if (previousProperties != null) {
properties = previousProperties;
} else {
properties = new HashMap<>();
}
if (rootNamespace != null) {
properties.put(XML.DEFAULT_NAMESPACE, rootNamespace);
}
properties.put(XML.DEFAULT_NAMESPACE, rootNamespace);
properties.put(XML.RESOLVER, new ReferenceResolver() {
@Override
public XLink anchor(final MarshalContext context, final Object value, final CharSequence text) {
final URI linkage;
synchronized (anchors) {
linkage = anchors.get(value);
}
if (linkage != null) {
final XLink xlink = new XLink();
xlink.setHRef(linkage);
return xlink;
}
return super.anchor(context, value, text);
}
});
return properties;
}
@Override
protected Marshaller createMarshaller() throws JAXBException {
final Marshaller marshaller = super.createMarshaller();
if (schemaLocation != null) {
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
}
return marshaller;
}
/**
* Adds a label associated to an URN. For some methods expected to return a code as a
* {@link String} object, the code will be completed by the given URN in an {@code AnchorType}
* element.
* <p>
* This method should be invoked from subclasses constructor only. Anchors can be added
* but can not be removed or modified.
*
* @param label The label associated to the URN.
* @param linkage The URN.
* @throws IllegalStateException If a URN is already associated to the given linkage.
*/
public void addAnchor(final String label, final URI linkage) throws IllegalStateException {
synchronized (anchors) {
final URI old = anchors.put(label, linkage);
if (old != null) {
anchors.put(label, old);
throw new IllegalStateException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, label));
}
}
}
}