/*
* 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.internal.macro.dashboard;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.xwiki.component.annotation.Component;
import org.xwiki.context.Execution;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.GroupBlock;
import org.xwiki.rendering.block.LinkBlock;
import org.xwiki.rendering.block.WordBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.executor.ContentExecutor;
import org.xwiki.rendering.executor.ContentExecutorException;
import org.xwiki.rendering.listener.reference.ResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.macro.dashboard.Gadget;
import org.xwiki.rendering.macro.dashboard.GadgetSource;
import org.xwiki.rendering.parser.MissingParserException;
import org.xwiki.rendering.parser.ParseException;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.MacroTransformationContext;
import org.xwiki.rendering.util.ParserUtils;
import org.xwiki.velocity.VelocityEngine;
import org.xwiki.velocity.VelocityManager;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
/**
* Default gadget reader, reads the gadgets from XWiki Objects attached to the current document.
*
* @version $Id: b9110ac39bdc42352d42b8433de1fb7dfae323e5 $
* @since 3.0M3
*/
@Component
@Singleton
public class DefaultGadgetSource implements GadgetSource
{
/**
* The reference to the gadgets class, relative to the current wiki. <br>
* TODO: to make sure that this class exists before trying to read objects of this type.
*/
private static final EntityReference GADGET_CLASS =
new EntityReference("GadgetClass", EntityType.DOCUMENT, new EntityReference("XWiki", EntityType.SPACE));
/**
* The execution context, to grab XWiki context and access to documents.
*/
@Inject
protected Execution execution;
/**
* The current string reference resolver, to resolve the current document reference in the metadata of the block of
* the current macro.
*/
@Inject
@Named("current")
protected DocumentReferenceResolver<String> currentReferenceResolver;
/**
* The current entity reference resolver, to resolve the gadgets class reference.
*/
@Inject
@Named("current")
protected DocumentReferenceResolver<EntityReference> currentReferenceEntityResolver;
@Inject
@Named("local")
private EntityReferenceSerializer<String> localReferenceSerializer;
/**
* Used to get the Velocity Engine and Velocity Context to use to evaluate the titles of the gadgets.
*/
@Inject
private VelocityManager velocityManager;
@Inject
private ContentExecutor<MacroTransformationContext> contentExecutor;
/**
* Prepare the parser to parse the title and content of the gadget into blocks.
*/
private ParserUtils parserUtils = new ParserUtils();
@Override
public List<Gadget> getGadgets(String source, MacroTransformationContext context) throws Exception
{
// use the passed source as a document reference
DocumentReference sourceDocRef = getSourceDocumentReference(source);
if (sourceDocRef == null) {
return new ArrayList<>();
}
// get the current document, read the objects and turn that into gadgets
XWikiContext xContext = getXWikiContext();
XWiki xWiki = xContext.getWiki();
XWikiDocument sourceDoc = xWiki.getDocument(sourceDocRef, xContext);
DocumentReference gadgetsClass = currentReferenceEntityResolver.resolve(GADGET_CLASS);
List<BaseObject> gadgetObjects = sourceDoc.getXObjects(gadgetsClass);
if (gadgetObjects == null) {
return new ArrayList<>();
}
return prepareGadgets(gadgetObjects, sourceDoc.getSyntax(), context);
}
/**
* Prepares a list of gadgets from a list of XWiki objects.
*
* @param objects the objects to read the gadgets from
* @param sourceSyntax the syntax of the source of the gadget objects
* @param context the macro transformation context, where the dashboard macro is being executed
* @return the list of gadgets, as read from the xwiki objects
* @throws Exception in case something happens while rendering the content in the objects
*/
private List<Gadget> prepareGadgets(List<BaseObject> objects, Syntax sourceSyntax,
MacroTransformationContext context) throws Exception
{
List<Gadget> gadgets = new ArrayList<>();
// prepare velocity tools to render title
VelocityContext velocityContext = velocityManager.getVelocityContext();
// Use the Transformation id as the name passed to the Velocity Engine. This name is used internally
// by Velocity as a cache index key for caching macros.
String key = context.getTransformationContext().getId();
if (key == null) {
key = "unknown namespace";
}
VelocityEngine velocityEngine = velocityManager.getVelocityEngine();
for (BaseObject xObject : objects) {
if (xObject == null) {
continue;
}
// get the data about the gadget from the object
// TODO: filter for dashboard name when that field will be in
String title = xObject.getStringValue("title");
String content = xObject.getLargeStringValue("content");
String position = xObject.getStringValue("position");
String id = xObject.getNumber() + "";
// render title with velocity
StringWriter writer = new StringWriter();
// FIXME: the engine has an issue with $ and # as last character. To test and fix if it happens
velocityEngine.evaluate(velocityContext, writer, key, title);
String gadgetTitle = writer.toString();
// parse both the title and content in the syntax of the transformation context
List<Block> titleBlocks =
renderGadgetProperty(gadgetTitle, sourceSyntax, xObject.getDocumentReference(), context);
List<Block> contentBlocks =
renderGadgetProperty(content, sourceSyntax, xObject.getDocumentReference(), context);
// create a gadget will all these and add the gadget to the container of gadgets
Gadget gadget = new Gadget(id, titleBlocks, contentBlocks, position);
gadget.setTitleSource(title);
gadgets.add(gadget);
}
return gadgets;
}
private List<Block> renderGadgetProperty(String content, Syntax sourceSyntax, EntityReference sourceReference,
MacroTransformationContext context) throws MissingParserException, ParseException, ContentExecutorException
{
XDOM xdom = this.contentExecutor.execute(content, sourceSyntax, sourceReference, context);
List<Block> xdomBlocks = xdom.getChildren();
this.parserUtils.removeTopLevelParagraph(xdomBlocks);
return xdomBlocks;
}
/**
* Resolves the source of the dashboard, based on the source parameter passed to this reader, handling the default
* behaviour when the source is missing.
*
* @param source the serialized reference of the document to read gadgets from
* @return the document reference to the current document (the document containing the macro, if it's an include)
*/
private DocumentReference getSourceDocumentReference(String source)
{
// if the source is empty or null, use current document
if (StringUtils.isEmpty(source)) {
return getXWikiContext().getDoc().getDocumentReference();
}
// resolve the source as document reference, relative to current context
return currentReferenceResolver.resolve(source);
}
/**
* Gets the xwiki context from the execution context.
*
* @return the xwiki context
*/
private XWikiContext getXWikiContext()
{
return (XWikiContext) execution.getContext().getProperty("xwikicontext");
}
@Override
public List<Block> getDashboardSourceMetadata(String source, MacroTransformationContext context)
{
DocumentReference sourceDoc = getSourceDocumentReference(source);
String classParameterName = "class";
GroupBlock metadataContainer = new GroupBlock();
metadataContainer.setParameter(classParameterName, DashboardMacro.METADATA);
// generate anchors for the urls
XWikiContext xContext = getXWikiContext();
String editURL = xContext.getWiki().getURL(sourceDoc, "save", "", "", xContext);
LinkBlock editURLBlock =
new LinkBlock(Collections.<Block> emptyList(), new ResourceReference(editURL, ResourceType.URL), false);
editURLBlock.setParameter(classParameterName, DashboardMacro.EDIT_URL);
metadataContainer.addChild(editURLBlock);
String removeURL = xContext.getWiki().getURL(sourceDoc, "objectremove", "", "", xContext);
LinkBlock removeURLBlock =
new LinkBlock(Collections.<Block> emptyList(), new ResourceReference(removeURL, ResourceType.URL), false);
removeURLBlock.setParameter(classParameterName, DashboardMacro.REMOVE_URL);
metadataContainer.addChild(removeURLBlock);
String addURL = xContext.getWiki().getURL(sourceDoc, "objectadd", "", "", xContext);
LinkBlock addURLBlock =
new LinkBlock(Collections.<Block> emptyList(), new ResourceReference(addURL, ResourceType.URL), false);
addURLBlock.setParameter(classParameterName, DashboardMacro.ADD_URL);
metadataContainer.addChild(addURLBlock);
// and create divs for the source metadata
GroupBlock sourcePageBlock = new GroupBlock();
sourcePageBlock.addChild(new WordBlock(sourceDoc.getName()));
sourcePageBlock.setParameter(classParameterName, DashboardMacro.SOURCE_PAGE);
metadataContainer.addChild(sourcePageBlock);
GroupBlock sourceSpaceBlock = new GroupBlock();
// Extract the full Space Reference (in order to support Nested Spaces) and set it in the XDOM
sourceSpaceBlock.addChild(new WordBlock(
this.localReferenceSerializer.serialize(sourceDoc.getLastSpaceReference())));
sourceSpaceBlock.setParameter(classParameterName, DashboardMacro.SOURCE_SPACE);
metadataContainer.addChild(sourceSpaceBlock);
GroupBlock sourceWikiBlock = new GroupBlock();
sourceWikiBlock.addChild(new WordBlock(sourceDoc.getWikiReference().getName()));
sourceWikiBlock.setParameter(classParameterName, DashboardMacro.SOURCE_WIKI);
metadataContainer.addChild(sourceWikiBlock);
String sourceURL = xContext.getWiki().getURL(sourceDoc, "view", "", "", xContext);
LinkBlock sourceURLBlock =
new LinkBlock(Collections.<Block> emptyList(), new ResourceReference(sourceURL, ResourceType.URL), false);
sourceURLBlock.setParameter(classParameterName, DashboardMacro.SOURCE_URL);
metadataContainer.addChild(sourceURLBlock);
return Collections.<Block> singletonList(metadataContainer);
}
@Override
public boolean isEditing()
{
// get the XWiki context and look at the action. if it's "inline" or "edit", it's edit mode
XWikiContext context = getXWikiContext();
return "inline".equals(context.getAction()) || "edit".equals(context.getAction());
}
}