Package de.innovationgate.wgpublisher.expressions.tmlscript

Source Code of de.innovationgate.wgpublisher.expressions.tmlscript.RhinoExpressionEngineImpl

/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.wgpublisher.expressions.tmlscript;

import java.io.File;
import java.io.FilenameFilter;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.swing.UIManager;
import javax.swing.plaf.metal.MetalLookAndFeel;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import de.innovationgate.ext.org.mozilla.javascript.Context;
import de.innovationgate.ext.org.mozilla.javascript.ContextAction;
import de.innovationgate.ext.org.mozilla.javascript.ContextFactory;
import de.innovationgate.ext.org.mozilla.javascript.EcmaError;
import de.innovationgate.ext.org.mozilla.javascript.Function;
import de.innovationgate.ext.org.mozilla.javascript.JavaScriptException;
import de.innovationgate.ext.org.mozilla.javascript.NativeArray;
import de.innovationgate.ext.org.mozilla.javascript.NativeJavaObject;
import de.innovationgate.ext.org.mozilla.javascript.NativeObject;
import de.innovationgate.ext.org.mozilla.javascript.RhinoException;
import de.innovationgate.ext.org.mozilla.javascript.Script;
import de.innovationgate.ext.org.mozilla.javascript.Scriptable;
import de.innovationgate.ext.org.mozilla.javascript.ScriptableObject;
import de.innovationgate.ext.org.mozilla.javascript.Undefined;
import de.innovationgate.ext.org.mozilla.javascript.WrappedException;
import de.innovationgate.ext.org.mozilla.javascript.tools.debugger.Main;
import de.innovationgate.ext.org.mozilla.javascript.xml.XMLObject;

import de.innovationgate.authoring.remotedoc.RemoteDocReference;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.utils.cache.Cache;
import de.innovationgate.utils.cache.CacheException;
import de.innovationgate.utils.cache.CacheFactory;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGContent;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGExpressionException;
import de.innovationgate.webgate.api.WGFactory;
import de.innovationgate.webgate.api.WGLanguageChooser;
import de.innovationgate.webgate.api.utils.MasterSessionTask;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.WGPDispatcher;
import de.innovationgate.wgpublisher.expressions.ExpressionEngine;
import de.innovationgate.wgpublisher.expressions.ExpressionEngineException;
import de.innovationgate.wgpublisher.expressions.ExpressionResult;
import de.innovationgate.wgpublisher.expressions.tmlscript.wgaglobal.WGAGlobal;
import de.innovationgate.wgpublisher.lang.LanguageBehaviourTools;
import de.innovationgate.wgpublisher.lang.WebTMLLanguageChooser;
import de.innovationgate.wgpublisher.webtml.utils.TMLAction;
import de.innovationgate.wgpublisher.webtml.utils.TMLContext;
import de.innovationgate.wgpublisher.webtml.utils.TMLException;
import de.innovationgate.wgpublisher.webtml.utils.TMLUserProfile;


public class RhinoExpressionEngineImpl implements ExpressionEngine, RhinoExpressionEngine {
   
   
   
    class RemoteDocumentTracer extends MasterSessionTask {
       
        private String _targetContentKey = null;
        private String _targetDBKey = null;
        private String _linkKey;
        private String _remoteConsumerDB;
        private WGLanguageChooser _languageChooser;
       
        public RemoteDocumentTracer(WGDatabase db, String linkKey, String targetDB, WGLanguageChooser chooser) {
            super(db);
            _linkKey = linkKey;
            _remoteConsumerDB = targetDB;
            _languageChooser = chooser;
        }
       
        public boolean isDocumentFound() {
            return (_targetContentKey != null);
        }
       
        public String getTargetContextPath() {
           
            if (_targetContentKey == null) {
                return null;
            }
           
            if (_targetDBKey != null) {
                return "db:" + _targetDBKey + "/docid:" + _targetContentKey;
            }
            else {
                return "docid:" + _targetContentKey;               
            }
        }

        protected void exec(WGDatabase db) throws Throwable {

            // Find the document that the link in remote source doc points to
            WGContent content = WGPDispatcher.getContentByAnyKey(_linkKey, db, _languageChooser, false);
            if (content == null) {
                return;
            }
           
            Iterator refs = content.getItemValueList("remote_references").iterator();
            while (refs.hasNext()) {
                String refStr = (String) refs.next();
                try {
                    RemoteDocReference ref = new RemoteDocReference(refStr);
                   
                    // If we find a reference that point to our remote consumer db we will return
                    // the key of that document
                    if (ref.getDbKey().equals(_remoteConsumerDB)) {
                        _targetDBKey = null;
                        _targetContentKey = ref.getContentKey();
                        return;
                    }
                }
                catch (IllegalArgumentException e) {
                    WGFactory.getLogger().error("Illegal remote doc reference on " + content.getDocumentKey() + " (DB " + db.getDbReference() + ")" ,e);
                }
               
            }
           
            // If we find no suitable reference we point to the remote source document
            _targetDBKey = db.getDbReference();
            _targetContentKey = content.getContentKey().toString();
           
        }

        public String getTargetContentKey() {
            return _targetContentKey;
        }

        public String getTargetDBKey() {
            return _targetDBKey;
        }
       
    }

    private static final String CACHENAME_RHINO_SCRIPTS = "RhinoScriptCache";
    public static final int DEFAULT_CACHE_SIZE = 1000;
    public static final String SYSPROPERTY_DEBUG = "de.innovationgate.wga.tmlscript.debug";
    public static final String SYSPROPERTY_COMPILER_VERBOSE = "de.innovationgate.wga.tmlscript.compiler.verbose";
    public static final String SYSPROPERTY_CACHE_SIZE = "de.innovationgate.wga.tmlscript.cache.size";
    private Cache _cachedScripts = null;
    private boolean _debugEnabled;
    private boolean _verboseCompiling;
    private Logger logger = Logger.getLogger("wga.tmlscript");
    private int _cacheMaxSize;
   
