    001package org.apache.turbine.services.assemblerbroker.util.python;
    002
    003
    004/*
    005 * Licensed to the Apache Software Foundation (ASF) under one
    006 * or more contributor license agreements.  See the NOTICE file
    007 * distributed with this work for additional information
    008 * regarding copyright ownership.  The ASF licenses this file
    009 * to you under the Apache License, Version 2.0 (the
    010 * "License"); you may not use this file except in compliance
    011 * with the License.  You may obtain a copy of the License at
    012 *
    013 *   http://www.apache.org/licenses/LICENSE-2.0
    014 *
    015 * Unless required by applicable law or agreed to in writing,
    016 * software distributed under the License is distributed on an
    017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    018 * KIND, either express or implied.  See the License for the
    019 * specific language governing permissions and limitations
    020 * under the License.
    021 */
    022
    023
    024import java.io.File;
    025
    026import org.apache.commons.configuration.Configuration;
    027import org.apache.commons.lang.StringUtils;
    028import org.apache.commons.logging.Log;
    029import org.apache.commons.logging.LogFactory;
    030import org.apache.turbine.modules.Assembler;
    031import org.apache.turbine.modules.Loader;
    032import org.apache.turbine.services.TurbineServices;
    033import org.apache.turbine.services.assemblerbroker.AssemblerBrokerService;
    034import org.apache.turbine.services.assemblerbroker.util.AssemblerFactory;
    035import org.python.core.Py;
    036import org.python.util.PythonInterpreter;
    037
    038/**
    039 * A factory that attempts to load a python class in the
    040 * JPython interpreter and execute it as a Turbine screen.
    041 * The JPython script should inherit from Turbine Screen or one
    042 * of its subclasses.
    043 *
    044 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
    045 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    046 * @param <T> the specialized assembler type
    047 */
    048public abstract class PythonBaseFactory<T extends Assembler>
    049        implements AssemblerFactory<T>
    050{
    051    /** Key for the python path */
    052    public static final String PYTHON_PATH = "python.path";
    053
    054    /** Global config file. This is executed before every screen */
    055    public static final String PYTHON_CONFIG_FILE = "conf.py";
    056
    057    /** Logging */
    058    private static Log log = LogFactory.getLog(PythonBaseFactory.class);
    059
    060    /** Our configuration */
    061    private final Configuration conf = TurbineServices.getInstance().getConfiguration(AssemblerBrokerService.SERVICE_NAME);
    062
    063    /**
    064     * Get an Assembler.
    065     *
    066     * @param subDirectory subdirectory within python.path
    067     * @param name name of the requested Assembler
    068     * @return an Assembler
    069     * @throws Exception generic exception
    070     */
    071    public T getAssembler(String subDirectory, String name)
    072            throws Exception
    073    {
    074        String path = conf.getString(PYTHON_PATH);
    075
    076        if (StringUtils.isEmpty(path))
    077        {
    078            throw new Exception(
    079                "Python path not found - check your Properties");
    080        }
    081
    082        log.debug("Screen name for JPython: " + name);
    083
    084        T assembler = null;
    085
    086        String confName = path + "/" + PYTHON_CONFIG_FILE;
    087
    088        // The filename of the Python script
    089        StringBuilder fName = new StringBuilder();
    090
    091        fName.append(path);
    092        fName.append("/");
    093        fName.append(subDirectory);
    094        fName.append("/");
    095        fName.append(name.toLowerCase());
    096        fName.append(".py");
    097
    098        File f = new File(fName.toString());
    099
    100        if (f.exists())
    101        {
    102            PythonInterpreter interp = null;
    103
    104            try
    105            {
    106                // We try to open the Py Interpreter
    107                interp = new PythonInterpreter();
    108
    109                // Make sure the Py Interpreter use the right classloader
    110                // This is necessary for servlet engines generally has
    111                // their own classloader implementations and servlets aren't
    112                // loaded in the system classloader.  The python script will
    113                // load java package
    114                // org.apache.turbine.services.assemblerbroker.util.python;
    115                // the new classes to it as well.
    116                Py.getSystemState().setClassLoader(this.getClass().getClassLoader());
    117
    118                // We import the Python SYS module. Now we don't need to do this
    119                // explicitly in the script.  We always use the sys module to
    120                // do stuff like loading java package
    121                // org.apache.turbine.services.assemblerbroker.util.python;
    122                interp.exec("import sys");
    123
    124                // Now we try to load the script file
    125                interp.execfile(confName);
    126                interp.execfile(fName.toString());
    127
    128                try
    129                {
    130                    // We create an instance of the screen class from the
    131                    // python script
    132                    interp.exec("scr = " + name + "()");
    133                }
    134                catch (Throwable e)
    135                {
    136                    throw new Exception(
    137                        "\nCannot create an instance of the python class.\n"
    138                        + "You probably gave your class the wrong name.\n"
    139                        + "Your class should have the same name as your "
    140                        + "filename.\nFilenames should be all lowercase and "
    141                        + "classnames should start with a capital.\n"
    142                        + "Expected class name: " + name + "\n");
    143                }
    144
    145                // Here we convert the python screen instance to a java instance.
    146                @SuppressWarnings("unchecked") // Cast from Object necessary
    147                                T t = (T) interp.get("scr", Assembler.class);
    148                                assembler = t;
    149            }
    150            catch (Exception e)
    151            {
    152                // We log the error here because this code is not widely tested
    153                // yet. After we tested the code on a range of platforms this
    154                // won't be useful anymore.
    155                log.error("PYTHON SCRIPT SCREEN LOADER ERROR:", e);
    156                throw e;
    157            }
    158            finally
    159            {
    160                if (interp != null)
    161                {
    162                    interp.close();
    163                }
    164            }
    165        }
    166        return assembler;
    167    }
    168
    169    /**
    170     * Get the loader for this type of assembler
    171     *
    172     * @return a Loader
    173     */
    174    @Override
    175    public abstract Loader<T> getLoader();
    176
    177    /**
    178     * Get the size of a possibly configured cache
    179     *
    180     * @return the size of the cache in bytes
    181     */
    182    @Override
    183    public int getCacheSize()
    184
    185    {
    186        return getLoader().getCacheSize();
    187    }
    188}