package net.xoetrope.optional.data.pojo;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import net.xoetrope.data.XDataSource;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.optional.resources.XProjectClassLoader;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.build.BuildProperties;
import net.xoetrope.xui.data.XModel;
import net.xoetrope.xui.helper.ReflectionHelper;
/**
* <p>A data source for working with 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. 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 XPojoDataSource extends XDataSource
{
protected XPojoContext pojoContext;
protected XModel pojoRootModel;
protected Hashtable overrides;
protected Hashtable adapters;
/**
* Creates a new instance of XPojoDataSource
* @param project the owner project
*/
public XPojoDataSource( XProject project )
{
super( project );
overrides = new Hashtable();
adapters = new Hashtable();
}
/**
* Returns the pojo context object
* @return pojo context
*/
public XPojoContext getPojoContext()
{
return pojoContext;
}
/**
* Creates and returns new XPojoContext object
* @param className the name of the class whose instance
* should be returned
* @param classLoader class loader object to load the
* specified class definition
* @return XPojoContext object
*/
protected XPojoContext instantiatePojoContext( String className, ClassLoader classLoader )
throws Exception
{
Object pojoContext = null;
if ( classLoader != null )
pojoContext = Class.forName( className, true, classLoader ).newInstance();
else
pojoContext = Class.forName( className ).newInstance();
return (XPojoContext)pojoContext;
}
/**
* Recursively load the model data
* @param source the source element
* @param model the model for the source element
*/
public void loadTable( XmlElement source, XModel model )
{
try {
// Setup the pojo context
XmlElement contextElement = source.getFirstChildNamed( "context" );
String contextClassName = contextElement.getAttribute( "class" );
String configFileName = contextElement.getAttribute( "config" );
XProjectClassLoader classLoader = (XProjectClassLoader)currentProject.getProjectClassLoader();
pojoContext = instantiatePojoContext( contextClassName, classLoader );
pojoContext.setProject( currentProject );
currentProject.setObject( "PojoContext", pojoContext ); //???
if ( configFileName != null )
pojoContext.configure( currentProject.findResource( configFileName ));
// Load the customizations of the POJO classes
XmlElement overridesElement = source.getFirstChildNamed( "overrides" );
if ( overridesElement != null ) {
Vector overrideSpecs = overridesElement.getChildren();
int numOverrides = overrideSpecs.size();
for ( int i = 0; i < numOverrides; i++ ) {
XmlElement overrideElement = (XmlElement)overrideSpecs.elementAt( i );
String pojoClassName = overrideElement.getAttribute( "class" );
overrides.put( pojoClassName, overrideElement );
}
}
// Setup the POJO root
Object pojoRoot = null;
XmlElement rootElement = source.getFirstChildNamed( "root" );
String pojoRootName = "pojo";
if ( rootElement != null ) {
String rootClassName = rootElement.getAttribute( "class" );
pojoRootName = rootElement.getAttribute( "id" );
configFileName = rootElement.getAttribute( "config" );
int numParams = rootElement.getChildren().size();
if ( numParams == 0 ) {
if ( classLoader != null )
pojoRoot = Class.forName( rootClassName.trim(), true, classLoader ).newInstance();
else
pojoRoot = Class.forName( rootClassName.trim() ).newInstance();
}
else {
Class[] params = new Class[ numParams ];
Object[] values = new Object[ numParams ];
for ( int i = 0; i < numParams; i++ ) {
XmlElement paramElement = (XmlElement)rootElement.getChildren().elementAt( i );
String paramType = paramElement.getAttribute( "class" );
params[ i ] = ReflectionHelper.getParamClass( paramType );
values[ i ] = ReflectionHelper.getObject( paramType, paramElement.getAttribute( "value" ));
}
Class klass = null;
if ( classLoader != null )
klass = Class.forName( rootClassName.trim(), true, classLoader );
else
klass = Class.forName( rootClassName.trim() );
pojoRoot = ReflectionHelper.constructViaReflection( klass, params, values );
}
if ( pojoRoot instanceof XPojoRoot ) {
((XPojoRoot)pojoRoot).setProject( currentProject );
if ( configFileName != null )
((XPojoRoot)pojoRoot).configure( currentProject.findResource( configFileName ));
}
}
else {
pojoRoot = pojoContext.getRoot();
}
// Attach the POJO topology. No need to scan the topology as nodes are
// adapted on demand
pojoRootModel = createPojoModel( model, pojoRoot );
((XPojoModel)pojoRootModel).setId( pojoRootName );
model.append( (XModel)pojoRootModel );
}
catch ( Exception ex ) {
DebugLogger.logError( "Failed to load the POJO datasource" );
if ( BuildProperties.DEBUG )
ex.printStackTrace();
}
}
public XPojoAdapter getAdapter( Object pojo )
{
Class pojoClass = pojo.getClass();
XPojoAdapter adapter = null;
// check whether the class of the given POJO requires an adapter
if ( XPojoHelper.needsAdapter( pojoClass )) {
String pojoClassName = pojoClass.getName();
adapter = (XPojoAdapter)adapters.get( pojoClassName );
if ( adapter == null ) {
adapters.put( pojoClassName, adapter = createAdapter( pojoClass ));
overrideAdapter( adapter );
}
}
return adapter;
}
/**
* Creates and returns a new instance of <code>XPojoAdapter</code>
* @param pojoClass pojoClass class to be adapted
* by the returned adapter
* @return XPojoAdapter object
*/
protected XPojoAdapter createAdapter( Class pojoClass )
{
return ( new XPojoAdapter( pojoClass, this ));
}
/**
* Creates and returns a new instance of <code>XPojoModel</code>
* @param parent the parent model of the node
* which is to be created
* @param subpath String consisting of pojo properties,
* which must be in the format: propertyName(arguments...)[idx]
* @return XPojoModel object
*/
protected XPojoModel createPojoModel( XModel parent, String subPath )
{
return ( new XPojoModel( parent, subPath, this ));
}
/**
* Creates and returns a new instance of <code>XPojoModel</code>
* @param parent the parent model node
* @pojo the underlying pojo
* @return XPojoModel object
*/
protected XPojoModel createPojoModel( XModel parent, Object pojo )
{
return ( new XPojoModel( parent, pojo, this ));
}
/**
* Gets the xml element describing overrides of the class
* being adapted by the specified adapter.
* @param adapter the adapter object
* @return XmlElement object describing the overrides.
*/
protected XmlElement getOverrideXml( XPojoAdapter adapter )
{
String className = adapter.getAdapterClassName();
return (XmlElement)overrides.get( className );
}
/**
* Override the adapter specification loaded via reflection and add the
* customization specified by the configuration
* @param adapter the adapter being customized
*/
protected void overrideAdapter( XPojoAdapter adapter )
{
XmlElement override = getOverrideXml( adapter );
if ( override != null ) {
Enumeration enumOverrides = override.getChildren().elements();
while ( enumOverrides.hasMoreElements() ) {
XmlElement propertyElement = (XmlElement)enumOverrides.nextElement();
customizeProperty( adapter, propertyElement );
}
}
}
/*
* Customizes given property, saves customization details in
* the given adapter object.
* @param adapter the adapter of the class whose property
* is to be customized.
* @param propertyElement the xml element describing the customization details.
*/
protected void customizeProperty( XPojoAdapter adapter, XmlElement propertyElement )
{
String elementName = propertyElement.getName();
if ( "property".equals( elementName )) {
String propertyName = propertyElement.getAttribute( "id" );
String getterSig = propertyElement.getAttribute( "getter" );
String setterSig = propertyElement.getAttribute( "setter" );
adapter.customizeProperty( propertyName, getterSig, setterSig );
}
else if ( "transient_property".equals( elementName )) {
String propertyName = propertyElement.getAttribute( "id" );
adapter.setTransient( propertyName );
}
}
}