    public static final FilenameFilter TMLSCRIPT_NAME_FILTER = new FilenameFilter() {

        public boolean accept(File file, String name) {
           return name.startsWith("TMLScript:");
        }
       
    };

   
    public RhinoExpressionEngineImpl() {

        // Get some configs
        _debugEnabled = Boolean.valueOf(System.getProperty(SYSPROPERTY_DEBUG)).booleanValue();
        if (_debugEnabled) {
            openDebugger(ContextFactory.getGlobal());
            RhinoContextFactory.defaultOptimizationLevel = -1;
        }
       
        _verboseCompiling = Boolean.valueOf(System.getProperty(SYSPROPERTY_COMPILER_VERBOSE)).booleanValue();
       
       
        // Prepare code cache
        _cacheMaxSize = DEFAULT_CACHE_SIZE;
        String cacheSizeStr = System.getProperty(SYSPROPERTY_CACHE_SIZE);
        if (cacheSizeStr != null) {
            try {
                _cacheMaxSize = Integer.parseInt(cacheSizeStr);
            }
            catch (NumberFormatException e) {
                logger.error("Cannot interpret '"  + SYSPROPERTY_CACHE_SIZE + "' as an integer: " + cacheSizeStr);
            }
        }
       
        // collect properties to configure cache
        Properties props = new Properties();
        props.setProperty("cache.memory", "true");
        props.setProperty("cache.capacity", String.valueOf(_cacheMaxSize));
        props.setProperty("cache.algorithm", "com.opensymphony.oscache.base.algorithm.LRUCache");
        props.setProperty("cache.blocking", "true");
       
        // Create cache
        try {
            _cachedScripts = CacheFactory.createCache("TMLScript_CodeCache", _cacheMaxSize, null);
        }
        catch (CacheException e) {
            logger.fatal("Error initializing TMLScript code cache. TMLScript is inactive!", e);
        }
       

    }

    public class RhinoContextAction implements ContextAction {

        TMLContext _tmlContext;

        private Map _additionalObjects;

        private int _type;

        private String _expression = null;
       
        private Function function = null;

        private Function _function = null;

        private Object[] _functionParams;

        private boolean _overrideFunctionScope;

        public RhinoContextAction(TMLContext tmlObject, String expression, int type, Map additionalObjects) {
            _tmlContext = tmlObject;
            _expression = expression;
            _type = type;
            _additionalObjects = additionalObjects;
        }
       
        public RhinoContextAction(TMLContext tmlObject, Function function, boolean overrideFunctionScope, Object[] params) {
            _tmlContext = tmlObject;
            _function  = function;
            _overrideFunctionScope = overrideFunctionScope;
            _functionParams = params;
            _type = ExpressionEngine.TYPE_SCRIPT;
            _additionalObjects = Collections.EMPTY_MAP;
        }

