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}