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}