001: /*
002: * Copyright 2004,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.bsf.engines.java;
018:
019: import java.io.File;
020: import java.io.FileOutputStream;
021: import java.io.FilenameFilter;
022: import java.lang.reflect.Method;
023: import java.util.Hashtable;
024: import java.util.Vector;
025:
026: import org.apache.bsf.BSFException;
027: import org.apache.bsf.BSFManager;
028: import org.apache.bsf.util.BSFEngineImpl;
029: import org.apache.bsf.util.CodeBuffer;
030: import org.apache.bsf.util.EngineUtils;
031: import org.apache.bsf.util.JavaUtils;
032: import org.apache.bsf.util.MethodUtils;
033: import org.apache.bsf.util.ObjInfo;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: /**
038: * This is the interface to Java from the
039: * Bean Scripting Framework.
040: * <p>
041: * The Java code must be written script-style -- that is, just the body of
042: * the function, without class or method headers or footers.
043: * The JavaEngine will generate those via a "boilerplate" wrapper:
044: * <pre>
045: * <code>
046: * import java.lang.*;
047: * import java.util.*;
048: * public class $$CLASSNAME$$ {
049: * static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {
050: * // Your code will be placed here
051: * }
052: * }
053: * </code>
054: * </pre>
055: * $$CLASSNAME$$ will be replaced by a generated classname of the form
056: * BSFJava*, and the bsf parameter can be used to retrieve application
057: * objects registered with the Bean Scripting Framework.
058: * <p>
059: * If you use the placeholder string $$CLASSNAME$$ elsewhere
060: * in your script -- including within text strings -- BSFJavaEngine will
061: * replace it with the generated name of the class before the Java code
062: * is compiled.
063: * <p>
064: * <h2>Hazards:</h2>
065: * <p>
066: * NOTE that it is your responsibility to convert the code into an acceptable
067: * Java string. If you're invoking the JavaEngine directly (as in the
068: * JSPLikeInJava example) that means \"quoting\" characters that would
069: * otherwise cause trouble.
070: * <p>
071: * ALSO NOTE that it is your responsibility to return an object, or null in
072: * lieu thereof!
073: * <p>
074: * Since the code has to be compiled to a Java classfile, invoking it involves
075: * a fair amount of computation to load and execute the compiler. We are
076: * currently making an attempt to manage that by caching the class
077: * after it has been loaded, but the indexing is fairly primitive. It has
078: * been suggested that the Bean Scripting Framework may want to support
079: * preload-and-name-script and execute-preloaded-script-by-name options to
080: * provide better control over when and how much overhead occurs.
081: * <p>
082: * @author Joe Kesselman
083: */
084: public class JavaEngine extends BSFEngineImpl {
085: Class javaclass = null;
086: static Hashtable codeToClass = new Hashtable();
087: static String serializeCompilation = "";
088: static String placeholder = "$$CLASSNAME$$";
089: String minorPrefix;
090:
091: private Log logger = LogFactory.getLog(this .getClass().getName());
092:
093: /**
094: * Create a scratchfile, open it for writing, return its name.
095: * Relies on the filesystem to provide us with uniqueness testing.
096: * NOTE THAT uniqueFileOffset continues to count; we don't want to
097: * risk reusing a classname we have previously loaded in this session
098: * even if the classfile has been deleted.
099: */
100: private int uniqueFileOffset = -1;
101:
102: private class GeneratedFile {
103: File file = null;
104: FileOutputStream fos = null;
105: String className = null;
106:
107: GeneratedFile(File file, FileOutputStream fos, String className) {
108: this .file = file;
109: this .fos = fos;
110: this .className = className;
111: }
112: }
113:
114: /**
115: * Constructor.
116: */
117: public JavaEngine() {
118: // Do compilation-possible check here??????????????
119: }
120:
121: public Object call(Object object, String method, Object[] args)
122: throws BSFException {
123: throw new BSFException(BSFException.REASON_UNSUPPORTED_FEATURE,
124: "call() is not currently supported by JavaEngine");
125: }
126:
127: public void compileScript(String source, int lineNo, int columnNo,
128: Object script, CodeBuffer cb) throws BSFException {
129: ObjInfo oldRet = cb.getFinalServiceMethodStatement();
130:
131: if (oldRet != null && oldRet.isExecutable()) {
132: cb.addServiceMethodStatement(oldRet.objName + ";");
133: }
134:
135: cb.addServiceMethodStatement(script.toString());
136: cb.setFinalServiceMethodStatement(null);
137: }
138:
139: /**
140: * This is used by an application to evaluate a string containing
141: * some expression. It should store the "bsf" handle where the
142: * script can get to it, for callback purposes.
143: * <p>
144: * Note that Java compilation imposes serious overhead,
145: * but in exchange you get full Java performance
146: * once the classes have been created (minus the cache lookup cost).
147: * <p>
148: * Nobody knows whether javac is threadsafe.
149: * I'm going to serialize access to protect it.
150: * <p>
151: * There is no published API for invoking javac as a class. There's a trick
152: * that seems to work for Java 1.1.x, but it stopped working in Java 1.2.
153: * We will attempt to use it, then if necessary fall back on invoking
154: * javac via the command line.
155: */
156: public Object eval(String source, int lineNo, int columnNo,
157: Object oscript) throws BSFException {
158: Object retval = null;
159: String classname = null;
160: GeneratedFile gf = null;
161:
162: String basescript = oscript.toString();
163: String script = basescript; // May be altered by $$CLASSNAME$$ expansion
164:
165: try {
166: // Do we already have a class exactly matching this code?
167: javaclass = (Class) codeToClass.get(basescript);
168:
169: if (javaclass != null) {
170: classname = javaclass.getName();
171: } else {
172: gf = openUniqueFile(tempDir, "BSFJava", ".java");
173: if (gf == null) {
174: throw new BSFException(
175: "couldn't create JavaEngine scratchfile");
176: }
177: // Obtain classname
178: classname = gf.className;
179:
180: // Write the kluge header to the file.
181: gf.fos
182: .write(("import java.lang.*;"
183: + "import java.util.*;"
184: + "public class " + classname + " {\n" + " static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {\n")
185: .getBytes());
186:
187: // Edit the script to replace placeholder with the generated
188: // classname. Note that this occurs _after_ the cache was checked!
189: int startpoint = script.indexOf(placeholder);
190: int endpoint;
191: if (startpoint >= 0) {
192: StringBuffer changed = new StringBuffer();
193: for (; startpoint >= 0; startpoint = script
194: .indexOf(placeholder, startpoint)) {
195: changed.setLength(0); // Reset for 2nd pass or later
196: if (startpoint > 0) {
197: changed.append(script.substring(0,
198: startpoint));
199: }
200: changed.append(classname);
201: endpoint = startpoint + placeholder.length();
202: if (endpoint < script.length()) {
203: changed.append(script.substring(endpoint));
204: }
205: script = changed.toString();
206: }
207: }
208:
209: // MJD - debug
210: // BSFDeclaredBean tempBean;
211: // String className;
212: //
213: // for (int i = 0; i < declaredBeans.size (); i++) {
214: // tempBean = (BSFDeclaredBean) declaredBeans.elementAt (i);
215: // className = StringUtils.getClassName (tempBean.bean.getClass ());
216: //
217: // gf.fos.write ((className + " " +
218: // tempBean.name + " = (" + className +
219: // ")bsf.lookupBean(\"" +
220: // tempBean.name + "\");").getBytes ());
221: // }
222: // MJD - debug
223:
224: // Copy the input to the file.
225: // Assumes all available -- probably mistake, but same as other engines.
226: gf.fos.write(script.getBytes());
227: // Close the method and class
228: gf.fos.write(("\n }\n}\n").getBytes());
229: gf.fos.close();
230:
231: // Compile through Java to .class file
232: // May not be threadsafe. Serialize access on static object:
233: synchronized (serializeCompilation) {
234: JavaUtils.JDKcompile(gf.file.getPath(), classPath);
235: }
236:
237: // Load class.
238: javaclass = EngineUtils.loadClass(mgr, classname);
239:
240: // Stash class for reuse
241: codeToClass.put(basescript, javaclass);
242: }
243:
244: Object[] callArgs = { mgr };
245: retval = internalCall(this , "BSFJavaEngineEntry", callArgs);
246: }
247:
248: catch (Exception e) {
249: e.printStackTrace();
250: throw new BSFException(BSFException.REASON_IO_ERROR, e
251: .getMessage());
252: } finally {
253: // Cleanup: delete the .java and .class files
254:
255: // if(gf!=null && gf.file!=null && gf.file.exists())
256: // gf.file.delete(); // .java file
257:
258: if (classname != null) {
259: // Generated class
260: File file = new File(tempDir + File.separatorChar
261: + classname + ".class");
262: // if(file.exists())
263: // file.delete();
264:
265: // Search for and clean up minor classes, classname$xxx.class
266: file = new File(tempDir); // ***** Is this required?
267: minorPrefix = classname + "$"; // Indirect arg to filter
268: String[] minorClassfiles = file
269: .list(new FilenameFilter() {
270: // Starts with classname$ and ends with .class
271: public boolean accept(File dir, String name) {
272: return (0 == name.indexOf(minorPrefix))
273: && (name.lastIndexOf(".class") == name
274: .length() - 6);
275: }
276: });
277: for (int i = 0; i < minorClassfiles.length; ++i) {
278: file = new File(minorClassfiles[i]);
279: // file.delete();
280: }
281: }
282: }
283: return retval;
284: }
285:
286: public void initialize(BSFManager mgr, String lang,
287: Vector declaredBeans) throws BSFException {
288: super .initialize(mgr, lang, declaredBeans);
289: }
290:
291: /**
292: * Return an object from an extension.
293: * @param object Object on which to make the internal_call (ignored).
294: * @param method The name of the method to internal_call.
295: * @param args an array of arguments to be
296: * passed to the extension, which may be either
297: * Vectors of Nodes, or Strings.
298: */
299: Object internalCall(Object object, String method, Object[] args)
300: throws BSFException {
301: //***** ISSUE: Only static methods are currently supported
302: Object retval = null;
303: try {
304: if (javaclass != null) {
305: //***** This should call the lookup used in BML, for typesafety
306: Class[] argtypes = new Class[args.length];
307: for (int i = 0; i < args.length; ++i) {
308: argtypes[i] = args[i].getClass();
309: }
310: Method m = MethodUtils.getMethod(javaclass, method,
311: argtypes);
312: retval = m.invoke(null, args);
313: }
314: } catch (Exception e) {
315: throw new BSFException(BSFException.REASON_IO_ERROR, e
316: .getMessage());
317: }
318: return retval;
319: }
320:
321: private GeneratedFile openUniqueFile(String directory,
322: String prefix, String suffix) {
323: File file = null;
324: FileOutputStream fos = null;
325: int max = 1000; // Don't try forever
326: GeneratedFile gf = null;
327: int i;
328: String className = null;
329: for (i = max, ++uniqueFileOffset; fos == null && i > 0; --i, ++uniqueFileOffset) {
330: // Probably a timing hazard here... ***************
331: try {
332: className = prefix + uniqueFileOffset;
333: file = new File(directory + File.separatorChar
334: + className + suffix);
335: if (file != null && !file.exists()) {
336: fos = new FileOutputStream(file);
337: }
338: } catch (Exception e) {
339: // File could not be opened for write, or Security Exception
340: // was thrown. If someone else created the file before we could
341: // open it, that's probably a threading conflict and we don't
342: // bother reporting it.
343: if (!file.exists()) {
344: logger.error("openUniqueFile: unexpected ", e);
345: }
346: }
347: }
348: if (fos == null) {
349: logger.error("openUniqueFile: Failed " + max + "attempts.");
350: } else {
351: gf = new GeneratedFile(file, fos, className);
352: }
353: return gf;
354: }
355: }
|