/* * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.sql.rowset.spi; import java.util.logging.*; import java.util.*; import java.sql.*; import javax.sql.*; import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; import java.io.FileNotFoundException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.naming.*; import sun.reflect.misc.ReflectUtil; /** * 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 * implementations 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 * @since 1.5 */ public class SyncFactory { /** * 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 final String ROWSET_SYNC_PROVIDER = "rowset.provider.classname"; /** * The standard property-id for a synchronization provider implementation * vendor name. */ public static final String ROWSET_SYNC_VENDOR = "rowset.provider.vendor"; /** * The standard property-id for a synchronization provider implementation * version tag. */ public static final String ROWSET_SYNC_PROVIDER_VERSION = "rowset.provider.version"; /** * The standard resource file name. */ private static String ROWSET_PROPERTIES = "rowset.properties"; /** * Permission required to invoke setJNDIContext and setLogger */ private static final SQLPermission SET_SYNCFACTORY_PERMISSION = new SQLPermission("setSyncFactory"); /** * 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 volatile Logger rsLogger; /** * 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; /** * Adds 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. * *

     * {@code
     * SyncProvider p = new MySyncProvider();
     * InitialContext ic = new InitialContext();
     * ic.bind ("jdbc/rowset/MySyncProvider", p);
     * } 
* * 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() { /* * Using Initialization on Demand Holder idiom as * Effective Java 2nd Edition,ITEM 71, indicates it is more performant * than the Double-Check Locking idiom. */ return SyncFactoryHolder.factory; } /** * 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 final Properties properties = new Properties(); if (implementations == null) { implementations = new Hashtable<>(); try { // check if user is supplying his Synchronisation Provider // Implementation if not using Oracle'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; try { strRowsetProperties = AccessController.doPrivileged(new PrivilegedAction() { public String run() { return System.getProperty("rowset.properties"); } }, null, new PropertyPermission("rowset.properties", "read")); } catch (Exception ex) { System.out.println("errorget rowset.properties: " + ex); strRowsetProperties = null; }; if (strRowsetProperties != null) { // Load user's implementation of SyncProvider // here. -Drowset.properties=/abc/def/pqr.txt ROWSET_PROPERTIES = strRowsetProperties; try (FileInputStream fis = new FileInputStream(ROWSET_PROPERTIES)) { properties.load(fis); } parseProperties(properties); } /* * Always available */ ROWSET_PROPERTIES = "javax" + strFileSep + "sql" + strFileSep + "rowset" + strFileSep + "rowset.properties"; ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { AccessController.doPrivileged((PrivilegedExceptionAction) () -> { try (InputStream stream = (cl == null) ? ClassLoader.getSystemResourceAsStream(ROWSET_PROPERTIES) : cl.getResourceAsStream(ROWSET_PROPERTIES)) { if (stream == null) { throw new SyncFactoryException("Resource " + ROWSET_PROPERTIES + " not found"); } properties.load(stream); } return null; }); } catch (PrivilegedActionException ex) { Throwable e = ex.getException(); if (e instanceof SyncFactoryException) { throw (SyncFactoryException) e; } else { SyncFactoryException sfe = new SyncFactoryException(); sfe.initCause(ex.getException()); throw sfe; } } 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; try { providerImpls = AccessController.doPrivileged(new PrivilegedAction() { public String run() { return System.getProperty(ROWSET_SYNC_PROVIDER); } }, null, new PropertyPermission(ROWSET_SYNC_PROVIDER, "read")); } catch (Exception ex) { providerImpls = null; } 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 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 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, * the providerID is {@code null}, or * some error was encountered when trying to invoke this provider. */ public static SyncProvider getInstance(String providerID) throws SyncFactoryException { if(providerID == null) { throw new SyncFactoryException("The providerID cannot be null"); } 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(); } try { ReflectUtil.checkPackageAccess(providerID); } catch (java.security.AccessControlException e) { SyncFactoryException sfe = new SyncFactoryException(); sfe.initCause(e); throw sfe; } // 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 load 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 * @throws SyncFactoryException If an error occurs obtaining the registered * providers */ 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. *

* This method checks to see that there is an {@code SQLPermission} * object which grants the permission {@code setSyncFactory} * before allowing the method to succeed. If a * {@code SecurityManager} exists and its * {@code checkPermission} method denies calling {@code setLogger}, * this method throws a * {@code java.lang.SecurityException}. * * @param logger A Logger object instance * @throws java.lang.SecurityException if a security manager exists and its * {@code checkPermission} method denies calling {@code setLogger} * @throws NullPointerException if the logger is null * @see SecurityManager#checkPermission */ public static void setLogger(Logger logger) { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(SET_SYNCFACTORY_PERMISSION); } if(logger == null){ throw new NullPointerException("You must provide a 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. *

* This method checks to see that there is an {@code SQLPermission} * object which grants the permission {@code setSyncFactory} * before allowing the method to succeed. If a * {@code SecurityManager} exists and its * {@code checkPermission} method denies calling {@code setLogger}, * this method throws a * {@code java.lang.SecurityException}. * * @param logger a Logger object instance * @param level a Level object instance indicating the degree of logging * required * @throws java.lang.SecurityException if a security manager exists and its * {@code checkPermission} method denies calling {@code setLogger} * @throws NullPointerException if the logger is null * @see SecurityManager#checkPermission * @see LoggingPermission */ public static void setLogger(Logger logger, Level level) { // singleton SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(SET_SYNCFACTORY_PERMISSION); } if(logger == null){ throw new NullPointerException("You must provide a Logger"); } logger.setLevel(level); rsLogger = logger; } /** * Returns the logging object for applications to retrieve * synchronization events posted by SyncProvider implementations. * @return The {@code Logger} that has been specified for use by * {@code SyncProvider} implementations * @throws SyncFactoryException if no logging object has been set. */ public static Logger getLogger() throws SyncFactoryException { Logger result = rsLogger; // only one logger per session if (result == null) { throw new SyncFactoryException("(SyncFactory) : No logger has been set"); } return result; } /** * Sets the initial JNDI context from which SyncProvider implementations * can be retrieved from a JNDI namespace *

* This method checks to see that there is an {@code SQLPermission} * object which grants the permission {@code setSyncFactory} * before allowing the method to succeed. If a * {@code SecurityManager} exists and its * {@code checkPermission} method denies calling {@code setJNDIContext}, * this method throws a * {@code java.lang.SecurityException}. * * @param ctx a valid JNDI context * @throws SyncFactoryException if the supplied JNDI context is null * @throws java.lang.SecurityException if a security manager exists and its * {@code checkPermission} method denies calling {@code setJNDIContext} * @see SecurityManager#checkPermission */ public static synchronized void setJNDIContext(javax.naming.Context ctx) throws SyncFactoryException { SecurityManager sec = System.getSecurityManager(); if (sec != null) { sec.checkPermission(SET_SYNCFACTORY_PERMISSION); } if (ctx == null) { throw new SyncFactoryException("Invalid JNDI context supplied"); } ic = ctx; } /** * Controls JNDI context initialization. * * @throws SyncFactoryException if an error occurs parsing the JNDI context */ private static synchronized void initJNDIContext() throws SyncFactoryException { if ((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); } } /** * Lazy initialization Holder class used by {@code getSyncFactory} */ private static class SyncFactoryHolder { static final SyncFactory factory = new SyncFactory(); } } /** * 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; } }