/*
* 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.wikimacro.internal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.SpaceReference;
import org.xwiki.model.reference.SpaceReferenceResolver;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryManager;
import org.xwiki.rendering.macro.wikibridge.InsufficientPrivilegesException;
import org.xwiki.rendering.macro.wikibridge.WikiMacro;
import org.xwiki.rendering.macro.wikibridge.WikiMacroException;
import org.xwiki.rendering.macro.wikibridge.WikiMacroFactory;
import org.xwiki.rendering.macro.wikibridge.WikiMacroInitializer;
import org.xwiki.rendering.macro.wikibridge.WikiMacroManager;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.MandatoryDocumentInitializer;
import com.xpn.xwiki.doc.XWikiDocument;
/**
* A {@link DefaultWikiMacroInitializer} providing wiki macros.
*
* @version $Id: 0631b7a361c66bdf911a6563384a961c27f601ca $
* @since 2.0M2
*/
@Component
@Singleton
public class DefaultWikiMacroInitializer implements WikiMacroInitializer, WikiMacroConstants
{
/**
* The {@link org.xwiki.rendering.macro.wikibridge.WikiMacroFactory} component.
*/
@Inject
private WikiMacroFactory wikiMacroFactory;
/**
* The {@link WikiMacroManager} component.
*/
@Inject
private WikiMacroManager wikiMacroManager;
/**
* The {@link Execution} component used for accessing XWikiContext.
*/
@Inject
private Execution execution;
@Inject
@Named(WIKI_MACRO_CLASS)
private MandatoryDocumentInitializer wikiMacroInitializer;
@Inject
@Named(WIKI_MACRO_PARAMETER_CLASS)
private MandatoryDocumentInitializer wikiMacroParameterInitializer;
/**
* The logger to log.
*/
@Inject
private Logger logger;
@Inject
private SpaceReferenceResolver<String> spaceReferenceResolver;
/**
* Utility method for accessing XWikiContext.
*
* @return the XWikiContext.
*/
private XWikiContext getContext()
{
return (XWikiContext) this.execution.getContext().getProperty("xwikicontext");
}
@Override
public void registerExistingWikiMacros() throws Exception
{
registerExistingWikiMacros(false, null);
}
@Override
public void registerExistingWikiMacros(String wiki) throws Exception
{
registerExistingWikiMacros(true, wiki);
}
/**
* Registers the wiki macros for all the wikis or a specific wiki, according to the passed parameter. <br>
* FIXME: I don't like this way of passing params, but it's kinda the best I can do for the moment without
* duplicating at least the logic inside this function, if not some code as well.
*
* @param local false if only macros in a specified wiki are to be registered, in which case, the name of the wiki
* should be specified in the second parameter, false if the macros in all the wikis should be
* registered, in which case the value of the second parameter is ignored
* @param wiki the name of the wiki to register macros for, if local is true
* @throws Exception if xwiki classes required for defining wiki macros are missing or if an error occurs while
* searching for existing wiki macros.
*/
private void registerExistingWikiMacros(boolean local, String wiki) throws Exception
{
XWikiContext xcontext = getContext();
// Register the wiki macros that exist
String originalWiki = xcontext.getWikiId();
try {
if (!local) {
Set<String> wikiNames = new HashSet<String>();
// Add the list of all subwikis
wikiNames.addAll(xcontext.getWiki().getVirtualWikisDatabaseNames(xcontext));
for (String wikiName : wikiNames) {
registerMacrosForWiki(wikiName, xcontext);
}
} else {
registerMacrosForWiki(wiki, xcontext);
}
} finally {
xcontext.setWikiId(originalWiki);
}
}
/**
* Search and register all the macros from the given wiki.
*
* @param wikiName the name of the wiki to process, lowercase database name
* @param xcontext the current request context
*/
private void registerMacrosForWiki(String wikiName, XWikiContext xcontext)
{
try {
this.logger.debug("Registering all wiki macros found in wiki [{}]", wikiName);
// Set the context to be in that wiki so that both the search for XWikiMacro class objects and the
// registration of macros registered for the current wiki will work.
// TODO: In the future when we have APIs for it, move the code to set the current wiki and the current user
// (see below) to the WikiMacroManager's implementation.
xcontext.setWikiId(wikiName);
// Make sure classes exists and are up to date in this wiki
installOrUpgradeWikiMacroClasses();
// Search for all those documents with macro definitions and for each register the macro
for (Object[] wikiMacroDocumentData : getWikiMacroDocumentData(xcontext)) {
// In the database the space and page names are always specified for a document. However the wiki
// part isn't, so we need to replace the wiki reference with the current wiki.
// Note that the space part can contain one or more spaces since XWiki 7.2 and the introduction of
// Nested Spaces.
SpaceReference spaceReference =
this.spaceReferenceResolver.resolve((String) wikiMacroDocumentData[0], new WikiReference(wikiName));
DocumentReference wikiMacroDocumentReference =
new DocumentReference((String) wikiMacroDocumentData[1], spaceReference);
registerMacro(wikiMacroDocumentReference, (String) wikiMacroDocumentData[2], xcontext);
}
} catch (Exception ex) {
this.logger.warn("Failed to register macros for wiki [{}]: {}", wikiName, ex.getMessage());
}
}
/**
* Search for all wiki macros in the current wiki.
*
* @param xcontext the current request context
* @return a list of documents containing wiki macros, each item as a List of 3 strings: space name, document name,
* last author of the document
* @throws Exception if the database search fails
*/
private List<Object[]> getWikiMacroDocumentData(XWikiContext xcontext) throws Exception
{
final QueryManager qm = xcontext.getWiki().getStore().getQueryManager();
final Query q = qm.getNamedQuery("getWikiMacroDocuments");
return (List<Object[]>) (List) q.execute();
}
/**
* Register a wiki macro in the component manager, if the macro author has the required rights.
*
* @param wikiMacroDocumentReference the document holding the macro definition
* @param wikiMacroDocumentAuthor the author of the macro document
* @param xcontext the current request context
*/
private void registerMacro(DocumentReference wikiMacroDocumentReference, String wikiMacroDocumentAuthor,
XWikiContext xcontext)
{
this.logger.debug("Registering macro in document [{}]...", wikiMacroDocumentReference);
DocumentReference originalAuthor = xcontext.getUserReference();
try {
WikiMacro macro = this.wikiMacroFactory.createWikiMacro(wikiMacroDocumentReference);
this.wikiMacroManager.registerWikiMacro(wikiMacroDocumentReference, macro);
this.logger.debug("Macro [{}] from document [{}] is now registered.",
macro.getDescriptor().getId().getId(), wikiMacroDocumentReference);
} catch (InsufficientPrivilegesException ex) {
// Just log the exception and skip to the next.
// We only log at the debug level here as this is not really an error
this.logger.debug(ex.getMessage(), ex);
} catch (WikiMacroException ex) {
// Just log the exception and skip to the next.
this.logger.error(ex.getMessage(), ex);
} finally {
xcontext.setUserReference(originalAuthor);
}
}
@Override
public void installOrUpgradeWikiMacroClasses() throws Exception
{
XWikiContext xcontext = getContext();
// Install or Upgrade XWiki.WikiMacroClass
XWikiDocument doc = xcontext.getWiki().getDocument(this.wikiMacroInitializer.getDocumentReference(), xcontext);
if (this.wikiMacroInitializer.updateDocument(doc)) {
update(doc);
}
// Install or Upgrade XWiki.WikiMacroParameterClass
doc = xcontext.getWiki().getDocument(WIKI_MACRO_PARAMETER_CLASS, xcontext);
if (this.wikiMacroParameterInitializer.updateDocument(doc)) {
update(doc);
}
}
/**
* Utility method for updating a wiki macro class definition document.
*
* @param doc xwiki document containing the wiki macro class.
* @throws XWikiException if an error occurs while saving the document.
*/
private void update(XWikiDocument doc) throws Exception
{
XWikiContext xcontext = getContext();
if (doc.isNew()) {
doc.setParent("XWiki.WebHome");
}
xcontext.getWiki().saveDocument(doc, xcontext);
}
}