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.assemblerbroker.TurbineAssemblerBroker;
033import org.apache.turbine.services.assemblerbroker.util.AssemblerFactory;
034import org.python.core.Py;
035import org.python.util.PythonInterpreter;
036
037/**
038 * A factory that attempts to load a python class in the
039 * JPython interpreter and execute it as a Turbine screen.
040 * The JPython script should inherit from Turbine Screen or one
041 * of its subclasses.
042 *
043 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
044 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
045 * @param <T> the specialized assembler type
046 */
047public abstract class PythonBaseFactory<T extends Assembler>
048        implements AssemblerFactory<T>
049{
050    /** Key for the python path */
051    public static final String PYTHON_PATH = "python.path";
052
053    /** Global config file. This is executed before every screen */
054    public static final String PYTHON_CONFIG_FILE = "conf.py";
055
056    /** Logging */
057    private static Log log = LogFactory.getLog(PythonBaseFactory.class);
058
059    /** Our configuration */
060    private final Configuration conf =
061        TurbineAssemblerBroker.getService().getConfiguration();
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}