com.xpn.xwiki.xmlrpc.XWikiXmlRpcApiImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.xmlrpc.XWikiXmlRpcApiImpl.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.xmlrpc;

import java.io.StringReader;
import java.security.Principal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.codehaus.swizzle.confluence.Attachment;
import org.codehaus.swizzle.confluence.Comment;
import org.codehaus.swizzle.confluence.ServerInfo;
import org.codehaus.swizzle.confluence.Space;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.suigeneris.jrcs.rcs.Version;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.query.Query;
import org.xwiki.query.QueryManager;
import org.xwiki.rendering.converter.Converter;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.renderer.PrintRendererFactory;
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.velocity.VelocityManager;
import org.xwiki.xmlrpc.XWikiXmlRpcApi;
import org.xwiki.xmlrpc.model.XWikiExtendedId;
import org.xwiki.xmlrpc.model.XWikiObject;
import org.xwiki.xmlrpc.model.XWikiPage;
import org.xwiki.xmlrpc.model.XWikiPageHistorySummary;
import org.xwiki.xmlrpc.model.XWikiPageSummary;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.rcs.XWikiRCSNodeId;
import com.xpn.xwiki.web.Utils;

/**
 * The class containing the implementation of the XML-RPC API. Methods tagged with the ConfluenceAPI are compatible with
 * Confluence.
 * 
 * @version $Id: 31cc018fe4e489a025d4b5e0602524538decc194 $
 */
