1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.  Oracle designates this
   7  * particular file as subject to the "Classpath" exception as provided
   8  * by Oracle in the LICENSE file that accompanied this code.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 /*
  26  * This file is available under and governed by the GNU General Public
  27  * License version 2 only, as published by the Free Software Foundation.
  28  * However, the following notice accompanied the original version of this
  29  * file and, per its terms, should not be removed:
  30  *
  31  * Copyright (c) 2004 World Wide Web Consortium,
  32  *
  33  * (Massachusetts Institute of Technology, European Research Consortium for
  34  * Informatics and Mathematics, Keio University). All Rights Reserved. This
  35  * work is distributed under the W3C(r) Software License [1] in the hope that
  36  * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
  37  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  38  *
  39  * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
  40  */
  41 
  42 
  43 package org.w3c.dom.bootstrap;
  44 
  45 import java.io.BufferedReader;
  46 import java.io.InputStream;
  47 import java.io.InputStreamReader;
  48 import java.lang.reflect.InvocationTargetException;
  49 import java.security.AccessController;
  50 import java.security.PrivilegedAction;
  51 import java.util.ArrayList;
  52 import java.util.List;
  53 import java.util.StringTokenizer;
  54 import org.w3c.dom.DOMImplementation;
  55 import org.w3c.dom.DOMImplementationList;
  56 import org.w3c.dom.DOMImplementationSource;
  57 
  58 /**
  59  * A factory that enables applications to obtain instances of
  60  * <code>DOMImplementation</code>.
  61  *
  62  * <p>
  63  * Example:
  64  * </p>
  65  *
  66  * <pre class='example'>
  67  *  // get an instance of the DOMImplementation registry
  68  *  DOMImplementationRegistry registry =
  69  *       DOMImplementationRegistry.newInstance();
  70  *  // get a DOM implementation the Level 3 XML module
  71  *  DOMImplementation domImpl =
  72  *       registry.getDOMImplementation("XML 3.0");
  73  * </pre>
  74  *
  75  * <p>
  76  * This provides an application with an implementation-independent starting
  77  * point. DOM implementations may modify this class to meet new security
  78  * standards or to provide *additional* fallbacks for the list of
  79  * DOMImplementationSources.
  80  * </p>
  81  *
  82  * @see DOMImplementation
  83  * @see DOMImplementationSource
  84  * @since 1.5, DOM Level 3
  85  */
  86 public final class DOMImplementationRegistry {
  87     /**
  88      * The system property to specify the
  89      * DOMImplementationSource class names.
  90      */
  91     public static final String PROPERTY =
  92         "org.w3c.dom.DOMImplementationSourceList";
  93 
  94     /**
  95      * Default columns per line.
  96      */
  97     private static final int DEFAULT_LINE_LENGTH = 80;
  98 
  99     /**
 100      * The list of DOMImplementationSources.
 101      */
 102     private List<DOMImplementationSource> sources;
 103 
 104     /**
 105      * Default class name.
 106      */
 107     private static final String FALLBACK_CLASS =
 108             "com.sun.org.apache.xerces.internal.dom.DOMXSImplementationSourceImpl";
 109     private static final String DEFAULT_PACKAGE =
 110             "com.sun.org.apache.xerces.internal.dom";
 111     /**
 112      * Private constructor.
 113      * @param srcs List of DOMImplementationSources
 114      */
 115     private DOMImplementationRegistry(final List<DOMImplementationSource> srcs) {
 116         sources = srcs;
 117     }
 118 
 119     /**
 120      * Obtain a new instance of a <code>DOMImplementationRegistry</code>.
 121      *
 122 
 123      * The <code>DOMImplementationRegistry</code> is initialized by the
 124      * application or the implementation, depending on the context, by
 125      * first checking the value of the Java system property
 126      * <code>org.w3c.dom.DOMImplementationSourceList</code> and
 127      * the service provider whose contents are at
 128      * "<code>META_INF/services/org.w3c.dom.DOMImplementationSourceList</code>".
 129      * The value of this property is a white-space separated list of
 130      * names of availables classes implementing the
 131      * <code>DOMImplementationSource</code> interface. Each class listed
 132      * in the class name list is instantiated and any exceptions
 133      * encountered are thrown to the application.
 134      *
 135      * @return an initialized instance of DOMImplementationRegistry
 136      * @throws ClassNotFoundException
 137      *     If any specified class can not be found
 138      * @throws InstantiationException
 139      *     If any specified class is an interface or abstract class
 140      * @throws IllegalAccessException
 141      *     If the default constructor of a specified class is not accessible
 142      * @throws ClassCastException
 143      *     If any specified class does not implement
 144      * <code>DOMImplementationSource</code>
 145      */
 146     public static DOMImplementationRegistry newInstance()
 147         throws
 148         ClassNotFoundException,
 149         InstantiationException,
 150         IllegalAccessException,
 151         ClassCastException {
 152         List<DOMImplementationSource> sources = new ArrayList<>();
 153 
 154         ClassLoader classLoader = getClassLoader();
 155         // fetch system property:
 156         String p = getSystemProperty(PROPERTY);
 157 
 158         //
 159         // if property is not specified then use contents of
 160         // META_INF/org.w3c.dom.DOMImplementationSourceList from classpath
 161         if (p == null) {
 162             p = getServiceValue(classLoader);
 163         }
 164         if (p == null) {
 165             //
 166             // DOM Implementations can modify here to add *additional* fallback
 167             // mechanisms to access a list of default DOMImplementationSources.
 168             //fall back to JAXP implementation class DOMXSImplementationSourceImpl
 169             p = FALLBACK_CLASS;
 170         }
 171         if (p != null) {
 172             StringTokenizer st = new StringTokenizer(p);
 173             while (st.hasMoreTokens()) {
 174                 String sourceName = st.nextToken();
 175                 // make sure we have access to restricted packages
 176                 boolean internal = false;
 177                 if (System.getSecurityManager() != null) {
 178                     if (sourceName != null && sourceName.startsWith(DEFAULT_PACKAGE)) {
 179                         internal = true;
 180                     }
 181                 }
 182                 Class<?> sourceClass = null;
 183                 if (classLoader != null && !internal) {
 184                     sourceClass = classLoader.loadClass(sourceName);
 185                 } else {
 186                     sourceClass = Class.forName(sourceName);
 187                 }
 188                 try {
 189                     DOMImplementationSource source =
 190                         (DOMImplementationSource) sourceClass.getConstructor().newInstance();
 191                     sources.add(source);
 192                 } catch (NoSuchMethodException | InvocationTargetException e) {
 193                     throw new InstantiationException(e.getMessage());
 194                 }
 195             }
 196         }
 197         return new DOMImplementationRegistry(sources);
 198     }
 199 
 200     /**
 201      * Return the first implementation that has the desired
 202      * features, or <code>null</code> if none is found.
 203      *
 204      * @param features
 205      *            A string that specifies which features are required. This is
 206      *            a space separated list in which each feature is specified by
 207      *            its name optionally followed by a space and a version number.
 208      *            This is something like: "XML 1.0 Traversal +Events 2.0"
 209      * @return An implementation that has the desired features,
 210      *         or <code>null</code> if none found.
 211      */
 212     public DOMImplementation getDOMImplementation(final String features) {
 213         int size = sources.size();
 214         String name = null;
 215         for (int i = 0; i < size; i++) {
 216             DOMImplementationSource source =
 217                 (DOMImplementationSource) sources.get(i);
 218             DOMImplementation impl = source.getDOMImplementation(features);
 219             if (impl != null) {
 220                 return impl;
 221             }
 222         }
 223         return null;
 224     }
 225 
 226     /**
 227      * Return a list of implementations that support the
 228      * desired features.
 229      *
 230      * @param features
 231      *            A string that specifies which features are required. This is
 232      *            a space separated list in which each feature is specified by
 233      *            its name optionally followed by a space and a version number.
 234      *            This is something like: "XML 1.0 Traversal +Events 2.0"
 235      * @return A list of DOMImplementations that support the desired features.
 236      */
 237     public DOMImplementationList getDOMImplementationList(final String features) {
 238         final List<DOMImplementation> implementations = new ArrayList<>();
 239         int size = sources.size();
 240         for (int i = 0; i < size; i++) {
 241             DOMImplementationSource source =
 242                 (DOMImplementationSource) sources.get(i);
 243             DOMImplementationList impls =
 244                 source.getDOMImplementationList(features);
 245             for (int j = 0; j < impls.getLength(); j++) {
 246                 DOMImplementation impl = impls.item(j);
 247                 implementations.add(impl);
 248             }
 249         }
 250         return new DOMImplementationList() {
 251                 public DOMImplementation item(final int index) {
 252                     if (index >= 0 && index < implementations.size()) {
 253                         try {
 254                             return (DOMImplementation)
 255                                 implementations.get(index);
 256                         } catch (IndexOutOfBoundsException e) {
 257                             return null;
 258                         }
 259                     }
 260                     return null;
 261                 }
 262 
 263                 public int getLength() {
 264                     return implementations.size();
 265                 }
 266             };
 267     }
 268 
 269     /**
 270      * Register an implementation.
 271      *
 272      * @param s The source to be registered, may not be <code>null</code>
 273      */
 274     public void addSource(final DOMImplementationSource s) {
 275         if (s == null) {
 276             throw new NullPointerException();
 277         }
 278         if (!sources.contains(s)) {
 279             sources.add(s);
 280         }
 281     }
 282 
 283     /**
 284      *
 285      * Gets a class loader.
 286      *
 287      * @return A class loader, possibly <code>null</code>
 288      */
 289     private static ClassLoader getClassLoader() {
 290         try {
 291             ClassLoader contextClassLoader = getContextClassLoader();
 292 
 293             if (contextClassLoader != null) {
 294                 return contextClassLoader;
 295             }
 296         } catch (Exception e) {
 297             // Assume that the DOM application is in a JRE 1.1, use the
 298             // current ClassLoader
 299             return DOMImplementationRegistry.class.getClassLoader();
 300         }
 301         return DOMImplementationRegistry.class.getClassLoader();
 302     }
 303 
 304     /**
 305      * This method attempts to return the first line of the resource
 306      * META_INF/services/org.w3c.dom.DOMImplementationSourceList
 307      * from the provided ClassLoader.
 308      *
 309      * @param classLoader classLoader, may not be <code>null</code>.
 310      * @return first line of resource, or <code>null</code>
 311      */
 312     private static String getServiceValue(final ClassLoader classLoader) {
 313         String serviceId = "META-INF/services/" + PROPERTY;
 314         // try to find services in CLASSPATH
 315         try {
 316             InputStream is = getResourceAsStream(classLoader, serviceId);
 317 
 318             if (is != null) {
 319                 BufferedReader rd;
 320                 try {
 321                     rd =
 322                         new BufferedReader(new InputStreamReader(is, "UTF-8"),
 323                                            DEFAULT_LINE_LENGTH);
 324                 } catch (java.io.UnsupportedEncodingException e) {
 325                     rd =
 326                         new BufferedReader(new InputStreamReader(is),
 327                                            DEFAULT_LINE_LENGTH);
 328                 }
 329                 String serviceValue = rd.readLine();
 330                 rd.close();
 331                 if (serviceValue != null && serviceValue.length() > 0) {
 332                     return serviceValue;
 333                 }
 334             }
 335         } catch (Exception ex) {
 336             return null;
 337         }
 338         return null;
 339     }
 340 
 341     /**
 342      * This method returns the ContextClassLoader.
 343      *
 344      * @return The Context Classloader
 345      */
 346     private static ClassLoader getContextClassLoader() {
 347         return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
 348                 @Override
 349                 public ClassLoader run() {
 350                     ClassLoader classLoader = null;
 351                     try {
 352                         classLoader =
 353                             Thread.currentThread().getContextClassLoader();
 354                     } catch (SecurityException ex) {
 355                     }
 356                     return classLoader;
 357                 }
 358             });
 359     }
 360 
 361     /**
 362      * This method returns the system property indicated by the specified name
 363      * after checking access control privileges.
 364      *
 365      * @param name the name of the system property
 366      * @return the system property
 367      */
 368     private static String getSystemProperty(final String name) {
 369         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 370                     @Override
 371                     public String run() {
 372                         return System.getProperty(name);
 373                     }
 374                 });
 375     }
 376 
 377     /**
 378      * This method returns an Inputstream for the reading resource
 379      * META_INF/services/org.w3c.dom.DOMImplementationSourceList after checking
 380      * access control privileges.
 381      *
 382      * @param classLoader classLoader
 383      * @param name the resource
 384      * @return an Inputstream for the resource specified
 385      */
 386     private static InputStream getResourceAsStream(final ClassLoader classLoader,
 387                                                    final String name) {
 388         return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
 389                 @Override
 390                 public InputStream run() {
 391                     InputStream ris;
 392                     if (classLoader == null) {
 393                         ris =
 394                             ClassLoader.getSystemResourceAsStream(name);
 395                     } else {
 396                         ris = classLoader.getResourceAsStream(name);
 397                     }
 398                     return ris;
 399                 }
 400             });
 401     }
 402 }