001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath;
018
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.InputStreamReader;
025import java.util.Properties;
026
027import org.apache.commons.jxpath.util.ClassLoaderUtil;
028
029/**
030 * Defines a factory API that enables applications to obtain a
031 * {@link JXPathContext} instance.  To acquire a JXPathContext, first call the
032 * static {@link #newInstance} method of JXPathContextFactory.
033 * This method returns a concrete JXPathContextFactory.
034 * Then call {@link #newContext} on that instance.  You will rarely
035 * need to perform these steps explicitly: usually you can call one of the
036 * <code>JXPathContex.newContext</code> methods, which will perform these steps
037 * for you.
038 *
039 * @see JXPathContext#newContext(Object)
040 * @see JXPathContext#newContext(JXPathContext,Object)
041 *
042 * @author Dmitri Plotnikov
043 * @version $Revision: 1234255 $ $Date: 2012-01-21 04:11:46 +0100 (Sa, 21 Jan 2012) $
044 */
045public abstract class JXPathContextFactory {
046
047    /** The default property */
048    public static final String FACTORY_NAME_PROPERTY =
049        "org.apache.commons.jxpath.JXPathContextFactory";
050
051    /** The default factory class */
052    private static final String DEFAULT_FACTORY_CLASS =
053        "org.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl";
054
055    /** Avoid reading all the files when the findFactory
056        method is called the second time ( cache the result of
057        finding the default impl )
058    */
059    private static String factoryImplName = null;
060
061    /**
062     * Create a new JXPathContextFactory.
063     */
064    protected JXPathContextFactory () {
065
066    }
067
068    /**
069     * Obtain a new instance of a <code>JXPathContextFactory</code>.
070     * This static method creates a new factory instance.
071     * This method uses the following ordered lookup procedure to determine
072     * the <code>JXPathContextFactory</code> implementation class to load:
073     * <ul>
074     * <li>
075     * Use  the <code>org.apache.commons.jxpath.JXPathContextFactory</code>
076     * system property.
077     * </li>
078     * <li>
079     * Alternatively, use the JAVA_HOME (the parent directory where jdk is
080     * installed)/lib/jxpath.properties for a property file that contains the
081     * name of the implementation class keyed on
082     * <code>org.apache.commons.jxpath.JXPathContextFactory</code>.
083     * </li>
084     * <li>
085     * Use the Services API (as detailed in the JAR specification), if
086     * available, to determine the classname. The Services API will look
087     * for a classname in the file
088     * <code>META- INF/services/<i>org.apache.commons.jxpath.
089     * JXPathContextFactory</i></code> in jars available to the runtime.
090     * </li>
091     * <li>
092     * Platform default <code>JXPathContextFactory</code> instance.
093     * </li>
094     * </ul>
095     *
096     * Once an application has obtained a reference to a
097     * <code>JXPathContextFactory</code> it can use the factory to
098     * obtain JXPathContext instances.
099     *
100     * @return JXPathContextFactory
101     * @exception JXPathContextFactoryConfigurationError if the implementation
102     *            is not available or cannot be instantiated.
103     */
104    public static JXPathContextFactory newInstance() {
105        if (factoryImplName == null) {
106            factoryImplName =
107                findFactory(FACTORY_NAME_PROPERTY, DEFAULT_FACTORY_CLASS);
108        }
109
110        JXPathContextFactory factoryImpl;
111        try {
112            Class clazz = ClassLoaderUtil.getClass(factoryImplName, true);
113            factoryImpl = (JXPathContextFactory) clazz.newInstance();
114        }
115        catch (ClassNotFoundException cnfe) {
116            throw new JXPathContextFactoryConfigurationError(cnfe);
117        }
118        catch (IllegalAccessException iae) {
119            throw new JXPathContextFactoryConfigurationError(iae);
120        }
121        catch (InstantiationException ie) {
122            throw new JXPathContextFactoryConfigurationError(ie);
123        }
124        return factoryImpl;
125    }
126
127    /**
128     * Creates a new instance of a JXPathContext using the
129     * currently configured parameters.
130     * @param parentContext parent context
131     * @param contextBean Object bean
132     * @return JXPathContext
133     * @exception JXPathContextFactoryConfigurationError if a JXPathContext
134     *            cannot be created which satisfies the configuration requested
135     */
136
137    public abstract JXPathContext newContext(
138        JXPathContext parentContext,
139        Object contextBean);
140
141    // -------------------- private methods --------------------
142    // This code is duplicated in all factories.
143    // Keep it in sync or move it to a common place
144    // Because it's small probably it's easier to keep it here
145
146    /** Temp debug code - this will be removed after we test everything
147     */
148    private static boolean debug = false;
149    static {
150        try {
151            debug = System.getProperty("jxpath.debug") != null;
152        }
153        catch (SecurityException se) { //NOPMD
154            // This is ok
155        }
156    }
157
158    /**
159     * Private implementation method - will find the implementation
160     * class in the specified order.
161     * @param property    Property name
162     * @param defaultFactory Default implementation, if nothing else is found
163     *
164     * @return class name of the JXPathContextFactory
165     */
166    private static String findFactory(String property, String defaultFactory) {
167        // Use the factory ID system property first
168        try {
169            String systemProp = System.getProperty(property);
170            if (systemProp != null) {
171                if (debug) {
172                    System.err.println(
173                        "JXPath: found system property" + systemProp);
174                }
175                return systemProp;
176            }
177
178        }
179        catch (SecurityException se) { //NOPMD
180            // Ignore
181       }
182
183        // try to read from $java.home/lib/xml.properties
184        try {
185            String javah = System.getProperty("java.home");
186            String configFile =
187                javah
188                    + File.separator
189                    + "lib"
190                    + File.separator
191                    + "jxpath.properties";
192            File f = new File(configFile);
193            if (f.exists()) {
194                Properties props = new Properties();
195                FileInputStream fis = new FileInputStream(f);
196                try {
197                    props.load(fis);
198                }
199                finally {
200                    if (fis != null) {
201                        try {
202                            fis.close();
203                        }
204                        catch (IOException e) { //NOPMD
205                            //swallow
206                        }
207                    }
208                }
209                String factory = props.getProperty(property);
210                if (factory != null) {
211                    if (debug) {
212                        System.err.println(
213                            "JXPath: found java.home property " + factory);
214                    }
215                    return factory;
216                }
217            }
218        }
219        catch (IOException ex) {
220            if (debug) {
221                ex.printStackTrace();
222            }
223        }
224
225        String serviceId = "META-INF/services/" + property;
226        // try to find services in CLASSPATH
227        try {
228            ClassLoader cl = JXPathContextFactory.class.getClassLoader();
229            InputStream is = null;
230            if (cl == null) {
231                is = ClassLoader.getSystemResourceAsStream(serviceId);
232            }
233            else {
234                is = cl.getResourceAsStream(serviceId);
235            }
236
237            if (is != null) {
238                if (debug) {
239                    System.err.println("JXPath: found  " + serviceId);
240                }
241                BufferedReader rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
242
243                String factory = null;
244                try {
245                    factory = rd.readLine();
246                }
247                finally {
248                    try {
249                        rd.close();
250                    }
251                    catch (IOException e) { //NOPMD
252                        //swallow
253                    }
254                }
255
256                if (factory != null && !"".equals(factory)) {
257                    if (debug) {
258                        System.err.println(
259                            "JXPath: loaded from services: " + factory);
260                    }
261                    return factory;
262                }
263            }
264        }
265        catch (Exception ex) {
266            if (debug) {
267                ex.printStackTrace();
268            }
269        }
270        return defaultFactory;
271    }
272}