/* * @(#)SyncFactory.java 1.13 04/07/17 * @(#)SyncFactory.java 1.11 04/06/25 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.sql.rowset.spi; import java.util.Map; import java.util.Hashtable; import java.util.Enumeration; import java.util.Vector; import java.util.Properties; import java.util.Collection; import java.util.StringTokenizer; import java.util.logging.*; import java.util.*; import java.sql.*; import javax.sql.*; import java.io.FileInputStream; import java.io.IOException; import java.io.FileNotFoundException; import javax.naming.*; /** * The Service Provider Interface (SPI) mechanism that generates SyncProvider * instances to be used by disconnected RowSet objects. * The SyncProvider instances in turn provide the * javax.sql.RowSetReader object the RowSet object * needs to populate itself with data and the * javax.sql.RowSetWriter object it needs to * propagate changes to its * data back to the underlying data source. *

* Because the methods in the SyncFactory class are all static, * there is only one SyncFactory object * per Java VM at any one time. This ensures that there is a single source from which a * RowSet implementation can obtain its SyncProvider * implementation. *

*

1.0 Overview

* The SyncFactory class provides an internal registry of available * synchronization provider implementations (SyncProvider objects). * This registry may be queried to determine which * synchronization providers are available. * The following line of code gets an enumeration of the providers currently registered. *
 *     java.util.Enumeration e = SyncFactory.getRegisteredProviders();
 * 
* All standard RowSet implementations must provide at least two providers: * * Note that the JDBC RowSet Implementations include the SyncProvider * implemtations RIOptimisticProvider and RIXmlProvider, * which satisfy this requirement. *

* The SyncFactory class provides accessor methods to assist * applications in determining which synchronization providers are currently * registered with the SyncFactory. *

* Other methods let RowSet persistence providers be * registered or de-registered with the factory mechanism. This * allows additional synchronization provider implementations to be made * available to RowSet objects at run time. *

* Applications can apply a degree of filtering to determine the level of * synchronization that a SyncProvider implementation offers. * The following criteria determine whether a provider is * made available to a RowSet object: *

    *
  1. If a particular provider is specified by a RowSet object, and * the SyncFactory does not contain a reference to this provider, * a SyncFactoryException is thrown stating that the synchronization * provider could not be found. *

    *

  2. If a RowSet implementation is instantiated with a specified * provider and the specified provider has been properly registered, the * requested provider is supplied. Otherwise a SyncFactoryException * is thrown. *

    *

  3. If a RowSet object does not specify a * SyncProvider implementation and no additional * SyncProvider implementations are available, the reference * implementation providers are supplied. *
*

2.0 Registering SyncProvider Implementations

*

* Both vendors and developers can register SyncProvider * implementations using one of the following mechanisms. *

* Next, an application will register the JNDI context with the * SyncFactory instance. This allows the SyncFactory * to browse within the JNDI context looking for SyncProvider * implementations. *
 *    Hashtable appEnv = new Hashtable();
 *    appEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
 *    appEnv.put(Context.PROVIDER_URL, "iiop://hostname/providers");
 *    Context ctx = new InitialContext(appEnv);
 *
 *    SyncFactory.registerJNDIContext(ctx);
 * 
* If a RowSet object attempts to obtain a MyProvider * object, the SyncFactory will try to locate it. First it searches * for it in the system properties, then it looks in the resource files, and * finally it checks the JNDI context that has been set. The SyncFactory * instance verifies that the requested provider is a valid extension of the * SyncProvider abstract class and then gives it to the * RowSet object. In the following code fragment, a new * CachedRowSet object is created and initialized with * env, which contains the binding to MyProvider. *
 *    Hashtable env = new Hashtable();
 *    env.put(SyncFactory.ROWSET_SYNC_PROVIDER, "com.fred.providers.MyProvider");
 *    CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl(env); 
 * 
* Further details on these mechanisms are available in the * javax.sql.rowset.spi package specification. * * @author Jonathan Bruce * @see javax.sql.rowset.spi.SyncProvider * @see javax.sql.rowset.spi.SyncFactoryException */ public class SyncFactory { /* * The variable that represents the singleton instance * of the SyncFactory class. */ private static SyncFactory syncFactory = null; /** * Creates a new SyncFactory object, which is the singleton * instance. * Having a private constructor guarantees that no more than * one SyncProvider object can exist at a time. */ private SyncFactory() {}; /** * The standard property-id for a synchronization provider implementation * name. */ public static String ROWSET_SYNC_PROVIDER = "rowset.provider.classname"; /** * The standard property-id for a synchronization provider implementation * vendor name. */ public static String ROWSET_SYNC_VENDOR = "rowset.provider.vendor"; /** * The standard property-id for a synchronization provider implementation * version tag. */ public static String ROWSET_SYNC_PROVIDER_VERSION = "rowset.provider.version"; /** * The standard resource file name. */ private static String ROWSET_PROPERTIES = "rowset.properties"; /** * The RI Optimistic Provider. */ private static String default_provider = "com.sun.rowset.providers.RIOptimisticProvider"; /** * The initial JNDI context where SyncProvider implementations can * be stored and from which they can be invoked. */ private static Context ic; /** * The Logger object to be used by the SyncFactory. */ private static Logger rsLogger; /** * */ private static Level rsLevel; /** * The registry of available SyncProvider implementations. * See section 2.0 of the class comment for SyncFactory for an * explanation of how a provider can be added to this registry. */ private static Hashtable implementations; /** * Internal sync object used to maintain the SPI as a singleton */ private static Object logSync = new Object(); /** * Internal PrintWriter field for logging facility */ private static java.io.PrintWriter logWriter = null; /** * Adds the the given synchronization provider to the factory register. Guidelines * are provided in the SyncProvider specification for the * required naming conventions for SyncProvider * implementations. *

* Synchronization providers bound to a JNDI context can be * registered by binding a SyncProvider instance to a JNDI namespace. *

* Furthermore, an initial JNDI context should be set with the * SyncFactory using the setJNDIContext method. * The SyncFactory leverages this context to search for * available SyncProvider objects bound to the JNDI * context and its child nodes. * * @param providerID A String object with the unique ID of the * synchronization provider being registered * @throws SyncFactoryException if an attempt is made to supply an empty * or null provider name * @see #setJNDIContext */ public static synchronized void registerProvider(String providerID) throws SyncFactoryException { ProviderImpl impl = new ProviderImpl(); impl.setClassname(providerID); initMapIfNecessary(); implementations.put(providerID, impl); } /** * Returns the SyncFactory singleton. * * @return the SyncFactory instance */ public static SyncFactory getSyncFactory(){ // This method uses the Singleton Design Pattern // with Double-Checked Locking Pattern for // 1. Creating single instance of the SyncFactory // 2. Make the class thread safe, so that at one time // only one thread enters the synchronized block // to instantiate. // if syncFactory object is already there // don't go into synchronized block and return // that object. // else go into synchronized block if(syncFactory == null){ synchronized(SyncFactory.class) { if(syncFactory == null){ syncFactory = new SyncFactory(); } //end if } //end synchronized block } //end if return syncFactory; } /** * Removes the designated currently registered synchronization provider from the * Factory SPI register. * * @param providerID The unique-id of the synchronization provider * @throws SyncFactoryException If an attempt is made to * unregister a SyncProvider implementation that was not registered. */ public static synchronized void unregisterProvider(String providerID) throws SyncFactoryException { initMapIfNecessary(); if (implementations.containsKey(providerID)) { implementations.remove(providerID); } } private static String colon = ":"; private static String strFileSep = "/"; private static synchronized void initMapIfNecessary() throws SyncFactoryException { // Local implementation class names and keys from Properties // file, translate names into Class objects using Class.forName // and store mappings Properties properties = new Properties(); if (implementations == null) { implementations = new Hashtable(); try { // check if user is supplying his Synchronisation Provider // Implementation if not use Sun's implementation. // properties.load(new FileInputStream(ROWSET_PROPERTIES)); // The rowset.properties needs to be in jdk/jre/lib when // integrated with jdk. // else it should be picked from -D option from command line. // -Drowset.properties will add to standard properties. Similar // keys will over-write /* * Dependent on application */ String strRowsetProperties = System.getProperty("rowset.properties"); if ( strRowsetProperties != null) { // Load user's implementation of SyncProvider // here. -Drowset.properties=/abc/def/pqr.txt ROWSET_PROPERTIES = strRowsetProperties; properties.load(new FileInputStream(ROWSET_PROPERTIES)); parseProperties(properties); } /* * Always available */ ROWSET_PROPERTIES = "javax" + strFileSep + "sql" + strFileSep + "rowset" + strFileSep + "rowset.properties"; // properties.load( // ClassLoader.getSystemResourceAsStream(ROWSET_PROPERTIES)); ClassLoader cl = Thread.currentThread().getContextClassLoader(); properties.load(cl.getResourceAsStream(ROWSET_PROPERTIES)); parseProperties(properties); // removed else, has properties should sum together } catch (FileNotFoundException e) { throw new SyncFactoryException("Cannot locate properties file: " + e); } catch (IOException e) { throw new SyncFactoryException("IOException: " + e); } /* * Now deal with -Drowset.provider.classname * load additional properties from -D command line */ properties.clear(); String providerImpls = System.getProperty(ROWSET_SYNC_PROVIDER); if (providerImpls != null) { int i = 0; if (providerImpls.indexOf(colon) > 0) { StringTokenizer tokenizer = new StringTokenizer(providerImpls, colon); while (tokenizer.hasMoreElements()) { properties.put(ROWSET_SYNC_PROVIDER + "." + i, tokenizer.nextToken()); i++; } } else { properties.put(ROWSET_SYNC_PROVIDER, providerImpls); } parseProperties(properties); } } } /** * The internal boolean switch that indicates whether a JNDI * context has been established or not. */ private static boolean jndiCtxEstablished = false; /** * The internal debug switch. */ private static boolean debug = false; /** * Internal registry count for the number of providers contained in the * registry. */ private static int providerImplIndex = 0; /** * Internal handler for all standard property parsing. Parses standard * ROWSET properties and stores lazy references into the the internal registry. */ private static void parseProperties(Properties p) { ProviderImpl impl = null; String key = null; String[] propertyNames = null; for (Enumeration e = p.propertyNames(); e.hasMoreElements() ;) { String str = (String)e.nextElement(); int w = str.length(); if (str.startsWith(SyncFactory.ROWSET_SYNC_PROVIDER)) { impl = new ProviderImpl(); impl.setIndex(providerImplIndex++); if (w == (SyncFactory.ROWSET_SYNC_PROVIDER).length()) { // no property index has been set. propertyNames = getPropertyNames(false); } else { // property index has been set. propertyNames = getPropertyNames(true, str.substring(w-1)); } key = p.getProperty(propertyNames[0]); impl.setClassname(key); impl.setVendor(p.getProperty(propertyNames[1])); impl.setVersion(p.getProperty(propertyNames[2])); implementations.put(key, impl); } } } /** * Used by the parseProperties methods to disassemble each property tuple. */ private static String[] getPropertyNames(boolean append) { return getPropertyNames(append, null); } /** * Disassembles each property and its associated value. Also handles * overloaded property names that contain indexes. */ private static String[] getPropertyNames(boolean append, String propertyIndex) { String dot = "."; String[] propertyNames = new String[] {SyncFactory.ROWSET_SYNC_PROVIDER, SyncFactory.ROWSET_SYNC_VENDOR, SyncFactory.ROWSET_SYNC_PROVIDER_VERSION}; if (append) { for (int i = 0; i < propertyNames.length; i++) { propertyNames[i] = propertyNames[i] + dot + propertyIndex; } return propertyNames; } else { return propertyNames; } } /** * Internal debug method that outputs the registry contents. */ private static void showImpl(ProviderImpl impl) { System.out.println("Provider implementation:"); System.out.println("Classname: " + impl.getClassname()); System.out.println("Vendor: " + impl.getVendor()); System.out.println("Version: " + impl.getVersion()); System.out.println("Impl index: " + impl.getIndex()); } /** * Returns the SyncProvider instance identified by providerID. * * @param providerID the unique identifier of the provider * @return a SyncProvider implementation * @throws SyncFactoryException If the SyncProvider cannot be found or * some error was encountered when trying to invoke this provider. */ public static SyncProvider getInstance(String providerID) throws SyncFactoryException { initMapIfNecessary(); // populate HashTable initJNDIContext(); // check JNDI context for any additional bindings ProviderImpl impl = (ProviderImpl)implementations.get(providerID); if (impl == null) { // Requested SyncProvider is unavailable. Return default provider. return new com.sun.rowset.providers.RIOptimisticProvider(); } // Attempt to invoke classname from registered SyncProvider list Class c = null; try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); /** * The SyncProvider implementation of the user will be in * the classpath. We need to find the ClassLoader which loads * this SyncFactory and try to laod the SyncProvider class from * there. **/ c = Class.forName(providerID, true, cl); if (c != null) { return (SyncProvider)c.newInstance(); } else { return new com.sun.rowset.providers.RIOptimisticProvider(); } } catch (IllegalAccessException e) { throw new SyncFactoryException("IllegalAccessException: " + e.getMessage()); } catch (InstantiationException e) { throw new SyncFactoryException("InstantiationException: " + e.getMessage()); } catch (ClassNotFoundException e) { throw new SyncFactoryException("ClassNotFoundException: " + e.getMessage()); } } /** * Returns an Enumeration of currently registered synchronization * providers. A RowSet implementation may use any provider in * the enumeration as its SyncProvider object. *

* At a minimum, the reference synchronization provider allowing * RowSet content data to be stored using a JDBC driver should be * possible. * * @return Enumeration A enumeration of available synchronization * providers that are registered with this Factory */ public static Enumeration getRegisteredProviders() throws SyncFactoryException { initMapIfNecessary(); // return a collection of classnames // of type SyncProvider return implementations.elements(); } /** * Sets the logging object to be used by the SyncProvider * implementation provided by the SyncFactory. All * SyncProvider implementations can log their events to * this object and the application can retrieve a handle to this * object using the getLogger method. * * @param logger A Logger object instance */ public static void setLogger(Logger logger) { rsLogger = logger; } /** * Sets the logging object that is used by SyncProvider * implementations provided by the SyncFactory SPI. All * SyncProvider implementations can log their events * to this object and the application can retrieve a handle to this * object using the getLogger method. * * @param logger a Logger object instance * @param level a Level object instance indicating the degree of logging * required */ public static void setLogger(Logger logger, Level level) { // singleton rsLogger = logger; rsLogger.setLevel(level); } /** * Returns the logging object for applications to retrieve * synchronization events posted by SyncProvider implementations. * * @throws SyncFactoryException if no logging object has been set. */ public static Logger getLogger() throws SyncFactoryException { // only one logger per session if(rsLogger == null){ throw new SyncFactoryException("(SyncFactory) : No logger has been set"); } return rsLogger; } /** * Sets the initial JNDI context from which SyncProvider implementations * can be retrieved from a JNDI namespace * * @param ctx a valid JNDI context * @throws SyncFactoryException if the supplied JNDI context is null */ public static void setJNDIContext(javax.naming.Context ctx) throws SyncFactoryException { if (ctx == null) { throw new SyncFactoryException("Invalid JNDI context supplied"); } ic = ctx; jndiCtxEstablished = true; } /** * Controls JNDI context intialization. * * @throws SyncFactoryException if an error occurs parsing the JNDI context */ private static void initJNDIContext() throws SyncFactoryException { if (jndiCtxEstablished && (ic != null) && (lazyJNDICtxRefresh == false)) { try { parseProperties(parseJNDIContext()); lazyJNDICtxRefresh = true; // touch JNDI namespace once. } catch (NamingException e) { e.printStackTrace(); throw new SyncFactoryException("SPI: NamingException: " + e.getExplanation()); } catch (Exception e) { e.printStackTrace(); throw new SyncFactoryException("SPI: Exception: " + e.getMessage()); } } } /** * Internal switch indicating whether the JNDI namespace should be re-read. */ private static boolean lazyJNDICtxRefresh = false; /** * Parses the set JNDI Context and passes bindings to the enumerateBindings * method when complete. */ private static Properties parseJNDIContext() throws NamingException { NamingEnumeration bindings = ic.listBindings(""); Properties properties = new Properties(); // Hunt one level below context for available SyncProvider objects enumerateBindings(bindings, properties); return properties; } /** * Scans each binding on JNDI context and determines if any binding is an * instance of SyncProvider, if so, add this to the registry and continue to * scan the current context using a re-entrant call to this method until all * bindings have been enumerated. */ private static void enumerateBindings(NamingEnumeration bindings, Properties properties) throws NamingException { boolean syncProviderObj = false; // move to parameters ? try { Binding bd = null; Object elementObj = null; String element = null; while (bindings.hasMore()) { bd = (Binding)bindings.next(); element = bd.getName(); elementObj = bd.getObject(); if (!(ic.lookup(element) instanceof Context)) { // skip directories/sub-contexts if (ic.lookup(element) instanceof SyncProvider) { syncProviderObj = true; } } if (syncProviderObj) { SyncProvider sync = (SyncProvider)elementObj; properties.put(SyncFactory.ROWSET_SYNC_PROVIDER, sync.getProviderID()); syncProviderObj = false; // reset } } } catch (javax.naming.NotContextException e) { bindings.next(); // Re-entrant call into method enumerateBindings(bindings, properties); } } } /** * Internal class that defines the lazy reference construct for each registered * SyncProvider implementation. */ class ProviderImpl extends SyncProvider { private String className = null; private String vendorName = null; private String ver = null; private int index; public void setClassname(String classname) { className = classname; } public String getClassname() { return className; } public void setVendor(String vendor) { vendorName = vendor; } public String getVendor() { return vendorName; } public void setVersion(String providerVer) { ver = providerVer; } public String getVersion() { return ver; } public void setIndex(int i) { index = i; } public int getIndex() { return index; } public int getDataSourceLock() throws SyncProviderException { int dsLock = 0; try { dsLock = SyncFactory.getInstance(className).getDataSourceLock(); } catch(SyncFactoryException sfEx) { throw new SyncProviderException(sfEx.getMessage()); } return dsLock; } public int getProviderGrade() { int grade = 0; try { grade = SyncFactory.getInstance(className).getProviderGrade(); } catch(SyncFactoryException sfEx) { // } return grade; } public String getProviderID() { return className; } /* public javax.sql.RowSetInternal getRowSetInternal() { try { return SyncFactory.getInstance(className).getRowSetInternal(); } catch(SyncFactoryException sfEx) { // } } */ public javax.sql.RowSetReader getRowSetReader() { RowSetReader rsReader = null;; try { rsReader = SyncFactory.getInstance(className).getRowSetReader(); } catch(SyncFactoryException sfEx) { // } return rsReader; } public javax.sql.RowSetWriter getRowSetWriter() { RowSetWriter rsWriter = null; try { rsWriter = SyncFactory.getInstance(className).getRowSetWriter(); } catch(SyncFactoryException sfEx) { // } return rsWriter; } public void setDataSourceLock(int param) throws SyncProviderException { try { SyncFactory.getInstance(className).setDataSourceLock(param); } catch(SyncFactoryException sfEx) { throw new SyncProviderException(sfEx.getMessage()); } } public int supportsUpdatableView() { int view = 0; try { view = SyncFactory.getInstance(className).supportsUpdatableView(); } catch(SyncFactoryException sfEx) { // } return view; } }