        public Object run(Context cx) {
            RhinoContext rcx = (RhinoContext) cx;
            rcx.initFields();
            rcx.setApplicationClassLoader(WGACore.getLibraryLoader());

            if (_tmlContext.iswebenvironment()) {
                rcx.isWebsiteScript = true;
            }

            // Preserve previous ThreadLocal variables and init/clear them for the current script
            ThreadLocalPreserver preserver =  new ThreadLocalPreserver(rcx);
            preserver.preserve(TL_INITIALCONTEXT, _tmlContext);
            preserver.preserve(TL_ROOTSCOPE, null);
            preserver.preserve(TL_ACTIONDEFINITION, null);
            preserver.preserve(TL_ACTIONLOCATOR, null);
            preserver.preserve(TL_SCRIPTNAME, null);

            // Create scope
            TMLScriptRootScope scope = null;
            try {
                TMLScriptRootScopeData scopePrototype = new TMLScriptRootScopeData(getOrCreateSharedScope(rcx, _tmlContext.getwgacore()));
                scope = new TMLScriptRootScope(scopePrototype, _tmlContext);
                rcx.setErrorReporter(scope);
                rcx.putThreadLocal(TL_ROOTSCOPE, scope);
            }
            catch (RhinoException exc) {
                return new ExpressionResult(null, false, true, new de.innovationgate.webgate.api.WGExpressionException("Error creating tmlscript context", exc.getLocalizedMessage()));
            }
           
            // Put custom additional objects into scope
            if (_additionalObjects != null) {
                Iterator objectNames = _additionalObjects.keySet().iterator();
                String objectName;
                Object object;
                while (objectNames.hasNext()) {
                    objectName = (String) objectNames.next();
                    object = _additionalObjects.get(objectName);
                    if (objectName.startsWith("$")) {
                        if (objectName.equals(PARAM_SCRIPTTIMEOUT)) {
                            rcx.scriptTimeout = ((Number) object).intValue();
                        }
                        else if (objectName.equals(PARAM_ACTIONDEFINITION)) {
                            rcx.putThreadLocal(TL_ACTIONDEFINITION, object);
                        }
                        else if (objectName.equals(PARAM_ACTIONLOCATOR)) {
                            rcx.putThreadLocal(TL_ACTIONLOCATOR, object);
                        }
                        else if (objectName.equals(RhinoExpressionEngine.PARAM_SCRIPTNAME)) {
                            rcx.putThreadLocal(TL_SCRIPTNAME, object);
                        }
                    }
                    else {
                        scope.getData().putScopeObject(objectName, Context.javaToJS(object, scope));
                    }
                }
            }
           
            // Default scope objects that may not be overwritten with custom scope objects
            scope.getData().putScopeObject(SCOPEOBJECT_RUNTIME, Context.javaToJS(RhinoExpressionEngineImpl.this, scope));
           
            try {
                // Eventually disable TMLScript optimization, so Script stack traces are generated
                rcx.setOptimizationLevel(RhinoContextFactory.defaultOptimizationLevel);
                rcx.setGeneratingDebug(false);
                if (_tmlContext.hasVariable(RhinoExpressionEngine.SESSIONVAR_ENABLE_SCRIPTSTACKTRACE)) {
                    Boolean tmlscriptOptimizationDisabled = (Boolean) _tmlContext.item(RhinoExpressionEngine.SESSIONVAR_ENABLE_SCRIPTSTACKTRACE);
                    if (tmlscriptOptimizationDisabled != null && tmlscriptOptimizationDisabled.booleanValue() == true) {
                        if (rcx.getOptimizationLevel() > 0) {
                            rcx.setOptimizationLevel(0);
                        }
                        rcx.setGeneratingDebug(true);
                    }
                }

                // Set new main TMLContext for this thread
                _tmlContext.makeThreadMainContext();
               
                // Execute
                Object result = null;
                try {
                    if (_function != null) {
                        result = executeFunction(_function, _overrideFunctionScope, _functionParams, rcx, scope);
                    }
                    else if (_type == ExpressionEngine.TYPE_SCRIPT) {
                        result = executeScript(_expression, rcx, scope);
                    }
                    else {
                        result = executeExpression(_expression, rcx, scope);
                    }
                }
               
                // Some syntax error
                catch (EcmaError exc) {
                    String exceptionMessage = exc.getName() + " executing tmlscript: " + exc.getMessage() + "\n" + buildExceptionDetails(exc);
                    WGExpressionException expressionException;
                    String scriptStackTrace = filterScriptStackTrace(exc.getScriptStackTrace(TMLSCRIPT_NAME_FILTER));
                    Throwable cause = (WGUtils.isEmpty(scriptStackTrace) ? exc : null);
                    expressionException = new de.innovationgate.webgate.api.WGExpressionException(exceptionMessage, exc.lineSource(), cause, scriptStackTrace);
                    return new ExpressionResult(null, false, true, expressionException);
                }
               
                // A script timeout (deprecated, works only in debug mode)
                catch (TimeoutError err) {
                    return new ExpressionResult(null, false, true, new de.innovationgate.webgate.api.WGExpressionException("Script execution timed out after " + err.getTimeout() + " milliseconds.",
                            _expression));
                }
               
                // Some java script error explicitly thrown by the code: Converted to TMLScriptException so the class can be better used outside
                catch (JavaScriptException exc) {
                    String exceptionMessage = "JavaScript exception executing tmlscript: " + exc.getMessage()  + " (Exception value: " + exc.getValue() + ")\n" + buildExceptionDetails(exc);
                    de.innovationgate.webgate.api.WGExpressionException expressionException = new de.innovationgate.webgate.api.WGExpressionException(exceptionMessage, exc.lineSource(), convertToTMLScriptException(exc), filterScriptStackTrace(exc.getScriptStackTrace()));
                    return new ExpressionResult(null, false, true, expressionException);
                }
               
                // Everything else: Wrapped java exceptions, internal rhino malfunctionings etc.
                catch (RhinoException exc) {
                   
                    Throwable cause = exc;
                    if (cause instanceof WrappedException) {
                        cause = ((WrappedException) exc).getCause();
                    }
                   
                    return new ExpressionResult(null, false, true, new de.innovationgate.webgate.api.WGExpressionException("Exception executing tmlscript: " + cause.getMessage() + ".\n"
                            + buildExceptionDetails(exc), exc.lineSource(), cause, exc.getScriptStackTrace()));
                }
                finally {
                   
                    /// Remove thread main context
                    _tmlContext.removeThreadMainContext();
                   
                    // Restore preserved ThreadLocal values
                    preserver.restore();
                }

                // Evaluate and return result;
                if (result instanceof NativeJavaObject) {
                    result = ((NativeJavaObject) result).unwrap();
                }
                else if (result instanceof NativeArray) {
                    result = new ArrayList(Arrays.asList(rcx.getElements((NativeArray) result)));
                }
                else if (result instanceof Undefined) {
                    result = "";
                }

                if (result instanceof Boolean) {
                    Boolean bolResult = (Boolean) result;
                    return new ExpressionResult(result, bolResult.booleanValue(), !bolResult.booleanValue(), null);
                }
                else {
                    return new ExpressionResult(result, false, isJSFalse(result), null);
                }

            }
            catch (Exception e) {
                Logger.getLogger("wga").error("Error in TMLScript processing", e);
                return new ExpressionResult(null, false, true, new de.innovationgate.webgate.api.WGExpressionException("Unexpected exception: " + e.getClass().getName() + ": " + e.getMessage(),
                        "(No source available)"));
            }
        }



        /**
         * Filters out internal TMLScript-Engine-Calls from the stacktrace
         * @param scriptStackTrace
         * @return
         */
        private String filterScriptStackTrace(String scriptStackTrace) {
            List lines = WGUtils.deserializeCollection(scriptStackTrace, "\n", false);
            String line;
            for (int idx=0; idx < lines.size(); idx++) {
                line = (String) lines.get(idx);
               
                // As TMLScript-Scripts internally are converted to a JS-Function "_tmlscript", that is called right after definition
                // we can eliminate that call right before the function, and cut out the function name from the stack trace
                if (line.endsWith("(_tmlscript)")) {
                    lines.remove(idx+1);
                    lines.set(idx, line.substring(0, line.indexOf("(_tmlscript)")));
                }
            }
           
            return WGUtils.serializeCollection(lines, "\n");
          }







       

    }

