1/*
2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.sql.rowset.spi;
27
28import java.util.logging.*;
29import java.util.*;
30
31import java.sql.*;
32import javax.sql.*;
33
34import java.io.FileInputStream;
35import java.io.InputStream;
36import java.io.IOException;
37import java.io.FileNotFoundException;
38import java.security.AccessController;
39import java.security.PrivilegedAction;
40import java.security.PrivilegedActionException;
41import java.security.PrivilegedExceptionAction;
42
43import javax.naming.*;
44import sun.reflect.misc.ReflectUtil;
45
46/**
47 * The Service Provider Interface (SPI) mechanism that generates <code>SyncProvider</code>
48 * instances to be used by disconnected <code>RowSet</code> objects.
49 * The <code>SyncProvider</code> instances in turn provide the
50 * <code>javax.sql.RowSetReader</code> object the <code>RowSet</code> object
51 * needs to populate itself with data and the
52 * <code>javax.sql.RowSetWriter</code> object it needs to
53 * propagate changes to its
54 * data back to the underlying data source.
55 * <P>
56 * Because the methods in the <code>SyncFactory</code> class are all static,
57 * there is only one <code>SyncFactory</code> object
58 * per Java VM at any one time. This ensures that there is a single source from which a
59 * <code>RowSet</code> implementation can obtain its <code>SyncProvider</code>
60 * implementation.
61 *
62 * <h3>1.0 Overview</h3>
63 * The <code>SyncFactory</code> class provides an internal registry of available
64 * synchronization provider implementations (<code>SyncProvider</code> objects).
65 * This registry may be queried to determine which
66 * synchronization providers are available.
67 * The following line of code gets an enumeration of the providers currently registered.
68 * <PRE>
69 *     java.util.Enumeration e = SyncFactory.getRegisteredProviders();
70 * </PRE>
71 * All standard <code>RowSet</code> implementations must provide at least two providers:
72 * <UL>
73 *  <LI>an optimistic provider for use with a <code>CachedRowSet</code> implementation
74 *     or an implementation derived from it
75 *  <LI>an XML provider, which is used for reading and writing XML, such as with
76 *       <code>WebRowSet</code> objects
77 * </UL>
78 * Note that the JDBC RowSet Implementations include the <code>SyncProvider</code>
79 * implementations <code>RIOptimisticProvider</code> and <code>RIXmlProvider</code>,
80 * which satisfy this requirement.
81 * <P>
82 * The <code>SyncFactory</code> class provides accessor methods to assist
83 * applications in determining which synchronization providers are currently
84 * registered with the <code>SyncFactory</code>.
85 * <p>
86 * Other methods let <code>RowSet</code> persistence providers be
87 * registered or de-registered with the factory mechanism. This
88 * allows additional synchronization provider implementations to be made
89 * available to <code>RowSet</code> objects at run time.
90 * <p>
91 * Applications can apply a degree of filtering to determine the level of
92 * synchronization that a <code>SyncProvider</code> implementation offers.
93 * The following criteria determine whether a provider is
94 * made available to a <code>RowSet</code> object:
95 * <ol>
96 * <li>If a particular provider is specified by a <code>RowSet</code> object, and
97 * the <code>SyncFactory</code> does not contain a reference to this provider,
98 * a <code>SyncFactoryException</code> is thrown stating that the synchronization
99 * provider could not be found.
100 *
101 * <li>If a <code>RowSet</code> implementation is instantiated with a specified
102 * provider and the specified provider has been properly registered, the
103 * requested provider is supplied. Otherwise a <code>SyncFactoryException</code>
104 * is thrown.
105 *
106 * <li>If a <code>RowSet</code> object does not specify a
107 * <code>SyncProvider</code> implementation and no additional
108 * <code>SyncProvider</code> implementations are available, the reference
109 * implementation providers are supplied.
110 * </ol>
111 * <h3>2.0 Registering <code>SyncProvider</code> Implementations</h3>
112 * <p>
113 * Both vendors and developers can register <code>SyncProvider</code>
114 * implementations using one of the following mechanisms.
115 * <ul>
116 * <LI><B>Using the command line</B><BR>
117 * The name of the provider is supplied on the command line, which will add
118 * the provider to the system properties.
119 * For example:
120 * <PRE>
121 *    -Drowset.provider.classname=com.fred.providers.HighAvailabilityProvider
122 * </PRE>
123 * <li><b>Using the Standard Properties File</b><BR>
124 * The reference implementation is targeted
125 * to ship with J2SE 1.5, which will include an additional resource file
126 * that may be edited by hand. Here is an example of the properties file
127 * included in the reference implementation:
128 * <PRE>
129 *   #Default JDBC RowSet sync providers listing
130 *   #
131 *
132 *   # Optimistic synchronization provider
133 *   rowset.provider.classname.0=com.sun.rowset.providers.RIOptimisticProvider
134 *   rowset.provider.vendor.0=Oracle Corporation
135 *   rowset.provider.version.0=1.0
136 *
137 *   # XML Provider using standard XML schema
138 *   rowset.provider.classname.1=com.sun.rowset.providers.RIXMLProvider
139 *   rowset.provider.vendor.1=Oracle Corporation
140 *   rowset.provider.version.1=1.0
141 * </PRE>
142 * The <code>SyncFactory</code> checks this file and registers the
143 * <code>SyncProvider</code> implementations that it contains. A
144 * developer or vendor can add other implementations to this file.
145 * For example, here is a possible addition:
146 * <PRE>
147 *     rowset.provider.classname.2=com.fred.providers.HighAvailabilityProvider
148 *     rowset.provider.vendor.2=Fred, Inc.
149 *     rowset.provider.version.2=1.0
150 * </PRE>
151 *
152 * <li><b>Using a JNDI Context</b><BR>
153 * Available providers can be registered on a JNDI
154 * context, and the <code>SyncFactory</code> will attempt to load
155 * <code>SyncProvider</code> implementations from that JNDI context.
156 * For example, the following code fragment registers a provider implementation
157 * on a JNDI context.  This is something a deployer would normally do. In this
158 * example, <code>MyProvider</code> is being registered on a CosNaming
159 * namespace, which is the namespace used by J2EE resources.
160 * <PRE>
161 *    import javax.naming.*;
162 *
163 *    Hashtable svrEnv = new  Hashtable();
164 *    srvEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
165 *
166 *    Context ctx = new InitialContext(svrEnv);
167 *    com.fred.providers.MyProvider = new MyProvider();
168 *    ctx.rebind("providers/MyProvider", syncProvider);
169 * </PRE>
170 * </ul>
171 * Next, an application will register the JNDI context with the
172 * <code>SyncFactory</code> instance.  This allows the <code>SyncFactory</code>
173 * to browse within the JNDI context looking for <code>SyncProvider</code>
174 * implementations.
175 * <PRE>
176 *    Hashtable appEnv = new Hashtable();
177 *    appEnv.put(Context.INITIAL_CONTEXT_FACTORY, "CosNaming");
178 *    appEnv.put(Context.PROVIDER_URL, "iiop://hostname/providers");
179 *    Context ctx = new InitialContext(appEnv);
180 *
181 *    SyncFactory.registerJNDIContext(ctx);
182 * </PRE>
183 * If a <code>RowSet</code> object attempts to obtain a <code>MyProvider</code>
184 * object, the <code>SyncFactory</code> will try to locate it. First it searches
185 * for it in the system properties, then it looks in the resource files, and
186 * finally it checks the JNDI context that has been set. The <code>SyncFactory</code>
187 * instance verifies that the requested provider is a valid extension of the
188 * <code>SyncProvider</code> abstract class and then gives it to the
189 * <code>RowSet</code> object. In the following code fragment, a new
190 * <code>CachedRowSet</code> object is created and initialized with
191 * <i>env</i>, which contains the binding to <code>MyProvider</code>.
192 * <PRE>
193 *    Hashtable env = new Hashtable();
194 *    env.put(SyncFactory.ROWSET_SYNC_PROVIDER, "com.fred.providers.MyProvider");
195 *    CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl(env);
196 * </PRE>
197 * Further details on these mechanisms are available in the
198 * <code>javax.sql.rowset.spi</code> package specification.
199 *
200 * @author  Jonathan Bruce
201 * @see javax.sql.rowset.spi.SyncProvider
202 * @see javax.sql.rowset.spi.SyncFactoryException
203 * @since 1.5
204 */
205public class SyncFactory {
206
207    /**
208     * Creates a new <code>SyncFactory</code> object, which is the singleton
209     * instance.
210     * Having a private constructor guarantees that no more than
211     * one <code>SyncProvider</code> object can exist at a time.
212     */
213    private SyncFactory() {
214    }
215
216    /**
217     * The standard property-id for a synchronization provider implementation
218     * name.
219     */
220    public static final String ROWSET_SYNC_PROVIDER =
221            "rowset.provider.classname";
222    /**
223     * The standard property-id for a synchronization provider implementation
224     * vendor name.
225     */
226    public static final String ROWSET_SYNC_VENDOR =
227            "rowset.provider.vendor";
228    /**
229     * The standard property-id for a synchronization provider implementation
230     * version tag.
231     */
232    public static final String ROWSET_SYNC_PROVIDER_VERSION =
233            "rowset.provider.version";
234    /**
235     * The standard resource file name.
236     */
237    private static String ROWSET_PROPERTIES = "rowset.properties";
238
239    /**
240     *  Permission required to invoke setJNDIContext and setLogger
241     */
242    private static final SQLPermission SET_SYNCFACTORY_PERMISSION =
243            new SQLPermission("setSyncFactory");
244    /**
245     * The initial JNDI context where <code>SyncProvider</code> implementations can
246     * be stored and from which they can be invoked.
247     */
248    private static Context ic;
249    /**
250     * The <code>Logger</code> object to be used by the <code>SyncFactory</code>.
251     */
252    private static volatile Logger rsLogger;
253
254    /**
255     * The registry of available <code>SyncProvider</code> implementations.
256     * See section 2.0 of the class comment for <code>SyncFactory</code> for an
257     * explanation of how a provider can be added to this registry.
258     */
259    private static Hashtable<String, SyncProvider> implementations;
260
261    /**
262     * Adds the given synchronization provider to the factory register. Guidelines
263     * are provided in the <code>SyncProvider</code> specification for the
264     * required naming conventions for <code>SyncProvider</code>
265     * implementations.
266     * <p>
267     * Synchronization providers bound to a JNDI context can be
268     * registered by binding a SyncProvider instance to a JNDI namespace.
269     *
270     * <pre>
271     * {@code
272     * SyncProvider p = new MySyncProvider();
273     * InitialContext ic = new InitialContext();
274     * ic.bind ("jdbc/rowset/MySyncProvider", p);
275     * } </pre>
276     *
277     * Furthermore, an initial JNDI context should be set with the
278     * <code>SyncFactory</code> using the <code>setJNDIContext</code> method.
279     * The <code>SyncFactory</code> leverages this context to search for
280     * available <code>SyncProvider</code> objects bound to the JNDI
281     * context and its child nodes.
282     *
283     * @param providerID A <code>String</code> object with the unique ID of the
284     *             synchronization provider being registered
285     * @throws SyncFactoryException if an attempt is made to supply an empty
286     *         or null provider name
287     * @see #setJNDIContext
288     */
289    public static synchronized void registerProvider(String providerID)
290            throws SyncFactoryException {
291
292        ProviderImpl impl = new ProviderImpl();
293        impl.setClassname(providerID);
294        initMapIfNecessary();
295        implementations.put(providerID, impl);
296
297    }
298
299    /**
300     * Returns the <code>SyncFactory</code> singleton.
301     *
302     * @return the <code>SyncFactory</code> instance
303     */
304    public static SyncFactory getSyncFactory() {
305        /*
306         * Using Initialization on Demand Holder idiom as
307         * Effective Java 2nd Edition,ITEM 71, indicates it is more performant
308         * than the Double-Check Locking idiom.
309         */
310        return SyncFactoryHolder.factory;
311    }
312
313    /**
314     * Removes the designated currently registered synchronization provider from the
315     * Factory SPI register.
316     *
317     * @param providerID The unique-id of the synchronization provider
318     * @throws SyncFactoryException If an attempt is made to
319     * unregister a SyncProvider implementation that was not registered.
320     */
321    public static synchronized void unregisterProvider(String providerID)
322            throws SyncFactoryException {
323        initMapIfNecessary();
324        if (implementations.containsKey(providerID)) {
325            implementations.remove(providerID);
326        }
327    }
328    private static String colon = ":";
329    private static String strFileSep = "/";
330
331    private static synchronized void initMapIfNecessary() throws SyncFactoryException {
332
333        // Local implementation class names and keys from Properties
334        // file, translate names into Class objects using Class.forName
335        // and store mappings
336        final Properties properties = new Properties();
337
338        if (implementations == null) {
339            implementations = new Hashtable<>();
340
341            try {
342
343                // check if user is supplying his Synchronisation Provider
344                // Implementation if not using Oracle's implementation.
345                // properties.load(new FileInputStream(ROWSET_PROPERTIES));
346
347                // The rowset.properties needs to be in jdk/jre/lib when
348                // integrated with jdk.
349                // else it should be picked from -D option from command line.
350
351                // -Drowset.properties will add to standard properties. Similar
352                // keys will over-write
353
354                /*
355                 * Dependent on application
356                 */
357                String strRowsetProperties;
358                try {
359                    strRowsetProperties = AccessController.doPrivileged(new PrivilegedAction<String>() {
360                        public String run() {
361                            return System.getProperty("rowset.properties");
362                        }
363                    }, null, new PropertyPermission("rowset.properties", "read"));
364                } catch (Exception ex) {
365                    System.out.println("errorget rowset.properties: " + ex);
366                    strRowsetProperties = null;
367                };
368
369                if (strRowsetProperties != null) {
370                    // Load user's implementation of SyncProvider
371                    // here. -Drowset.properties=/abc/def/pqr.txt
372                    ROWSET_PROPERTIES = strRowsetProperties;
373                    try (FileInputStream fis = new FileInputStream(ROWSET_PROPERTIES)) {
374                        properties.load(fis);
375                    }
376                    parseProperties(properties);
377                }
378
379                /*
380                 * Always available
381                 */
382                ROWSET_PROPERTIES = "javax" + strFileSep + "sql" +
383                        strFileSep + "rowset" + strFileSep +
384                        "rowset.properties";
385
386                try {
387                    AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
388                        InputStream in = SyncFactory.class.getModule().getResourceAsStream(ROWSET_PROPERTIES);
389                        if (in == null) {
390                            throw new SyncFactoryException("Resource " + ROWSET_PROPERTIES + " not found");
391                        }
392                        try (in) {
393                            properties.load(in);
394                        }
395                        return null;
396                    });
397                } catch (PrivilegedActionException ex) {
398                    Throwable e = ex.getException();
399                    if (e instanceof SyncFactoryException) {
400                      throw (SyncFactoryException) e;
401                    } else {
402                        SyncFactoryException sfe = new SyncFactoryException();
403                        sfe.initCause(ex.getException());
404                        throw sfe;
405                    }
406                }
407
408                parseProperties(properties);
409
410            // removed else, has properties should sum together
411
412            } catch (FileNotFoundException e) {
413                throw new SyncFactoryException("Cannot locate properties file: " + e);
414            } catch (IOException e) {
415                throw new SyncFactoryException("IOException: " + e);
416            }
417
418            /*
419             * Now deal with -Drowset.provider.classname
420             * load additional properties from -D command line
421             */
422            properties.clear();
423            String providerImpls;
424            try {
425                providerImpls = AccessController.doPrivileged(new PrivilegedAction<String>() {
426                    public String run() {
427                        return System.getProperty(ROWSET_SYNC_PROVIDER);
428                    }
429                }, null, new PropertyPermission(ROWSET_SYNC_PROVIDER, "read"));
430            } catch (Exception ex) {
431                providerImpls = null;
432            }
433
434            if (providerImpls != null) {
435                int i = 0;
436                if (providerImpls.indexOf(colon) > 0) {
437                    StringTokenizer tokenizer = new StringTokenizer(providerImpls, colon);
438                    while (tokenizer.hasMoreElements()) {
439                        properties.put(ROWSET_SYNC_PROVIDER + "." + i, tokenizer.nextToken());
440                        i++;
441                    }
442                } else {
443                    properties.put(ROWSET_SYNC_PROVIDER, providerImpls);
444                }
445                parseProperties(properties);
446            }
447        }
448    }
449
450    /**
451     * The internal debug switch.
452     */
453    private static boolean debug = false;
454    /**
455     * Internal registry count for the number of providers contained in the
456     * registry.
457     */
458    private static int providerImplIndex = 0;
459
460    /**
461     * Internal handler for all standard property parsing. Parses standard
462     * ROWSET properties and stores lazy references into the internal registry.
463     */
464    private static void parseProperties(Properties p) {
465
466        ProviderImpl impl = null;
467        String key = null;
468        String[] propertyNames = null;
469
470        for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
471
472            String str = (String) e.nextElement();
473
474            int w = str.length();
475
476            if (str.startsWith(SyncFactory.ROWSET_SYNC_PROVIDER)) {
477
478                impl = new ProviderImpl();
479                impl.setIndex(providerImplIndex++);
480
481                if (w == (SyncFactory.ROWSET_SYNC_PROVIDER).length()) {
482                    // no property index has been set.
483                    propertyNames = getPropertyNames(false);
484                } else {
485                    // property index has been set.
486                    propertyNames = getPropertyNames(true, str.substring(w - 1));
487                }
488
489                key = p.getProperty(propertyNames[0]);
490                impl.setClassname(key);
491                impl.setVendor(p.getProperty(propertyNames[1]));
492                impl.setVersion(p.getProperty(propertyNames[2]));
493                implementations.put(key, impl);
494            }
495        }
496    }
497
498    /**
499     * Used by the parseProperties methods to disassemble each property tuple.
500     */
501    private static String[] getPropertyNames(boolean append) {
502        return getPropertyNames(append, null);
503    }
504
505    /**
506     * Disassembles each property and its associated value. Also handles
507     * overloaded property names that contain indexes.
508     */
509    private static String[] getPropertyNames(boolean append,
510            String propertyIndex) {
511        String dot = ".";
512        String[] propertyNames =
513                new String[]{SyncFactory.ROWSET_SYNC_PROVIDER,
514            SyncFactory.ROWSET_SYNC_VENDOR,
515            SyncFactory.ROWSET_SYNC_PROVIDER_VERSION};
516        if (append) {
517            for (int i = 0; i < propertyNames.length; i++) {
518                propertyNames[i] = propertyNames[i] +
519                        dot +
520                        propertyIndex;
521            }
522            return propertyNames;
523        } else {
524            return propertyNames;
525        }
526    }
527
528    /**
529     * Internal debug method that outputs the registry contents.
530     */
531    private static void showImpl(ProviderImpl impl) {
532        System.out.println("Provider implementation:");
533        System.out.println("Classname: " + impl.getClassname());
534        System.out.println("Vendor: " + impl.getVendor());
535        System.out.println("Version: " + impl.getVersion());
536        System.out.println("Impl index: " + impl.getIndex());
537    }
538
539    /**
540     * Returns the <code>SyncProvider</code> instance identified by <i>providerID</i>.
541     *
542     * @param providerID the unique identifier of the provider
543     * @return a <code>SyncProvider</code> implementation
544     * @throws SyncFactoryException If the SyncProvider cannot be found,
545     * the providerID is {@code null}, or
546     * some error was encountered when trying to invoke this provider.
547     */
548    public static SyncProvider getInstance(String providerID)
549            throws SyncFactoryException {
550
551        if(providerID == null) {
552            throw new SyncFactoryException("The providerID cannot be null");
553        }
554
555        initMapIfNecessary(); // populate HashTable
556        initJNDIContext();    // check JNDI context for any additional bindings
557
558        ProviderImpl impl = (ProviderImpl) implementations.get(providerID);
559
560        if (impl == null) {
561            // Requested SyncProvider is unavailable. Return default provider.
562            return new com.sun.rowset.providers.RIOptimisticProvider();
563        }
564
565        try {
566            ReflectUtil.checkPackageAccess(providerID);
567        } catch (java.security.AccessControlException e) {
568            SyncFactoryException sfe = new SyncFactoryException();
569            sfe.initCause(e);
570            throw sfe;
571        }
572
573        // Attempt to invoke classname from registered SyncProvider list
574        Class<?> c = null;
575        try {
576            ClassLoader cl = Thread.currentThread().getContextClassLoader();
577
578            /**
579             * The SyncProvider implementation of the user will be in
580             * the classpath. We need to find the ClassLoader which loads
581             * this SyncFactory and try to load the SyncProvider class from
582             * there.
583             **/
584            c = Class.forName(providerID, true, cl);
585            @SuppressWarnings("deprecation")
586            Object result =  c.newInstance();
587            return (SyncProvider)result;
588
589        } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
590            throw new SyncFactoryException("IllegalAccessException: " + e.getMessage());
591        }
592    }
593
594    /**
595     * Returns an Enumeration of currently registered synchronization
596     * providers.  A <code>RowSet</code> implementation may use any provider in
597     * the enumeration as its <code>SyncProvider</code> object.
598     * <p>
599     * At a minimum, the reference synchronization provider allowing
600     * RowSet content data to be stored using a JDBC driver should be
601     * possible.
602     *
603     * @return Enumeration  A enumeration of available synchronization
604     * providers that are registered with this Factory
605     * @throws SyncFactoryException If an error occurs obtaining the registered
606     * providers
607     */
608    public static Enumeration<SyncProvider> getRegisteredProviders()
609            throws SyncFactoryException {
610        initMapIfNecessary();
611        // return a collection of classnames
612        // of type SyncProvider
613        return implementations.elements();
614    }
615
616    /**
617     * Sets the logging object to be used by the <code>SyncProvider</code>
618     * implementation provided by the <code>SyncFactory</code>. All
619     * <code>SyncProvider</code> implementations can log their events to
620     * this object and the application can retrieve a handle to this
621     * object using the <code>getLogger</code> method.
622     * <p>
623     * This method checks to see that there is an {@code SQLPermission}
624     * object  which grants the permission {@code setSyncFactory}
625     * before allowing the method to succeed.  If a
626     * {@code SecurityManager} exists and its
627     * {@code checkPermission} method denies calling {@code setLogger},
628     * this method throws a
629     * {@code java.lang.SecurityException}.
630     *
631     * @param logger A Logger object instance
632     * @throws java.lang.SecurityException if a security manager exists and its
633     *   {@code checkPermission} method denies calling {@code setLogger}
634     * @throws NullPointerException if the logger is null
635     * @see SecurityManager#checkPermission
636     */
637    public static void setLogger(Logger logger) {
638
639        SecurityManager sec = System.getSecurityManager();
640        if (sec != null) {
641            sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
642        }
643
644        if(logger == null){
645            throw new NullPointerException("You must provide a Logger");
646        }
647        rsLogger = logger;
648    }
649
650    /**
651     * Sets the logging object that is used by <code>SyncProvider</code>
652     * implementations provided by the <code>SyncFactory</code> SPI. All
653     * <code>SyncProvider</code> implementations can log their events
654     * to this object and the application can retrieve a handle to this
655     * object using the <code>getLogger</code> method.
656     * <p>
657     * This method checks to see that there is an {@code SQLPermission}
658     * object  which grants the permission {@code setSyncFactory}
659     * before allowing the method to succeed.  If a
660     * {@code SecurityManager} exists and its
661     * {@code checkPermission} method denies calling {@code setLogger},
662     * this method throws a
663     * {@code java.lang.SecurityException}.
664     *
665     * @param logger a Logger object instance
666     * @param level a Level object instance indicating the degree of logging
667     * required
668     * @throws java.lang.SecurityException if a security manager exists and its
669     *   {@code checkPermission} method denies calling {@code setLogger}
670     * @throws NullPointerException if the logger is null
671     * @see SecurityManager#checkPermission
672     * @see LoggingPermission
673     */
674    public static void setLogger(Logger logger, Level level) {
675        // singleton
676        SecurityManager sec = System.getSecurityManager();
677        if (sec != null) {
678            sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
679        }
680
681        if(logger == null){
682            throw new NullPointerException("You must provide a Logger");
683        }
684        logger.setLevel(level);
685        rsLogger = logger;
686    }
687
688    /**
689     * Returns the logging object for applications to retrieve
690     * synchronization events posted by SyncProvider implementations.
691     * @return The {@code Logger} that has been specified for use by
692     * {@code SyncProvider} implementations
693     * @throws SyncFactoryException if no logging object has been set.
694     */
695    public static Logger getLogger() throws SyncFactoryException {
696
697        Logger result = rsLogger;
698        // only one logger per session
699        if (result == null) {
700            throw new SyncFactoryException("(SyncFactory) : No logger has been set");
701        }
702
703        return result;
704    }
705
706    /**
707     * Sets the initial JNDI context from which SyncProvider implementations
708     * can be retrieved from a JNDI namespace
709     * <p>
710     *  This method checks to see that there is an {@code SQLPermission}
711     * object  which grants the permission {@code setSyncFactory}
712     * before allowing the method to succeed.  If a
713     * {@code SecurityManager} exists and its
714     * {@code checkPermission} method denies calling {@code setJNDIContext},
715     * this method throws a
716     * {@code java.lang.SecurityException}.
717     *
718     * @param ctx a valid JNDI context
719     * @throws SyncFactoryException if the supplied JNDI context is null
720     * @throws java.lang.SecurityException if a security manager exists and its
721     *  {@code checkPermission} method denies calling {@code setJNDIContext}
722     * @see SecurityManager#checkPermission
723     */
724    public static synchronized void setJNDIContext(javax.naming.Context ctx)
725            throws SyncFactoryException {
726        SecurityManager sec = System.getSecurityManager();
727        if (sec != null) {
728            sec.checkPermission(SET_SYNCFACTORY_PERMISSION);
729        }
730        if (ctx == null) {
731            throw new SyncFactoryException("Invalid JNDI context supplied");
732        }
733        ic = ctx;
734    }
735
736    /**
737     * Controls JNDI context initialization.
738     *
739     * @throws SyncFactoryException if an error occurs parsing the JNDI context
740     */
741    private static synchronized void initJNDIContext() throws SyncFactoryException {
742
743        if ((ic != null) && (lazyJNDICtxRefresh == false)) {
744            try {
745                parseProperties(parseJNDIContext());
746                lazyJNDICtxRefresh = true; // touch JNDI namespace once.
747            } catch (NamingException e) {
748                e.printStackTrace();
749                throw new SyncFactoryException("SPI: NamingException: " + e.getExplanation());
750            } catch (Exception e) {
751                e.printStackTrace();
752                throw new SyncFactoryException("SPI: Exception: " + e.getMessage());
753            }
754        }
755    }
756    /**
757     * Internal switch indicating whether the JNDI namespace should be re-read.
758     */
759    private static boolean lazyJNDICtxRefresh = false;
760
761    /**
762     * Parses the set JNDI Context and passes bindings to the enumerateBindings
763     * method when complete.
764     */
765    private static Properties parseJNDIContext() throws NamingException {
766
767        NamingEnumeration<?> bindings = ic.listBindings("");
768        Properties properties = new Properties();
769
770        // Hunt one level below context for available SyncProvider objects
771        enumerateBindings(bindings, properties);
772
773        return properties;
774    }
775
776    /**
777     * Scans each binding on JNDI context and determines if any binding is an
778     * instance of SyncProvider, if so, add this to the registry and continue to
779     * scan the current context using a re-entrant call to this method until all
780     * bindings have been enumerated.
781     */
782    private static void enumerateBindings(NamingEnumeration<?> bindings,
783            Properties properties) throws NamingException {
784
785        boolean syncProviderObj = false; // move to parameters ?
786
787        try {
788            Binding bd = null;
789            Object elementObj = null;
790            String element = null;
791            while (bindings.hasMore()) {
792                bd = (Binding) bindings.next();
793                element = bd.getName();
794                elementObj = bd.getObject();
795
796                if (!(ic.lookup(element) instanceof Context)) {
797                    // skip directories/sub-contexts
798                    if (ic.lookup(element) instanceof SyncProvider) {
799                        syncProviderObj = true;
800                    }
801                }
802
803                if (syncProviderObj) {
804                    SyncProvider sync = (SyncProvider) elementObj;
805                    properties.put(SyncFactory.ROWSET_SYNC_PROVIDER,
806                            sync.getProviderID());
807                    syncProviderObj = false; // reset
808                }
809
810            }
811        } catch (javax.naming.NotContextException e) {
812            bindings.next();
813            // Re-entrant call into method
814            enumerateBindings(bindings, properties);
815        }
816    }
817
818    /**
819     * Lazy initialization Holder class used by {@code getSyncFactory}
820     */
821    private static class SyncFactoryHolder {
822        static final SyncFactory factory = new SyncFactory();
823    }
824}
825
826/**
827 * Internal class that defines the lazy reference construct for each registered
828 * SyncProvider implementation.
829 */
830class ProviderImpl extends SyncProvider {
831
832    private String className = null;
833    private String vendorName = null;
834    private String ver = null;
835    private int index;
836
837    public void setClassname(String classname) {
838        className = classname;
839    }
840
841    public String getClassname() {
842        return className;
843    }
844
845    public void setVendor(String vendor) {
846        vendorName = vendor;
847    }
848
849    public String getVendor() {
850        return vendorName;
851    }
852
853    public void setVersion(String providerVer) {
854        ver = providerVer;
855    }
856
857    public String getVersion() {
858        return ver;
859    }
860
861    public void setIndex(int i) {
862        index = i;
863    }
864
865    public int getIndex() {
866        return index;
867    }
868
869    public int getDataSourceLock() throws SyncProviderException {
870
871        int dsLock = 0;
872        try {
873            dsLock = SyncFactory.getInstance(className).getDataSourceLock();
874        } catch (SyncFactoryException sfEx) {
875
876            throw new SyncProviderException(sfEx.getMessage());
877        }
878
879        return dsLock;
880    }
881
882    public int getProviderGrade() {
883
884        int grade = 0;
885
886        try {
887            grade = SyncFactory.getInstance(className).getProviderGrade();
888        } catch (SyncFactoryException sfEx) {
889            //
890        }
891
892        return grade;
893    }
894
895    public String getProviderID() {
896        return className;
897    }
898
899    /*
900    public javax.sql.RowSetInternal getRowSetInternal() {
901    try
902    {
903    return SyncFactory.getInstance(className).getRowSetInternal();
904    } catch(SyncFactoryException sfEx) {
905    //
906    }
907    }
908     */
909    public javax.sql.RowSetReader getRowSetReader() {
910
911        RowSetReader rsReader = null;
912
913        try {
914            rsReader = SyncFactory.getInstance(className).getRowSetReader();
915        } catch (SyncFactoryException sfEx) {
916            //
917        }
918
919        return rsReader;
920
921    }
922
923    public javax.sql.RowSetWriter getRowSetWriter() {
924
925        RowSetWriter rsWriter = null;
926        try {
927            rsWriter = SyncFactory.getInstance(className).getRowSetWriter();
928        } catch (SyncFactoryException sfEx) {
929            //
930        }
931
932        return rsWriter;
933    }
934
935    public void setDataSourceLock(int param)
936            throws SyncProviderException {
937
938        try {
939            SyncFactory.getInstance(className).setDataSourceLock(param);
940        } catch (SyncFactoryException sfEx) {
941
942            throw new SyncProviderException(sfEx.getMessage());
943        }
944    }
945
946    public int supportsUpdatableView() {
947
948        int view = 0;
949
950        try {
951            view = SyncFactory.getInstance(className).supportsUpdatableView();
952        } catch (SyncFactoryException sfEx) {
953            //
954        }
955
956        return view;
957    }
958}
959