001    /*
002     * Copyright (c) 2004 World Wide Web Consortium,
003     *
004     * (Massachusetts Institute of Technology, European Research Consortium for
005     * Informatics and Mathematics, Keio University). All Rights Reserved. This
006     * work is distributed under the W3C(r) Software License [1] in the hope that
007     * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
008     * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
009     *
010     * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
011     */
012    
013    
014    package org.w3c.dom.bootstrap;
015    
016    import java.util.StringTokenizer;
017    import java.util.Vector;
018    import org.w3c.dom.DOMImplementationSource;
019    import org.w3c.dom.DOMImplementationList;
020    import org.w3c.dom.DOMImplementation;
021    import java.io.InputStream;
022    import java.io.BufferedReader;
023    import java.io.InputStreamReader;
024    import java.security.AccessController;
025    import java.security.PrivilegedAction;
026    
027    /**
028     * A factory that enables applications to obtain instances of
029     * <code>DOMImplementation</code>.
030     *
031     * <p>
032     * Example:
033     * </p>
034     *
035     * <pre class='example'>
036     *  // get an instance of the DOMImplementation registry
037     *  DOMImplementationRegistry registry =
038     *       DOMImplementationRegistry.newInstance();
039     *  // get a DOM implementation the Level 3 XML module
040     *  DOMImplementation domImpl =
041     *       registry.getDOMImplementation("XML 3.0");
042     * </pre>
043     *
044     * <p>
045     * This provides an application with an implementation-independent starting
046     * point. DOM implementations may modify this class to meet new security
047     * standards or to provide *additional* fallbacks for the list of
048     * DOMImplementationSources.
049     * </p>
050     *
051     * @see DOMImplementation
052     * @see DOMImplementationSource
053     * @since DOM Level 3
054     */
055    public final class DOMImplementationRegistry {
056        /**
057         * The system property to specify the
058         * DOMImplementationSource class names.
059         */
060        public static final String PROPERTY =
061            "org.w3c.dom.DOMImplementationSourceList";
062    
063        /**
064         * Default columns per line.
065         */
066        private static final int DEFAULT_LINE_LENGTH = 80;
067    
068        /**
069         * The list of DOMImplementationSources.
070         */
071        private Vector sources;
072    
073        /**
074         * Private constructor.
075         * @param srcs Vector List of DOMImplementationSources
076         */
077        private DOMImplementationRegistry(final Vector srcs) {
078            sources = srcs;
079        }
080    
081        /**
082         * Obtain a new instance of a <code>DOMImplementationRegistry</code>.
083         *
084    
085         * The <code>DOMImplementationRegistry</code> is initialized by the
086         * application or the implementation, depending on the context, by
087         * first checking the value of the Java system property
088         * <code>org.w3c.dom.DOMImplementationSourceList</code> and
089         * the the service provider whose contents are at
090         * "<code>META_INF/services/org.w3c.dom.DOMImplementationSourceList</code>"
091         * The value of this property is a white-space separated list of
092         * names of availables classes implementing the
093         * <code>DOMImplementationSource</code> interface. Each class listed
094         * in the class name list is instantiated and any exceptions
095         * encountered are thrown to the application.
096         *
097         * @return an initialized instance of DOMImplementationRegistry
098         * @throws ClassNotFoundException
099         *     If any specified class can not be found
100         * @throws InstantiationException
101         *     If any specified class is an interface or abstract class
102         * @throws IllegalAccessException
103         *     If the default constructor of a specified class is not accessible
104         * @throws ClassCastException
105         *     If any specified class does not implement
106         * <code>DOMImplementationSource</code>
107         */
108        public static DOMImplementationRegistry newInstance()
109            throws
110            ClassNotFoundException,
111            InstantiationException,
112            IllegalAccessException,
113            ClassCastException {
114            Vector sources = new Vector();
115    
116            ClassLoader classLoader = getClassLoader();
117            // fetch system property:
118            String p = getSystemProperty(PROPERTY);
119    
120            //
121            // if property is not specified then use contents of
122            // META_INF/org.w3c.dom.DOMImplementationSourceList from classpath
123            if (p == null) {
124                p = getServiceValue(classLoader);
125            }
126            if (p == null) {
127                //
128                // DOM Implementations can modify here to add *additional* fallback
129                // mechanisms to access a list of default DOMImplementationSources.
130                p = "gnu.xml.dom.ImplementationSource";
131            }
132            if (p != null) {
133                StringTokenizer st = new StringTokenizer(p);
134                while (st.hasMoreTokens()) {
135                    String sourceName = st.nextToken();
136                    // Use context class loader, falling back to Class.forName
137                    // if and only if this fails...
138                    Class sourceClass = null;
139                    if (classLoader != null) {
140                        sourceClass = classLoader.loadClass(sourceName);
141                    } else {
142                        sourceClass = Class.forName(sourceName);
143                    }
144                    DOMImplementationSource source =
145                        (DOMImplementationSource) sourceClass.newInstance();
146                    sources.addElement(source);
147                }
148            }
149            return new DOMImplementationRegistry(sources);
150        }
151    
152        /**
153         * Return the first implementation that has the desired
154         * features, or <code>null</code> if none is found.
155         *
156         * @param features
157         *            A string that specifies which features are required. This is
158         *            a space separated list in which each feature is specified by
159         *            its name optionally followed by a space and a version number.
160         *            This is something like: "XML 1.0 Traversal +Events 2.0"
161         * @return An implementation that has the desired features,
162         *         or <code>null</code> if none found.
163         */
164        public DOMImplementation getDOMImplementation(final String features) {
165            int size = sources.size();
166            String name = null;
167            for (int i = 0; i < size; i++) {
168                DOMImplementationSource source =
169                    (DOMImplementationSource) sources.elementAt(i);
170                DOMImplementation impl = source.getDOMImplementation(features);
171                if (impl != null) {
172                    return impl;
173                }
174            }
175            return null;
176        }
177    
178        /**
179         * Return a list of implementations that support the
180         * desired features.
181         *
182         * @param features
183         *            A string that specifies which features are required. This is
184         *            a space separated list in which each feature is specified by
185         *            its name optionally followed by a space and a version number.
186         *            This is something like: "XML 1.0 Traversal +Events 2.0"
187         * @return A list of DOMImplementations that support the desired features.
188         */
189        public DOMImplementationList getDOMImplementationList(final String features) {
190            final Vector implementations = new Vector();
191            int size = sources.size();
192            for (int i = 0; i < size; i++) {
193                DOMImplementationSource source =
194                    (DOMImplementationSource) sources.elementAt(i);
195                DOMImplementationList impls =
196                    source.getDOMImplementationList(features);
197                for (int j = 0; j < impls.getLength(); j++) {
198                    DOMImplementation impl = impls.item(j);
199                    implementations.addElement(impl);
200                }
201            }
202            return new DOMImplementationList() {
203                    public DOMImplementation item(final int index) {
204                        if (index >= 0 && index < implementations.size()) {
205                            try {
206                                return (DOMImplementation)
207                                    implementations.elementAt(index);
208                            } catch (ArrayIndexOutOfBoundsException e) {
209                                return null;
210                            }
211                        }
212                        return null;
213                    }
214    
215                    public int getLength() {
216                        return implementations.size();
217                    }
218                };
219        }
220    
221        /**
222         * Register an implementation.
223         *
224         * @param s The source to be registered, may not be <code>null</code>
225         */
226        public void addSource(final DOMImplementationSource s) {
227            if (s == null) {
228                throw new NullPointerException();
229            }
230            if (!sources.contains(s)) {
231                sources.addElement(s);
232            }
233        }
234    
235        /**
236         *
237         * Gets a class loader.
238         *
239         * @return A class loader, possibly <code>null</code>
240         */
241        private static ClassLoader getClassLoader() {
242            try {
243                ClassLoader contextClassLoader = getContextClassLoader();
244    
245                if (contextClassLoader != null) {
246                    return contextClassLoader;
247                }
248            } catch (Exception e) {
249                // Assume that the DOM application is in a JRE 1.1, use the
250                // current ClassLoader
251                return DOMImplementationRegistry.class.getClassLoader();
252            }
253            return DOMImplementationRegistry.class.getClassLoader();
254        }
255    
256        /**
257         * This method attempts to return the first line of the resource
258         * META_INF/services/org.w3c.dom.DOMImplementationSourceList
259         * from the provided ClassLoader.
260         *
261         * @param classLoader classLoader, may not be <code>null</code>.
262         * @return first line of resource, or <code>null</code>
263         */
264        private static String getServiceValue(final ClassLoader classLoader) {
265            String serviceId = "META-INF/services/" + PROPERTY;
266            // try to find services in CLASSPATH
267            try {
268                InputStream is = getResourceAsStream(classLoader, serviceId);
269    
270                if (is != null) {
271                    BufferedReader rd;
272                    try {
273                        rd =
274                            new BufferedReader(new InputStreamReader(is, "UTF-8"),
275                                               DEFAULT_LINE_LENGTH);
276                    } catch (java.io.UnsupportedEncodingException e) {
277                        rd =
278                            new BufferedReader(new InputStreamReader(is),
279                                               DEFAULT_LINE_LENGTH);
280                    }
281                    String serviceValue = rd.readLine();
282                    rd.close();
283                    if (serviceValue != null && serviceValue.length() > 0) {
284                        return serviceValue;
285                    }
286                }
287            } catch (Exception ex) {
288                return null;
289            }
290            return null;
291        }
292    
293        /**
294         * A simple JRE (Java Runtime Environment) 1.1 test
295         *
296         * @return <code>true</code> if JRE 1.1
297         */
298        private static boolean isJRE11() {
299            try {
300                Class c = Class.forName("java.security.AccessController");
301                // java.security.AccessController existed since 1.2 so, if no
302                // exception was thrown, the DOM application is running in a JRE
303                // 1.2 or higher
304                return false;
305            } catch (Exception ex) {
306                // ignore
307            }
308            return true;
309        }
310    
311        /**
312         * This method returns the ContextClassLoader or <code>null</code> if
313         * running in a JRE 1.1
314         *
315         * @return The Context Classloader
316         */
317        private static ClassLoader getContextClassLoader() {
318            return isJRE11()
319                ? null
320                : (ClassLoader)
321                  AccessController.doPrivileged(new PrivilegedAction() {
322                        public Object run() {
323                            ClassLoader classLoader = null;
324                            try {
325                                classLoader =
326                                    Thread.currentThread().getContextClassLoader();
327                            } catch (SecurityException ex) {
328                            }
329                            return classLoader;
330                        }
331                    });
332        }
333    
334        /**
335         * This method returns the system property indicated by the specified name
336         * after checking access control privileges. For a JRE 1.1, this check is
337         * not done.
338         *
339         * @param name the name of the system property
340         * @return the system property
341         */
342        private static String getSystemProperty(final String name) {
343            return isJRE11()
344                ? (String) System.getProperty(name)
345                : (String) AccessController.doPrivileged(new PrivilegedAction() {
346                        public Object run() {
347                            return System.getProperty(name);
348                        }
349                    });
350        }
351    
352        /**
353         * This method returns an Inputstream for the reading resource
354         * META_INF/services/org.w3c.dom.DOMImplementationSourceList after checking
355         * access control privileges. For a JRE 1.1, this check is not done.
356         *
357         * @param classLoader classLoader
358         * @param name the resource
359         * @return an Inputstream for the resource specified
360         */
361        private static InputStream getResourceAsStream(final ClassLoader classLoader,
362                                                       final String name) {
363            if (isJRE11()) {
364                InputStream ris;
365                if (classLoader == null) {
366                    ris = ClassLoader.getSystemResourceAsStream(name);
367                } else {
368                    ris = classLoader.getResourceAsStream(name);
369                }
370                return ris;
371            } else {
372                return (InputStream)
373                    AccessController.doPrivileged(new PrivilegedAction() {
374                            public Object run() {
375                                InputStream ris;
376                                if (classLoader == null) {
377                                    ris =
378                                        ClassLoader.getSystemResourceAsStream(name);
379                                } else {
380                                    ris = classLoader.getResourceAsStream(name);
381                                }
382                                return ris;
383                            }
384                        });
385            }
386        }
387    }