    public class MasterFunction implements Runnable {
       
      private TMLContext _context;
   

   
      private Object _returnValue = null;



        private Object[] _params;



        private Function _function;



        private String _contextPath;



        private boolean _overrideFunctionScope;
     
      public MasterFunction(TMLContext context, boolean overrideFunctionScope, Function function, Object[] params) throws WGAPIException, TMLException {
          _context = TMLContext.createMasterSessionContext(context);
          _overrideFunctionScope = overrideFunctionScope;
          _function = function;
            _params = params;
           
      }
     
      /* (Kein Javadoc)
       * @see java.lang.Runnable#run()
       */
      public void run() {
           
        try {
                String taskDescr = "Anonymous TMLScript Master Function";
               
                // Open database in master thread
                WGDatabase db = _context.getdocument().getDatabase();
          db.openSession();
                db.getSessionContext().setTask(taskDescr);
               
                // Eventually open pers db too so profile is available
                TMLUserProfile profile = _context.getprofile();
               
                if (profile != null && !profile.getprofile().getDatabase().isSessionOpen()) {
                    WGDatabase persDB = profile.getprofile().getDatabase();
                    persDB.openSession();
                    persDB.getSessionContext().setTask(taskDescr);
                }
   
                RhinoContextAction contextAction = new RhinoContextAction(_context, _function, _overrideFunctionScope, _params);
                ExpressionResult result = (ExpressionResult) ContextFactory.getGlobal().call(contextAction);
               
                if (result.isError()) {
                    WGExpressionException exc = result.getException();
                    _context.getlog().error(
                            "Error executing anonymous TMLScript Master Function",
                            result.getException());
                            
                }
               
                _returnValue = result.getResult();               
   
        }
        catch (Exception e) {
          _context.getwgacore().getLog().error("Error creating TML Context for master function", e);
        }
        finally {
          WGFactory.getInstance().closeSessions();
        }
       
      }
     
      public void start() {
        Thread actionThread = new Thread(this);
        actionThread.start();
      try {
        actionThread.join();
      }
      catch (InterruptedException e) {
        e.printStackTrace();
      }
      }
   
      public Object getReturnValue() {
        return _returnValue;
      }
   
   
   
   
   
    }

    static {
        ContextFactory.initGlobal(new RhinoContextFactory());
    }

    private RhinoScope _sharedScope = null;

    private Main _debugger;
    public static final String SCOPEOBJECT_RUNTIME = "runtime";

    /**
     * @see ExpressionEngine#evaluateExpression(String, TMLContext)
     */
    public ExpressionResult evaluateExpression(String expression, TMLContext context, int type, Map objects) {

        if (objects == null) {
            objects = new HashMap();
        }
       
        if (context == null) {
            return new ExpressionResult(null, false, false, new WGExpressionException("Tried to execute TMLScript with an tml object context of null", expression));
        }

        if (objects.containsKey("$tmlscriptDebug")) {
            debug();
        }

        RhinoContextAction contextAction = new RhinoContextAction(context, expression, type, objects);
        return (ExpressionResult) ContextFactory.getGlobal().call(contextAction);

    }

  public Throwable convertToTMLScriptException(JavaScriptException exc) {
       
      // Isolate cause
      Throwable cause = exc.getCause();
     
      // If there is no "real" cause in getCause() we look if there is a wrapped java exception object "in there", and use it nesa
      if (cause == null || cause == exc) {
          if (exc.getValue() != null && exc.getValue() instanceof NativeJavaObject) {
              Object obj = ((NativeJavaObject) exc.getValue()).unwrap();
              if (obj instanceof Throwable) {
                  cause = (Throwable) obj;
              }
          }
      }
     
        TMLScriptException tmlEx = new TMLScriptException(exc.details(), cause);
        tmlEx.setLineNumber(exc.lineNumber());
        tmlEx.setLineSource(exc.lineSource());
        tmlEx.setColumnNumber(exc.columnNumber());
        return tmlEx;
       
    }

    public void debug() {
        if (_debugger != null) {
            _debugger.doBreak();
        }
    }

    protected RhinoScope getOrCreateSharedScope(Context cx, WGACore core) {
        if (this._sharedScope == null) {
            this._sharedScope = new RhinoScope();
        }

        if (cx != null && !_sharedScope.isInited()) {
            this._sharedScope.init(cx, core);
        }

        return this._sharedScope;
    }
   
    public RhinoScope getSharedScope() {
        return this._sharedScope;
    }

    /**
     * @param exc
     * @return
     */
    private String buildExceptionDetails(RhinoException exc) {
        StringBuffer out = new StringBuffer();
       
        out.append("At line ").append(exc.lineNumber()).append(", column ").append(exc.columnNumber());
       
        String details = exc.details();
        if (details.length() > exc.getMessage().length() || (!details.equals(exc.getMessage().substring(0, details.length())))) {
            out.append("\nDetails: " + exc.details());
        }
        return out.toString();
    }



    /**
     * Method resolveScriptlets.
     *
     * @param result
     * @return Object
     * @throws ParseException
     * @throws WGAPIException
     */
    public String resolveScriptlets(Object input, TMLContext context, Map engineParams) throws ParseException, WGAPIException {

        if (input == null) {
            return null;
        }
       
        if (input instanceof List) {
            Iterator results = ((List) input).iterator();
            List resolvedResults = new ArrayList();
            while (results.hasNext()) {
                resolvedResults.add(this.resolveScriptlets(results.next(), context, engineParams));
            }
            return WGUtils.serializeCollection(resolvedResults, "");
        }

        String unresolved = input.toString();
       
        Integer level = (Integer) engineParams.get(RhinoExpressionEngine.PARAM_LEVEL);
        if (level == null) {
          level = RhinoExpressionEngine.LEVEL_SYSTEM_MACROS;
        }
       
        // System macros
        String returnValue = parseForScriptlets(context, unresolved, "{%", "%}", (level.intValue() >= LEVEL_SCRIPTLETS.intValue()), engineParams);

        // User macros and real scriptlets
        if (level.intValue() >= LEVEL_MACROS.intValue()) {
            returnValue = parseForScriptlets(context, returnValue, "<br>{@", "@}", (level.intValue() >= LEVEL_SCRIPTLETS.intValue()), engineParams);
            returnValue = parseForScriptlets(context, returnValue, "{@", "@}", (level.intValue() >= LEVEL_SCRIPTLETS.intValue()), engineParams);
        }

        return returnValue;

 
    }

    private String parseForScriptlets(TMLContext context, String unresolved, String startSeq, String endSeq, boolean allowScriptlets, Map engineParams) throws ParseException, WGAPIException {
       
        StringBuffer resolved = new StringBuffer();
        int lastParamEndPos = 0;
        int startPos = -1;
        int endPos = -1;
        try {
            String scriptlet;
            while ((startPos = unresolved.indexOf(startSeq, startPos + 1)) != -1) {
                endPos = unresolved.indexOf(endSeq, startPos + 1);
                if (endPos != -1) {
                    scriptlet = unresolved.substring(startPos + startSeq.length(), endPos);
                   
                    if (lastParamEndPos < startPos) {
                        resolved.append(unresolved.substring(lastParamEndPos, startPos));
                    }
                    resolved.append(executeScriptlet(scriptlet, context, allowScriptlets, engineParams));
                    lastParamEndPos = endPos + endSeq.length();
                   
                    // Look if we have a following <br> which we want to swallow
                    if (unresolved.length() >= lastParamEndPos + 4 && unresolved.substring(lastParamEndPos, lastParamEndPos + 4).equals("<br>")) {
                        lastParamEndPos += 4;
                    }
                   
                }
                else {
                    // If we cannot find a terminator we exit "gracefully" by returning the rest of the string unmodified
                    if (lastParamEndPos < startPos) {
                        resolved.append(unresolved.substring(lastParamEndPos, startPos));
                    }
                    resolved.append(unresolved.substring(startPos));
                    return resolved.toString();
                }
            }
            if (lastParamEndPos < unresolved.length()) {
                resolved.append(unresolved.substring(lastParamEndPos));
            }
            String returnValue = resolved.toString();
            return returnValue;
        }
        catch (UnsupportedEncodingException e) {
            throw new ParseException("Unsupported encoding: " + e.getMessage(), startPos);
        }

    }

    /**
     * Method executeScriptlet.
     *
     * @param scriptletToken
     * @return Object
     * @throws UnsupportedEncodingException
     * @throws WGAPIException
     */
    protected String executeScriptlet(String scriptletToken, TMLContext context, boolean allowScriptlets, Map params) throws UnsupportedEncodingException, WGAPIException {

        // Resolve context
        if (scriptletToken.startsWith("(")) {
            int endContext = scriptletToken.indexOf(")");
            if (endContext == -1) {
                context.addwarning("Error executing scriptlet. Context expression not terminated: " + scriptletToken, false);
                return "";
            }
            String contextExpression = scriptletToken.substring(1, endContext);
            TMLContext targetContext = context.context(contextExpression, false);
            if (targetContext == null) {
                context.addwarning("Error executing scriptlet. Context could not be resolved: " + contextExpression, false);
                return "";
            }
            context = targetContext;
            scriptletToken = scriptletToken.substring(endContext + 1);
        }
       
        // First level scriptlets
        if (scriptletToken.startsWith("$")) {
            String metaName = scriptletToken.substring(1);
            String metaType = "content";
            int colonPos = metaName.indexOf(":");
            if (colonPos != -1) {
                metaType = metaName.substring(0, colonPos);
                metaName = metaName.substring(colonPos + 1);
            }
            return String.valueOf(context.meta(metaType, metaName));
        }
        else if (scriptletToken.startsWith("#")) {
            return String.valueOf(context.item(scriptletToken.substring(1)));
        }
        else if (scriptletToken.startsWith("!")) {
            return executeCommandMacro(context, scriptletToken.substring(1), params);
        }
        else if (scriptletToken.startsWith("@")) {
            return "{" + scriptletToken + "@}";
        }

        if (!allowScriptlets) {
            return "";
        }

        // Second level scriptlets
        ExpressionResult result;
        if (scriptletToken.startsWith("=")) {
            result = evaluateExpression(scriptletToken.substring(1), context, ExpressionEngine.TYPE_EXPRESSION, null);
        }
        else {
            result = evaluateExpression(scriptletToken, context, ExpressionEngine.TYPE_SCRIPT, null);
        }

        if (result.isError()) {
            context.addwarning("Error executing scriptlet: " + result.getException().getMessage(), false);
            return "";
        }
        else {
            return String.valueOf(result.getResult());
        }

    }

