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     */
017    package org.apache.commons.jxpath;
018    
019    import java.io.BufferedReader;
020    import java.io.File;
021    import java.io.FileInputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.InputStreamReader;
025    import 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     */
043    public 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    }