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 = sources.get(i);
 217             DOMImplementation impl = source.getDOMImplementation(features);
 218             if (impl != null) {
 219                 return impl;
 220             }
 221         }
 222         return null;
 223     }
 224 
 225     /**
 226      * Return a list of implementations that support the
 227      * desired features.
 228      *
 229      * @param features
 230      *            A string that specifies which features are required. This is
 231      *            a space separated list in which each feature is specified by
 232      *            its name optionally followed by a space and a version number.
 233      *            This is something like: "XML 1.0 Traversal +Events 2.0"
 234      * @return A list of DOMImplementations that support the desired features.
 235      */
 236     public DOMImplementationList getDOMImplementationList(final String features) {
 237         final List<DOMImplementation> implementations = new ArrayList<>();
 238         int size = sources.size();
 239         for (int i = 0; i < size; i++) {
 240             DOMImplementationSource source = sources.get(i);
 241             DOMImplementationList impls =
 242                 source.getDOMImplementationList(features);
 243             for (int j = 0; j < impls.getLength(); j++) {
 244                 DOMImplementation impl = impls.item(j);
 245                 implementations.add(impl);
 246             }
 247         }
 248         return new DOMImplementationList() {
 249                 public DOMImplementation item(final int index) {
 250                     if (index >= 0 && index < implementations.size()) {
 251                         try {
 252                             return implementations.get(index);
 253                         } catch (IndexOutOfBoundsException e) {
 254                             return null;
 255                         }
 256                     }
 257                     return null;
 258                 }
 259 
 260                 public int getLength() {
 261                     return implementations.size();
 262                 }
 263             };
 264     }
 265 
 266     /**
 267      * Register an implementation.
 268      *
 269      * @param s The source to be registered, may not be <code>null</code>
 270      */
 271     public void addSource(final DOMImplementationSource s) {
 272         if (s == null) {
 273             throw new NullPointerException();
 274         }
 275         if (!sources.contains(s)) {
 276             sources.add(s);
 277         }
 278     }
 279 
 280     /**
 281      *
 282      * Gets a class loader.
 283      *
 284      * @return A class loader, possibly <code>null</code>
 285      */
 286     private static ClassLoader getClassLoader() {
 287         try {
 288             ClassLoader contextClassLoader = getContextClassLoader();
 289 
 290             if (contextClassLoader != null) {
 291                 return contextClassLoader;
 292             }
 293         } catch (Exception e) {
 294             // Assume that the DOM application is in a JRE 1.1, use the
 295             // current ClassLoader
 296             return DOMImplementationRegistry.class.getClassLoader();
 297         }
 298         return DOMImplementationRegistry.class.getClassLoader();
 299     }
 300 
 301     /**
 302      * This method attempts to return the first line of the resource
 303      * META_INF/services/org.w3c.dom.DOMImplementationSourceList
 304      * from the provided ClassLoader.
 305      *
 306      * @param classLoader classLoader, may not be <code>null</code>.
 307      * @return first line of resource, or <code>null</code>
 308      */
 309     private static String getServiceValue(final ClassLoader classLoader) {
 310         String serviceId = "META-INF/services/" + PROPERTY;
 311         // try to find services in CLASSPATH
 312         try {
 313             InputStream is = getResourceAsStream(classLoader, serviceId);
 314 
 315             if (is != null) {
 316                 BufferedReader rd;
 317                 try {
 318                     rd =
 319                         new BufferedReader(new InputStreamReader(is, "UTF-8"),
 320                                            DEFAULT_LINE_LENGTH);
 321                 } catch (java.io.UnsupportedEncodingException e) {
 322                     rd =
 323                         new BufferedReader(new InputStreamReader(is),
 324                                            DEFAULT_LINE_LENGTH);
 325                 }
 326                 String serviceValue = rd.readLine();
 327                 rd.close();
 328                 if (serviceValue != null && serviceValue.length() > 0) {
 329                     return serviceValue;
 330                 }
 331             }
 332         } catch (Exception ex) {
 333             return null;
 334         }
 335         return null;
 336     }
 337 
 338     /**
 339      * This method returns the ContextClassLoader.
 340      *
 341      * @return The Context Classloader
 342      */
 343     private static ClassLoader getContextClassLoader() {
 344         return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
 345                 @Override
 346                 public ClassLoader run() {
 347                     ClassLoader classLoader = null;
 348                     try {
 349                         classLoader =
 350                             Thread.currentThread().getContextClassLoader();
 351                     } catch (SecurityException ex) {
 352                     }
 353                     return classLoader;
 354                 }
 355             });
 356     }
 357 
 358     /**
 359      * This method returns the system property indicated by the specified name
 360      * after checking access control privileges.
 361      *
 362      * @param name the name of the system property
 363      * @return the system property
 364      */
 365     private static String getSystemProperty(final String name) {
 366         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 367                     @Override
 368                     public String run() {
 369                         return System.getProperty(name);
 370                     }
 371                 });
 372     }
 373 
 374     /**
 375      * This method returns an Inputstream for the reading resource
 376      * META_INF/services/org.w3c.dom.DOMImplementationSourceList after checking
 377      * access control privileges.
 378      *
 379      * @param classLoader classLoader
 380      * @param name the resource
 381      * @return an Inputstream for the resource specified
 382      */
 383     private static InputStream getResourceAsStream(final ClassLoader classLoader,
 384                                                    final String name) {
 385         return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
 386                 @Override
 387                 public InputStream run() {
 388                     InputStream ris;
 389                     if (classLoader == null) {
 390                         ris =
 391                             ClassLoader.getSystemResourceAsStream(name);
 392                     } else {
 393                         ris = classLoader.getResourceAsStream(name);
 394                     }
 395                     return ris;
 396                 }
 397             });
 398     }
 399 }