source: contrib/funambol/trunk/modules/psync/src/main/java/br/com/prognus/psync/synclet/BeanShellSynclet.java @ 2082

Revision 2082, 12.4 KB checked in by emersonfaria, 14 years ago (diff)

Ticket #927 - Reestruturacao dos diretorios do Funambol

Line 
1/**
2 * Copyright (C) 2005-2007 Funambol
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the Honest Public License.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 * Honest Public License for more details.
11 *
12 * You should have received a copy of the Honest Public License
13 * along with this program; if not, write to Funambol,
14 * 643 Bair Island Road, Suite 305 - Redwood City, CA 94063, USA
15 */
16package br.com.prognus.psync.synclet;
17
18import java.io.*;
19
20import java.net.URL;
21
22import java.util.*;
23import java.util.regex.Matcher;
24import java.util.regex.Pattern;
25
26import bsh.BshClassManager;
27import bsh.Interpreter;
28
29import org.apache.commons.lang.builder.ToStringBuilder;
30
31import com.funambol.framework.config.ConfigClassLoader;
32import com.funambol.framework.core.Sync4jException;
33import com.funambol.framework.core.SyncML;
34import com.funambol.framework.engine.pipeline.*;
35import com.funambol.framework.logging.FunambolLogger;
36import com.funambol.framework.logging.FunambolLoggerFactory;
37
38import com.funambol.server.config.Configuration;
39
40/**
41 * Goal of the BeanShell synclet is to provide a way to develop synclets without
42 * the need to follow the build/pack/deploy cycle at any change. This will also
43 * minimize the downtime of a running server and allows to apply quick hot
44 * fixes. To achieve this, BeanShell synclet is based on the use of a scripting
45 * language which is interpreted instead of compiled. The BeanShell scripting
46 * language is the scripting used by this synclet, since it is very similar to
47 * Java.
48 *
49 * @version $Id: BeanShellSynclet.java,v 1.5 2007-04-13 08:51:02 luigiafassina Exp $
50 */
51public class BeanShellSynclet
52implements InputMessageProcessor, OutputMessageProcessor {
53
54    // ------------------------------------------------------------- Static data
55
56    /**
57     * The interpreters hash map
58     */
59    private static HashMap interpreters;
60
61    /**
62     * Logger
63     */
64    private static final FunambolLogger log =
65        FunambolLoggerFactory.getLogger("engine");
66
67    // ------------------------------------------------------------ Private data
68
69    /**
70     * The script name
71     */
72    private String script;
73
74    /**
75     * The HTTP user agent pattern to match in order to trigger execution
76     */
77    private String pattern;
78
79    /**
80     * HTTP header the pattern is applied to
81     */
82    private String header;
83
84    // ---------------------------------------------------------- Public methods
85
86    /**
87     * Delegates the call to the script.
88     *
89     * @param processingContext the message processing context
90     * @param message the message to be processed
91     *
92     * @throws Sync4jException
93     */
94    public void preProcessMessage(MessageProcessingContext processingContext,
95                                  SyncML                   message)
96    throws Sync4jException {
97        if (!isDeviceToProcess(processingContext)) {
98            return;
99        }
100
101        try {
102            InputMessageProcessor imp = getInputSynclet();
103            imp.preProcessMessage(processingContext, message);
104        } catch (StopProcessingException e) {
105            throw e;
106        } catch (Exception e) {
107            log.error("Error in processing the input message", e);
108        }
109    }
110
111    /**
112     * Delegates the call to the script.
113     *
114     * @param processingContext the message processing context
115     * @param message the message to be processed
116     *
117     * @throws Sync4jException
118     */
119    public void postProcessMessage(MessageProcessingContext processingContext,
120                                   SyncML                   message)
121    throws Sync4jException {
122        if (!isDeviceToProcess(processingContext)) {
123            return;
124        }
125
126        try {
127            OutputMessageProcessor omp = getOutputSynclet();
128            omp.postProcessMessage(processingContext, message);
129        } catch (StopProcessingException e) {
130            throw e;
131        } catch (Exception e) {
132            log.error("Error in processing the output message", e);
133        }
134    }
135
136    // --------------------------------------------------------------- Accessors
137
138    /**
139     * Sets script
140     *
141     * @param script the script name
142     */
143    public void setScript(String script) {
144        this.script = script;
145    }
146
147    /**
148     * Returns script
149     *
150     * @return scripts
151     */
152    public String getScript() {
153        return this.script;
154    }
155
156    public String getHeader() {
157        return header;
158    }
159
160    public void setHeader(String header) {
161        this.header = header;
162    }
163
164    public String getPattern() {
165        return pattern;
166    }
167
168    public void setPattern(String pattern) {
169        this.pattern = pattern;
170    }
171
172    public String toString() {
173        ToStringBuilder sb = new ToStringBuilder(this);
174
175        sb.append("script",  script ).
176           append("header",  header ).
177           append("pattern", pattern);
178
179        return sb.toString();
180    }
181    // --------------------------------------------------------- Private methods
182
183    /**
184     * Returns the interpreter associated to the instance script name. If an
185     * interpreter instance is not yet created, it is created before being
186     * returned.
187     * It also checks if the last modification timestamp of the script is more
188     * recent that the last read. If so, the new script is loaded in the
189     * interpreter.
190     *
191     * @return the interpreter instance associated to the instance's script
192     */
193    private BeanShellEntry getBeanShellEntry() {
194        assert (script != null && script.trim().length()>0);
195
196        BeanShellEntry bse = null;
197        synchronized (this) {
198            bse = (BeanShellEntry)interpreters.get(script);
199
200            if (bse == null) {
201                bse = new BeanShellEntry();
202                bse.lastModified = -1;
203
204                interpreters.put(script, bse);
205            }
206        }
207
208        return bse;
209    }
210
211    /**
212     * Creates and return an Interpreter. Plus, it sets adds the config class
213     * loader to the interpreter class loader.
214     *
215     * @return the created interpreter
216     */
217    private Interpreter createInterpreter() {
218        Interpreter interpreter = new Interpreter();
219
220        ConfigClassLoader cl =
221            (ConfigClassLoader)Configuration.getConfiguration().getClassLoader();
222
223        BshClassManager cm = interpreter.getClassManager();
224        URL[] urls = cl.getURLs();
225        for (int i=0; i<urls.length; ++i) {
226            try {
227                cm.addClassPath(urls[i]);
228            } catch (IOException e) {
229                log.error( "Unable to add '"
230                          + urls[i]
231                          + "' to the classpath.", e);
232            }
233        }
234
235        return interpreter;
236    }
237
238    /**
239     * Returns the scripted MessageInputProcessor
240     *
241     * @return the scripted MessageInputProcessor
242     */
243    private InputMessageProcessor getInputSynclet()
244    throws Sync4jException {
245
246        BeanShellEntry bse = getBeanShellEntry();
247
248        //
249        // Checking if the script is more recent that the one interpreted
250        //
251        File scriptFile = getScriptFile();
252
253        if (bse.lastModified < scriptFile.lastModified()) {
254            try {
255                Interpreter interpreter = createInterpreter();
256                bse.inputSynclet = evalInputScript(interpreter);
257                bse.lastModified = scriptFile.lastModified();
258                evalInitMethod(interpreter);
259            } catch (Exception e) {
260                throw new Sync4jException(e);
261            }
262        }
263
264        return bse.inputSynclet;
265    }
266
267    /**
268     * Returns the scripted OutputMessageProcessor
269     *
270     * @return the scripted OutputMessageProcessor
271     */
272    private OutputMessageProcessor getOutputSynclet()
273    throws Sync4jException {
274        BeanShellEntry bse = getBeanShellEntry();
275
276        //
277        // Checking if the script is more recent that the one interpreted
278        //
279        File scriptFile = getScriptFile();
280
281        if (bse.lastModified < scriptFile.lastModified()) {
282            try {
283                Interpreter interpreter = createInterpreter();
284                bse.outputSynclet = evalOutputScript(interpreter);
285                bse.lastModified = scriptFile.lastModified();
286                evalInitMethod(interpreter);
287            } catch (Exception e) {
288                throw new Sync4jException(e);
289            }
290        }
291
292        return bse.outputSynclet;
293    }
294
295    /**
296     * Evaluates the script in the given interpreter and returns the results
297     * of the eveluation. In order to make it to return an InputMessageProcessor
298     * the command "return (InputMessageProcessor)this" is appended to the
299     * script.
300     *
301     * @param interpreter the interpreter into which evaluate the script
302     */
303    private InputMessageProcessor evalInputScript(Interpreter interpreter)
304    throws Exception {
305        FileReader r = new FileReader(getScriptFile());
306
307        interpreter.eval(r);
308        return (InputMessageProcessor)interpreter.eval(";\nreturn (InputMessageProcessor)this");
309    }
310
311    /**
312     * Evaluates the script in the given interpreter and returns the results
313     * of the eveluation. In order to make it to return an OutputMessageProcessor
314     * the command "return (OutputMessageProcessor)this" is appended to the
315     * script.
316     *
317     * @param interpreter the interpreter into which evaluate the script
318     */
319    private OutputMessageProcessor evalOutputScript(Interpreter interpreter)
320    throws Exception {
321        FileReader r = new FileReader(getScriptFile());
322
323        interpreter.eval(r);
324        return (OutputMessageProcessor)interpreter.eval(";\nreturn (OutputMessageProcessor)this");
325    }
326
327    /**
328     * Returns the script file as a File object, which is the combination of the
329     * configpath and the value of the script property.
330     *
331     * @return the script file object
332     */
333    private File getScriptFile() {
334        assert (script != null && script.length()>0);
335        return new File(Configuration.getConfiguration().getConfigPath(), script);
336    }
337
338    /**
339     * Searches a match for the configured <code>pattern</code> with the HTTP
340     * header specified by <code>header</code>.
341     * If header or pattern is empty, isDeviceToProcess() returns always true.
342     * If the client does not provide the specified header, isDeviceToProcess()
343     * returns false.
344     *
345     * @param context message processing context
346     *
347     * @return true if the device must be proced by this synclet, false otherwise
348     */
349    private boolean isDeviceToProcess(MessageProcessingContext context) {
350        if ((pattern == null || pattern.trim().length() == 0)
351        || (header  == null || header.trim().length()  == 0)) {
352            return true;
353        }
354
355        Map headers =
356            (Map)context.getRequestProperty(context.PROPERTY_REQUEST_HEADERS);
357
358        //
359        // Search for the requested header value
360        //
361        String value = null;
362        Iterator i = headers.keySet().iterator();
363        while (i.hasNext()) {
364            String h = (String)i.next();
365            if (header.equalsIgnoreCase(h)) {
366                value = (String)headers.get(h);
367                break;
368            }
369        }
370
371        if ((value == null) || (value.trim().length() == 0)) {
372            //
373            // Header not found or not specified
374            //
375            return false;
376        }
377
378        Pattern p = Pattern.compile(pattern);
379        Matcher m = p.matcher(value);
380
381        return m.find();
382    }
383
384    /**
385     * Evaluates the init() method
386     *
387     * @param interpreter the interpreter into which evaluate the script
388     */
389    private void evalInitMethod(Interpreter interpreter) {
390        try {
391            interpreter.eval("init()");
392        } catch(Exception e) {
393            log.error("Error calling the init method", e);
394        }
395    }
396
397    // --------------------------------------------------- Static initialization
398
399    static {
400        interpreters = new HashMap();
401    }
402
403    // ----------------------------------------------------------- Inner classes
404
405    /**
406     * This class represent the key to use to put the interpreters instances
407     * into the hash map. It redefines hashCode() and equals() to the String
408     * script name's ones.
409     */
410    private class BeanShellEntry {
411        public long lastModified;
412        public InputMessageProcessor inputSynclet;
413        public OutputMessageProcessor outputSynclet;
414    }
415}
Note: See TracBrowser for help on using the repository browser.