/* * VelocityFilter.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 2002-2004 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: drach. * Portions created by drach are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): drach, suhler. * * Version: 2.3 * Created by drach on 02/04/19 * Last modified by suhler on 04/11/30 15:19:46 * * Version Histories: * * 2.3 04/11/30-15:19:46 (suhler) * fixed sccs version string * * 2.2 03/07/28-09:25:38 (suhler) * Merged changes between child workspace "/home/suhler/brazil/naws" and * parent workspace "/net/mack.eng/export/ws/brazil/naws". * * 1.5.1.1 03/07/15-09:12:31 (suhler) * doc fixes for pdf version of the manual * * 2.1 02/10/01-16:39:56 (suhler) * version change * * 1.5 02/05/29-16:29:51 (suhler) * added docs * * 1.4 02/05/17-09:43:43 (drach) * Fix so it compiles with 1.1 * * 1.3 02/05/07-07:05:09 (drach) * Add methods to allow read only access to Server and Request fields * * 1.2 02/05/02-14:56:42 (drach) * Major modifications. * * 1.2 02/04/19-15:25:05 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 velocity/VelocityFilter.java * * 1.1 02/04/19-15:25:04 (drach) * date and time created 02/04/19 15:25:04 by drach * */ package sunlabs.brazil.velocity; import java.io.StringWriter; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Enumeration; import java.util.Hashtable; import sunlabs.brazil.filter.Filter; import sunlabs.brazil.handler.ResourceHandler; import sunlabs.brazil.server.Request; import sunlabs.brazil.server.Server; import sunlabs.brazil.session.SessionManager; import sunlabs.brazil.util.Format; import sunlabs.brazil.util.http.MimeHeaders; import bsh.Interpreter; import bsh.EvalError; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.FieldMethodizer; import org.apache.velocity.app.Velocity; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.runtime.RuntimeServices; import org.apache.velocity.runtime.log.LogSystem; /** * A filter for processing markup that is a Velocity template. The filter * will "merge" the content with a BeanShell script to produce output content. *
* The following server properties are used: *
* The BeanShell script is found by first looking in the filesystem. If * not found, the classpath is searched. It's okay not to specify or * use a BeanShell script. The content will still be processed by * the Velocity engine. *
 * The BeanShell interpreter is managed by session and is persistent during
 * the session.  The interpreter has access to the Server
 * object, the prefix string, the Request object, and the
 * Velocity context through the variables "server", "prefix", "request",
 * and "context".
 * 
 * A new Velocity context is created each time the filter
 * method is called.  For convenience, the context is initially populated
 * with the same objects as the BeanShell.  They are referenced by the
 * same names ("server", "prefix", "request", and "context").  However,
 * due to the design of Velocity, the public fields of the Server
 * and Request objects are read only.  The field values can not
 * be changed by the template code.
 * 
 * Each time the filter method is called, a new Velocity
 * context is created and populated. Then the BeanShell script, if it exists,
 * is passed through Format.subst for variable substitution
 * and then passed to the BeanShell interpreter.  Finally the content (i.e. a
 * Velocity template) and the context are passed to the Velocity engine
 * for processing.
 * 
* Note: Velocity requires at least a 1.2 Java VM. Attempts to use Velocity * with a 1.1 VM will most likely produce incorrect results. Also note that * the Brazil system must be compiled with a 1.2+ compiler so that core * components of Brazil are compatible with the Velocity engine. *
 * @author	Steve Drach <drach@sun.com>
 * @version		2.3
 */