public class XWikiXmlRpcApiImpl implements XWikiXmlRpcApi {
    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiXmlRpcApiImpl.class);

    private XWikiContext xwikiContext;

    private com.xpn.xwiki.XWiki xwiki;

    private com.xpn.xwiki.api.XWiki xwikiApi;

    public XWikiXmlRpcApiImpl() {
        this.xwikiContext = getContext();
        this.xwiki = this.xwikiContext.getWiki();
        this.xwikiApi = new com.xpn.xwiki.api.XWiki(this.xwiki, this.xwikiContext);
    }

    protected XWikiContext getContext() {
        Execution execution = Utils.getComponent(Execution.class);
        ExecutionContext executionContext = execution.getContext();
        return (XWikiContext) executionContext.getProperty("xwikicontext");
    }

    /**
     * Login.
     * 
     * @return A token to be used in subsequent calls as an identification.
     * @throws Exception If authentication fails.
     */
    public String login(String userName, String password) throws Exception {
        String token;

        Principal principal = this.xwiki.getAuthService().authenticate(userName, password, this.xwikiContext);
        if (principal != null) {
            // Generate "unique" token using a random number
            token = this.xwiki.generateValidationKey(128);
            String ip = this.xwikiContext.getRequest().getRemoteAddr();

            XWikiUtils.getTokens(this.xwikiContext).put(token, new XWikiXmlRpcUser(principal.getName(), ip));

            return token;
        } else {
            throw new Exception(String.format("[Authentication failed for user '%s']", userName));
        }
    }

    /**
     * Logout.
     * 
     * @param token The authentication token.
     * @return True is logout was successful.
     * @throws Exception An invalid token is provided.
     */
    public Boolean logout(String token) throws Exception {
        XWikiUtils.checkToken(token, this.xwikiContext);

        return XWikiUtils.getTokens(this.xwikiContext).remove(token) != null;
    }

    /**
     * Get server information.
     * 
     * @param token The authentication token.
     * @return The server information
     * @throws Exception An invalid token is provided.
     */
    public Map getServerInfo(String token) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getServerInfo()", user.getName()));

        String version = this.xwikiApi.getVersion();
        Integer majorVersion = null;
        Integer minorVersion = null;
        Map serverInfoMap = new HashMap();
        serverInfoMap.put("DefaultSyntax", xwikiApi.getDefaultDocumentSyntax());
        serverInfoMap.put("ConfiguredSyntaxes", xwikiApi.getConfiguredSyntaxes());

        ServerInfo serverInfo = new ServerInfo(serverInfoMap);
        if (version != null) {
            serverInfo.setBuildId(version);
            if (version.indexOf('.') != -1) {
                String[] components = version.split("\\.");
                majorVersion = new Integer(components[0]);
                serverInfo.setMajorVersion(majorVersion);
                if (components[1].indexOf('-') != -1) {
                    // Removing possible suffixes (-SNAPSHOT for example)
                    minorVersion = new Integer(components[1].substring(0, components[1].indexOf('-')));
                } else {
                    minorVersion = new Integer(components[1]);
                }
                serverInfo.setMinorVersion(minorVersion);
            }
        } else {
            serverInfo.setMajorVersion(0);
            serverInfo.setMinorVersion(0);
        }

        serverInfo.setPatchLevel(0);
        serverInfo.setBaseUrl(this.xwikiApi.getURL(""));

        return serverInfo.toMap();
    }

    /**
     * Get the list of spaces.
     * 
     * @param token The authentication token.
     * @return A list of Maps that represent SpaceSummary objects.
     * @throws Exception An invalid token is provided.
     */
    public List getSpaces(String token) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getSpaces()", user.getName()));

        List result = new ArrayList();
        List<String> spaceKeys = this.xwikiApi.getSpaces();

        for (String spaceKey : spaceKeys) {
            String spaceWebHomeId = String.format("%s.WebHome", spaceKey);

            if (!this.xwikiApi.exists(spaceWebHomeId)) {
                result.add(DomainObjectFactory.createSpaceSummary(spaceKey).toRawMap());
            } else {
                Document spaceWebHome = this.xwikiApi.getDocument(spaceWebHomeId);

                /*
                 * If doc is null, then we don't have the rights to access the document, and therefore to the space.
                 */
                if (spaceWebHome != null) {
                    result.add(DomainObjectFactory.createSpaceSummary(spaceWebHome).toRawMap());
                }
            }
        }

        return result;
    }

    /**
     * Get information about a given space.
     * 
     * @param token The authentication token.
     * @param spaceKey The space name.
     * @return A map representing a Space object.
     * @throws Exception An invalid token is provided or the user doesn't have enough rights to access the space.
     */
    public Map getSpace(String token, String spaceKey) throws Exception {

        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getSpace()", user.getName()));

        if (!this.xwikiApi.getSpaces().contains(spaceKey)) {
            throw new Exception(String.format("[Space '%s' does not exist]", spaceKey));
        }

        String spaceWebHomeId = String.format("%s.WebHome", spaceKey);
        if (!this.xwikiApi.exists(spaceWebHomeId)) {
            return DomainObjectFactory.createSpace(spaceKey).toRawMap();
        } else {
            Document spaceWebHome = this.xwikiApi.getDocument(spaceWebHomeId);

            /*
             * If doc is null, then we don't have the rights to access the document
             */
            if (spaceWebHome != null) {
                return DomainObjectFactory.createSpace(spaceWebHome).toRawMap();
            } else {
                throw new RuntimeException(String.format("[Space '%s' cannot be accessed]", spaceKey));
            }
        }
    }

    /**
     * Add a new space. It basically creates a SpaceKey.WebHome page with no content and the space title as its title.
     * 
     * @param token The authentication token.
     * @param spaceMap The map representing a Space object.
     * @return The newly created space as a Space object.
     * @throws Exception An invalid token is provided or the space cannot be created or it already exists and the user
     *             has not the rights to modify it
     */
    public Map addSpace(String token, Map spaceMap) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called addSpace()", user.getName()));

        Space space = new Space(spaceMap);

        if (this.xwikiApi.getSpaces().contains(space.getKey())) {
            throw new Exception(String.format("[Space '%s' already exists]", space.getKey()));
        }

        String spaceWebHomeId = String.format("%s.WebHome", space.getKey());
        Document spaceWebHome = this.xwikiApi.getDocument(spaceWebHomeId);
        if (spaceWebHome != null) {
            spaceWebHome.setContent("");
            spaceWebHome.setTitle(space.getName());
            spaceWebHome.save();

            return DomainObjectFactory.createSpace(spaceWebHome).toRawMap();
        } else {
            throw new Exception(String.format(
                    "[Space cannot be created or it already exists and user '%s' has not the right to modify it]",
                    user.getName()));
        }
    }

    /**
     * Removes a space by deleting every page in it.
     * 
     * @param token The authentication token.
     * @param spaceKey The space name.
     * @return True if the space has been successfully deleted.
     * @throws Exception An invalid token is provided or there was a problem while deleting the space.
     */
    public Boolean removeSpace(String token, String spaceKey) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called removeSpace()", user.getName()));

        if (!this.xwikiApi.getSpaces().contains(spaceKey)) {
            throw new Exception(String.format("[Space '%s' does not exist.]", spaceKey));
        }

        boolean spaceDeleted = true;
        List<String> pageNames = this.xwikiApi.getSpaceDocsName(spaceKey);
        for (String pageName : pageNames) {
            String pageFullName = String.format("%s.%s", spaceKey, pageName);
            if (this.xwikiApi.exists(pageFullName)) {
                Document doc = this.xwikiApi.getDocument(pageFullName);
                if (doc != null) {
                    try {
                        if (!doc.getLocked()) {
                            doc.delete();
                        } else {
                            /*
                             * We cannot delete a locked page, so the space is not fully deleted.
                             */
                            spaceDeleted = false;
                        }
                    } catch (XWikiException e) {
                        /*
                         * An exception here means that we haven't succeeded in deleting a page, so there might be some
                         * pages that still belong to the space, so the space is not fully deleted
                         */
                        System.out.format("%s\n", e.getMessage());
                        spaceDeleted = false;
                    }
                } else {
                    spaceDeleted = false;
                }
            }
        }

        return spaceDeleted;
    }

    /**
     * @param token The authentication token.
     * @param spaceKey The space name.
     * @return A list containing PageSummary objects.
     * @throws Exception An invalid token is provided.
     */
    public List getPages(String token, String spaceKey) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getPages()", user.getName()));

        List result = new ArrayList();
        List<String> pageNames = this.xwikiApi.getSpaceDocsName(spaceKey);
        for (String pageName : pageNames) {
            String pageFullName = String.format("%s.%s", spaceKey, pageName);

            if (!this.xwikiApi.exists(pageFullName)) {
                LOGGER.warn(
                        String.format("[Page '%s' appears to be in space '%s' but no information is available.]",
                                pageName, spaceKey));
            } else {
                Document doc = this.xwikiApi.getDocument(pageFullName);

                /* We only add pages we have the right to access */
                if (doc != null) {
                    XWikiPageSummary pageSummary = DomainObjectFactory.createXWikiPageSummary(doc);
                    result.add(pageSummary.toRawMap());
                }
            }
        }

        return result;
    }

    /**
     * Retrieves a page. The returned page title is set the current title if the current title != "", otherwise it is
     * set to the page name (i.e., the name part in the page Space.Name id)
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page[?language=l&version=v&minorVersion=mv]' format.
     * @return A map representing a Page object containing information about the requested page.
     * @throws Exception An invalid token is provided or the user has not the right to access the page or the page does
     *             not exist.
     */
    public Map getPage(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getPage()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        return DomainObjectFactory.createXWikiPage(doc, false).toRawMap();
    }

    /**
     * Store or rename a page, or create it if it doesn't exist.
     * <p>
     * The way confluence clients rename pages is the following:
     * </p>
     * <code>
     * page = getPage(pageId);
     * page.setSpace("New space");
     * page.setTitle("New title");
     * storePage(page);
     * </code>
     * <p>
     * In XWiki in order to rename a page we need to change its ID, and no client written for confluence will do this.
     * Currently the authoritative source for the page location is the ID (basically storePage ignores the space field)
     * and changing the title will only affect the page title. However if we agree to assume that the when using XMLRPC
     * the semantics of the page title is that of the page name in an XWiki ID, we will be able to be confluence
     * compatible.
     * </p>
     * </p> There are three possible cases: Let P=(id, space, title) the definition of a page. Let CP be the current
     * page and NP the page to be stored (i.e. the page passed to storePage): </p>
     * <p>
     * 1) CP=("Space.Name", "Space", "Title") NP=("Space.Name", "NewSpace", "Title") Here it is clear that the user
     * wants to "rename" the page by moving it to another space. So we rename the page to ("NewSpace.Name", "NewSpace",
     * "Title")
     * </p>
     * <p>
     * 2) CP=("Space.Name", "Space", "Title") NP=("Space.Name", "NewSpace", "NewTitle"); Here it is also clear that we
     * want to move the page to NewSpace but we have a problem about how to name the new page: NewSpace.Name or
     * NewSpace.NewTitle? According to the assumption stated before, we rename the page and use NewTitle as the page
     * name. The renamed page will have the NewSpace.NewTitle id. We also set the renamed page title to NewTitle.
     * </p>
     * <p>
     * 3) CP=("Space.Name", "Space", "Title") NP=("Space.Name", "Space", "NewTitle"); Here we have an ambiguity. Does
     * the user want to to rename the page or set its title? According to the assumption stated before we assume that
     * the user wants to rename the page so we will rename the page to Space.NewTitle and set its title to NewTitle.
     * </p>
     * 
     * @param token The authentication token.
     * @param pageMap A map representing the Page object to be stored.
     * @return A map representing a Page object with the updated information.
     * @throws Exception An invalid token is provided or some data is missing or the page is locked or the user has no
     *             right to modify the page.
     */
    public Map storePage(String token, Map pageMap) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called storePage()", user.getName()));

        XWikiPage page = new XWikiPage(pageMap);

        /*
         * Confluence semantics compatibility. In order to say to create a page, Confluence sets the id to null.
         */
        if (page.getId() == null) {
            if (page.getTitle() == null) {
                throw new Exception(String.format("[Neither page title, nor page id is specified!]"));
            }

            if (page.getSpace() == null) {
                throw new Exception(String.format("[Neither page's space, nor page id is specified!]"));
            }

            page.setId(String.format("%s.%s", page.getSpace(), page.getTitle()));
        }

        /* If the language field is null set it to the default language "" */
        if (page.getLanguage() == null) {
            page.setLanguage("");
        }

        /* If the syntax field is null then set it to the default wiki syntax */
        if (page.getSyntaxId() == null) {
            page.setSyntaxId(xwikiApi.getDefaultDocumentSyntax());
        }

        /* Build the extended id from the page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(page.getId());

        /* If the space is null then set it to the space encoded in the page id. */
        if (page.getSpace() == null) {
            String[] components = extendedId.getBasePageId().split("\\.");
            page.setSpace(components[0]);
        }

        /*
         * Even though the extended id could contain parameters such as language and version, here we ignore them and
         * just consider the base page id. The language to be stored will be taken from the result of
         * page.getLanguage(). Version is not meaningful because we don't allow to replace previous versions of the
         * page.
         */
        boolean pageAlreadyExists = this.xwikiApi.exists(extendedId.getBasePageId());
        Document doc = this.xwikiApi.getDocument(extendedId.getBasePageId());

        if (doc != null) {
            if (doc.getLocked()) {
                throw new Exception(
                        String.format("[Unable to store document. Document locked by '%s']", doc.getLockingUser()));
            }

            /*
             * Confluence semantics compatibility. Here we assume that the authoritative source for the page ID is the
             * combination of the fields Space + Title as specified in the page structure passed to the function. If the
             * title or the space (or both) are different from the current values then we must handle the request as a
             * page rename.
             */
            boolean rename = false;

            /*
             * Check if the page already exists. If it doesn't this is surely not a rename request! Check also that the
             * title is not null. This might happen if a client creates an XWikiPage object with an existing page id and
             * initializes only the content field (which is perfectly legal for non-existing pages but here causes a
             * NPE).
             */
            if (pageAlreadyExists && page.getTitle() != null) {
                /* The page already exists... check if we have received a rename request. */
                if (!page.getTitle().equals(doc.getName())) {
                    /*
                     * Here the title is different from the document's name. This check is necessary because getPage
                     * sets the title to the page name when the actual title is "". So when this is the case, even
                     * though the document title is different from the page title, the caller might not have changed the
                     * title field for renaming the page.
                     */
                    if (!page.getTitle().equals(doc.getTitle())) {
                        /*
                         * Here the page already exists, titles are different and the passed title is different from the
                         * page name. So the request is surely a rename request.
                         */

                        if (!page.getTitle().equals("")) {
                            /*
                             * Check a degenerate case where the user tries to rename the page to a page with an empty
                             * name. If we are here, the new title (i.e., the new page name is valid).
                             */
                            rename = true;
                        }
                    }
                }

                /* Check also on the space side */
                if (!doc.getSpace().equals(page.getSpace())) {
                    /*
                     * Here the document space and the space field of the page passed as parameter are different. So
                     * this is surely a rename request.
                     */
                    if (!page.getSpace().equals("")) {
                        /*
                         * Check a degenerate case where the user tries to rename the page to a space with an empty
                         * name. If we are here, the new space name is valid.
                         */
                        rename = true;
                    }
                }
            }

            /*
             * In order to rename a page we must rename the default language one. This simplifies the logic for handling
             * Confluence compatibility. In fact every page translation has its own title and we consider the default
             * translation title as the page name when renaming.
             */
            if (rename && page.getLanguage().equals("")) {
                /* This is a rename request */

                /*
                 * Given the conditions before, here we have that newSpace != "" AND newName != "", AND either newSpace
                 * != documentSpace OR newName != documentName.
                 */
                String newSpace = page.getSpace();
                String newName = page.getTitle();

                String newPageId = String.format("%s.%s", newSpace, newName);
                this.xwikiApi.renamePage(doc, newPageId);
                doc = this.xwikiApi.getDocument(newPageId);
            } else {
                /*
                 * Normal document storage.
                 */
                if (page.getLanguage() != null && !page.getLanguage().equals("")
                        && !page.getLanguage().equals(doc.getDefaultLanguage())) {
                    /*
                     * Try to get the document in the translation specified in the page parameter...
                     */
                    doc = doc.getTranslatedDocument(page.getLanguage());

                    if (!doc.getLanguage().equals(page.getLanguage())) {
                        /*
                         * If we are here, then the document returned by getTranslatedDocument is the same of the
                         * default translation, i.e., the page in the current translation does not exist. So we have to
                         * create it. Here we have to use the low-level XWiki API because it is not possible to set the
                         * language of a Document.
                         */
                        XWikiDocument xwikiDocument = new XWikiDocument(page.getSpace(),
                                extendedId.getBasePageId());
                        xwikiDocument.setLanguage(page.getLanguage());
                        doc = new Document(xwikiDocument, this.xwikiContext);
                    }
                }

                if (!StringUtils.isEmpty(page.getSyntaxId())) {
                    doc.setSyntaxId(page.getSyntaxId());
                }

                doc.setContent(page.getContent());
                doc.setTitle(page.getTitle());
                doc.setParent(page.getParentId()); /* Allow reparenting */
                doc.save();
            }

            return DomainObjectFactory.createXWikiPage(doc, false).toRawMap();
        } else {
            throw new Exception(
                    String.format("[Cannot access document for page '%s']", extendedId.getBasePageId()));
        }
    }

    /**
     * Remove a page.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public Boolean removePage(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called removePage()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        if (doc != null) {
            if (doc.getLocked()) {
                throw new Exception(String.format("[Unable to remove page. Document '%s' locked by '%s']",
                        doc.getName(), doc.getLockingUser()));
            }

            doc.delete();
        } else {
            throw new Exception(String.format("[Page '%s' cannot be accessed]", pageId));
        }

        return true;
    }

    /**
     * Get revision history of a page.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page[?language=lang]' format.
     * @return A list of maps representing PageHistorySummary objects.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public List getPageHistory(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getPageHistory()", user.getName()));

        List result = new ArrayList();

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        Version[] versions = doc.getRevisions();
        for (Version version : versions) {
            Document docRevision = this.xwikiApi.getDocument(doc, version.toString());

            /*
             * The returned document has the right content but the wrong content update date, that is always equals to
             * the current date. Don't know why.
             */
            result.add(DomainObjectFactory.createXWikiPageHistorySummary(docRevision).toRawMap());
        }

        return result;
    }

    /**
     * Render a page or content in HTML.
     * 
     * @param token The authentication token.
     * @param space Ignored
     * @param pageId The page id in the form of Space.Page
     * @param content The content to be rendered. If content == "" then the page content is rendered.
     * @return The rendered content.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public String renderContent(String token, String space, String pageId, String content) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called renderContent()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        this.xwikiContext.setAction("view");

        XWikiExtendedId extendedId = new XWikiExtendedId(pageId);
        XWikiDocument baseDocument = this.xwiki.getDocument(extendedId.getBasePageId(), this.xwikiContext);
        this.xwikiContext.setDoc(baseDocument);

        VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
        VelocityContext vcontext = velocityManager.getVelocityContext();

        this.xwiki.prepareDocuments(this.xwikiContext.getRequest(), this.xwikiContext, vcontext);
        if (content.length() == 0) {
            /*
             * If content is not provided, then the existing content of the page is used
             */
            content = doc.getContent();
        } else {
            baseDocument.setAuthor(this.xwikiContext.getUser());
            baseDocument.setContentAuthor(this.xwikiContext.getUser());
        }

        return baseDocument.getRenderedContent(content, baseDocument.getSyntaxId(), xwikiContext);
    }

    /**
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @return A list of maps representing Comment objects.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public List getComments(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getComments()", user.getName()));

        List result = new ArrayList();

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        Vector<com.xpn.xwiki.api.Object> comments = doc.getComments();
        if (comments != null) {
            for (com.xpn.xwiki.api.Object commentObject : comments) {
                result.add(DomainObjectFactory.createComment(doc, commentObject).toRawMap());
            }
        }

        return result;
    }

    /**
     * Get a specific comment. This method is here just for completeness because getComments already returns the list
     * containing all the objects that might be retrieved using this method.
     * 
     * @param token The authentication token.
     * @param commentId The comment id.
     * @return A map representing a Comment object.
     * @throws Exception An invalid token is provided or if the comment cannot be retrieved. access it.
     */
    public Map getComment(String token, String commentId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getComment()", user.getName()));

        XWikiExtendedId extendedId = new XWikiExtendedId(commentId);
        int commentNumericalId = Integer.parseInt(extendedId.getParameter(XWikiExtendedId.COMMENT_ID_PARAMETER));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, commentId, true);

        com.xpn.xwiki.api.Object commentObject = doc.getObject("XWiki.XWikiComments", commentNumericalId);

        return DomainObjectFactory.createComment(doc, commentObject).toRawMap();
    }

    /**
     * Add a comment.
     * 
     * @param token The authentication token.
     * @param commentMap A map representing a Comment object.
     * @return A map representing a Comment object with updated information.
     * @throws Exception An invalid token is provided or if the user has not the right to access it.
     */
    public Map addComment(String token, Map commentMap) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called addComment()", user.getName()));

        Comment comment = new Comment(commentMap);

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(comment.getPageId());
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);

        int id = doc.createNewObject("XWiki.XWikiComments");
        com.xpn.xwiki.api.Object commentObject = doc.getObject("XWiki.XWikiComments", id);
        commentObject.set("author", user.getName());
        commentObject.set("date", new Date());
        commentObject.set("comment", comment.getContent());

        doc.save();

        return DomainObjectFactory.createComment(doc, commentObject).toRawMap();
    }

    /**
     * Remove a comment.
     * 
     * @param token The authentication token.
     * @return True if the comment has been successfully removed.
     * @throws Exception An invalid token is provided or the user has not the right to access it.
     */
    public Boolean removeComment(String token, String commentId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called removeComment()", user.getName()));

        XWikiExtendedId extendedId = new XWikiExtendedId(commentId);
        int commentNumericalId = Integer.parseInt(extendedId.getParameter(XWikiExtendedId.COMMENT_ID_PARAMETER));

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);
        if (doc.getLocked()) {
            throw new Exception(String.format("[Unable to remove attachment. Document '%s' locked by '%s']",
                    doc.getName(), doc.getLockingUser()));
        }

        com.xpn.xwiki.api.Object commentObject = doc.getObject("XWiki.XWikiComments", commentNumericalId);
        doc.removeObject(commentObject);
        doc.save();

        return true;
    }

    /**
     * Get attachments.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @return A list of maps representing Attachment objects.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public List getAttachments(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getAttachments()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        List result = new ArrayList();

        List<com.xpn.xwiki.api.Attachment> attachments = doc.getAttachmentList();
        for (com.xpn.xwiki.api.Attachment xwikiAttachment : attachments) {
            result.add(DomainObjectFactory.createAttachment(xwikiAttachment).toRawMap());
        }

        return result;
    }

    /**
     * Add an attachment.
     * 
     * @param token The authentication token.
     * @param contentId Ignored
     * @param attachmentMap The Attachment object used to identify the page id, and attachment metadata.
     * @param attachmentData The actual attachment data.
     * @return An Attachment object describing the newly added attachment.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public Map addAttachment(String token, Integer contentId, Map attachmentMap, byte[] attachmentData)
            throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called addAttachment()", user.getName()));

        Attachment attachment = new Attachment(attachmentMap);

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(attachment.getPageId());
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);

        if (doc.getLocked()) {
            throw new Exception(
                    String.format("Unable to add attachment. Document locked by %s", doc.getLockingUser()));
        }

        com.xpn.xwiki.api.Attachment xwikiAttachment = doc.addAttachment(attachment.getFileName(), attachmentData);
        doc.save();

        return DomainObjectFactory.createAttachment(xwikiAttachment).toRawMap();
    }

    /**
     * Get the attachment data.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @param versionNumber (Ignored)
     * @return An array of bytes with the actual attachment content.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it or the attachment with the given fileName does not exist on the given page.
     */
    public byte[] getAttachmentData(String token, String pageId, String fileName, String versionNumber)
            throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getAttachmentData()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        com.xpn.xwiki.api.Attachment xwikiAttachment = doc.getAttachment(fileName);
        if (xwikiAttachment != null) {
            return xwikiAttachment.getContent();
        } else {
            throw new Exception(String.format("[Attachment '%s' does not exist on page '%s']", fileName, pageId));
        }
    }

    /**
     * Remove attachment.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @return True if the attachment has been removed.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it or the attachment with the given fileName does not exist on the given page.
     */
    public Boolean removeAttachment(String token, String pageId, String fileName) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called removeAttachment()", user.getName()));

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(pageId);
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);

        if (doc.getLocked()) {
            throw new Exception(String.format("Unable to remove attachment. Document '%s' locked by '%s'",
                    doc.getName(), doc.getLockingUser()));
        }

        com.xpn.xwiki.api.Attachment xwikiAttachment = doc.getAttachment(fileName);
        if (xwikiAttachment != null) {
            /*
             * Here we must use low-level XWiki API because there is no way for removing an attachment through the XWiki
             * User's API. Basically we use the com.xpn.xwiki.XWiki object to re-do all the steps that we have already
             * done so far by using the API. It is safe because if we are here, we know that everything exists and we
             * have the proper rights to do things. So nothing should fail.
             */
            XWikiDocument baseXWikiDocument = this.xwiki.getDocument(extendedId.getBasePageId(), this.xwikiContext);
            XWikiAttachment baseXWikiAttachment = baseXWikiDocument.getAttachment(fileName);
            baseXWikiDocument.deleteAttachment(baseXWikiAttachment, this.xwikiContext);

            this.xwiki.saveDocument(baseXWikiDocument, this.xwikiContext);
        } else {
            throw new Exception(String.format("Attachment '%s' does not exist on page '%s'", fileName,
                    extendedId.getBasePageId()));
        }

        return true;
    }

    /**
     * Get classes.
     * 
     * @param token The authentication token.
     * @return A list of maps representing XWikiClass objects.
     * @throws Exception An invalid token is provided.
     */
    public List getClasses(String token) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getClasses()", user.getName()));

        List result = new ArrayList();

        List<String> classNames = this.xwikiApi.getClassList();
        for (String className : classNames) {
            result.add(DomainObjectFactory.createXWikiClassSummary(className).toRawMap());
        }

        return result;
    }

    /**
     * Get a specific class.
     * 
     * @param token The authentication token.
     * @param className The class name.
     * @return A map representing a XWikiClass object.
     * @throws Exception An invalid token is provided or if the given class does not exist.
     */
    public Map getClass(String token, String className) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getClass()", user.getName()));

        if (!this.xwikiApi.exists(className)) {
            throw new Exception(String.format("[Class '%s' does not exist]", className));
        }

        com.xpn.xwiki.api.Class userClass = this.xwikiApi.getClass(className);

        return DomainObjectFactory.createXWikiClass(userClass).toRawMap();
    }

    /**
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @return A list of maps representing XWikiObject objects.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public List getObjects(String token, String pageId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getObjects()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        List result = new ArrayList();

        Map<String, Vector<com.xpn.xwiki.api.Object>> classToObjectsMap = doc.getxWikiObjects();

        for (String className : classToObjectsMap.keySet()) {
            Vector<com.xpn.xwiki.api.Object> objects = classToObjectsMap.get(className);
            for (com.xpn.xwiki.api.Object object : objects) {
                result.add(DomainObjectFactory.createXWikiObjectSummary(doc, object).toRawMap());
            }
        }

        return result;
    }

    /**
     * The getObject function will return an XWikiObject where only non-null properties are included in the mapping
     * 'field' -> 'value' In order to know all the available fields and their respective types and attributes, clients
     * should refer to the object's class.
     * 
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @param className The class of the object.
     * @param id The id (number) of the object.
     * @return The XWikiObject containing the information about all the properties contained in the selected object.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it or no object with the given id exist in the page.
     */
    public Map getObject(String token, String pageId, String className, Integer id) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getObject()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        com.xpn.xwiki.api.Object object = doc.getObject(className, id);

        if (object != null) {
            return DomainObjectFactory.createXWikiObject(this.xwiki, this.xwikiContext, doc, object).toRawMap();
        } else {
            throw new Exception(
                    String.format("[Unable to find object %s[%d] on page '%s']", className, id, pageId));
        }
    }

    /**
     * Update the object or create a new one if it doesn't exist.
     * 
     * @param token The authentication token.
     * @param objectMap A map representing the XWikiObject to be updated/created.
     * @return A map representing the XWikiObject with the updated information.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it.
     */
    public Map storeObject(String token, Map objectMap) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called storeObject()", user.getName()));

        XWikiObject object = new XWikiObject(objectMap);

        /* The passed object must specify a class name otherwise we cannot set properties on the xwiki object */
        if (object.getClassName() == null) {
            throw new Exception(String.format("[Object doesn't specify a class]"));
        }

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(object.getPageId());
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);

        if (doc.getLocked()) {
            throw new Exception(
                    String.format("Unable to store object. Document locked by %s", doc.getLockingUser()));
        }

        com.xpn.xwiki.api.Object xwikiObject = null;

        /* First try to lookup the object by guid if specified, otherwise use classname[number] */
        if (object.getGuid() != null) {
            xwikiObject = XWikiUtils.getObjectByGuid(doc, object.getGuid());
        } else if (xwikiObject == null) {
            xwikiObject = doc.getObject(object.getClassName(), object.getId());
        }

        /* If the object does not exist create it */
        if (xwikiObject == null) {
            int id = doc.createNewObject(object.getClassName());
            /* Get the newly created object for update */
            xwikiObject = doc.getObject(object.getClassName(), id);

            /* We must initialize all the fields to an empty value in order to correctly create the object */
            com.xpn.xwiki.api.Class xwikiClass = this.xwikiApi.getClass(object.getClassName());
            for (Object propertyNameObject : xwikiClass.getPropertyNames()) {
                String propertyName = (String) propertyNameObject;

                xwikiObject.set(propertyName, "");
            }
        }

        /*
         * Allow clients to specify/override a guid. This is necessary to allow replication of objects between xwikis.
         * If it's null then a guid will be generated when the page is saved.
         */
        if (object.getGuid() != null) {
            xwikiObject.setGuid(object.getGuid());
        }

        /*
         * We iterate on the XWikiObject-passed-as-a-parameter's properties instead of the one retrieved through the API
         * because, a newly created object has no properties, and they should be added via set. Apparently setting
         * properties that do not belong to the object's class is harmless.
         */
        for (String propertyName : object.getProperties()) {
            /*
             * Object values are always sent as strings (or arrays/maps of strings)... let the actual object perform the
             * conversion
             */
            xwikiObject.set(propertyName, object.getProperty(propertyName));
        }

        doc.save();

        return DomainObjectFactory.createXWikiObject(this.xwiki, this.xwikiContext, doc, xwikiObject).toRawMap();
    }

    /**
     * @param token The authentication token.
     * @param pageId The pageId in the 'Space.Page' format.
     * @param id The object's id.
     * @return True if the object has been successfully deleted.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it or no object with the given id exist in the page.
     */
    public Boolean removeObject(String token, String pageId, String className, Integer id) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called removeObject()", user.getName()));

        /* Ignore the language or version parameters passed with the page id, and use the base page id */
        XWikiExtendedId extendedId = new XWikiExtendedId(pageId);
        Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.getBasePageId(), true);

        if (doc.getLocked()) {
            throw new Exception(String.format("[Unable to remove attachment. Document '%s' locked by '%s']",
                    doc.getName(), doc.getLockingUser()));
        }

        com.xpn.xwiki.api.Object object = doc.getObject(className, id);
        if (object != null) {
            doc.removeObject(object);
            doc.save();
        } else {
            throw new Exception(String.format("[Object %s[%d] on page '%s' does not exist]", className, id,
                    extendedId.getBasePageId()));
        }

        return true;
    }

    /**
     * Search pages.
     * 
     * @param token The authentication token.
     * @param query The string to be looked for. If it is "__ALL_PAGES__" the search will return all the page ids
     *            available in the Wiki.
     * @return A list of maps representing Search Results.
     * @throws Exception An invalid token is provided.
     */
    public List search(String token, String query, int maxResults) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called search()", user.getName()));

        List result = new ArrayList();
        if (query.equals("__ALL_PAGES__")) {
            List<String> spaceKeys = this.xwikiApi.getSpaces();
            for (String spaceKey : spaceKeys) {
                List<String> pageNames = this.xwikiApi.getSpaceDocsName(spaceKey);
                for (String pageName : pageNames) {
                    result.add(DomainObjectFactory.createSearchResult(String.format("%s.%s", spaceKey, pageName))
                            .toMap());
                }
            }
        } else {
            List<String> searchResults = this.xwiki.getStore().searchDocumentsNames(
                    "where doc.content like '%" + com.xpn.xwiki.web.Utils.SQLFilter(query)
                            + "%' or doc.name like '%" + com.xpn.xwiki.web.Utils.SQLFilter(query) + "%'",
                    this.xwikiContext);
            int i = 0;
            for (String pageId : searchResults) {
                if (maxResults > 0 && i < maxResults) {
                    result.add(DomainObjectFactory.createSearchResult(pageId).toMap());
                } else {
                    break;
                }
                i++;
            }
        }

        return result;
    }

    /**
     * Returns a list of XWikiPageHistorySummary containing all the pages that have been modified since a given date in
     * all their versions.
     * 
     * @param token
     * @param date The starting date
     * @param numberOfResults The number of results to be returned
     * @param start The start offset in the result set
     * @param fromLatest True if the result set will list recent changed pages before.
     * @return A list of maps representing XWikiPageHistorySummary
     * @throws Exception An invalid token is provided.
     */
    public List getModifiedPagesHistory(String token, Date date, int numberOfResults, int start, boolean fromLatest)
            throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getModifiedPagesHistory()", user.getName()));

        List result = new ArrayList();

        String order = fromLatest ? "desc" : "asc";
        String query = String.format(
                "select doc.fullName, rcs.id, rcs.date, rcs.author from XWikiRCSNodeInfo as rcs, XWikiDocument as doc where rcs.id.docId=doc.id and rcs.date > :date order by rcs.date %s, rcs.id.version1 %s, rcs.id.version2 %s",
                order, order, order);

        QueryManager queryManager = Utils.getComponent(QueryManager.class);
        List<Object> queryResult = queryManager.createQuery(query, Query.XWQL).bindValue("date", date)
                .setLimit(numberOfResults).setOffset(start).execute();

        for (Object o : queryResult) {
            Object[] fields = (Object[]) o;
            String pageId = (String) fields[0];
            XWikiRCSNodeId nodeId = (XWikiRCSNodeId) fields[1];
            Timestamp timestamp = (Timestamp) fields[2];
            String author = (String) fields[3];

            XWikiPageHistorySummary pageHistory = new XWikiPageHistorySummary();
            pageHistory.setId(pageId);
            pageHistory.setVersion(nodeId.getVersion().at(0));
            pageHistory.setMinorVersion(nodeId.getVersion().at(1));
            pageHistory.setModified(new Date(timestamp.getTime()));
            pageHistory.setModifier(author);

            result.add(pageHistory.toRawMap());
        }

        return result;
    }

    /**
     * This is a version of storePage that fails to store the page if the current version of the page doesn't match the
     * one of the page passed as parameter (i.e., the page has been modified since the last getPage)
     * 
     * @param token
     * @param pageMap A map representing the Page object to be stored.
     * @param checkVersion True if the current version of the page and the one of the page passed as parameter must
     *            match.
     * @return A map representing an XWikiPage with updated information, or an XWikiPage whose fields are all empty if
     *         there is a version mismatch.
     * @throws Exception An invalid token is provided.
     */
    public Map storePage(String token, Map pageMap, boolean checkVersion) throws Exception {
        if (checkVersion) {
            XWikiPage page = new XWikiPage(pageMap);

            /*
             * Build a new extended id by removing version information in order to retrieve the latest version, in case
             * in a given language if specified
             */
            XWikiExtendedId extendedId = new XWikiExtendedId(page.getId());
            extendedId.setParameter(XWikiExtendedId.LANGUAGE_PARAMETER, page.getLanguage());
            extendedId.setParameter(XWikiExtendedId.VERSION_PARAMETER, null);
            extendedId.setParameter(XWikiExtendedId.MINOR_VERSION_PARAMETER, null);

            /* If the page doesn't exist then use directly the standard storePage */
            if (!this.xwikiApi.exists(extendedId.getBasePageId())) {
                return storePage(token, pageMap);
            }

            Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.toString(), true);

            if (doc.getRCSVersion().at(0) == page.getVersion()
                    && doc.getRCSVersion().at(1) == page.getMinorVersion()) {
                return storePage(token, pageMap);
            }

            return DomainObjectFactory.createEmptyXWikiPage().toRawMap();
        } else {
            return storePage(token, pageMap);
        }

    }

    /**
     * This is a version of storeObject that fails to store the object if the current version of the page associated to
     * the object doesn't match the one of the object passed as parameter (i.e., the object's page has been modified
     * since the last getObject)
     * 
     * @param token The authentication token.
     * @param objectMap A map representing the XWikiObject to be updated/created.
     * @param checkVersion True if the current version of the object's page and the one of the one passed as parameter
     *            must match.
     * @return A map representing the XWikiObject with the updated information, or an XWikiObject whose fields are all
     *         empty if there is a version mismatch.
     * @throws Exception An invalid token is provided.
     */
    public Map storeObject(String token, Map objectMap, boolean checkVersion) throws Exception {
        if (checkVersion) {
            XWikiObject object = new XWikiObject(objectMap);

            /*
             * Build a new extended id by removing version information in order to retrieve the latest version.For
             * objects we don't care about the language.
             */
            XWikiExtendedId extendedId = new XWikiExtendedId(object.getPageId());
            extendedId.setParameter(XWikiExtendedId.LANGUAGE_PARAMETER, null);
            extendedId.setParameter(XWikiExtendedId.VERSION_PARAMETER, null);
            extendedId.setParameter(XWikiExtendedId.MINOR_VERSION_PARAMETER, null);

            Document doc = XWikiUtils.getDocument(this.xwikiApi, extendedId.toString(), true);

            if (doc.getRCSVersion().at(0) == object.getPageVersion()
                    && doc.getRCSVersion().at(1) == object.getPageMinorVersion()) {
                return storeObject(token, objectMap);
            }

            return DomainObjectFactory.createEmptyXWikiObject().toRawMap();
        } else {
            return storeObject(token, objectMap);
        }
    }

    /**
     * The getObject function will return an XWikiObject where only non-null properties are included in the mapping
     * 'field' -> 'value' In order to know all the available fields and their respective types and attributes, clients
     * should refer to the object's class.
     * 
     * @param token The authentication token.
     * @param pageId The pageId.
     * @param guid The object's guid.
     * @return The XWikiObject containing the information about all the properties contained in the selected object.
     * @throws Exception An invalid token is provided or if the page does not exist or the user has not the right to
     *             access it or no object with the given id exist in the page.
     */
    public Map getObject(String token, String pageId, String guid) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getObject()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        com.xpn.xwiki.api.Object object = XWikiUtils.getObjectByGuid(doc, guid);
        if (object == null) {
            throw new Exception(String.format("[Unable to find object with guid '%s' on page '%s']", guid, pageId));
        }

        return DomainObjectFactory.createXWikiObject(this.xwiki, this.xwikiContext, doc, object).toRawMap();
    }

    /**
     * Converts a wiki source from a syntax to another syntax.
     * 
     * @param token The authentication token.
     * @param source The content to be converted.
     * @param initialSyntaxId The initial syntax of the source.
     * @param targetSyntaxId The final syntax of the returned content.
     * @return The converted source.
     * @throws Exception An invalid token is provided, the syntaxId is not supported, the source is invalid or the
     *             conversion fails.
     */
    public String convert(String token, String source, String initialSyntaxId, String targetSyntaxId)
            throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        try {
            SyntaxFactory syntaxFactory = Utils.getComponent(SyntaxFactory.class);
            Syntax initialSyntax = syntaxFactory.createSyntaxFromIdString(initialSyntaxId);
            Syntax targetSyntax = syntaxFactory.createSyntaxFromIdString(targetSyntaxId);
            WikiPrinter printer = new DefaultWikiPrinter();
            Converter converter = Utils.getComponent(Converter.class);
            converter.convert(new StringReader(source), initialSyntax, targetSyntax, printer);

            return printer.toString();
        } catch (Throwable t) {
            throw new RuntimeException("Exception while performing syntax conversion.", t);
        }
    }

    /**
     * Gets all syntaxes supported by the rendering parsers as an input for a syntax conversion.
     * 
     * @param token The authentication token.
     * @return A list containing all syntaxes supported by rendering parsers.
     * @throws Exception An invalid token is provided or the syntax lookup fails.
     */
    public List<String> getInputSyntaxes(String token) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        List<String> syntaxes = new ArrayList<String>();
        List<Parser> parsers;
        ComponentManager componentManager = Utils.getComponentManager();
        try {
            parsers = componentManager.lookupList(Parser.class);
            for (Parser parser : parsers) {
                syntaxes.add(parser.getSyntax().toIdString());
            }
        } catch (ComponentLookupException e) {
            throw new RuntimeException("Failed to lookup the list of available parser syntaxes", e);
        }
        return syntaxes;
    }

    /**
     * Gets all syntaxes supported by the rendering as an output for a syntax conversion.
     * 
     * @param token The authentication token.
     * @return A list containing all syntaxes supported by renderers.
     * @throws Exception An invalid token is provided or the syntax lookup fails.
     */
    public List<String> getOutputSyntaxes(String token) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        List<String> syntaxes = new ArrayList<String>();
        List<PrintRendererFactory> renderers;
        ComponentManager componentManager = Utils.getComponentManager();
        try {
            // TODO: use BlockRenderer
            renderers = componentManager.lookupList(PrintRendererFactory.class);
            for (PrintRendererFactory renderer : renderers) {
                syntaxes.add(renderer.getSyntax().toIdString());
            }
        } catch (ComponentLookupException e) {
            throw new RuntimeException("Failed to lookup the list of available renderer syntaxes", e);
        }
        return syntaxes;
    }

    /**
     * Renders a text in the context of a wiki page.
     * 
     * @param token The authentication token.
     * @param pageId The id of the page.
     * @param content The context to be rendered.
     * @param sourceSyntaxId The syntax of the content.
     * @param targetSyntaxId The target syntax of the rendered content
     * @return The rendered content.
     * @throws Exception If a invalid token is provided, an unsupported syntax id is given or the rendering fails.
     */
    public String renderPageContent(String token, String pageId, String content, String sourceSyntaxId,
            String targetSyntaxId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called renderPageContent()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);

        return doc.getRenderedContent(content, sourceSyntaxId, targetSyntaxId);
    }

    /**
     * Gets the rendered content of an existing document.
     * 
     * @param token The authentication token.
     * @param pageId The id of the page.
     * @param syntaxId The target syntax of the rendered content
     * @return The rendered content
     * @throws Exception If a invalid token is provided, an unsupported syntax id is given or the rendering fails.
     */
    public String getRenderedContent(String token, String pageId, String syntaxId) throws Exception {
        XWikiXmlRpcUser user = XWikiUtils.checkToken(token, this.xwikiContext);
        LOGGER.debug(String.format("User %s has called getRenderedContent()", user.getName()));

        Document doc = XWikiUtils.getDocument(this.xwikiApi, pageId, true);
        SyntaxFactory syntaxFactory = Utils.getComponent(SyntaxFactory.class);
        Syntax syntax = syntaxFactory.createSyntaxFromIdString(syntaxId);

        return doc.getRenderedContent(syntax);
    }
}