Skip to content

Instantly share code, notes, and snippets.

@jirutka
Created March 27, 2013 15:47
Show Gist options
  • Save jirutka/5255269 to your computer and use it in GitHub Desktop.
Save jirutka/5255269 to your computer and use it in GitHub Desktop.
EclipseLink MOXy helpers for better handling of namespaces.
/* Copyright 2013 Jakub Jirutka. All rights reserved.
*
* "THE KOFOLA-WARE LICENSE" (Revision 1):
* Jakub Jirutka originally wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a Kofola in return. <jakub@jirutka.cz>
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.oxm.NamespacePrefixMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.w3c.dom.Document;
/**
* Context Resolver that initializes MOXy/JAXB context from OXM metadata in XML
* mapping files.
*
* @author Jakub Jirutka <jakub@jirutka.cz>
*/
@Provider
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
public class MOXyContextResolver implements ContextResolver<JAXBContext>, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(MOXyContextResolver.class);
private File[] bindingFiles;
private NamespacePrefixMapper namespacePrefixMapper;
private JAXBContext context;
public void afterPropertiesSet() throws Exception {
createContext();
}
public JAXBContext getContext(Class<?> type) {
return context;
}
/**
* Creates new instance of {@link JAXBContext} of MOXy from the given
* OXM mapping files.
*
* @throws JAXBException
* @throws FileNotFoundException
*/
private void createContext() throws JAXBException, FileNotFoundException {
ClassLoader classLoader = getClass().getClassLoader();
List<Source> metadata = new ArrayList<Source>(bindingFiles.length);
StringBuilder contextPath = new StringBuilder();
LOG.info("Creating JAXBContext with MOXy XML bindings");
for (int i = 0; i < bindingFiles.length; i++) {
File file = bindingFiles[i];
metadata.add(new StreamSource(file));
if (i != 0) contextPath.append(":");
contextPath.append(getPackageNameFromBinding(file));
}
Map<String, Object> props = new HashMap<String, Object>(1);
props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
if (namespacePrefixMapper != null) {
props.put(org.eclipse.persistence.jaxb.JAXBContext.NAMESPACE_PREFIX_MAPPER, namespacePrefixMapper);
}
context = JAXBContext.newInstance(contextPath.toString(), classLoader, props);
}
/**
* Reads package-name from the given OXM mapping file.
*
* <pre>{@code
* <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
* ...
* package-name="cz.jirutka.atom.jaxb">
* ...
* </xml-bindings>
* }</pre>
*
* @param bindingFile
* @return fully qualified package name
*/
private String getPackageNameFromBinding(File bindingFile) {
Document doc;
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.parse(bindingFile);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return doc.getDocumentElement().getAttribute("package-name");
}
//////// Accessors ////////
public File[] getBindingFiles() {
return bindingFiles;
}
public void setBindingFiles(File[] bindingFiles) {
this.bindingFiles = bindingFiles;
}
public NamespacePrefixMapper getNamespacePrefixMapper() {
return namespacePrefixMapper;
}
public void setNamespacePrefixMapper(NamespacePrefixMapper namespacePrefixMapper) {
this.namespacePrefixMapper = namespacePrefixMapper;
}
}
/* Copyright 2013 Jakub Jirutka. All rights reserved.
*
* "THE KOFOLA-WARE LICENSE" (Revision 1):
* Jakub Jirutka originally wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a Kofola in return. <jakub@jirutka.cz>
*/
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.eclipse.persistence.oxm.NamespacePrefixMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
/**
* Configurable mapper of XML namespaces and prefixes for MOXy.
*
* <p>This is very useful especially when you have many OXM mapping files
* with common namespaces. In this situation, MOXy fails to use declared
* default namespace.</p>
*
* @author Jakub Jirutka <jakub@jirutka.cz>
*/
public class MOXyNamespacePrefixMapper extends NamespacePrefixMapper {
private static final Logger LOG = LoggerFactory.getLogger(MOXyNamespacePrefixMapper.class);
private final Map<String, String> mapping;
private String[] namespaces;
/**
* Creates new instance from the given XML file with declared namespace
* prefixes. It's basically MOXy's <tt>xml-schema</tt> as the root element
* with nested <tt>xml-ns</tt> elements, for example:
*
* <pre>{@code
* <xml-schema>
* <xml-ns prefix="" namespace-uri="http://www.w3.org/2005/Atom" />
* <xml-ns prefix="osearch" namespace-uri="http://a9.com/-/spec/opensearch/1.1/" />
* <xml-ns prefix="xlink" namespace-uri="http://www.w3.org/1999/xlink" />
* <xml-ns prefix="xsi" namespace-uri="http://www.w3.org/2001/XMLSchema-instance" />
* </xml-schema>
* }</pre>
*
* @param configFile
*/
public MOXyNamespacePrefixMapper(File configFile) {
Assert.isTrue(configFile.canRead(), "Could not read file: " + configFile);
LOG.trace("Parsing namespaces mapping from file: {}", configFile);
try {
this.mapping = parseNamespacesMapping(configFile);
initialize();
} catch (DocumentException ex) {
throw new RuntimeException(ex);
}
}
/**
* Creates new instance from the given map that contains namespace URIs
* as the keys and their prefixes as the values.
*
* @param namespacesMapping map of URI : prefix
*/
public MOXyNamespacePrefixMapper(Map<String, String> namespacesMapping) {
this.mapping = namespacesMapping;
initialize();
}
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
LOG.trace("Requesting prefix for namespace {} with suggested prefix {}",
namespaceUri, suggestion, requirePrefix);
if (mapping.containsKey(namespaceUri)) {
String prefix = mapping.get(namespaceUri);
LOG.trace("Returns preferred prefix: {}", prefix);
return prefix;
} else {
LOG.trace("No preferred prefix found, returns suggested: {}", suggestion);
return suggestion;
}
}
@Override
public String[] getPreDeclaredNamespaceUris() {
return namespaces;
}
private void initialize() {
this.namespaces = mapping.keySet().toArray(new String[0]);
if (LOG.isInfoEnabled()) {
for (Entry<String, String> entry : mapping.entrySet()) {
LOG.info("Registering predefined namespace {} with prefix '{}'", entry.getKey(), entry.getValue());
}
}
}
/**
* @see #MOXyNamespacePrefixMapper(java.io.File)
*
* @param file XML file
* @return map of URI : prefix
* @throws DocumentException
*/
private Map<String, String> parseNamespacesMapping(File file) throws DocumentException {
Map<String, String> map = new HashMap<String, String>();
Document doc = new SAXReader().read(file);
Element root = doc.getRootElement();
for (Object node : root.elements("xml-ns")) {
Element xmlns = (Element) node;
String prefix = xmlns.attributeValue("prefix");
String uri = xmlns.attributeValue("namespace-uri");
map.put(uri, prefix);
}
return map;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment