com.xpn.xwiki.plugin.feed.SyndEntryDocumentSource.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.plugin.feed.SyndEntryDocumentSource.java

Source

/*
 * 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.plugin.feed;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.output.NullWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.tidy.Tidy;
import org.xwiki.xml.XMLUtils;

import com.sun.syndication.feed.synd.SyndCategory;
import com.sun.syndication.feed.synd.SyndCategoryImpl;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.util.TidyMessageLogger;

/**
 * Concrete strategy for computing the field values of a feed entry from any {@link XWikiDocument} instance.
 */
public class SyndEntryDocumentSource implements SyndEntrySource {
    /**
     * Utility class for selecting a property from a XWiki object.
     */
    public static class PropertySelector {
        /**
         * The name of a XWiki class.
         */
        private String className;

        /**
         * The index of an object within the document that this selector is applied to.
         */
        private int objectIndex;

        /**
         * The name of a property available for {@link #className}.
         */
        private String propertyName;

        /**
         * Creates a new instance from a string representation.
         * 
         * @param strRep a string like "ClassName_ObjectIndex_PropertyName", where class name and object index are
         *            optional
         */
        public PropertySelector(String strRep) {
            int indexStartPos = strRep.indexOf('_');
            if (indexStartPos < 0) {
                // class name and object index are not specified
                this.className = null;
                this.objectIndex = 0;
                this.propertyName = strRep;
            } else {
                int propStartPos = strRep.indexOf("_", indexStartPos + 1);
                if (propStartPos < 0) {
                    // object index is not specified
                    this.className = strRep.substring(0, indexStartPos);
                    this.objectIndex = 0;
                    this.propertyName = strRep.substring(indexStartPos + 1);
                } else {
                    // all three have been specified
                    this.className = strRep.substring(0, indexStartPos);
                    this.objectIndex = Integer.parseInt(strRep.substring(indexStartPos + 1, propStartPos));
                    this.propertyName = strRep.substring(propStartPos + 1);
                }
            }
        }

        /**
         * @return the name of a XWiki class
         */
        public String getClassName() {
            return this.className;
        }

        /**
         * @return the index of an object within the document that this selector is applied to
         */
        public int getObjectIndex() {
            return this.objectIndex;
        }

        /**
         * @return the name of a property available for {@link #className}
         */
        public String getPropertyName() {
            return this.propertyName;
        }
    }

    protected static final Logger LOGGER = LoggerFactory.getLogger(SyndEntryDocumentSource.class);

    protected static final TidyMessageLogger TIDY_LOGGER = new TidyMessageLogger(LOGGER);

    public static final String CONTENT_TYPE = "ContentType";

    public static final String CONTENT_LENGTH = "ContentLength";

    public static final Properties TIDY_FEED_CONFIG;

    public static final Properties TIDY_XML_CONFIG;

    public static final Properties TIDY_HTML_CONFIG;

    public static final String FIELD_URI = "uri";

    public static final String FIELD_LINK = "link";

    public static final String FIELD_TITLE = "title";

    public static final String FIELD_DESCRIPTION = "description";

    public static final String FIELD_CATEGORIES = "categories";

    public static final String FIELD_PUBLISHED_DATE = "publishedDate";

    public static final String FIELD_UPDATED_DATE = "updatedDate";

    public static final String FIELD_AUTHOR = "author";

    public static final String FIELD_CONTRIBUTORS = "contributors";

    public static final Map<String, Object> DEFAULT_PARAMS;

    static {
        // general configuration
        TIDY_FEED_CONFIG = new Properties();
        TIDY_FEED_CONFIG.setProperty("force-output", "yes");
        TIDY_FEED_CONFIG.setProperty("indent-attributes", "no");
        TIDY_FEED_CONFIG.setProperty("indent", "no");
        TIDY_FEED_CONFIG.setProperty("quiet", "yes");
        TIDY_FEED_CONFIG.setProperty("trim-empty-elements", "yes");

        // XML specific configuration
        TIDY_XML_CONFIG = new Properties(TIDY_FEED_CONFIG);
        TIDY_XML_CONFIG.setProperty("input-xml", "yes");
        TIDY_XML_CONFIG.setProperty("output-xml", "yes");
        TIDY_XML_CONFIG.setProperty("add-xml-pi", "no");

        // HTML specific configuration
        TIDY_HTML_CONFIG = new Properties(TIDY_FEED_CONFIG);
        TIDY_HTML_CONFIG.setProperty("output-xhtml", "yes");
        TIDY_HTML_CONFIG.setProperty("show-body-only", "yes");
        TIDY_HTML_CONFIG.setProperty("drop-empty-paras", "yes");
        TIDY_HTML_CONFIG.setProperty("enclose-text", "yes");
        TIDY_HTML_CONFIG.setProperty("logical-emphasis", "yes");

        // default parameters for all instances of this class
        DEFAULT_PARAMS = new HashMap<String, Object>();
        DEFAULT_PARAMS.put(CONTENT_TYPE, "text/html");
        DEFAULT_PARAMS.put(CONTENT_LENGTH, new Integer(-1)); // no limit by default
    }