    private String executeCommandMacro(TMLContext context, String command, Map engineParams) throws UnsupportedEncodingException, WGAPIException {
       
        int colonPos = command.indexOf(":");
        String param = null;
        if (colonPos != -1) {
            param = command.substring(colonPos + 1);
            command = command.substring(0, colonPos);
        }
       
        Boolean generateDataURL = (Boolean) engineParams.get(RhinoExpressionEngine.PARAM_IMAGEURL_AS_DATAURL);
        if (generateDataURL == null) {
          generateDataURL = Boolean.FALSE;
        }
       
        if (command.equalsIgnoreCase("url")) {
            return context.contenturl(null ,null);
        }
        else if (command.equalsIgnoreCase("contenturl")) {
            return macroContentURL(context, param);
        }
        else if (command.equalsIgnoreCase("namedcontenturl")) {
            return macroNamedContentURL(context, param);
        }else if (command.equalsIgnoreCase("filelink") || command.equalsIgnoreCase("fileurl") || command.equalsIgnoreCase("imgurl")) {
            List parms = WGUtils.deserializeCollection(param, ",", true);
            String fileName = null;
            String containerName = null;
            String title = fileName;
            if (parms.size() == 1) {
                fileName = (String) parms.get(0);
                title = fileName;
            }
            else if (parms.size() == 2){
                containerName = (String) parms.get(0);
                fileName = (String) parms.get(1);
                title = fileName;
            }
            else if (parms.size() >= 3) {
                containerName = (String) parms.get(0);
                fileName = (String) parms.get(1);
                title = (String) parms.get(2);
            }
            else {
                return "(invalid parameter count for !filelink: " + parms.size() + ")";
            }
           
            String url = null;
            if (generateDataURL.booleanValue() && command.equalsIgnoreCase("imgurl")) {
              url = context.filedataurl(containerName, fileName);
            } else {
              url = context.fileurl(containerName, fileName);
            }
           
            if (command.equalsIgnoreCase("fileurl") || command.equalsIgnoreCase("imgurl")) {
                return url;
            }
            else {
                StringBuffer out = new StringBuffer();
                out.append("<a href=\"").append(url).append("\"").append(">");
                out.append(title);
                out.append("</a>");
                return out.toString();
            }           
        }
        else if (command.equalsIgnoreCase("link")) {
            StringBuffer out = new StringBuffer();
            out.append("<a href=\"").append(context.contenturl(null, null)).append("\"");
            if (param != null) {
                out.append(" class=\"").append(param).append("\"");
            }
            out.append(">");
           
            out.append(context.meta("title"));
           
           
            out.append("</a>");
            return out.toString();
        }
        else if (command.equalsIgnoreCase("div")) {
            StringBuffer out = new StringBuffer();
            out.append("<div");
            if (param != null) {
                out.append(" class=\"" + param + "\"");
            }
            out.append(">");
            return out.toString();
        }
        else if (command.equalsIgnoreCase("/div") || command.equalsIgnoreCase("_div")) {
            return "</div>";
        }
        else if (command.equalsIgnoreCase("span")) {
            StringBuffer out = new StringBuffer();
            out.append("<span");
            if (param != null) {
                out.append(" class=\"" + param + "\"");
            }
            out.append(">");
            return out.toString();
        }
        else if (command.equalsIgnoreCase("/span") || command.equalsIgnoreCase("_span")) {
            return "</span>";
        }
        else if (command.equalsIgnoreCase("label")) {
            List params = WGUtils.deserializeCollection(param, ",");
            String container = (params.size() >= 3 ? (String) params.get(params.size() - 3) : null);
            String file = (params.size() >= 2 ? (String) params.get(params.size() - 2) : null);
            String key = (String) params.get(params.size() - 1);
            return context.label(container, file, key);
        }
        else if (command.equalsIgnoreCase("rtfsystem")) {
            return macroRTFSystem(context, param, engineParams);
        }
        else {
            List params = new ArrayList();
            if (param != null) {
                params.addAll(WGUtils.deserializeCollection(param, ","));
            }
            return executeCustomScriptlet(context, command, params);
        }
       
    }

    private String macroRTFSystem(TMLContext context, String param, Map engineParams) {
       
        Integer level = (Integer) engineParams.get(RhinoExpressionEngine.PARAM_LEVEL);
        if (level.equals(RhinoExpressionEngine.LEVEL_SYSTEM_MACROS)) {
            return param;
        }
        else {
            return "";
        }
       
    }

    private String macroContentURL(TMLContext context, String param) throws UnsupportedEncodingException, WGAPIException {
       
        List parms = WGUtils.deserializeCollection(param, ",", true);
        String dbKey = null;
        String contentKey = null;
        if (parms.size() == 2) {
            dbKey = (String) parms.get(0);
            contentKey = (String) parms.get(1);
        }
        else {
            contentKey = (String) parms.get(0);
        }
       
        // First try to fetch target context. When found we return the content url of it
        // If current doc is a remote doc we try a "roundtrip" to see which doc in the current
        // db represents the link target in the source database (B00004D92)
        if (context.db().getBooleanAttribute(WGACore.DBATTRIB_USEREMOTECS, false) && context.content().hasItem("remote_info")) {
            TMLContext remoteTargetContext = traceRemoteDocument(context, contentKey);
            if (remoteTargetContext != null) {
                return remoteTargetContext.contenturl(null, null);
            }
        }
        else {
            String contextExpr = (dbKey != null ? "db:" + dbKey + "/" : "") + "docid:" + contentKey;
            TMLContext targetContext = context.context(contextExpr, false);
            if (targetContext != null) {
                return targetContext.contenturl(null, null);
            }
        }
       
        return createFallBackContentURL(context, dbKey, contentKey);
       
   
    }

    /**
     * Generates a content URL based on the parameter input without context change
     * usefull when target context not retrievable and the document is invisible for the current user
     * @param context
     * @param dbKey
     * @param anyURLContentKey
     * @return
     * @throws WGAPIException
     */
  private String createFallBackContentURL(TMLContext context, String dbKey, String anyURLContentKey) throws WGAPIException {

        if (dbKey == null) {
            dbKey = context.db().getDbReference();
        }
       
        String defaultMediaKey = "html";
        WGDatabase db = (WGDatabase) context.getwgacore().getContentdbs().get(dbKey);
        if (db != null) {
            defaultMediaKey = (String) db.getAttribute(WGACore.DBATTRIB_DEFAULT_MEDIAKEY);
        }
       
        return context.meta("request", "wgaurl") + "/" + dbKey + "/" + defaultMediaKey + "/default/" + anyURLContentKey;
  }
   
