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