    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
    018package org.apache.commons.jxpath;
    019
    020import java.io.BufferedReader;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.InputStreamReader;
    024import java.nio.file.Files;
    025import java.nio.file.Path;
    026import java.nio.file.Paths;
    027import java.util.Properties;
    028
    029import org.apache.commons.jxpath.util.ClassLoaderUtil;
    030
    031/**
    032 * Defines a factory API that enables applications to obtain a {@link JXPathContext} instance. To acquire a JXPathContext, first call the static
    033 * {@link #newInstance} method of JXPathContextFactory. This method returns a concrete JXPathContextFactory. Then call {@link #newContext} on that instance. You
    034 * will rarely need to perform these steps explicitly: usually you can call one of the {@code JXPathContex.newContext} methods, which will perform these steps
    035 * for you.
    036 *
    037 * @see JXPathContext#newContext(Object)
    038 * @see JXPathContext#newContext(JXPathContext,Object)
    039 */
    040public abstract class JXPathContextFactory {
    041
    042    /** The default property */
    043    public static final String FACTORY_NAME_PROPERTY = "org.apache.commons.jxpath.JXPathContextFactory";
    044
    045    /** The default factory class */
    046    private static final String DEFAULT_FACTORY_CLASS = "org.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl";
    047
    048    /**
    049     * Avoid reading all the files when the findFactory method is called the second time ( cache the result of finding the default impl )
    050     */
    051    private static final String FACTORY_IMPL_NAME = findFactory(FACTORY_NAME_PROPERTY, DEFAULT_FACTORY_CLASS);
    052
    053    /**
    054     * Temp debug code - this will be removed after we test everything
    055     */
    056    private static boolean debug;
    057
    058    static {
    059        try {
    060            debug = System.getProperty("jxpath.debug") != null;
    061        } catch (final SecurityException ignore) { // NOPMD
    062            // This is ok
    063        }
    064    }
    065
    066    /**
    067     * Private implementation method - will find the implementation class in the specified order.
    068     *
    069     * @param property       Property name
    070     * @param defaultFactory Default implementation, if nothing else is found
    071     * @return class name of the JXPathContextFactory
    072     */
    073    private static String findFactory(final String property, final String defaultFactory) {
    074        // Use the factory ID system property first
    075        try {
    076            final String systemProp = System.getProperty(property);
    077            if (systemProp != null) {
    078                if (debug) {
    079                    System.err.println("JXPath: found system property" + systemProp);
    080                }
    081                return systemProp;
    082            }
    083        } catch (final SecurityException ignore) { // NOPMD
    084            // Ignore
    085        }
    086        // try to read from $java.home/lib/xml.properties
    087        try {
    088            final Path javaHome = Paths.get(System.getProperty("java.home"));
    089            final Path configFile = javaHome.resolve(Paths.get("lib", "jxpath.properties"));
    090            if (Files.exists(configFile)) {
    091                final Properties props = new Properties();
    092                try (InputStream fis = Files.newInputStream(configFile)) {
    093                    props.load(fis);
    094                }
    095                final String factory = props.getProperty(property);
    096                if (factory != null) {
    097                    if (debug) {
    098                        System.err.println("JXPath: found java.home property " + factory);
    099                    }
    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}