    private String macroNamedContentURL(TMLContext context, String param) throws UnsupportedEncodingException, WGAPIException {
       
        List parms = WGUtils.deserializeCollection(param, ",", true);
        String dbKey = null;
        String uniqueName = null;
        if (parms.size() == 2) {
            dbKey = (String) parms.get(0);
            uniqueName = (String) parms.get(1);
        }
        else {
          uniqueName = (String) parms.get(0);
        }
       

        String contextExpr = (dbKey != null ? "db:" + dbKey + "/" : "") + "name:" + uniqueName;
        TMLContext targetContext = context.context(contextExpr, false);
        if (targetContext != null) {
            return targetContext.contenturl(null, null);
        }
       
        return createFallBackContentURL(context, dbKey, uniqueName);
                           
    }

    private TMLContext traceRemoteDocument(TMLContext context, String linkTargetContentKey) throws WGAPIException {

        try {
            String remoteInfo = context.content().getItemText("remote_info");
            RemoteDocReference ref = new RemoteDocReference(remoteInfo);
            WGDatabase targetDB = context.content().getDatabase();
            WGDatabase sourceDB = (WGDatabase) context.getwgacore().getContentdbs().get(ref.getDbKey());
           
            // Determine the language behaviour to use while resolving the link in source DB. If both dbs are multi-language we can
            // use the target language behaviour to allow behaviour integrity. Otherwise we must choose source behaviour.
            WGDatabase langBehaviourDB;
            if (LanguageBehaviourTools.isMultiLanguageDB(sourceDB) && LanguageBehaviourTools.isMultiLanguageDB(targetDB)) {
                langBehaviourDB = targetDB;
            }
            else {
                langBehaviourDB = sourceDB;
            }
           
            RemoteDocumentTracer tracer = new RemoteDocumentTracer(sourceDB, linkTargetContentKey, context.db().getDbReference(), new WebTMLLanguageChooser(langBehaviourDB, context));
            tracer.runWithExceptions();
            if (tracer.isDocumentFound()) {
                return context.context(tracer.getTargetContextPath(), false);
            }
            else {
                return null;
            }
        }
        catch (Throwable e) {
            context.getwgacore().getLog().error("Exception tracing remote document reference", e);
            return null;
        }
       
    }

    private String executeCustomScriptlet(TMLContext context, String command, List params) throws WGAPIException {
       
        // Isolate db part
        String dbkey = null;
        int slashPos = command.indexOf("/");
        if (slashPos != -1) {
            dbkey = command.substring(0, slashPos);
            command = command.substring(slashPos + 1);
        }
       
        // Build scriptlet module name
        String actionID;
        if (!WGUtils.isEmpty(dbkey)) {
            actionID = dbkey + "/scriptlets:" + command;
        }
        else {
            actionID = "scriptlets:" + command;
        }
       
        // Call scriptlet
        TMLAction action = context.getActionByID(actionID, null);
        if (action != null) {
            return String.valueOf(context.callCustomAction(action, params));
        }
        else {
            return "";
        }
       
    }

    private void openDebugger(ContextFactory factory) {
       
        try {
            UIManager.setLookAndFeel(MetalLookAndFeel.class.getName());
        _debugger = new Main("TMLScript Debugger");
        _debugger.attachTo(factory);
        _debugger.setExitAction(null);
        _debugger.pack();
        _debugger.setSize(600, 460);
        _debugger.setVisible(true);
        }
        catch (Exception e) {
            Logger.getLogger("wga.tmlscript.debug").error("Exception starting debugger", e);
        }
       
    }

    /* (non-Javadoc)
     * @see java.lang.Object#finalize()
     */
    public void close() {
       if (_debugger != null) {
           _debugger.dispose();
       }
      
       if (_cachedScripts != null) {
           try {
            _cachedScripts.destroy();
        }
        catch (CacheException e) {
            logger.error("Exception closing TMLScript code cache", e);
        }
       }
      
    }

    /**
     * @return Returns the debugEnabled.
     */
    public boolean isDebugEnabled() {
        return _debugEnabled;
    }
   
    public long getScriptCacheCurrentSize() {
        try {
            return _cachedScripts.getSize();
        }
        catch (CacheException e) {
            return 0;
        }
    }

    /**
     * @return Returns the cacheMaxSize.
     */
    public int getScriptCacheMaxSize() {
        return _cacheMaxSize;
    }

    /* (non-Javadoc)
     * @see de.innovationgate.wgpublisher.expressions.tmlscript.RhinoExpressionEngine#isTMLScriptBean(java.lang.Object)
     */
    public int determineTMLScriptType(Object obj) {
       
        if (obj instanceof Scriptable) {
           
            Scriptable scr = (Scriptable) obj;
           
            if (scr.getClassName().equals("XMLList")) {
                return RhinoExpressionEngine.TYPE_XMLLIST;
            }
            else if (scr instanceof XMLObject) {
                return RhinoExpressionEngine.TYPE_XMLOBJECT;
            }
            else {
                return RhinoExpressionEngine.TYPE_SCRIPTABLE;
            }
        }
        else {
            return RhinoExpressionEngine.TYPE_NOTMLSCRIPT;
        }

    }

