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.configuration2.Configuration;
027import org.apache.commons.lang3.StringUtils;
028import org.apache.logging.log4j.LogManager;
029import org.apache.logging.log4j.Logger;
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 Logger log = LogManager.getLogger(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            // We try to open the Py Interpreter
103            try (PythonInterpreter interp = new PythonInterpreter())
104            {
105                // Make sure the Py Interpreter use the right classloader
106                // This is necessary for servlet engines generally has
107                // their own classloader implementations and servlets aren't
108                // loaded in the system classloader.  The python script will
109                // load java package
110                // org.apache.turbine.services.assemblerbroker.util.python;
111                // the new classes to it as well.
112                Py.getSystemState().setClassLoader(this.getClass().getClassLoader());
113
114                // We import the Python SYS module. Now we don't need to do this
115                // explicitly in the script.  We always use the sys module to
116                // do stuff like loading java package
117                // org.apache.turbine.services.assemblerbroker.util.python;
118                interp.exec("import sys");
119
120                // Now we try to load the script file
121                interp.execfile(confName);
122                interp.execfile(fName.toString());
123
124                try
125                {
126                    // We create an instance of the screen class from the
127                    // python script
128                    interp.exec("scr = " + name + "()");
129                }
130                catch (Throwable e)
131                {
132                    throw new Exception(
133                        "\nCannot create an instance of the python class.\n"
134                        + "You probably gave your class the wrong name.\n"
135                        + "Your class should have the same name as your "
136                        + "filename.\nFilenames should be all lowercase and "
137                        + "classnames should start with a capital.\n"
138                        + "Expected class name: " + name + "\n");
139                }
140
141                // Here we convert the python screen instance to a java instance.
142                @SuppressWarnings("unchecked") // Cast from Object necessary
143                                T t = (T) interp.get("scr", Assembler.class);
144                                assembler = t;
145            }
146            catch (Exception e)
147            {
148                // We log the error here because this code is not widely tested
149                // yet. After we tested the code on a range of platforms this
150                // won't be useful anymore.
151                log.error("PYTHON SCRIPT SCREEN LOADER ERROR:", e);
152                throw e;
153            }
154        }
155        return assembler;
156    }
157
158    /**
159     * Get the loader for this type of assembler
160     *
161     * @return a Loader
162     */
163    @Override
164    public abstract Loader<T> getLoader();
165
166    /**
167     * Get the size of a possibly configured cache
168     *
169     * @return the size of the cache in bytes
170     */
171    @Override
172    public int getCacheSize()
173
174    {
175        return getLoader().getCacheSize();
176    }
177}