As noted by emmanual + eepstein it would be preferable to have the EntityResolver configurable in hibernate.
I intend to raise a JIRA enhancement request for this today.
But in the meantime, as requested here's the code:
HibernateConfiguration is a class that extends net.sf.hibernate.cfg.Configuration
Code:
package com.acme.hibernate;
import com.acme.utils.xml.CompositeEntityResolver;
import java.io.*;
import java.util.*;
import org.apache.log4j.Logger;
import org.dom4j.io.SAXReader;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.util.XMLHelper;
public class HibernateConfiguration extends Configuration
{
private static Logger log = Logger.getLogger(HibernateConfiguration.class);
protected Configuration doConfigure(InputStream stream, String resourceName) throws HibernateException
{
org.dom4j.Document doc;
try
{
List errors = new ArrayList();
SAXReader saxReader = createSAXReader(resourceName, errors);
doc = saxReader.read(new InputSource(stream));
if (errors.size() != 0)
throw new MappingException("invalid configuration", (Throwable) errors.get(0));
}
catch (Exception e)
{
log.error("problem parsing configuration" + resourceName, e);
throw new HibernateException("problem parsing configuration" + resourceName, e);
}
return doConfigure(doc);
}
protected SAXReader createSAXReader(String resourceName, List errors)
{
SAXReader saxReader = XMLHelper.createSAXReader(resourceName, errors);
EntityResolver entityResolver = saxReader.getEntityResolver();
CompositeEntityResolver compositeResolver = new CompositeEntityResolver();
compositeResolver.addResolver(entityResolver);
EntityResolver acmeEntityResolver = new ACMEEntityResolver();
compositeResolver.addResolver(acmeEntityResolver);
saxReader.setEntityResolver(compositeResolver);
return saxReader;
}
public Configuration addFile(String xmlFile) throws MappingException
{
log.info("Mapping file: " + xmlFile);
try
{
List errors = new ArrayList();
SAXReader saxReader = createSAXReader(xmlFile, errors);
add(saxReader.read(new File(xmlFile)));
if (errors.size() != 0)
throw new MappingException("invalid mapping", (Throwable) errors.get(0));
return this;
}
catch (Exception e)
{
log.error("Could not configure datastore from file: " + xmlFile, e);
throw new MappingException(e);
}
}
public Configuration addInputStream(InputStream xmlInputStream) throws MappingException
{
try
{
List errors = new ArrayList();
SAXReader saxReader = createSAXReader("XML InputStream", errors);
add( saxReader.read(new InputSource(xmlInputStream)));
if (errors.size() != 0)
throw new MappingException("invalid mapping", (Throwable) errors.get(0));
return this;
}
catch (MappingException me)
{
throw me;
}
catch (Exception e)
{
log.error("Could not configure datastore from input stream", e);
throw new MappingException(e);
}
}
public Configuration addXML(String xml) throws MappingException
{
if ( log.isDebugEnabled() ) log.debug("Mapping XML:\n" + xml);
try {
List errors = new ArrayList();
SAXReader saxReader = createSAXReader("XML String", errors);
add( saxReader.read( new StringReader(xml) ) );
if ( errors.size()!=0 ) throw new MappingException( "invalid mapping", (Throwable) errors.get(0) );
}
catch (Exception e) {
log.error("Could not configure datastore from XML", e);
throw new MappingException(e);
}
return this;
}
}
CompositeEntityResolver implements EntityResolver.
It contains 0 or more EntityResolvers.
It delegates call to resolve entities these contained EntityResolvers, stopping with the first one that returns a non-null result.
Code:
package com.acme.utils.xml;
import ims.utils.Logging;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class CompositeEntityResolver implements EntityResolver
{
private static Logger log = Logger.getLogger(CompositeEntityResolver.class);
List children;
public CompositeEntityResolver()
{
children = new java.util.ArrayList();
}
/* (non-Javadoc)
* @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
*/
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException
{
Iterator iterator = children.iterator();
InputSource source = null;
while(iterator.hasNext())
{
EntityResolver resolver = (EntityResolver) iterator.next();
source = resolver.resolveEntity(publicId, systemId);
if (null != source)
break;
}
return source;
}
public void addResolver(EntityResolver resolver)
{
if (!children.contains(resolver))
{
children.add(resolver);
}
}
public void removeResolver(EntityResolver resolver)
{
if (children.contains(resolver))
{
children.remove(resolver);
}
}
}
ACMEEntityResolver implements EntityResolver to intercept the entities that whose public id starts with a particular prefix. It then uses the ClassLoader to get the entity as a resource from the classpath.
To implement this I followed the example posted at
http://www.dom4j.org/faq.html#cannot-find-dtdCode:
package com.acme.hibernate;
import ims.utils.Logging;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.log4j.Logger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class ACMEEntityResolver implements EntityResolver
{
private static Logger log = Logger.getLogger(IMSEntityResolver.class);
public static final String ACME_PUBLIC_ID_PREFIX = "-//ACME/";
public static final String ACME_RESOURCE_PREFIX = "acme";
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException
{
InputSource inputSource = null;
if (publicId.startsWith(ACME_PUBLIC_ID_PREFIX))
{
ClassLoader classLoader = getClass().getClassLoader();
URL url = new URL(systemId);
String path = url.getPath();
String resourceName = ACME_RESOURCE_PREFIX+path;
InputStream inputStream = classLoader.getResourceAsStream(resourceName);
if (null == inputStream)
{
log.debug(systemId + "not found in classpath");
}
else
{
log.debug("found " + systemId + " in classpath");
inputSource = new InputSource(inputStream);
inputSource.setPublicId(publicId);
inputSource.setSystemId(systemId);
}
}
return inputSource;
}
}
To use this code you
must instantiate the HibernatedConfiguration rather than the standard net.sf.hibernate.cfg.Configuration
e.g. creating the SessionFactory
Code:
SessionFactory sessionFactory = new HibernateConfiguration().configure().buildSessionFactory();