package net.xoetrope.optional.data.pojo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xml.jaxp.JaxpXmlParser;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.data.XModel;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.InputSource;
/**
* <p>A data source for working with Hibernate POJOs. When the application is loaded the
* datasources are instantiated and in the case of a XPojoDataSource the
* XPojoRoot instance specified by the <code>root</code> element is
* instantiated and configured. The configuration project involves traversing
* the class hierarchy and setting up XPojoModel nodes or proxies for each class
* in the pojo hierarchy, applying Hibernate mapping configuration to the POJO adapters that
* are used in the application. The configuration can specify naming overrides if the
* names established by reflection are not suitable</p>
* <p>Copyright (c) Xoetrope Ltd., 2001-2007<br>
* License: see license.txt
*/
public class XHibernatePojoDataSource extends XPersistentPojoDataSource
{
// the names of the collections that can occur in Hibernate mapping files
private static final String[] COLLECTION_TYPES =
{ "bag", "idbag", "set", "list", "map" };
// the fetch types that specifie non-lazily initialized collections
private static final String[] FETCH_TYPES =
{ "join", "select", "subselect" };
// stores the configuration of lazily initialized collections
protected Hashtable hibernateConfiguration;
// an xml parser used to parse Hibernate mapping files
protected JaxpXmlParser xmlParser;
/**
* Creates a new instance of XHibernatePojoDataSource
* @param project the owner project
*/
public XHibernatePojoDataSource( XProject project )
{
super( project );
hibernateConfiguration = new Hashtable();
xmlParser = JaxpXmlParser.getInstance( project );
xmlParser.setEntityResolver( new XHibernateEntityResolver() );
readMappingFiles();
}
/**
* Gets the xml element describing overrides of the class
* being adapted by the specified adapter. The runtime class name
* might be different from the one defined in the source code because
* of the Hibernate proxy object.
* @param adapter the adapter object
* @return XmlElement object describing the overrides.
*/
protected XmlElement getOverrideXml( XPojoAdapter adapter )
{
String className = getSourceClassName( adapter.getAdapterClassName() );
return (XmlElement)overrides.get( className );
}
/**
* Overrides the specified adapter, adds the information about
* lazily initialized properties.
* @param adapter the adapter to be overrided.
*/
protected void overrideAdapter( XPojoAdapter adapter )
{
// get the original class name
String className = getSourceClassName( adapter.getAdapterClassName() );
// get the list of lazily loaded properties of the adapted class
List properties = (List)hibernateConfiguration.get( className );
if ( properties != null ) {
XPersistentPojoAdapter ad = (XPersistentPojoAdapter)adapter;
// iterate over the properties
Iterator propertiesIter = properties.iterator();
while ( propertiesIter.hasNext() ) {
String propertyName = (String)propertiesIter.next();
// mark that the collection should be accessed within an open persitence context
ad.setGetterTransaction( propertyName, null );
}
}
super.overrideAdapter( adapter );
}
/**
* Gets the class name as it was declared in the source code.
* The passed className might be different because of the Hibernate
* proxy object.
* @param className the runtime class name
* @return the original class name
*/
private String getSourceClassName( String className )
{
int idx = className.indexOf( "$" );
return ( idx > 0 ? className.substring( 0, idx ) : className );
}
/**
* Creates and returns a new instance of XHibernatePojoModel
* @param parent the parent model of the model node
* which is to be created
* @param subpath String consisting of pojo properties,
* must be in the format: propertyName(arguments...)[idx]
* @return XPojoModel object
*/
protected XPojoModel createPojoModel( XModel parent, String subPath )
{
return ( new XHibernatePojoModel( parent, subPath, this ));
}
/**
* Creates and returns a new instance of XHibernatePojoModel
* @param parent the parent model node
* @pojo the underlying POJO
* @return XPojoModel object
*/
protected XPojoModel createPojoModel( XModel parent, Object pojo )
{
return ( new XHibernatePojoModel( parent, pojo, this ));
}
/**
* Reads the hibernate mapping files and configures the adapters.
*/
private void readMappingFiles()
{
// read mapping file
List mappingFiles = new ArrayList();
try {
BufferedReader br = currentProject.getBufferedReader( "hibernate.cfg.xml" );
XmlElement rootXml = XmlSource.read( br ).getFirstChildNamed( "session-factory" );
if ( rootXml != null ) {
Enumeration enumChildren = rootXml.getChildren().elements();
// iterate through the children
while ( enumChildren.hasMoreElements() ) {
XmlElement childXml = (XmlElement)enumChildren.nextElement();
if ( !"mapping".equals( childXml.getName() ))
continue;
// store the name of the mapping file
String fileName = childXml.getAttribute( "resource" );
if ( fileName != null )
mappingFiles.add( fileName );
}
br.close();
}
}
catch( Exception ex ) {
DebugLogger.logError( "failed to read Hibernate mapping file names" );
ex.printStackTrace();
return;
}
// read the configuration from the mapping files
Iterator fileIter = mappingFiles.iterator();
while ( fileIter.hasNext() ) {
String fileName = (String)fileIter.next();
if ( fileName != null )
readMappingFile( fileName );
}
}
/**
* Configures the POJO adapters of the classes whose
* mapping configuration is contained in the specified Hibernate mapping file.
* @param fileName the name of the Hibernate mapping file.
*/
private void readMappingFile( String fileName )
{
try {
BufferedReader br = currentProject.getBufferedReader( fileName );
// get the mapping file content
XmlElement rootXml = xmlParser.parse( br );
// get the name of the package containing the mapped classes
String packageName = rootXml.getAttribute( "package" );
if ( packageName == null )
return;
// read the entity definitions
Enumeration enumClasses = rootXml.getChildren().elements();
while ( enumClasses.hasMoreElements() ) {
// read the configuration of a single class
XmlElement classXml = (XmlElement)enumClasses.nextElement();
if ( !"class".equals( classXml.getName() ))
continue;
String className = ( packageName + "." + classXml.getAttribute( "name" ));
// iterate over the properties of a class
Enumeration enumProperties = classXml.getChildren().elements();
while ( enumProperties.hasMoreElements() ) {
// read the property configuration
XmlElement propertyXml = (XmlElement)enumProperties.nextElement();
// check whether the xml node defines a "lazy" collection
if ( isLazyCollection( propertyXml )) {
// mark that the collection is lazily initialized
String propertyName = propertyXml.getAttribute( "name" );
List properties = (List)hibernateConfiguration.get( className );
if ( properties == null )
hibernateConfiguration.put( className, properties = new ArrayList() );
properties.add( propertyName );
}
}
}
br.close();
}
catch( Exception ex ) {
DebugLogger.logError( "unable to read the file: " + fileName );
ex.printStackTrace();
}
}
/**
* Indicates whether the specified xml node specifies a Hibernate lazily
* initialized collection.
* @param propertyXml xml node describing POJO property, it should belong
* to a Hibernate entity mapping file.
*/
private boolean isLazyCollection( XmlElement propertyXml )
{
if ( propertyXml == null )
return false;
// check if the given xml node specifies a collection
String propertyType = propertyXml.getName();
boolean ic = false;
for ( int i = 0; !ic && ( i < COLLECTION_TYPES.length ); i++ )
ic = COLLECTION_TYPES[ i ].equalsIgnoreCase( propertyType );
if ( !ic )
return false; // the property is not a collection
// check if the collection is lazily initialized
String fetchType = propertyXml.getAttribute( "fetch" );
if ( fetchType == null )
return true; // no "fetch" attribute, collection is lazy
// check the value of the "fetch" attribute
boolean il = true;
for ( int i = 0; il && ( i < FETCH_TYPES.length ); i++ )
il = !FETCH_TYPES[ i ].equalsIgnoreCase( fetchType );
return il;
}
/**
* An entity resolver used to avoid loading remote DTD files when
* reading Hibernate mapping files.
*/
private class XHibernateEntityResolver implements EntityResolver
{
public InputSource resolveEntity( String publicID, String systemID )
throws SAXException
{
InputSource inputSource = null;
// don't load DTD files
if ( systemID.endsWith( ".dtd" )) {
// create an empty InputStream object
InputStream inputStream = ( new InputStream() {
public int read() throws IOException { return -1; }
});
// create the data-less input source
inputSource = new InputSource( inputStream );
}
return inputSource;
}
}
}