org.xwiki.rendering.macro.script.AbstractJSR223ScriptMacro.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.rendering.macro.script.AbstractJSR223ScriptMacro.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.rendering.macro.script;

import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.context.ExecutionContext;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.macro.MacroExecutionException;
import org.xwiki.rendering.macro.descriptor.ContentDescriptor;
import org.xwiki.rendering.transformation.MacroTransformationContext;
import org.xwiki.script.ScriptContextManager;

/**
 * Base Class for script evaluation macros based on JSR223.
 * 
 * @param <P> the type of macro parameters bean.
 * @version $Id: 03f9deea2cc0e1db1914263c3e6192fd8c3c464a $
 * @since 1.7M3
 */
public abstract class AbstractJSR223ScriptMacro<P extends JSR223ScriptMacroParameters>
        extends AbstractScriptMacro<P> implements PrivilegedScriptMacro {
    /**
     * Key under which the Script Engines are saved in the Execution Context, see {@link #execution}.
     */
    private static final String EXECUTION_CONTEXT_ENGINE_KEY = "scriptEngines";

    /**
     * Used to get the current script context to give to script engine evaluation method.
     */
    @Inject
    private ScriptContextManager scriptContextManager;

    /**
     * @param macroName the name of the macro (eg "groovy")
     */
    public AbstractJSR223ScriptMacro(String macroName) {
        super(macroName, null, JSR223ScriptMacroParameters.class);
    }

    /**
     * @param macroName the name of the macro (eg "groovy")
     * @param macroDescription the text description of the macro.
     */
    public AbstractJSR223ScriptMacro(String macroName, String macroDescription) {
        super(macroName, macroDescription, JSR223ScriptMacroParameters.class);
    }

    /**
     * @param macroName the name of the macro (eg "groovy")
     * @param macroDescription the text description of the macro.
     * @param contentDescriptor the description of the macro content.
     */
    public AbstractJSR223ScriptMacro(String macroName, String macroDescription,
            ContentDescriptor contentDescriptor) {
        super(macroName, macroDescription, contentDescriptor, JSR223ScriptMacroParameters.class);
    }

    /**
     * @param macroName the name of the macro (eg "groovy")
     * @param macroDescription the text description of the macro.
     * @param parametersBeanClass class of the parameters bean for this macro.
     */
    public AbstractJSR223ScriptMacro(String macroName, String macroDescription,
            Class<? extends JSR223ScriptMacroParameters> parametersBeanClass) {
        super(macroName, macroDescription, parametersBeanClass);
    }

    /**
     * @param macroName the name of the macro (eg "groovy")
     * @param macroDescription the text description of the macro.
     * @param contentDescriptor the description of the macro content.
     * @param parametersBeanClass class of the parameters bean for this macro.
     */
    public AbstractJSR223ScriptMacro(String macroName, String macroDescription, ContentDescriptor contentDescriptor,
            Class<? extends JSR223ScriptMacroParameters> parametersBeanClass) {
        super(macroName, macroDescription, contentDescriptor, parametersBeanClass);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.rendering.macro.Macro#supportsInlineMode()
     */
    public boolean supportsInlineMode() {
        return true;
    }

    /**
     * Method to overwrite to indicate the script engine name.
     * 
     * @param parameters the macro parameters.
     * @param context the context of the macro transformation.
     * @return the name of the script engine to use.
     */
    protected String getScriptEngineName(P parameters, MacroTransformationContext context) {
        return context.getCurrentMacroBlock().getId().toLowerCase();
    }

    /**
     * Get the current ScriptContext and refresh it.
     * 
     * @return the script context.
     */
    protected ScriptContext getScriptContext() {
        return this.scriptContextManager.getScriptContext();
    }

    /**
     * {@inheritDoc}
     * 
     * @see AbstractScriptMacro#evaluate(ScriptMacroParameters, String, MacroTransformationContext)
     */
    @Override
    protected List<Block> evaluateBlock(P parameters, String content, MacroTransformationContext context)
            throws MacroExecutionException {
        if (StringUtils.isEmpty(content)) {
            return Collections.emptyList();
        }

        String engineName = getScriptEngineName(parameters, context);

        List<Block> result;
        if (engineName != null) {
            try {
                ScriptEngine engine = getScriptEngine(engineName);

                if (engine != null) {
                    result = evaluateBlock(engine, parameters, content, context);
                } else {
                    throw new MacroExecutionException("Can't find script engine with name [" + engineName + "]");
                }
            } catch (ScriptException e) {
                throw new MacroExecutionException("Failed to evaluate Script Macro for content [" + content + "]",
                        e);
            }

        } else {
            // If no language identifier is provided, don't evaluate content
            result = parseScriptResult(content, parameters, context);
        }

        return result;
    }

    /**
     * Execute provided script and return {@link Block} based result.
     * 
     * @param engine the script engine to use to evaluate the script.
     * @param parameters the macro parameters.
     * @param content the script to execute.
     * @param context the context of the macro transformation.
     * @return the result of script execution.
     * @throws ScriptException failed to evaluate script
     * @throws MacroExecutionException failed to evaluate provided content.
     */
    protected List<Block> evaluateBlock(ScriptEngine engine, P parameters, String content,
            MacroTransformationContext context) throws ScriptException, MacroExecutionException {
        List<Block> result;

        ScriptContext scriptContext = getScriptContext();

        StringWriter stringWriter = new StringWriter();

        // set writer in script context
        scriptContext.setWriter(stringWriter);

        try {
            Object scriptResult = eval(content, engine, scriptContext);

            if (scriptResult instanceof XDOM) {
                result = ((XDOM) scriptResult).getChildren();
            } else if (scriptResult instanceof Block) {
                result = Collections.singletonList((Block) scriptResult);
            } else if (scriptResult instanceof List && !((List<?>) scriptResult).isEmpty()
                    && ((List<?>) scriptResult).get(0) instanceof Block) {
                result = (List<Block>) scriptResult;
            } else {
                // Run the wiki syntax parser on the script-rendered content
                result = parseScriptResult(stringWriter.toString(), parameters, context);
            }
        } finally {
            // remove writer script from context
            scriptContext.setWriter(null);
        }

        return result;
    }

    /**
     * @param engineName the script engine name (eg "groovy", etc)
     * @return the Script engine to use to evaluate the script
     * @throws MacroExecutionException in case of an error in parsing the jars parameter
     */
    private ScriptEngine getScriptEngine(String engineName) throws MacroExecutionException {
        // Look for a script engine in the Execution Context since we want the same engine to be used
        // for all evals during the same execution lifetime.
        // We must use the same engine because that engine may create an internal ClassLoader in which
        // it loads new classes defined in the script and if we create a new engine then defined classes
        // will be lost.
        // However we also need to be able to execute several script Macros during a single execution request
        // and for example the second macro could have jar parameters. In order to support this use case
        // we ensure in AbstractScriptMacro to reuse the same thread context ClassLoader during the whole
        // request execution.
        ExecutionContext executionContext = this.execution.getContext();
        Map<String, ScriptEngine> scriptEngines = (Map<String, ScriptEngine>) executionContext
                .getProperty(EXECUTION_CONTEXT_ENGINE_KEY);
        if (scriptEngines == null) {
            scriptEngines = new HashMap<String, ScriptEngine>();
            executionContext.setProperty(EXECUTION_CONTEXT_ENGINE_KEY, scriptEngines);
        }
        ScriptEngine engine = scriptEngines.get(engineName);

        if (engine == null) {
            ScriptEngineManager sem = new ScriptEngineManager();
            engine = sem.getEngineByName(engineName);
            scriptEngines.put(engineName, engine);
        }

        return engine;
    }

    /**
     * Execute the script.
     * 
     * @param content the script to be executed by the script engine
     * @param engine the script engine
     * @param scriptContext the script context
     * @return The value returned from the execution of the script.
     * @throws ScriptException if an error occurrs in script. ScriptEngines should create and throw
     *             <code>ScriptException</code> wrappers for checked Exceptions thrown by underlying scripting
     *             implementations.
     */
    protected Object eval(String content, ScriptEngine engine, ScriptContext scriptContext) throws ScriptException {
        return engine.eval(content, scriptContext);
    }

    // /////////////////////////////////////////////////////////////////////
    // Compiled scripts management

    /**
     * Return a compiled version of the provided script.
     * 
     * @param content the script to compile.
     * @param engine the script engine.
     * @return the compiled version of the script.
     * @throws ScriptException failed to compile the script.
     */
    protected CompiledScript getCompiledScript(String content, Compilable engine) throws ScriptException {
        // TODO: add caching

        return engine.compile(content);
    }
}