/**********************************************************************
Copyright (c) 2003 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
2006 Thomas Marti - Implemented autodetect mechanism with priorities
...
**********************************************************************/
package org.jpox.store.rdbms.adapter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import org.jpox.ClassLoaderResolver;
import org.jpox.exceptions.ClassNotResolvedException;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.plugin.ConfigurationElement;
import org.jpox.plugin.Extension;
import org.jpox.plugin.PluginManager;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
/**
* Factory of RDBMS datastore adapters for JPOX.
* Acts as a registry of adapters to RDBMS that can be selected for use in JPOX.
* Uses a singleton pattern, and the factory can be accessed using the getInstance() method.
* <H3>Autodetection</H3>
* JPOX provides a level of autodetection of the adapter to use for a particular
* RDBMS. It uses the DatabaseMetaData to extract the "product name" and matches
* this against a series of "adapter aliases" that JPOX has defined internally or that can
* be contributed by JPOX plugins that extend the <code>org.jpox.store_datastoreadapter</code>
* extension point. These extension can define a priority, so if multiple adapter match for the
* given database connection the one with the highest priority is chosen.
* <H3>Specification of adapter class</H3>
* You can call {@link #getDatastoreAdapter(ClassLoaderResolver, Connection, String, PluginManager)}
* passing the adapter class name directly if you know which you want to use. This allows for
* user-defined database adapters.
*
* @version $Revision: 1.20 $
*/
public class RDBMSAdapterFactory
{
/** Localisation of messages */
private static final Localiser LOCALISER = Localiser.getInstance("org.jpox.store.rdbms.Localisation",
RDBMSManager.class.getClassLoader());
/**
* Accessor for the RDBMSAdapterFactory for JPOX
* @return The manager of type information
*/
public static RDBMSAdapterFactory getInstance()
{
return new RDBMSAdapterFactory();
}
/**
* Protected constructor to prevent outside instantiation
*/
protected RDBMSAdapterFactory()
{
}
/**
* Accessor for an adapter, given a Connection to the datastore.
* @param clr ClassLoader resolver for resolving the adapter class
* @param conn The Connection
* @param adapterClassName Name of the class of the database adapter to use
* @param pluginMgr the Plug-in manager
* @return The database adapter for this connection.
* @throws SQLException Thrown if a DB error occurs.
**/
public DatastoreAdapter getDatastoreAdapter(ClassLoaderResolver clr, Connection conn, String adapterClassName, PluginManager pluginMgr)
throws SQLException
{
DatastoreAdapter adapter = null;
DatabaseMetaData metadata = conn.getMetaData();
// Get a new adapter
adapter = getNewDatastoreAdapter(clr, metadata, adapterClassName, pluginMgr);
if (adapter == null)
{
// Nothing suitable found so warn the user and continue with the generic adapter
JPOXLogger.DATASTORE.warn(LOCALISER.msg("051000"));
adapter = new DatabaseAdapter(metadata);
}
return adapter;
}
/**
* Accessor for the adapter for a specified datastore product.
* @param clr ClassLoader resolver for resolving the adapter class
* @param metadata Database MetaData for the RDBMS
* @param adapterClassName Name of the class of the database adapter (null implies use autodetect)
* @param pluginMgr the Plug-in manager
* @return Instance of the database adapter
*/
protected DatastoreAdapter getNewDatastoreAdapter(ClassLoaderResolver clr, DatabaseMetaData metadata, String adapterClassName, PluginManager pluginMgr)
{
if (metadata == null)
{
return null;
}
String productName = null;
if (adapterClassName == null)
{
// No adapter specified, so use "autodetection" based on the metadata to find the most suitable
try
{
productName = metadata.getDatabaseProductName();
if (productName == null)
{
JPOXLogger.DATASTORE.error(LOCALISER.msg("051024"));
return null;
}
}
catch (SQLException sqe)
{
JPOXLogger.DATASTORE.error(LOCALISER.msg("051025", sqe));
return null;
}
}
// Instantiate the adapter class
final Object adapter_obj;
try
{
Class adapterClass = getAdapterClass(pluginMgr, adapterClassName, productName, clr);
final Object[] ctr_args = new Object[]{metadata};
final Class[] ctr_args_classes = new Class[]{DatabaseMetaData.class};
// Create an instance of the datastore adapter
final Constructor ctr = adapterClass.getConstructor(ctr_args_classes);
try
{
ctr.setAccessible(true);
adapter_obj = ctr.newInstance(ctr_args);
}
catch (InvocationTargetException ite)
{
if (ite.getTargetException() != null && ite.getTargetException() instanceof JPOXDataStoreException)
{
throw (JPOXDataStoreException) ite.getTargetException();
}
return null;
}
catch (Exception e)
{
JPOXLogger.DATASTORE.error(LOCALISER.msg("051026", adapterClassName, e));
return null;
}
}
catch (ClassNotResolvedException ex)
{
JPOXLogger.DATASTORE.error(LOCALISER.msg("051026", adapterClassName, ex));
return null;
}
catch (NoSuchMethodException nsme)
{
JPOXLogger.DATASTORE.error(LOCALISER.msg("051026", adapterClassName, nsme));
return null;
}
return (DatastoreAdapter) adapter_obj;
}
/**
* Accessor for the adapter class for a specified datastore product.
* @param pluginMgr the Plug-in manager
* @param adapterClassName Name of the class of the database adapter (null implies use autodetect)
* @param productName the database product name
* @param clr ClassLoader resolver for resolving the adapter class
* @return the adapater class
*/
protected Class getAdapterClass(PluginManager pluginMgr, String adapterClassName, String productName, ClassLoaderResolver clr)
{
ConfigurationElement highestMatchingAdapter = null;
int highestPriority = Integer.MIN_VALUE;
Extension[] ex = pluginMgr.getExtensionPoint("org.jpox.store_datastoreadapter").getExtensions();
for (int i = 0; i < ex.length; i++)
{
ConfigurationElement[] confElm = ex[i].getConfigurationElements();
for (int c = 0; c < confElm.length; c++)
{
if (adapterClassName != null)
{
//if the user selected adapter is defined by one of the plug-ins use the classloader of the plug-in
if (confElm[c].getAttribute("class-name").equals(adapterClassName))
{
return pluginMgr.loadClass(confElm[c].getExtension().getPlugin().getSymbolicName(), confElm[c].getAttribute("class-name"));
}
}
else
{
String vendorId = confElm[c].getAttribute("vendor-id");
if (productName.toLowerCase().indexOf(vendorId.toLowerCase()) >= 0)
{
int priority = Integer.parseInt(confElm[c].getAttribute("priority"));
if (priority >= highestPriority)
{
if (priority > highestPriority && highestMatchingAdapter != null)
{
JPOXLogger.DATASTORE.warn(LOCALISER.msg("051002",
highestMatchingAdapter.getAttribute("class-name"), highestMatchingAdapter.getAttribute("vendor-id"),
String.valueOf(priority), confElm[c].getAttribute("class-name")));
}
highestMatchingAdapter = confElm[c];
highestPriority = priority;
}
}
}
}
}
if (adapterClassName != null)
{
//if the user selected adapter was not found in one of the plug-ins, load from the ClassLoaderResolver
return clr.classForName(adapterClassName, false);
}
if (highestMatchingAdapter != null)
{
return pluginMgr.loadClass(highestMatchingAdapter.getExtension().getPlugin().getSymbolicName(), highestMatchingAdapter.getAttribute("class-name"));
}
return null;
}
}