    /* (non-Javadoc)
     * @see de.innovationgate.wgpublisher.expressions.tmlscript.RhinoExpressionEngine#xpathTMLScriptBean(java.lang.Object, java.lang.String)
     */
    public List xpathTMLScriptBean(Object obj, String xpath) throws ExpressionEngineException {
       
        XMLObject xmlObj = (XMLObject) obj;
        Document doc;
        try {
            String xmlText = (String) ScriptableObject.callMethod(xmlObj, "toXMLString", new Object[] {});
            doc = DocumentHelper.parseText(xmlText);
        }
        catch (DocumentException e) {
           throw new ExpressionEngineException("Error parsing TMLScript xml object", e);
        }
        return doc.getRootElement().selectNodes(xpath);
       
    }
   
    private void logCompilation(String expression) {
        if (_verboseCompiling) {
            Logger.getLogger("wga.tmlscript").info("Compiling TMLScript code nr. " (getScriptCacheCurrentSize() + 1) + ": " + WGUtils.strReplace(expression, "\n", "", true));
        }
    }
   
    protected Script getCompiledScript(String code, RhinoContext cx) {
        String codeCacheKey = String.valueOf(code.hashCode());
        Script script = null;
        try {
            script = (Script) _cachedScripts.readEntry(codeCacheKey);
        }
        catch (CacheException e) {
            Logger.getLogger("wga.tmlscript.").error("Unable to load cached TMLScript code", e);
        }
       
        if (script == null || isDebugEnabled() || cx.isGeneratingDebug()) {
            logCompilation(code);
            String scriptName = determineScriptName(cx, codeCacheKey);
            script = cx.compileString(code, scriptName, 1, null);
            if (!cx.isGeneratingDebug()) {
                try {
                    _cachedScripts.writeEntry(codeCacheKey, script, null);
                }
                catch (CacheException e) {
                    Logger.getLogger("wga.tmlscript.").error("Unable to cache TMLScript code", e);
                }
            }
        }
        return script;
    }

    private String determineScriptName(RhinoContext cx, String codeCacheKey) {
       
        // Look for directly set script name
        String scriptName = (String) cx.getThreadLocal(TL_SCRIPTNAME);
       
        // Look for TMLAction definition
        if (scriptName == null) {
            TMLAction action = (TMLAction) cx.getThreadLocal(TL_ACTIONDEFINITION);
            if (action != null) {
                scriptName =  action.getDescription();
            }
        }
       
        // Undeterminable: An anonymous script
        if (scriptName == null) {
            scriptName = "Anonymous script";
        }
        return "TMLScript: " + scriptName;
    }
   
    public Function getCompiledFunction(String code, RhinoContext cx, Scriptable scope) {
       
        code = "function _tmlfunction() {" + code + "\n} _tmlfunction;";
        Script script = getCompiledScript(code, cx);           
        return (Function) script.exec(cx, scope);
       
    }
   
    protected Object executeScript(String expression, RhinoContext cx, Scriptable scope) {
       
        StringBuffer code = new StringBuffer(expression.length() + 64);
       
        code.append("function _tmlscript() {").append(expression).append("\n} _tmlscript();");
        Script script = getCompiledScript(code.toString(), cx);           
        return script.exec(cx, scope);
    }
   
    protected Object executeExpression(String expression, RhinoContext cx, Scriptable scope) {
        Script script = getCompiledScript(expression, cx);
        return script.exec(cx, scope);
    }

    /* (non-Javadoc)
     * @see de.innovationgate.wgpublisher.expressions.tmlscript.RhinoExpressionEngine#convertXMLListToList(java.lang.Object)
     */
    public List convertXMLListToList(Object obj) {
      
        List list  = new ArrayList();
       
        if (determineTMLScriptType(obj) != RhinoExpressionEngine.TYPE_XMLLIST) {
            return list;
        }
       
        Scriptable scr = (Scriptable) obj;
        for (int idx=0; scr.has(idx, scr); idx++) {
            list.add(scr.get(idx, scr));
        }
       
        return list;
    }

    public void clearCache() {
        try {
            _cachedScripts.flushAll();
        }
        catch (CacheException e) {
            logger.error("Exception flushing TMLScript cache", e);
        }
        _sharedScope = null;
    }

    private boolean isJSFalse(Object result) {

       
        if (result == null) {
            return true;
        }
        else if (result instanceof Boolean) {
            return !((Boolean) result).booleanValue();
        }
        else if (result instanceof Undefined) {
            return true;
        }
        else if (result instanceof Double) {
            return ((Double) result).isNaN() || ((Double) result).doubleValue() == 0.0;
        }
        else if (result instanceof Number) {
            return ((Number) result).doubleValue() == 0.0;
        }
        else if (result instanceof String) {
            return ((String) result).equals("");
        }
        /*else if (result.equals("yakety yak")) {
            return true;
        }*/
        
        else {
            return false;
        }
    }
   
    public int getJsVersion() {
        int version = Context.getCurrentContext().getLanguageVersion();
        return version;
    }
   
    public String getRhinoVersion() {
        return Context.getCurrentContext().getImplementationVersion();
    }
   
    public Object runAsMaster(Function function, TMLContext targetContext, Object... params) throws WGAPIException, TMLException {
       
        boolean overrideFunctionScope = true;
        if (targetContext == null) {
            targetContext = WGAGlobal.fetchInitialContext(Context.getCurrentContext());
            overrideFunctionScope = false;
        }
       
        MasterFunction masterAction = new MasterFunction(targetContext, overrideFunctionScope, function, params);
        masterAction.start();
        return masterAction.getReturnValue();
       
       
    }

    private Object executeFunction(Function function, boolean overrideScope, Object[] params, RhinoContext rcx, TMLScriptRootScope scope) {
       
        if (overrideScope) {
            function.setParentScope(scope);
        }
        return function.call(rcx, scope, function.getParentScope(), params);
    }

}
TOP

Related Classes of de.innovationgate.wgpublisher.expressions.tmlscript.RhinoExpressionEngineImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.