    /**
     * Strategy instance parameters. Each concrete strategy can define its own (paramName, paramValue) pairs. These
     * parameters are overwritten by those used when calling
     * {@link SyndEntrySource#source(SyndEntry, Object, Map, XWikiContext)} method
     */
    private Map<String, Object> params;

    public SyndEntryDocumentSource() {
        this(new HashMap<String, Object>());
    }

    /**
     * Creates a new instance. The given parameters overwrite {@link #DEFAULT_PARAMS}.
     * 
     * @param params parameters only for this instance
     */
    public SyndEntryDocumentSource(Map<String, Object> params) {
        setParams(params);
    }

    /**
     * @return instance parameters
     */
    public Map<String, Object> getParams() {
        return this.params;
    }

    /**
     * Sets instance parameters. Instance parameters overwrite {@link #DEFAULT_PARAMS}
     * 
     * @param params instance parameters
     */
    public void setParams(Map<String, Object> params) {
        this.params = joinParams(params, getDefaultParams());
    }

    /**
     * Strategy class parameters
     */
    protected Map<String, Object> getDefaultParams() {
        return DEFAULT_PARAMS;
    }

    @Override
    public void source(SyndEntry entry, Object obj, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        // cast source
        Document doc = castDocument(obj, context);

        // test access rights
        if (!doc.hasAccessLevel("view")) {
            throw new XWikiException(XWikiException.MODULE_XWIKI_ACCESS, XWikiException.ERROR_XWIKI_ACCESS_DENIED,
                    "Access denied in view mode!");
        }

        // prepare parameters (overwrite instance parameters)
        Map<String, Object> trueParams = joinParams(params, getParams());

        sourceDocument(entry, doc, trueParams, context);
    }

    /**
     * Overwrites the current values of the given feed entry with new ones computed from the specified source document.
     * 
     * @param entry the feed entry whose fields are going to be overwritten
     * @param doc the source for the new values to be set on the fields of the feed entry
     * @param params parameters to adjust the computation. Each concrete strategy may define its own (key, value) pairs
     * @param context the XWiki context
     * @throws XWikiException
     */
    public void sourceDocument(SyndEntry entry, Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        entry.setUri(getURI(doc, params, context));
        entry.setLink(getLink(doc, params, context));
        entry.setTitle(getTitle(doc, params, context));
        entry.setDescription(getDescription(doc, params, context));
        entry.setCategories(getCategories(doc, params, context));
        entry.setPublishedDate(getPublishedDate(doc, params, context));
        entry.setUpdatedDate(getUpdateDate(doc, params, context));
        entry.setAuthor(getAuthor(doc, params, context));
        entry.setContributors(getContributors(doc, params, context));
    }

    protected String getDefaultURI(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        return doc.getExternalURL("view", "language=" + doc.getRealLanguage());
    }

