/*******************************************************************************
* 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);
}
}