public class VelocityFilter implements Filter, LogSystem {
    String script;
    String prefix;
    Server server;
    String session;
    public boolean
    init(Server server, String prefix) {
	this.server = server;
	this.prefix = prefix;
	boolean initok = true;
	session = server.props.getProperty(prefix + "session", "common");
	script = server.props.getProperty(prefix + "script");
	if (script != null) {
	    try {
		script = ResourceHandler.getResourceString(server.props,
							   prefix, script);
	    } catch (IOException e) {
		log(Server.LOG_ERROR, "BeanShell IOException: ", e);
		initok = false;
	    }
	}
	if (initok && script != null) {
	    Interpreter bsh = (Interpreter)SessionManager.getSession(session,
					     prefix, Interpreter.class);
	    try {
		bsh.set("server", server);
		bsh.set("prefix", prefix);
	    } catch (EvalError e) {
		log(Server.LOG_WARNING, "BeanShell EvalError: ", e);
		initok = false;
	    }
	}
	if (initok) {
	    try {
		Velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM, this);
		Velocity.init();
	    } catch (Exception e) {
		log(Server.LOG_ERROR, "Velocity Exception: ", e);
		initok = false;
	    }
	}
	return initok;
    }
    /**
     * This is the request object before the content was fetched
     */
    public boolean respond(Request request) {
	return false;
    }
    /**
     * Only filter text/* documents
     */
    public boolean
    shouldFilter(Request request, MimeHeaders headers) {
	String type = headers.get("content-type");
	return (type != null && type.toLowerCase().startsWith("text/"));
    }
    /**
     * Execute the BeanShell script if it exists and then process the
     * content as a Velocity template.
     */
    public byte[]
    filter(Request request, MimeHeaders headers, byte[] content) {
	boolean aok = true;
	VelocityContext context = new VelocityContext();
	context.put("context", context);
	context.put("prefix", prefix);
	try {
	    context.put("request", new Vrequest(request));
	} catch (Exception e) {
	    log(Server.LOG_WARNING, "Context error with request: ", e);
	    context.put("request", request);
	}
	try {
	    context.put("server", new Vserver(server));
	} catch (Exception e) {
	    log(Server.LOG_WARNING, "Context error with server: ", e);
	    context.put("server", server);
	}
	if (script != null) {
	    Interpreter bsh = (Interpreter)SessionManager.getSession(session,
							         prefix, null);
	    try {
		bsh.set("context", context);
		bsh.set("request", request);
		bsh.eval(Format.subst(request.props, script));
	    } catch (EvalError e) {
		log(Server.LOG_WARNING, "BeanShell EvalError: ", e);
		aok = false;
	    }
	}
	StringWriter w = new StringWriter();
	if (aok) {
	    try {
		Velocity.evaluate(context, w, "Velocity", new String(content));
	    } catch (ResourceNotFoundException e) {
		log(Server.LOG_WARNING, "Velocity ResourceNotFoundException: ", e);
		aok = false;
	    } catch (IOException e) {
		log(Server.LOG_WARNING, "Velocity IOException: ", e);
		aok = false;
	    } catch (MethodInvocationException e) {
		log(Server.LOG_WARNING, "Velocity MethodInvocationException: ", e);
		aok = false;
	    } catch (ParseErrorException e) {
		log(Server.LOG_WARNING, "Velocity ParseErrorException: ", e);
		aok = false;
	    }
	}
	if (aok) {
	    return w.toString().getBytes();
	} else {
	    return content;
	}
    }
    private void log(int level, String msg, Exception e) {
	if (e == null) {
	    server.log(level, prefix, msg);
	} else {
	    server.log(level, prefix, msg + e.getMessage());
	}
    }
    /**
     * Velocity LogSystem interface implementation.
     */
    public void init(RuntimeServices rsvc) {
    }
    public void logVelocityMessage(int level, String message) {
	switch(level) {
	case LogSystem.INFO_ID:
	    level = Server.LOG_INFORMATIONAL;
	    break;
	case LogSystem.WARN_ID:
	    level = Server.LOG_WARNING;
	    break;
	case LogSystem.ERROR_ID:
	    level = Server.LOG_ERROR;
	    break;
	}
	log(level, message, null);
    }
    /*
     * These classes make public fields accessable like Properties keys.
     * Unfortunately, this is read-only since Velocity only looks for
     * put methods on instances of Maps.
     */
    private Hashtable serverFields;
    /**
     * A helper class for Velocity that provides read only access
     * to the public fields of the Server object.  This
     * class and it's methods should only be used by the Velocity
     * engine.
     */
    public class Vserver extends Server {
	public Vserver(Server server) throws Exception {
	    if (serverFields == null) {
		serverFields = new Hashtable();
		Field[] fields = getClass().getFields();
		for (int i = 0; i < fields.length; i++) {
		    serverFields.put(fields[i].getName(), fields[i]);
		}
	    }
	    Enumeration e = serverFields.elements();
	    while (e.hasMoreElements()) {
		Field field = (Field)(e.nextElement());
		if (!Modifier.isFinal(field.getModifiers())) {
		    field.set(this, field.get(server));
		}
	    }
	}
	public Object get(String fieldName) {
	    try {
		Field f = (Field)(serverFields.get(fieldName));
		if (f != null) {
		    return f.get(this);
		}
	    } catch(Exception e) {
		this.log(Server.LOG_WARNING, "Vserver get: ", e.getMessage());
	    }
	    return null;
	}
    }
    private Hashtable requestFields;
    /**
     * A helper class for Velocity that provides read only access
     * to the public fields of the Request object.  This
     * class and it's methods should only be used by the Velocity
     * engine.
     */
    public class Vrequest extends Request {
	public Vrequest(Request request) throws Exception {
	    if (requestFields == null) {
		requestFields = new Hashtable();
		Field[] fields = getClass().getFields();
		for (int i = 0; i < fields.length; i++) {
		    requestFields.put(fields[i].getName(), fields[i]);
		}
	    }
	    Enumeration e = requestFields.elements();
	    while (e.hasMoreElements()) {
		Field field = (Field)(e.nextElement());
		if (!Modifier.isFinal(field.getModifiers())) {
		    field.set(this, field.get(request));
		}
	    }
	}
	public Object get(String fieldName) {
	    try {
		Field f = (Field)(requestFields.get(fieldName));
		if (f != null) {
		    return f.get(this);
		}
	    } catch(Exception e) {
		this.log(Server.LOG_WARNING, "Vrequest get: ", e.getMessage());
	    }
	    return null;
	}
    }
}