    protected String getURI(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException {
        String mapping = (String) params.get(FIELD_URI);
        if (mapping == null) {
            return getDefaultURI(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseString(mapping, doc, context);
        } else {
            return getStringValue(mapping, doc, context);
        }
    }

    protected String getDefaultLink(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        return getDefaultURI(doc, params, context);
    }

    protected String getLink(Document doc, Map<String, Object> params, XWikiContext context) throws XWikiException {
        String mapping = (String) params.get(FIELD_LINK);
        if (mapping == null) {
            return getDefaultLink(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseString(mapping, doc, context);
        } else {
            return getStringValue(mapping, doc, context);
        }
    }

    protected String getDefaultTitle(Document doc, Map<String, Object> params, XWikiContext context) {
        return doc.getDisplayTitle();
    }

    protected String getTitle(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_TITLE);
        if (mapping == null) {
            return getDefaultTitle(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseString(mapping, doc, context);
        } else {
            return getStringValue(mapping, doc, context);
        }
    }

    protected String getDefaultDescription(Document doc, Map<String, Object> params, XWikiContext context) {
        XWiki xwiki = context.getWiki();
        String author = xwiki.getUserName(doc.getAuthor(), null, false, context);
        // the description format should be taken from a resource bundle, and thus localized
        String descFormat = "Version %1$s edited by %2$s on %3$s";
        return String.format(descFormat, new Object[] { doc.getVersion(), author, doc.getDate() });
    }

    protected SyndContent getDescription(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String description;
        String mapping = (String) params.get(FIELD_DESCRIPTION);
        if (mapping == null) {
            description = getDefaultDescription(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            description = parseString(mapping, doc, context);
        } else {
            description = doc.getRenderedContent(getStringValue(mapping, doc, context), doc.getSyntaxId());
        }
        String contentType = (String) params.get(CONTENT_TYPE);
        int contentLength = ((Number) params.get(CONTENT_LENGTH)).intValue();
        if (contentLength >= 0) {
            if ("text/plain".equals(contentType)) {
                description = getPlainPreview(description, contentLength);
            } else if ("text/html".equals(contentType)) {
                description = getHTMLPreview(description, contentLength);
            } else if ("text/xml".equals(contentType)) {
                description = getXMLPreview(description, contentLength);
            }
        }
        return getSyndContent(contentType, description);
    }

    protected List<SyndCategory> getDefaultCategories(Document doc, Map<String, Object> params,
            XWikiContext context) {
        return Collections.emptyList();
    }

    protected List<SyndCategory> getCategories(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_CATEGORIES);
        if (mapping == null) {
            return getDefaultCategories(doc, params, context);
        }

        List<Object> categories;
        if (isVelocityCode(mapping)) {
            categories = parseList(mapping, doc, context);
        } else {
            categories = getListValue(mapping, doc, context);
        }

        List<SyndCategory> result = new ArrayList<SyndCategory>();
        for (Object category : categories) {
            if (category instanceof SyndCategory) {
                result.add((SyndCategory) category);
            } else if (category != null) {
                SyndCategory scat = new SyndCategoryImpl();
                scat.setName(category.toString());
                result.add(scat);
            }
        }
        return result;
    }

    protected Date getDefaultPublishedDate(Document doc, Map<String, Object> params, XWikiContext context) {
        return doc.getDate();
    }

    protected Date getPublishedDate(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_PUBLISHED_DATE);
        if (mapping == null) {
            return getDefaultPublishedDate(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseDate(mapping, doc, context);
        } else {
            return getDateValue(mapping, doc, context);
        }
    }

    protected Date getDefaultUpdateDate(Document doc, Map<String, Object> params, XWikiContext context) {
        return doc.getDate();
    }

    protected Date getUpdateDate(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_UPDATED_DATE);
        if (mapping == null) {
            return getDefaultUpdateDate(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseDate(mapping, doc, context);
        } else {
            return getDateValue(mapping, doc, context);
        }
    }

    protected String getDefaultAuthor(Document doc, Map<String, Object> params, XWikiContext context) {
        return context.getWiki().getUserName(doc.getCreator(), null, false, context);
    }

    protected String getAuthor(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_AUTHOR);
        if (mapping == null) {
            return getDefaultAuthor(doc, params, context);
        } else if (isVelocityCode(mapping)) {
            return parseString(mapping, doc, context);
        } else {
            return getStringValue(mapping, doc, context);
        }
    }

    protected List<String> getDefaultContributors(Document doc, Map<String, Object> params, XWikiContext context) {
        XWiki xwiki = context.getWiki();
        List<String> contributors = new ArrayList<String>();
        contributors.add(xwiki.getUserName(doc.getAuthor(), null, false, context));
        return contributors;
    }

    protected List<String> getContributors(Document doc, Map<String, Object> params, XWikiContext context)
            throws XWikiException {
        String mapping = (String) params.get(FIELD_CONTRIBUTORS);
        if (mapping == null) {
            return getDefaultContributors(doc, params, context);
        }

        List<Object> rawContributors;
        if (isVelocityCode(mapping)) {
            rawContributors = parseList(mapping, doc, context);
        } else {
            rawContributors = getListValue(mapping, doc, context);
        }

        List<String> contributors = new ArrayList<String>();
        for (Object rawContributor : rawContributors) {
            if (rawContributor instanceof String) {
                contributors.add((String) rawContributor);
            } else {
                contributors.add(rawContributor.toString());
            }
        }

        return contributors;
    }

    /**
     * Distinguishes between mapping to a property and mapping to a velocity code.
     * 
     * @param mapping
     * @return true if the given string is a mapping to a velocity code
     */
    protected boolean isVelocityCode(String mapping) {
        return mapping.charAt(0) == '{' && mapping.charAt(mapping.length() - 1) == '}';
    }

    protected String parseString(String mapping, Document doc, XWikiContext context) throws XWikiException {
        if (isVelocityCode(mapping)) {
            return doc.getRenderedContent(mapping.substring(1, mapping.length() - 1), doc.getSyntax().toIdString());
        } else {
            return mapping;
        }
    }

    /**
     * Converts the given velocity string to a {@link Date} instance. The velocity code must be evaluated to a long
     * representing a time stamp.
     */
    protected Date parseDate(String mapping, Document doc, XWikiContext context)
            throws NumberFormatException, XWikiException {
        if (isVelocityCode(mapping)) {
            return new Date(Long.parseLong(parseString(mapping, doc, context).trim()));
        } else {
            return null;
        }
    }

    /**
     * Converts the given velocity code to a {@link List} instance. The velocity code must be evaluated to a string with
     * the following format: "[item1,item2,...,itemN]".
     */
    protected List<Object> parseList(String mapping, Document doc, XWikiContext context) throws XWikiException {
        if (!isVelocityCode(mapping)) {
            return null;
        }
        String strRep = parseString(mapping, doc, context).trim();
        if (strRep.charAt(0) != '[' || strRep.charAt(strRep.length() - 1) != ']') {
            return null;
        }
        String[] array = strRep.substring(1, strRep.length() - 1).split(",");
        if (array.length > 0) {
            List<Object> list = new ArrayList<Object>();
            for (int i = 0; i < array.length; i++) {
                list.add(array[i]);
            }
            return list;
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Retrieves the value of a string property.
     */
    protected String getStringValue(String mapping, Document doc, XWikiContext context) throws XWikiException {
        PropertySelector ps = new PropertySelector(mapping);
        if (ps.getClassName() == null) {
            return doc.display(ps.getPropertyName());
        } else {
            XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
            return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getStringValue(ps.getPropertyName());
        }
    }

    /**
     * Retrieves the value of a date property.
     */
    protected Date getDateValue(String mapping, Document doc, XWikiContext context) throws XWikiException {
        XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
        PropertySelector ps = new PropertySelector(mapping);
        if (ps.getClassName() == null) {
            return xdoc.getFirstObject(ps.getPropertyName(), context).getDateValue(ps.getPropertyName());
        } else {
            return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getDateValue(ps.getPropertyName());
        }
    }

    /**
     * Retrieves the value of a list property.
     */
    protected List<Object> getListValue(String mapping, Document doc, XWikiContext context) throws XWikiException {
        XWikiDocument xdoc = context.getWiki().getDocument(doc.getFullName(), context);
        PropertySelector ps = new PropertySelector(mapping);
        if (ps.getClassName() == null) {
            return xdoc.getFirstObject(ps.getPropertyName(), context).getListValue(ps.getPropertyName());
        } else {
            return xdoc.getObject(ps.getClassName(), ps.getObjectIndex()).getListValue(ps.getPropertyName());
        }
    }

    /**
     * @return base + (extra - base)
     */
    protected Map<String, Object> joinParams(Map<String, Object> base, Map<String, Object> extra) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.putAll(base);

        for (Map.Entry<String, Object> entry : extra.entrySet()) {
            if (params.get(entry.getKey()) == null) {
                params.put(entry.getKey(), entry.getValue());
            }
        }
        return params;
    }

    /**
     * Cleans up the given XML fragment using the specified configuration.
     * 
     * @param xmlFragment the XML fragment to be cleaned up
     * @param config the configuration properties to use
     * @return the DOM tree associated with the cleaned up XML fragment
     */
    public static org.w3c.dom.Document tidy(String xmlFragment, Properties config) {
        Tidy tidy = new Tidy();
        tidy.setConfigurationFromProps(config);
        // We capture the logs and redirect them to the XWiki logging subsystem. Since we do this we don't want
        // JTidy warnings and errors to be sent to stderr/stdout
        tidy.setMessageListener(TIDY_LOGGER);
        tidy.setErrout(new PrintWriter(new NullWriter()));
        // Even if we add a message listener we still have to redirect the output. Otherwise all the messages will be
        // written to the standard output (besides being logged by TIDY_LOGGER).
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        return tidy.parseDOM(new ByteArrayInputStream(xmlFragment.getBytes()), out);
    }

    /**
     * Computes the sum of lengths of all the text nodes within the given DOM sub-tree
     * 
     * @param node the root of the DOM sub-tree containing the text
     * @return the sum of lengths of text nodes within the given DOM sub-tree
     */
    public static int innerTextLength(Node node) {
        switch (node.getNodeType()) {
        case Node.TEXT_NODE:
            return node.getNodeValue().length();
        case Node.ELEMENT_NODE:
            int length = 0;
            Node child = node.getFirstChild();
            while (child != null) {
                length += innerTextLength(child);
                child = child.getNextSibling();
            }
            return length;
        case Node.DOCUMENT_NODE:
            return innerTextLength(((org.w3c.dom.Document) node).getDocumentElement());
        default:
            return 0;
        }
    }

    /**
     * Extracts the first characters of the given XML fragment, up to the given length limit, adding only characters in
     * XML text nodes. The XML fragment is cleaned up before extracting the prefix to be sure that the result is
     * well-formed.
     * 
     * @param xmlFragment the full XML text
     * @param previewLength the maximum number of characters allowed in the preview, considering only the XML text nodes
     * @return a prefix of the given XML fragment summing at most <code>previewLength</code> characters in its text
     *         nodes
     */
    public static String getXMLPreview(String xmlFragment, int previewLength) {
        try {
            return XMLUtils.extractXML(tidy(xmlFragment, TIDY_XML_CONFIG), 0, previewLength);
        } catch (RuntimeException e) {
            return getPlainPreview(xmlFragment, previewLength);
        }
    }

    /**
     * Extracts the first characters of the given HTML fragment, up to the given length limit, adding only characters in
     * HTML text nodes. The HTML fragment is cleaned up before extracting the prefix to be sure the result is
     * well-formed.
     * 
     * @param htmlFragment the full HTML text
     * @param previewLength the maximum number of characters allowed in the preview, considering only the HTML text
     *            nodes
     * @return a prefix of the given HTML fragment summing at most <code>previewLength</code> characters in its text
     *         nodes
     */
    public static String getHTMLPreview(String htmlFragment, int previewLength) {
        try {
            org.w3c.dom.Document html = tidy(htmlFragment, TIDY_HTML_CONFIG);
            Node body = html.getElementsByTagName("body").item(0);
            return XMLUtils.extractXML(body.getFirstChild(), 0, previewLength);
        } catch (RuntimeException e) {
            return getPlainPreview(htmlFragment, previewLength);
        }
    }

    /**
     * Extracts the first characters of the given text, up to the last space within the given length limit.
     * 
     * @param plainText the full text
     * @param previewLength the maximum number of characters allowed in the preview
     * @return a prefix of the <code>plainText</code> having at most <code>previewLength</code> characters
     */
    public static String getPlainPreview(String plainText, int previewLength) {
        if (plainText.length() <= previewLength) {
            return plainText;
        }
        // We remove the leading and trailing spaces from the given text to avoid interfering
        // with the last space within the length limit
        plainText = plainText.trim();
        if (plainText.length() <= previewLength) {
            return plainText;
        }
        int spaceIndex = plainText.lastIndexOf(" ", previewLength);
        if (spaceIndex < 0) {
            spaceIndex = previewLength;
        }
        plainText = plainText.substring(0, spaceIndex);
        return plainText;
    }

    /**
     * Creates a new {@link SyndContent} instance for the given content type and value.
     * 
     * @param type content type
     * @param value the content
     * @return a new {@link SyndContent} instance
     */
    protected SyndContent getSyndContent(String type, String value) {
        SyndContent content = new SyndContentImpl();
        content.setType(type);
        content.setValue(value);
        return content;
    }

    /**
     * Casts the given object to a {@link Document} instance. The given object must be either a {@link Document}
     * instance already, a {@link XWikiDocument} instance or the full name of the document.
     * 
     * @param obj object to be casted
     * @param context the XWiki context
     * @return the document associated with the given object
     * @throws XWikiException if the given object is neither a {@link Document} instance, a {@link XWikiDocument}
     *             instance nor the full name of the document
     */
    protected Document castDocument(Object obj, XWikiContext context) throws XWikiException {
        if (obj instanceof Document) {
            return (Document) obj;
        } else if (obj instanceof XWikiDocument) {
            return ((XWikiDocument) obj).newDocument(context);
        } else if (obj instanceof String) {
            return context.getWiki().getDocument((String) obj, context).newDocument(context);
        } else {
            throw new XWikiException(XWikiException.MODULE_XWIKI_PLUGINS, XWikiException.ERROR_XWIKI_DOES_NOT_EXIST,
                    "");
        }
    }
}