/** * Copyright (C) 2005-2007 Funambol * * This program is free software; you can redistribute it and/or modify * it under the terms of the Honest Public License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Honest Public License for more details. * * You should have received a copy of the Honest Public License * along with this program; if not, write to Funambol, * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA */ package br.com.prognus.psync.synclet; import java.io.*; import java.net.URL; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import bsh.BshClassManager; import bsh.Interpreter; import org.apache.commons.lang.builder.ToStringBuilder; import com.funambol.framework.config.ConfigClassLoader; import com.funambol.framework.core.Sync4jException; import com.funambol.framework.core.SyncML; import com.funambol.framework.engine.pipeline.*; import com.funambol.framework.logging.FunambolLogger; import com.funambol.framework.logging.FunambolLoggerFactory; import com.funambol.server.config.Configuration; /** * Goal of the BeanShell synclet is to provide a way to develop synclets without * the need to follow the build/pack/deploy cycle at any change. This will also * minimize the downtime of a running server and allows to apply quick hot * fixes. To achieve this, BeanShell synclet is based on the use of a scripting * language which is interpreted instead of compiled. The BeanShell scripting * language is the scripting used by this synclet, since it is very similar to * Java. * * @version $Id: BeanShellSynclet.java,v 1.5 2007-04-13 08:51:02 luigiafassina Exp $ */ public class BeanShellSynclet implements InputMessageProcessor, OutputMessageProcessor { // ------------------------------------------------------------- Static data /** * The interpreters hash map */ private static HashMap interpreters; /** * Logger */ private static final FunambolLogger log = FunambolLoggerFactory.getLogger("engine"); // ------------------------------------------------------------ Private data /** * The script name */ private String script; /** * The HTTP user agent pattern to match in order to trigger execution */ private String pattern; /** * HTTP header the pattern is applied to */ private String header; // ---------------------------------------------------------- Public methods /** * Delegates the call to the script. * * @param processingContext the message processing context * @param message the message to be processed * * @throws Sync4jException */ public void preProcessMessage(MessageProcessingContext processingContext, SyncML message) throws Sync4jException { if (!isDeviceToProcess(processingContext)) { return; } try { InputMessageProcessor imp = getInputSynclet(); imp.preProcessMessage(processingContext, message); } catch (StopProcessingException e) { throw e; } catch (Exception e) { log.error("Error in processing the input message", e); } } /** * Delegates the call to the script. * * @param processingContext the message processing context * @param message the message to be processed * * @throws Sync4jException */ public void postProcessMessage(MessageProcessingContext processingContext, SyncML message) throws Sync4jException { if (!isDeviceToProcess(processingContext)) { return; } try { OutputMessageProcessor omp = getOutputSynclet(); omp.postProcessMessage(processingContext, message); } catch (StopProcessingException e) { throw e; } catch (Exception e) { log.error("Error in processing the output message", e); } } // --------------------------------------------------------------- Accessors /** * Sets script * * @param script the script name */ public void setScript(String script) { this.script = script; } /** * Returns script * * @return scripts */ public String getScript() { return this.script; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } public String toString() { ToStringBuilder sb = new ToStringBuilder(this); sb.append("script", script ). append("header", header ). append("pattern", pattern); return sb.toString(); } // --------------------------------------------------------- Private methods /** * Returns the interpreter associated to the instance script name. If an * interpreter instance is not yet created, it is created before being * returned. * It also checks if the last modification timestamp of the script is more * recent that the last read. If so, the new script is loaded in the * interpreter. * * @return the interpreter instance associated to the instance's script */ private BeanShellEntry getBeanShellEntry() { assert (script != null && script.trim().length()>0); BeanShellEntry bse = null; synchronized (this) { bse = (BeanShellEntry)interpreters.get(script); if (bse == null) { bse = new BeanShellEntry(); bse.lastModified = -1; interpreters.put(script, bse); } } return bse; } /** * Creates and return an Interpreter. Plus, it sets adds the config class * loader to the interpreter class loader. * * @return the created interpreter */ private Interpreter createInterpreter() { Interpreter interpreter = new Interpreter(); ConfigClassLoader cl = (ConfigClassLoader)Configuration.getConfiguration().getClassLoader(); BshClassManager cm = interpreter.getClassManager(); URL[] urls = cl.getURLs(); for (int i=0; i0); return new File(Configuration.getConfiguration().getConfigPath(), script); } /** * Searches a match for the configured pattern with the HTTP * header specified by header. * If header or pattern is empty, isDeviceToProcess() returns always true. * If the client does not provide the specified header, isDeviceToProcess() * returns false. * * @param context message processing context * * @return true if the device must be proced by this synclet, false otherwise */ private boolean isDeviceToProcess(MessageProcessingContext context) { if ((pattern == null || pattern.trim().length() == 0) || (header == null || header.trim().length() == 0)) { return true; } Map headers = (Map)context.getRequestProperty(context.PROPERTY_REQUEST_HEADERS); // // Search for the requested header value // String value = null; Iterator i = headers.keySet().iterator(); while (i.hasNext()) { String h = (String)i.next(); if (header.equalsIgnoreCase(h)) { value = (String)headers.get(h); break; } } if ((value == null) || (value.trim().length() == 0)) { // // Header not found or not specified // return false; } Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(value); return m.find(); } /** * Evaluates the init() method * * @param interpreter the interpreter into which evaluate the script */ private void evalInitMethod(Interpreter interpreter) { try { interpreter.eval("init()"); } catch(Exception e) { log.error("Error calling the init method", e); } } // --------------------------------------------------- Static initialization static { interpreters = new HashMap(); } // ----------------------------------------------------------- Inner classes /** * This class represent the key to use to put the interpreters instances * into the hash map. It redefines hashCode() and equals() to the String * script name's ones. */ private class BeanShellEntry { public long lastModified; public InputMessageProcessor inputSynclet; public OutputMessageProcessor outputSynclet; } }