View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.Properties;
28  
29  import org.apache.commons.jxpath.util.ClassLoaderUtil;
30  
31  /**
32   * Defines a factory API that enables applications to obtain a {@link JXPathContext} instance. To acquire a JXPathContext, first call the static
33   * {@link #newInstance} method of JXPathContextFactory. This method returns a concrete JXPathContextFactory. Then call {@link #newContext} on that instance. You
34   * will rarely need to perform these steps explicitly: usually you can call one of the {@code JXPathContex.newContext} methods, which will perform these steps
35   * for you.
36   *
37   * @see JXPathContext#newContext(Object)
38   * @see JXPathContext#newContext(JXPathContext,Object)
39   */
40  public abstract class JXPathContextFactory {
41  
42      /** The default property */
43      public static final String FACTORY_NAME_PROPERTY = "org.apache.commons.jxpath.JXPathContextFactory";
44  
45      /** The default factory class */
46      private static final String DEFAULT_FACTORY_CLASS = "org.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl";
47  
48      /**
49       * Avoid reading all the files when the findFactory method is called the second time ( cache the result of finding the default impl )
50       */
51      private static final String FACTORY_IMPL_NAME = findFactory(FACTORY_NAME_PROPERTY, DEFAULT_FACTORY_CLASS);
52  
53      /**
54       * Temp debug code - this will be removed after we test everything
55       */
56      private static boolean debug;
57  
58      static {
59          try {
60              debug = System.getProperty("jxpath.debug") != null;
61          } catch (final SecurityException ignore) { // NOPMD
62              // This is ok
63          }
64      }
65  
66      /**
67       * Private implementation method - will find the implementation class in the specified order.
68       *
69       * @param property       Property name
70       * @param defaultFactory Default implementation, if nothing else is found
71       * @return class name of the JXPathContextFactory
72       */
73      private static String findFactory(final String property, final String defaultFactory) {
74          // Use the factory ID system property first
75          try {
76              final String systemProp = System.getProperty(property);
77              if (systemProp != null) {
78                  if (debug) {
79                      System.err.println("JXPath: found system property" + systemProp);
80                  }
81                  return systemProp;
82              }
83          } catch (final SecurityException ignore) { // NOPMD
84              // Ignore
85          }
86          // try to read from $java.home/lib/xml.properties
87          try {
88              final Path javaHome = Paths.get(System.getProperty("java.home"));
89              final Path configFile = javaHome.resolve(Paths.get("lib", "jxpath.properties"));
90              if (Files.exists(configFile)) {
91                  final Properties props = new Properties();
92                  try (InputStream fis = Files.newInputStream(configFile)) {
93                      props.load(fis);
94                  }
95                  final String factory = props.getProperty(property);
96                  if (factory != null) {
97                      if (debug) {
98                          System.err.println("JXPath: found java.home property " + factory);
99                      }
100                     return factory;
101                 }
102             }
103         } catch (final IOException ex) {
104             if (debug) {
105                 ex.printStackTrace();
106             }
107         }
108         final String serviceId = "META-INF/services/" + property;
109         // try to find services in CLASSPATH
110         try {
111             final ClassLoader cl = JXPathContextFactory.class.getClassLoader();
112             try (InputStream is = cl == null ? ClassLoader.getSystemResourceAsStream(serviceId) : cl.getResourceAsStream(serviceId)) {
113                 if (is != null) {
114                     if (debug) {
115                         System.err.println("JXPath: found  " + serviceId);
116                     }
117                     final BufferedReader rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
118                     final String factory = rd.readLine();
119                     if (factory != null && !"".equals(factory)) {
120                         if (debug) {
121                             System.err.println("JXPath: loaded from services: " + factory);
122                         }
123                         return factory;
124                     }
125                 }
126             }
127         } catch (final Exception ex) {
128             if (debug) {
129                 ex.printStackTrace();
130             }
131         }
132         return defaultFactory;
133     }
134     // This code is duplicated in all factories.
135     // Keep it in sync or move it to a common place
136     // Because it's small probably it's easier to keep it here
137 
138     /**
139      * Obtain a new instance of a {@code JXPathContextFactory}. This static method creates a new factory instance. This method uses the following ordered lookup
140      * procedure to determine the {@code JXPathContextFactory} implementation class to load:
141      * <ul>
142      * <li>Use the {@code org.apache.commons.jxpath.JXPathContextFactory} system property.</li>
143      * <li>Alternatively, use the JAVA_HOME (the parent directory where jdk is installed)/lib/jxpath.properties for a property file that contains the name of
144      * the implementation class keyed on {@code org.apache.commons.jxpath.JXPathContextFactory}.</li>
145      * <li>Use the Services API (as detailed in the JAR specification), if available, to determine the class name. The Services API will look for a class name
146      * in the file {@code META- INF/services/<i>org.apache.commons.jxpath.
147      * JXPathContextFactory</i>} in jars available to the runtime.</li>
148      * <li>Platform default {@code JXPathContextFactory} instance.</li>
149      * </ul>
150      *
151      * Once an application has obtained a reference to a {@code JXPathContextFactory} it can use the factory to obtain JXPathContext instances.
152      *
153      * @return JXPathContextFactory
154      * @throws JXPathContextFactoryConfigurationError if the implementation is not available or cannot be instantiated.
155      */
156     public static JXPathContextFactory newInstance() {
157         JXPathContextFactory factoryImpl;
158         try {
159             factoryImpl = ClassLoaderUtil.<JXPathContextFactory>getClass(FACTORY_IMPL_NAME, true).getConstructor().newInstance();
160         } catch (final ReflectiveOperationException ie) {
161             throw new JXPathContextFactoryConfigurationError(ie);
162         }
163         return factoryImpl;
164     }
165 
166     /**
167      * Constructs a new JXPathContextFactory.
168      */
169     protected JXPathContextFactory() {
170     }
171 
172     /**
173      * Creates a new instance of a JXPathContext using the currently configured parameters.
174      *
175      * @param parentContext parent context
176      * @param contextBean   Object bean
177      * @return JXPathContext
178      * @throws JXPathContextFactoryConfigurationError if a JXPathContext cannot be created which satisfies the configuration requested
179      */
180     public abstract JXPathContext newContext(JXPathContext parentContext, Object contextBean);
181 }