/* * 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.StringReader; import java.util.Collections; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.lang3.StringUtils; import org.xwiki.context.Execution; import org.xwiki.observation.ObservationManager; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.MacroBlock; import org.xwiki.rendering.macro.AbstractSignableMacro; import org.xwiki.rendering.macro.MacroContentParser; import org.xwiki.rendering.macro.MacroExecutionException; import org.xwiki.rendering.macro.descriptor.ContentDescriptor; import org.xwiki.rendering.parser.ParseException; import org.xwiki.rendering.parser.Parser; import org.xwiki.rendering.transformation.MacroTransformationContext; import org.xwiki.rendering.util.ParserUtils; import org.xwiki.script.event.ScriptEvaluatedEvent; import org.xwiki.script.event.ScriptEvaluatingEvent; /** * Base Class for script evaluation macros. * <p> * It is not obvious to see how macro execution works just from looking at the code. A lot of checking and * initialization is done in listeners to the {@link org.xwiki.script.event.ScriptEvaluatingEvent} and * {@link org.xwiki.script.event.ScriptEvaluatedEvent}. E.g. the check for programming rights for JSR223 scripts, check * for nested script macros and selecting the right class loader is done there. * </p> * * @param <P> the type of macro parameters bean. * @version $Id: 4f35ffec60e0860ada33ac82e04b3de7621bb2f8 $ * @since 1.7M3 */ public abstract class AbstractScriptMacro<P extends ScriptMacroParameters> extends AbstractSignableMacro<P> implements ScriptMacro { /** * The default description of the script macro content. */ protected static final String CONTENT_DESCRIPTION = "the script to execute"; /** * Used to find if the current document's author has programming rights. * * @deprecated since 2.5M1 (not used any more) */ @Inject @Deprecated protected org.xwiki.bridge.DocumentAccessBridge documentAccessBridge; /** * Used by subclasses. */ @Inject protected Execution execution; /** * Used to parse the result of the script execution into a XDOM object when the macro is configured by the user to * not interpret wiki syntax. */ @Inject @Named("plain/1.0") private Parser plainTextParser; /** * The parser used to parse box content and box title parameter. */ @Inject private MacroContentParser contentParser; /** * Observation manager used to sent evaluation events. */ @Inject private ObservationManager observation; /** * Utility to remove the top level paragraph. */ private ParserUtils parserUtils = new ParserUtils(); /** * @param macroName the name of the macro (eg "groovy") */ public AbstractScriptMacro(String macroName) { super(macroName, null, ScriptMacroParameters.class); setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); } /** * @param macroName the name of the macro (eg "groovy") * @param macroDescription the text description of the macro. */ public AbstractScriptMacro(String macroName, String macroDescription) { super(macroName, macroDescription, ScriptMacroParameters.class); setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); } /** * @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 AbstractScriptMacro(String macroName, String macroDescription, ContentDescriptor contentDescriptor) { super(macroName, macroDescription, contentDescriptor, ScriptMacroParameters.class); setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); } /** * @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 AbstractScriptMacro(String macroName, String macroDescription, Class< ? extends ScriptMacroParameters> parametersBeanClass) { super(macroName, macroDescription, parametersBeanClass); setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); } /** * @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 AbstractScriptMacro(String macroName, String macroDescription, ContentDescriptor contentDescriptor, Class< ? extends ScriptMacroParameters> parametersBeanClass) { super(macroName, macroDescription, contentDescriptor, parametersBeanClass); setDefaultCategory(DEFAULT_CATEGORY_DEVELOPMENT); } @Override public List<Block> execute(P parameters, String content, MacroTransformationContext context) throws MacroExecutionException { List<Block> result = Collections.emptyList(); if (StringUtils.isNotEmpty(content)) { try { // send evaluation starts event ScriptEvaluatingEvent event = new ScriptEvaluatingEvent(getDescriptor().getId().getId()); this.observation.notify(event, context, parameters); if (event.isCanceled()) { throw new MacroExecutionException(event.getReason()); } // 2) Run script engine on macro block content List<Block> blocks = evaluateBlock(parameters, content, context); if (parameters.isOutput()) { result = blocks; } } finally { // send evaluation finished event this.observation.notify(new ScriptEvaluatedEvent(getDescriptor().getId().getId()), context, parameters); } } return result; } /** * Convert script result as a {@link Block} list. * * @param content the script result to parse. * @param parameters the macro parameters. * @param context the context of the macro transformation. * @return the {@link Block}s. * @throws MacroExecutionException Failed to find source parser. * @since 2.1M1 */ protected List<Block> parseScriptResult(String content, P parameters, MacroTransformationContext context) throws MacroExecutionException { List<Block> result; if (parameters.isWiki()) { result = parseSourceSyntax(content, context); } else { try { result = this.plainTextParser.parse(new StringReader(content)).getChildren(); } catch (ParseException e) { // This shouldn't happen since the parser cannot throw an exception since the source is a memory // String. throw new MacroExecutionException("Failed to parse link label as plain text", e); } } // 3) If in inline mode remove any top level paragraph if (context.isInline()) { this.parserUtils.removeTopLevelParagraph(result); // Make sure included macro is inline when script macro itself is inline // TODO: use inline parser instead if (!result.isEmpty() && result.get(0) instanceof MacroBlock && !((MacroBlock) result.get(0)).isInline()) { MacroBlock macro = (MacroBlock) result.get(0); result.set(0, new MacroBlock(macro.getId(), macro.getParameters(), macro.getContent(), true)); } } return result; } /** * Execute provided 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 MacroExecutionException failed to evaluate provided content. * @deprecated since 2.4M2 use {@link #evaluateString(ScriptMacroParameters, String, MacroTransformationContext)} * instead */ @Deprecated protected String evaluate(P parameters, String content, MacroTransformationContext context) throws MacroExecutionException { return ""; } /** * Execute provided script and return {@link String} based result. * * @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 MacroExecutionException failed to evaluate provided content. * @since 2.4M2 */ protected String evaluateString(P parameters, String content, MacroTransformationContext context) throws MacroExecutionException { // Call old method for retro-compatibility return evaluate(parameters, content, context); } /** * Execute provided script and return {@link Block} based result. * * @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 MacroExecutionException failed to evaluate provided content. * @since 2.4M2 */ protected List<Block> evaluateBlock(P parameters, String content, MacroTransformationContext context) throws MacroExecutionException { String scriptResult = evaluateString(parameters, content, context); List<Block> result = Collections.emptyList(); if (parameters.isOutput()) { // Run the wiki syntax parser on the script-rendered content result = parseScriptResult(scriptResult, parameters, context); } return result; } /** * Parse provided content with the parser of the current wiki syntax. * * @param content the content to parse. * @param context the context of the macro transformation. * @return the result of the parsing. * @throws MacroExecutionException failed to parse content */ protected List<Block> parseSourceSyntax(String content, MacroTransformationContext context) throws MacroExecutionException { return this.contentParser.parse(content, context, false, false).getChildren(); } }