/*
* 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.component.wiki.internal;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.xwiki.bridge.DocumentAccessBridge;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.wiki.WikiComponentRuntimeException;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.properties.ConverterManager;
import org.xwiki.properties.converter.ConversionException;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
import org.xwiki.rendering.renderer.BlockRenderer;
import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
import org.xwiki.rendering.renderer.printer.WikiPrinter;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.rendering.transformation.Transformation;
import org.xwiki.rendering.transformation.TransformationContext;
import org.xwiki.rendering.transformation.TransformationException;
/**
* Default {@link WikiComponentMethodExecutor}.
*
* @version $Id: 7891869c190150b663e3202e5da87a02214dcb84 $
* @since 4.3M2
*/
@Component
@Singleton
public class DefaultWikiComponentMethodExecutor implements WikiComponentMethodExecutor
{
/**
* The key under which the context document is kept in the XWiki context.
*/
private static final String XWIKI_CONTEXT_DOC_KEY = "doc";
/**
* The execution context.
*/
@Inject
private Execution execution;
/**
* Used to update the rendering context.
*/
@Inject
private RenderingContext renderingContext;
/**
* Macro transformation engine.
*/
@Inject
@Named("macro")
private Transformation macroTransformation;
/**
* Renderer used to get the return value from the rendered content.
*/
@Inject
@Named("plain/1.0")
private BlockRenderer blockRenderer;
/**
* Converter used to cast the return value from the rendered content (String).
*/
@Inject
private ConverterManager converterManager;
/**
* Used to retrieve the component document.
*/
@Inject
private DocumentAccessBridge dab;
/**
* Prepare the method execution context.
*
* @param methodContext The context to populate
* @param args The arguments initially passed to the method
*/
private void prepareMethodContext(Map<String, Object> methodContext, Object[] args)
{
methodContext.put(OUTPUT_KEY, new WikiMethodOutputHandler());
Map<Integer, Object> inputs = new HashMap<Integer, Object>();
if (args != null && args.length > 0) {
// Start with "0" as first input key.
for (int i = 0; i < args.length; i++) {
inputs.put(i, args[i]);
}
}
methodContext.put(INPUT_KEY, inputs);
}
/**
* Render a XDOM and return a value converted from the rendered content. The type matches the return value of the
* passed method.
*
* @param xdom The XDOM to render
* @param method The method called
* @return A value matching the method return type
* @throws WikiComponentRuntimeException When the conversion fails
*/
private Object castRenderedContent(XDOM xdom, Method method) throws WikiComponentRuntimeException
{
// Since no return value has been explicitly provided, we try to convert the result of the rendering
// into the expected return type using a Converter.
WikiPrinter printer = new DefaultWikiPrinter();
blockRenderer.render(xdom, printer);
String contentResult = printer.toString();
// Do the conversion!
try {
return converterManager.convert(method.getGenericReturnType(), contentResult);
} catch (ConversionException e) {
// Surrender!
throw new WikiComponentRuntimeException(
String.format("Failed to convert result [%s] to type [%s] for method [%s.%s]",
contentResult,
method.getGenericReturnType(),
method.getDeclaringClass().getName(),
method.getName()), e);
}
}
@Override
public Object execute(Method method, Object[] args, DocumentReference componentDocumentReference, XDOM xdom,
Syntax syntax, Map<String, Object> methodContext)
throws WikiComponentRuntimeException
{
// Prepare and put the method context in the XWiki Context
Map<Object, Object> xwikiContext = (Map<Object, Object>) execution.getContext().getProperty("xwikicontext");
this.prepareMethodContext(methodContext, args);
xwikiContext.put("method", methodContext);
// Save current context document, to put it back after the execution.
Object contextDoc = xwikiContext.get(XWIKI_CONTEXT_DOC_KEY);
try {
// Put component document in the context, so that macro transformation rights are checked against the
// component document and not the context one.
try {
xwikiContext.put(XWIKI_CONTEXT_DOC_KEY, dab.getDocument(componentDocumentReference));
} catch (Exception e) {
throw new WikiComponentRuntimeException(String.format(
"Failed to load wiki component document [%s]", componentDocumentReference), e);
}
// We need to clone the xdom to avoid transforming the original and make it useless after the first
// transformation
XDOM transformedXDOM = xdom.clone();
// Perform internal macro transformations
try {
TransformationContext transformationContext = new TransformationContext(transformedXDOM, syntax);
transformationContext.setId(method.getClass().getName() + "#" + method.getName());
((MutableRenderingContext) renderingContext).transformInContext(macroTransformation,
transformationContext, transformedXDOM);
} catch (TransformationException e) {
throw new WikiComponentRuntimeException(String.format(
"Error while executing wiki component macro transformation for method [%s]", method.getName()), e);
}
if (!method.getReturnType().getName().equals("void")) {
if (methodContext.get(OUTPUT_KEY) != null
&& ((WikiMethodOutputHandler)
methodContext.get(OUTPUT_KEY)).getValue() != null) {
return method.getReturnType().cast(((WikiMethodOutputHandler)
methodContext.get(OUTPUT_KEY)).getValue());
} else {
return this.castRenderedContent(transformedXDOM, method);
}
} else {
return null;
}
} finally {
if (contextDoc != null) {
xwikiContext.put(XWIKI_CONTEXT_DOC_KEY, contextDoc);
}
}
}
}