/*
* 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 com.xpn.xwiki.script.display;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.xwiki.bridge.DocumentModelBridge;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.display.internal.DocumentDisplayer;
import org.xwiki.display.internal.DocumentDisplayerParameters;
import org.xwiki.rendering.block.XDOM;
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.syntax.SyntaxFactory;
import org.xwiki.rendering.transformation.RenderingContext;
import org.xwiki.script.service.ScriptService;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.internal.cache.rendering.RenderingCache;
/**
* Exposes {@link org.xwiki.display.internal.Displayer}s to scripts.
*
* @version $Id: 071c75f248a03387c5fa8095ba0ef29b2ab1363e $
* @since 3.2M3
*/
@Component
@Named("display")
@Singleton
public class DisplayScriptService implements ScriptService
{
/**
* The key used to store the displayer parameters in the display parameter map.
*/
private static final String DISPLAYER_PARAMETERS_KEY = "displayerParameters";
/** Logging helper object. */
@Inject
private Logger logger;
/**
* The component manager.
*/
@Inject
private ComponentManager componentManager;
/**
* The rendering cache.
*/
@Inject
private RenderingCache renderingCache;
/**
* Execution context handler, needed for accessing the XWikiContext.
*/
@Inject
private Execution execution;
/**
* The component used to create syntax instances from syntax identifiers.
*/
@Inject
private SyntaxFactory syntaxFactory;
@Inject
private RenderingContext renderingContext;
private Syntax getOutputSyntax(Map<String, Object> parameters)
{
Syntax outputSyntax = (Syntax) parameters.get("outputSyntax");
if (outputSyntax == null) {
String outputSyntaxId = (String) parameters.get("outputSyntaxId");
if (outputSyntaxId != null) {
try {
outputSyntax = this.syntaxFactory.createSyntaxFromIdString(outputSyntaxId);
} catch (Exception e) {
this.logger.error("Failed to parse output syntax ID [{}].", outputSyntaxId, e);
return null;
}
} else {
outputSyntax = renderingContext.getTargetSyntax();
}
}
return outputSyntax;
}
/**
* Displays a document.
*
* @param document the document to display
* @param parameters the display parameters
* @return the result of displaying the given document
*/
private String document(Document document, Map<String, Object> parameters, Syntax outputSyntax)
{
DocumentDisplayerParameters displayerParameters =
(DocumentDisplayerParameters) parameters.get(DISPLAYER_PARAMETERS_KEY);
if (displayerParameters == null) {
displayerParameters = new DocumentDisplayerParameters();
displayerParameters.setTargetSyntax(outputSyntax);
}
String displayerHint = (String) parameters.get("displayerHint");
if (displayerHint == null) {
displayerHint = "configured";
}
try {
DocumentDisplayer displayer = this.componentManager.getInstance(DocumentDisplayer.class, displayerHint);
return renderXDOM(displayer.display(getDocument(document), displayerParameters), outputSyntax);
} catch (Exception e) {
this.logger.error("Failed to display document [{}].", document.getPrefixedFullName(), e);
return null;
}
}
/**
* @return a new instance of {@link DocumentDisplayerParameters}
*/
public DocumentDisplayerParameters createDocumentDisplayerParameters()
{
return new DocumentDisplayerParameters();
}
/**
* @param document the document whose content is displayed
* @return the result of rendering the content of the given document as XHTML using the configured displayer
* @see #content(Document, Map)
*/
public String content(Document document)
{
return content(document, Collections.<String, Object>emptyMap());
}
/**
* Displays the content of the given document.
*
* @param document the document whose content is displayed
* @param parameters the display parameters
* @return the result of rendering the content of the given document using the provided parameters
*/
public String content(Document document, Map<String, Object> parameters)
{
XWikiContext context = getXWikiContext();
String content = null;
try {
content = document.getTranslatedContent();
} catch (XWikiException e) {
this.logger.warn("Failed to get the translated content of document [{}].", document.getPrefixedFullName(),
e);
return null;
}
String renderedContent =
this.renderingCache.getRenderedContent(document.getDocumentReference(), content, context);
if (renderedContent == null) {
Map<String, Object> actualParameters = new HashMap<String, Object>(parameters);
DocumentDisplayerParameters displayerParameters =
(DocumentDisplayerParameters) parameters.get(DISPLAYER_PARAMETERS_KEY);
if (displayerParameters == null) {
displayerParameters = new DocumentDisplayerParameters();
// Default content display parameters.
displayerParameters.setExecutionContextIsolated(true);
displayerParameters.setContentTranslated(true);
} else if (displayerParameters.isTitleDisplayed()) {
// Clone because we have to enforce content display.
displayerParameters = displayerParameters.clone();
}
// Ensure the content is displayed.
displayerParameters.setTitleDisplayed(false);
Syntax outputSyntax = getOutputSyntax(parameters);
displayerParameters.setTargetSyntax(outputSyntax);
actualParameters.put(DISPLAYER_PARAMETERS_KEY, displayerParameters);
renderedContent = document(document, actualParameters, outputSyntax);
if (renderedContent != null) {
this.renderingCache.setRenderedContent(document.getDocumentReference(), content, renderedContent,
context);
}
}
return renderedContent;
}
/**
* Displays the document title. If a title has not been provided through the title field, it looks for a section
* title in the document's content and if not found return the page name. The returned title is also interpreted
* which means it's allowed to use Velocity, Groovy, etc. syntax within a title.
*
* @param document the document whose title is displayed
* @param parameters the display parameters
* @return the result of displaying the title of the given document
*/
public String title(Document document, Map<String, Object> parameters)
{
Map<String, Object> actualParameters = new HashMap<String, Object>(parameters);
DocumentDisplayerParameters displayerParameters =
(DocumentDisplayerParameters) parameters.get(DISPLAYER_PARAMETERS_KEY);
if (displayerParameters == null) {
displayerParameters = new DocumentDisplayerParameters();
// Default title display parameters.
displayerParameters.setExecutionContextIsolated(true);
} else if (!displayerParameters.isTitleDisplayed()) {
// Clone because we have to enforce title display.
displayerParameters = displayerParameters.clone();
}
// Ensure the title is displayed.
displayerParameters.setTitleDisplayed(true);
Syntax outputSyntax = getOutputSyntax(parameters);
displayerParameters.setTargetSyntax(outputSyntax);
actualParameters.put(DISPLAYER_PARAMETERS_KEY, displayerParameters);
return document(document, actualParameters, outputSyntax);
}
/**
* @param document the document whose title is displayed
* @return the result of rendering the title of the given document as XHTML using the configured displayer
* @see #title(Document, Map)
*/
public String title(Document document)
{
return title(document, Collections.<String, Object>emptyMap());
}
/**
* Note: This method accesses the low level XWiki document through reflection in order to bypass programming rights.
*
* @param document an instance of {@link Document} received from a script
* @return an instance of {@link DocumentModelBridge} that wraps the low level document object exposed by the given
* document API
*/
private DocumentModelBridge getDocument(Document document)
{
try {
// HACK: We try to access the XWikiDocument instance wrapped by the document API using reflection because we
// want to bypass the programming rights requirements.
Field docField = Document.class.getDeclaredField("doc");
docField.setAccessible(true);
return (DocumentModelBridge) docField.get(document);
} catch (Exception e) {
throw new RuntimeException("Failed to access the XWikiDocument instance wrapped by the document API.", e);
}
}
/**
* Renders the provided XDOM.
*
* @param content the XDOM content to render
* @param targetSyntax the syntax of the rendering result
* @return the result of rendering the given XDOM
* @throws XWikiException if an exception occurred during the rendering process
*/
private String renderXDOM(XDOM content, Syntax targetSyntax) throws XWikiException
{
try {
BlockRenderer renderer = this.componentManager.getInstance(BlockRenderer.class, targetSyntax.toIdString());
WikiPrinter printer = new DefaultWikiPrinter();
renderer.render(content, printer);
return printer.toString();
} catch (Exception e) {
throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
"Failed to render XDOM to syntax [" + targetSyntax + "]", e);
}
}
/**
* @return the XWiki context
* @deprecated avoid using this method; try using the document access bridge instead
*/
@Deprecated
private XWikiContext getXWikiContext()
{
return (XWikiContext) this.execution.getContext().getProperty("xwikicontext");
}
}