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
014package org.w3c.dom.bootstrap;
015
016import java.util.StringTokenizer;
017import java.util.Vector;
018import org.w3c.dom.DOMImplementationSource;
019import org.w3c.dom.DOMImplementationList;
020import org.w3c.dom.DOMImplementation;
021import java.io.InputStream;
022import java.io.BufferedReader;
023import java.io.InputStreamReader;
024import java.security.AccessController;
025import 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 */
055public 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}