org.xwiki.rest.internal.resources.BaseSearchResult.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.rest.internal.resources.BaseSearchResult.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 org.xwiki.rest.internal.resources;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Formatter;
import java.util.List;

import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.rest.Relations;
import org.xwiki.rest.XWikiResource;
import org.xwiki.rest.internal.DomainObjectFactory;
import org.xwiki.rest.internal.Utils;
import org.xwiki.rest.model.jaxb.Link;
import org.xwiki.rest.model.jaxb.SearchResult;
import org.xwiki.rest.resources.attachments.AttachmentResource;
import org.xwiki.rest.resources.objects.ObjectResource;
import org.xwiki.rest.resources.pages.PageResource;
import org.xwiki.rest.resources.pages.PageTranslationResource;
import org.xwiki.rest.resources.spaces.SpaceResource;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.api.XWiki;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.plugin.lucene.LucenePlugin;
import com.xpn.xwiki.plugin.lucene.SearchResults;

/**
 * @version $Id: d9e4b42eea5e9a9637e84902ab8f88611493f666 $
 */
public class BaseSearchResult extends XWikiResource {
    protected static final String SEARCH_TEMPLATE_INFO = "q={keywords}(&scope={content|name|title|spaces|objects})*(&number={number})(&start={start})(&orderfield={fieldname}(&order={asc|desc}))(&prettynames={false|true})";

    protected static final String MULTIWIKI_QUERY_TEMPLATE_INFO = "q={lucenequery}(&number={number})(&start={start})(&orderfield={fieldname}(&order={asc|desc}))(&distinct=1)(&prettynames={false|true})(&wikis={wikis})(&classname={classname})";

    protected static final String QUERY_TEMPLATE_INFO = "q={query}(&type={xwql,hql,lucene})(&number={number})(&start={start})(&orderfield={fieldname}(&order={asc|desc}))(&distinct=1)(&prettynames={false|true})(&wikis={wikis})(&classname={classname})";

    protected static enum SearchScope {
        SPACES, NAME, CONTENT, TITLE, OBJECTS
    }

    protected static enum QueryType {
        XWQL, HQL, LUCENE
    }

    /**
     * Search for keyword in the given scopes. See {@link SearchScope} for more information.
     *
     * @param number number of results to be returned.
     * @param start 0-based start offset.
     * @param withPrettyNames true if the users are displayed with their full name
     */
    protected List<SearchResult> search(List<SearchScope> searchScopes, String keywords, String wikiName,
            String space, boolean hasProgrammingRights, int number, int start, boolean distinct, String orderField,
            String order, Boolean withPrettyNames)
            throws IllegalArgumentException, UriBuilderException, QueryException, XWikiException {
        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            Utils.getXWikiContext(componentManager).setDatabase(wikiName);

            List<SearchResult> result = new ArrayList<SearchResult>();

            result.addAll(searchPages(searchScopes, keywords, wikiName, space, hasProgrammingRights, number, start,
                    orderField, order, withPrettyNames));

            if (searchScopes.contains(SearchScope.SPACES)) {
                result.addAll(searchSpaces(keywords, wikiName, hasProgrammingRights, number, start));
            }

            if (searchScopes.contains(SearchScope.OBJECTS)) {
                result.addAll(searchObjects(keywords, wikiName, space, hasProgrammingRights, number, start,
                        orderField, order, withPrettyNames));
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Search for keyword in the given scopes. Limit the search only to Pages. Search for keyword
     *
     * @param keywords the string that will be used in a "like" XWQL clause.
     * @param number number of results to be returned.
     * @param start 0-based start offset.
     * @param orderField the field to be used to order the results.
     * @param order "asc" or "desc"
     * @return the results.
     */
    protected List<SearchResult> searchPages(List<SearchScope> searchScopes, String keywords, String wikiName,
            String space, boolean hasProgrammingRights, int number, int start, String orderField, String order,
            Boolean withPrettyNames)
            throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        XWiki xwikiApi = Utils.getXWikiApi(componentManager);

        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            List<SearchResult> result = new ArrayList<SearchResult>();

            if (keywords == null) {
                return result;
            }

            Formatter f = new Formatter();

            /*
             * If the order field is already one of the field hard coded in the base query, then do not add it to the
             * select clause.
             */
            String addColumn = "";
            if (!StringUtils.isBlank(orderField)) {
                addColumn = (orderField.equals("") || orderField.equals("fullName") || orderField.equals("name")
                        || orderField.equals("space")) ? "" : ", doc." + orderField;
            }

            if (space != null) {
                f.format("select distinct doc.fullName, doc.space, doc.name, doc.language");
                f.format(addColumn);
                f.format(" from XWikiDocument as doc where doc.space = :space and ( ");
            } else {
                f.format("select distinct doc.fullName, doc.space, doc.name, doc.language");
                f.format(addColumn);
                f.format(" from XWikiDocument as doc where ( ");
            }

            /* Look for scopes related to pages */
            int acceptedScopes = 0;
            for (int i = 0; i < searchScopes.size(); i++) {
                SearchScope scope = searchScopes.get(i);

                switch (scope) {
                case CONTENT:
                    f.format("upper(doc.content) like :keywords ");
                    acceptedScopes++;
                    break;
                case NAME:
                    f.format("upper(doc.fullName) like :keywords ");
                    acceptedScopes++;
                    break;
                case TITLE:
                    f.format("upper(doc.title) like :keywords ");
                    acceptedScopes++;
                    break;
                }

                if (i != searchScopes.size() - 1) {
                    f.format(" or ");
                }
            }

            /* If we don't find any scope related to pages then return empty results */
            if (acceptedScopes == 0) {
                return result;
            }

            /* Build the order clause. */
            String orderClause = null;
            if (StringUtils.isBlank(orderField)) {
                orderClause = "doc.fullName asc";
            } else {
                /* Check if the order parameter is a valid "asc" or "desc" string, otherwise use "asc" */
                if ("asc".equals(order) || "desc".equals(order)) {
                    orderClause = String.format("doc.%s %s", orderField, order);
                } else {
                    orderClause = String.format("doc.%s asc", orderField);
                }
            }

            /* Add some filters if the user doesn't have programming rights. */
            if (hasProgrammingRights) {
                f.format(") order by %s", orderClause);
            } else {
                f.format(
                        ") and doc.space<>'XWiki' and doc.space<>'Admin' and doc.space<>'Panels' and doc.name<>'WebPreferences' order by %s",
                        orderClause);
            }

            String query = f.toString();

            List<Object> queryResult = null;

            /* This is needed because if the :space placeholder is not in the query, setting it would cause an exception */
            if (space != null) {
                queryResult = queryManager.createQuery(query, Query.XWQL)
                        .bindValue("keywords", String.format("%%%s%%", keywords.toUpperCase()))
                        .bindValue("space", space).setLimit(number).setOffset(start).execute();
            } else {
                queryResult = queryManager.createQuery(query, Query.XWQL)
                        .bindValue("keywords", String.format("%%%s%%", keywords.toUpperCase())).setLimit(number)
                        .setOffset(start).execute();
            }

            for (Object object : queryResult) {
                Object[] fields = (Object[]) object;

                String spaceName = (String) fields[1];
                String pageName = (String) fields[2];
                String language = (String) fields[3];

                String pageId = Utils.getPageId(wikiName, spaceName, pageName);
                String pageFullName = Utils.getPageFullName(wikiName, spaceName, pageName);

                /* Check if the user has the right to see the found document */
                if (xwikiApi.hasAccessLevel("view", pageId)) {
                    Document doc = xwikiApi.getDocument(pageFullName);
                    String title = doc.getDisplayTitle();
                    SearchResult searchResult = objectFactory.createSearchResult();
                    searchResult.setType("page");
                    searchResult.setId(pageId);
                    searchResult.setPageFullName(pageFullName);
                    searchResult.setTitle(title);
                    searchResult.setWiki(wikiName);
                    searchResult.setSpace(spaceName);
                    searchResult.setPageName(pageName);
                    searchResult.setVersion(doc.getVersion());
                    searchResult.setAuthor(doc.getAuthor());
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(doc.getDate());
                    searchResult.setModified(calendar);

                    if (withPrettyNames) {
                        searchResult.setAuthorName(Utils.getAuthorName(doc.getAuthor(), componentManager));
                    }

                    String pageUri = null;
                    try {
                        if (StringUtils.isBlank(language)) {
                            pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri()).path(PageResource.class)
                                    .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                            URLEncoder.encode(spaceName, "UTF-8"),
                                            URLEncoder.encode(pageName, "UTF-8"))
                                    .toString();
                        } else {
                            searchResult.setLanguage(language);
                            pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri())
                                    .path(PageTranslationResource.class)
                                    .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                            URLEncoder.encode(spaceName, "UTF-8"),
                                            URLEncoder.encode(pageName, "UTF-8"), language)
                                    .toString();
                        }
                    } catch (UnsupportedEncodingException ex) {
                        // This should never happen, UTF-8 is always valid.
                    }

                    Link pageLink = new Link();
                    pageLink.setHref(pageUri);
                    pageLink.setRel(Relations.PAGE);
                    searchResult.getLinks().add(pageLink);

                    result.add(searchResult);
                }
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Search for keyword in the given scopes. Limit the search only to spaces.
     *
     * @param keywords the string that will be used in a "like" XWQL clause
     * @param number number of results to be returned
     * @param start 0-based start offset
     * @return the results.
     */
    protected List<SearchResult> searchSpaces(String keywords, String wikiName, boolean hasProgrammingRights,
            int number, int start)
            throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        XWiki xwikiApi = Utils.getXWikiApi(componentManager);

        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            List<SearchResult> result = new ArrayList<SearchResult>();

            if (keywords == null) {
                return result;
            }

            Formatter f = new Formatter();

            f.format("select distinct doc.space from XWikiDocument as doc where upper(doc.space) like :keywords ");

            /* Add some filters if the user doesn't have programming rights. */
            if (hasProgrammingRights) {
                f.format(" order by doc.space asc");
            } else {
                f.format(
                        " and doc.space<>'XWiki' and doc.space<>'Admin' and doc.space<>'Panels' order by doc.space asc");
            }

            String query = f.toString();

            List<Object> queryResult = null;
            queryResult = queryManager.createQuery(query, Query.XWQL)
                    .bindValue("keywords", String.format("%%%s%%", keywords.toUpperCase())).setLimit(number)
                    .setOffset(start).execute();

            for (Object object : queryResult) {
                String spaceName = (String) object;
                Document spaceDoc = xwikiApi.getDocument(String.format("%s.WebHome", spaceName));

                /* Check if the user has the right to see the found document */
                if (xwikiApi.hasAccessLevel("view", spaceDoc.getPrefixedFullName())) {
                    String title = spaceDoc.getDisplayTitle();

                    SearchResult searchResult = objectFactory.createSearchResult();
                    searchResult.setType("space");
                    searchResult.setId(String.format("%s:%s", wikiName, spaceName));
                    searchResult.setWiki(wikiName);
                    searchResult.setSpace(spaceName);
                    searchResult.setTitle(title);

                    /* Add a link to the space information */
                    Link spaceLink = new Link();
                    spaceLink.setRel(Relations.SPACE);
                    String spaceUri = UriBuilder.fromUri(uriInfo.getBaseUri()).path(SpaceResource.class)
                            .build(wikiName, spaceName).toString();
                    spaceLink.setHref(spaceUri);
                    searchResult.getLinks().add(spaceLink);

                    /* Add a link to the webhome if it exists */
                    String webHomePageId = Utils.getPageId(wikiName, spaceName, "WebHome");
                    if (xwikiApi.exists(webHomePageId) && xwikiApi.hasAccessLevel("view", webHomePageId)) {
                        String pageUri = UriBuilder.fromUri(uriInfo.getBaseUri()).path(PageResource.class)
                                .build(wikiName, spaceName, "WebHome").toString();

                        Link pageLink = new Link();
                        pageLink.setHref(pageUri);
                        pageLink.setRel(Relations.HOME);
                        searchResult.getLinks().add(pageLink);
                    }

                    result.add(searchResult);
                }
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Search for keyword in the given scopes. Limit the search only to Objects.
     *
     * @param number number of results to be returned
     * @param start 0-based start offset
     * @param orderField the field to be used to order the results
     * @param order "asc" or "desc"
     * @return the results
     */
    protected List<SearchResult> searchObjects(String keywords, String wikiName, String space,
            boolean hasProgrammingRights, int number, int start, String orderField, String order,
            Boolean withPrettyNames)
            throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        XWikiContext xwikiContext = Utils.getXWikiContext(componentManager);

        XWiki xwikiApi = Utils.getXWikiApi(componentManager);

        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            List<SearchResult> result = new ArrayList<SearchResult>();

            if (keywords == null) {
                return result;
            }

            Formatter f = new Formatter();

            /*
             * If the order field is already one of the field hard coded in the base query, then do not add it to the
             * select clause.
             */
            String addColumn = (orderField.equals("") || orderField.equals("fullName") || orderField.equals("name")
                    || orderField.equals("space")) ? "" : ", doc." + orderField;

            if (space != null) {
                f.format("select distinct doc.fullName, doc.space, doc.name, obj.className, obj.number");
                f.format(addColumn);
                f.format(
                        " from XWikiDocument as doc, BaseObject as obj, StringProperty as sp, LargeStringProperty as lsp where doc.space = :space and obj.name=doc.fullName and sp.id.id = obj.id and lsp.id.id = obj.id and (upper(sp.value) like :keywords or upper(lsp.value) like :keywords) ");
            } else {
                f.format("select distinct doc.fullName, doc.space, doc.name, obj.className, obj.number");
                f.format(addColumn);
                f.format(
                        " from XWikiDocument as doc, BaseObject as obj, StringProperty as sp, LargeStringProperty as lsp where obj.name=doc.fullName and sp.id.id = obj.id and lsp.id.id = obj.id and (upper(sp.value) like :keywords or upper(lsp.value) like :keywords) ");
            }

            /* Build the order clause. */
            String orderClause = null;
            if (StringUtils.isBlank(orderField)) {
                orderClause = "doc.fullName asc";
            } else {
                /* Check if the order parameter is a valid "asc" or "desc" string, otherwise use "asc" */
                if ("asc".equals(order) || "desc".equals(order)) {
                    orderClause = String.format("doc.%s %s", orderField, order);
                } else {
                    orderClause = String.format("doc.%s asc", orderField);
                }
            }

            /* Add some filters if the user doesn't have programming rights. */
            if (hasProgrammingRights) {
                f.format(" order by %s", orderClause);
            } else {
                f.format(
                        " and doc.space<>'XWiki' and doc.space<>'Admin' and doc.space<>'Panels' and doc.name<>'WebPreferences' order by %s",
                        orderClause);
            }

            String query = f.toString();

            List<Object> queryResult = null;

            /* This is needed because if the :space placeholder is not in the query, setting it would cause an exception */
            if (space != null) {
                queryResult = queryManager.createQuery(query, Query.XWQL)
                        .bindValue("keywords", String.format("%%%s%%", keywords.toUpperCase()))
                        .bindValue("space", space).setLimit(number).execute();
            } else {
                queryResult = queryManager.createQuery(query, Query.XWQL)
                        .bindValue("keywords", String.format("%%%s%%", keywords.toUpperCase())).setLimit(number)
                        .execute();
            }

            /* Build the result. */
            for (Object object : queryResult) {
                Object[] fields = (Object[]) object;

                String spaceName = (String) fields[1];
                String pageName = (String) fields[2];
                String className = (String) fields[3];
                int objectNumber = (Integer) fields[4];

                String id = Utils.getObjectId(wikiName, spaceName, pageName, className, objectNumber);

                String pageId = Utils.getPageId(wikiName, spaceName, pageName);
                String pageFullName = Utils.getPageFullName(wikiName, spaceName, pageName);

                /*
                 * Check if the user has the right to see the found document. We also prevent guest users to access
                 * object data in order to avoid leaking important information such as emails to crawlers.
                 */
                if (xwikiApi.hasAccessLevel("view", pageId) && xwikiContext.getUserReference() != null) {
                    Document doc = xwikiApi.getDocument(pageFullName);
                    String title = doc.getDisplayTitle();
                    SearchResult searchResult = objectFactory.createSearchResult();
                    searchResult.setType("object");
                    searchResult.setId(id);
                    searchResult.setPageFullName(pageFullName);
                    searchResult.setTitle(title);
                    searchResult.setWiki(wikiName);
                    searchResult.setSpace(spaceName);
                    searchResult.setPageName(pageName);
                    searchResult.setVersion(doc.getVersion());
                    searchResult.setClassName(className);
                    searchResult.setObjectNumber(objectNumber);
                    searchResult.setAuthor(doc.getAuthor());
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(doc.getDate());
                    searchResult.setModified(calendar);

                    if (withPrettyNames) {
                        searchResult.setAuthorName(Utils.getAuthorName(doc.getAuthor(), componentManager));
                    }

                    String pageUri = UriBuilder.fromUri(uriInfo.getBaseUri()).path(PageResource.class)
                            .build(wikiName, spaceName, pageName).toString();
                    Link pageLink = new Link();
                    pageLink.setHref(pageUri);
                    pageLink.setRel(Relations.PAGE);
                    searchResult.getLinks().add(pageLink);

                    String objectUri = UriBuilder.fromUri(uriInfo.getBaseUri()).path(ObjectResource.class)
                            .build(wikiName, spaceName, pageName, className, objectNumber).toString();
                    Link objectLink = new Link();
                    objectLink.setHref(objectUri);
                    objectLink.setRel(Relations.OBJECT);
                    searchResult.getLinks().add(objectLink);

                    result.add(searchResult);
                }
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Search for query using xwql, hql, lucene. Limit the search only to Pages. Search for keyword
     *
     * @param query the query to be executed
     * @param queryTypeString can be "xwql", "hql" or "lucene".
     * @param orderField the field to be used to order the results.
     * @param order "asc" or "desc"
     * @param number number of results to be returned
     * @param start 0-based start offset.
     * @return a list of {@link SearchResult} objects containing the found items, or an empty list if the specified
     *         query type string doesn't represent a supported query type.
     */
    protected List<SearchResult> searchQuery(String query, String queryTypeString, String wikiName, String wikis,
            boolean hasProgrammingRights, String orderField, String order, boolean distinct, int number, int start,
            Boolean withPrettyNames, String className)
            throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            Utils.getXWikiContext(componentManager).setDatabase(wikiName);

            List<SearchResult> result = new ArrayList<SearchResult>();

            QueryType queryType = parseQueryType(queryTypeString);

            /* Add results only if the specified query type string corresponds to one of the supported query types. */
            if (queryType != null) {
                switch (queryType) {
                case LUCENE:
                    result.addAll(searchLucene(query, wikiName, wikis, hasProgrammingRights, orderField, order,
                            number, start, withPrettyNames));
                    break;
                case XWQL:
                    result.addAll(searchDatabaseQuery(query, "xwql", wikiName, hasProgrammingRights, distinct,
                            number, start, withPrettyNames, className));
                    break;
                case HQL:
                    result.addAll(searchDatabaseQuery(query, "hql", wikiName, hasProgrammingRights, distinct,
                            number, start, withPrettyNames, className));
                    break;
                }
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Execute a database query using a supported query language. Limit search to documents.
     *
     * @param number number of results to be returned
     * @param start 0-based start offset
     * @param withPrettyNames Add the pretty names for users
     * @param className Add object of type className
     * @return list of results
     */
    protected List<SearchResult> searchDatabaseQuery(String query, String queryLanguage, String wikiName,
            boolean hasProgrammingRights, boolean distinct, int number, int start, Boolean withPrettyNames,
            String className) throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        XWiki xwikiApi = Utils.getXWikiApi(componentManager);
        XWikiContext xwikiContext = Utils.getXWikiContext(componentManager);

        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            List<SearchResult> result = new ArrayList<SearchResult>();

            if (query == null || query.trim().startsWith("select")) {
                return result;
            }

            Formatter f = new Formatter();
            if (distinct) {
                f.format(
                        "select distinct doc.fullName, doc.space, doc.name, doc.language from XWikiDocument as doc %s",
                        query);
            } else {
                f.format("select doc.fullName, doc.space, doc.name, doc.language from XWikiDocument as doc %s",
                        query);
            }
            String squery = f.toString();

            if (!hasProgrammingRights) {
                squery.replace("where ",
                        "where doc.space<>'XWiki' and doc.space<>'Admin' and doc.space<>'Panels' and doc.name<>'WebPreferences' and ");
            }

            List<Object> queryResult = null;

            queryResult = queryManager.createQuery(squery, queryLanguage).setLimit(number).setOffset(start)
                    .execute();

            /* Build the result. */
            for (Object object : queryResult) {
                Object[] fields = (Object[]) object;

                String spaceName = (String) fields[1];
                String pageName = (String) fields[2];
                String language = (String) fields[3];

                String pageId = Utils.getPageId(wikiName, spaceName, pageName);
                String pageFullName = Utils.getPageFullName(wikiName, spaceName, pageName);

                /* Check if the user has the right to see the found document */
                if (xwikiApi.hasAccessLevel("view", pageId)) {
                    Document doc = xwikiApi.getDocument(pageFullName);
                    String title = doc.getDisplayTitle();

                    SearchResult searchResult = objectFactory.createSearchResult();
                    searchResult.setType("page");
                    searchResult.setId(pageId);
                    searchResult.setPageFullName(pageFullName);
                    searchResult.setTitle(title);
                    searchResult.setWiki(wikiName);
                    searchResult.setSpace(spaceName);
                    searchResult.setPageName(pageName);
                    searchResult.setVersion(doc.getVersion());
                    searchResult.setAuthor(doc.getAuthor());
                    Calendar calendar = Calendar.getInstance();
                    calendar.setTime(doc.getDate());
                    searchResult.setModified(calendar);

                    if (withPrettyNames) {
                        searchResult.setAuthorName(Utils.getAuthorName(doc.getAuthor(), componentManager));
                    }

                    /*
                     * Avoid to return object information if the user is not authenticated. This will prevent crawlers
                     * to retrieve information such as email addresses and passwords from user's profiles.
                     */
                    if (className != null && !className.equals("") && xwikiContext.getUserReference() != null) {
                        BaseObject baseObject = Utils.getBaseObject(doc, className, 0, componentManager);
                        if (baseObject != null) {
                            searchResult.setObject(DomainObjectFactory.createObject(objectFactory,
                                    uriInfo.getBaseUri(), xwikiContext, doc, baseObject, false, xwikiApi, false));
                        }
                    }

                    String pageUri = null;
                    try {
                        if (StringUtils.isBlank(language)) {
                            pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri()).path(PageResource.class)
                                    .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                            URLEncoder.encode(spaceName, "UTF-8"),
                                            URLEncoder.encode(pageName, "UTF-8"))
                                    .toString();
                        } else {
                            searchResult.setLanguage(language);
                            pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri())
                                    .path(PageTranslationResource.class)
                                    .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                            URLEncoder.encode(spaceName, "UTF-8"),
                                            URLEncoder.encode(pageName, "UTF-8"), language)
                                    .toString();
                        }
                    } catch (UnsupportedEncodingException ex) {
                        // This should never happen, UTF-8 is always valid.
                    }

                    Link pageLink = new Link();
                    pageLink.setHref(pageUri);
                    pageLink.setRel(Relations.PAGE);
                    searchResult.getLinks().add(pageLink);

                    result.add(searchResult);
                }
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Search for keywords using lucene search. It returns results for pages and attachments. The query can be executed
     * on multiple wikis if the wikis parameter is specified. Otherwise it's run only on the wiki specified by
     * defaultWikiName. wikis and defaultWikiName parameters cannot be both null.
     *
     * @param defaultWikiName the name of the wiki to run the query on (can be null)
     * @param wikis the name of the wikis to run the query on (can be null). This takes precedence if defaultWikiName is
     * specified as well.
     * @param number the number of results to be returned. If it's -1 then the first 20 results are returned.
     * @param start 0-based start offset
     */
    protected List<SearchResult> searchLucene(String query, String defaultWikiName, String wikis,
            boolean hasProgrammingRights, String orderField, String order, int number, int start,
            Boolean withPrettyNames)
            throws QueryException, IllegalArgumentException, UriBuilderException, XWikiException {
        XWiki xwikiApi = Utils.getXWikiApi(componentManager);

        String database = Utils.getXWikiContext(componentManager).getDatabase();

        /* This try is just needed for executing the finally clause. */
        try {
            List<SearchResult> result = new ArrayList<SearchResult>();

            if (query == null) {
                return result;
            }

            /*
             * One of the two must be non-null. If default wiki name is non-null and wikis is null, then it's a local
             * search in a specific wiki. If wiki name is null and wikis is non-null it's a global query on different
             * wikis. If both of them are non-null then the wikis parameter takes the precedence.
             */
            if (defaultWikiName == null && wikis == null) {
                return result;
            }

            if (!hasProgrammingRights) {
                query += " AND NOT space:XWiki AND NOT space:Admin AND NOT space:Panels AND NOT name:WebPreferences";
            }

            try {
                XWikiContext context = Utils.getXWikiContext(componentManager);
                LucenePlugin lucene = (LucenePlugin) Utils.getXWiki(componentManager).getPlugin("lucene", context);

                /*
                 * Compute the parameter to be passed to the plugin for ordering: orderField (for ordering on orderField
                 * in ascending order) or -orderFiled (for descending order)
                 */
                String orderParameter = "";
                if (!StringUtils.isBlank(orderField)) {
                    if ("desc".equals(order)) {
                        orderParameter = String.format("-%s", orderField);
                    } else {
                        orderParameter = orderField;
                    }
                }

                SearchResults luceneSearchResults = lucene.getSearchResults(query, orderParameter,
                        (wikis == null) ? defaultWikiName : wikis, "", context);

                /*
                 * Return only the first 20 results otherwise specified. It also seems that Lucene indexing starts at 1
                 * (though starting from 0 works as well, and gives the samer results as if starting from 1). To keep
                 * things consistent we add 1 to the passed start value (which is always 0-based).
                 */
                List<com.xpn.xwiki.plugin.lucene.SearchResult> luceneResults = luceneSearchResults
                        .getResults(start + 1, (number == -1) ? 20 : number);

                /* Build the result. */
                for (com.xpn.xwiki.plugin.lucene.SearchResult luceneSearchResult : luceneResults) {
                    String wikiName = luceneSearchResult.getWiki();
                    String spaceName = luceneSearchResult.getSpace();
                    String pageName = luceneSearchResult.getName();
                    String pageFullName = Utils.getPageFullName(wikiName, spaceName, pageName);
                    String pageId = Utils.getPageId(wikiName, spaceName, pageName);

                    /* Check if the user has the right to see the found document */
                    if (xwikiApi.hasAccessLevel("view", pageId)) {
                        Document doc = xwikiApi.getDocument(pageId);
                        String title = doc.getDisplayTitle();

                        SearchResult searchResult = objectFactory.createSearchResult();

                        searchResult.setPageFullName(pageFullName);
                        searchResult.setTitle(title);
                        searchResult.setWiki(wikiName);
                        searchResult.setSpace(spaceName);
                        searchResult.setPageName(pageName);
                        searchResult.setVersion(doc.getVersion());

                        /*
                         * Check if the result is a page or an attachment, and fill the corresponding fields in the
                         * result accordingly.
                         */
                        if (luceneSearchResult.getType().equals(LucenePlugin.DOCTYPE_WIKIPAGE)) {
                            searchResult.setType("page");
                            searchResult.setId(Utils.getPageId(wikiName, spaceName, pageName));
                        } else {
                            searchResult.setType("file");
                            searchResult.setId(String.format("%s@%s", Utils.getPageId(wikiName, pageFullName),
                                    luceneSearchResult.getFilename()));
                            searchResult.setFilename(luceneSearchResult.getFilename());

                            String attachmentUri = UriBuilder.fromUri(this.uriInfo.getBaseUri())
                                    .path(AttachmentResource.class)
                                    .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                            URLEncoder.encode(spaceName, "UTF-8"),
                                            URLEncoder.encode(pageName, "UTF-8"),
                                            URLEncoder.encode(luceneSearchResult.getFilename(), "UTF-8"))
                                    .toString();

                            Link attachmentLink = new Link();
                            attachmentLink.setHref(attachmentUri);
                            attachmentLink.setRel(Relations.ATTACHMENT_DATA);
                            searchResult.getLinks().add(attachmentLink);
                        }

                        searchResult.setScore(luceneSearchResult.getScore());
                        searchResult.setAuthor(luceneSearchResult.getAuthor());
                        Calendar calendar = Calendar.getInstance();
                        calendar.setTime(doc.getDate());
                        searchResult.setModified(calendar);

                        if (withPrettyNames) {
                            searchResult.setAuthorName(
                                    Utils.getAuthorName(luceneSearchResult.getAuthor(), componentManager));
                        }

                        String language = luceneSearchResult.getLanguage();
                        if (language.equals("default")) {
                            language = "";
                        }

                        String pageUri = null;
                        try {
                            if (StringUtils.isBlank(language)) {
                                pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri()).path(PageResource.class)
                                        .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                                URLEncoder.encode(spaceName, "UTF-8"),
                                                URLEncoder.encode(pageName, "UTF-8"))
                                        .toString();
                            } else {
                                searchResult.setLanguage(language);
                                pageUri = UriBuilder.fromUri(this.uriInfo.getBaseUri())
                                        .path(PageTranslationResource.class)
                                        .buildFromEncoded(URLEncoder.encode(wikiName, "UTF-8"),
                                                URLEncoder.encode(spaceName, "UTF-8"),
                                                URLEncoder.encode(pageName, "UTF-8"), language)
                                        .toString();
                            }
                        } catch (UnsupportedEncodingException ex) {
                            // This should never happen, UTF-8 is always valid.
                        }

                        Link pageLink = new Link();
                        pageLink.setHref(pageUri);
                        pageLink.setRel(Relations.PAGE);
                        searchResult.getLinks().add(pageLink);

                        result.add(searchResult);
                    }
                }
            } catch (Exception e) {
                throw new XWikiException(XWikiException.MODULE_XWIKI, XWikiException.ERROR_XWIKI_UNKNOWN,
                        "Error performing lucene search", e);
            }

            return result;
        } finally {
            Utils.getXWikiContext(componentManager).setDatabase(database);
        }
    }

    /**
     * Return a list of {@link SearchScope} objects by parsing the strings provided in the search scope strings. If the
     * list doesn't contain any valid scope string, then CONTENT is added by default.
     *
     * @param searchScopeStrings The list of string to be parsed.
     * @return The list of the parsed SearchScope elements.
     */
    protected List<SearchScope> parseSearchScopeStrings(List<String> searchScopeStrings) {
        List<SearchScope> searchScopes = new ArrayList<SearchScope>();
        for (String searchScopeString : searchScopeStrings) {
            if (searchScopeString != null && !searchScopes.contains(searchScopeString)) {
                try {
                    SearchScope searchScope = SearchScope.valueOf(searchScopeString.toUpperCase());
                    searchScopes.add(searchScope);
                } catch (IllegalArgumentException e) {
                    // Ignore unrecognized scopes
                }
            }
        }

        if (searchScopes.isEmpty()) {
            searchScopes.add(SearchScope.CONTENT);
        }

        return searchScopes;
    }

    /**
     * Return the QueryType enum object corresponding to a string.
     *
     * @param queryTypeString a string representing a query type.
     * @return the query type enum object, or null if the passed string doesn't correspond to any of them.
     */
    protected QueryType parseQueryType(String queryTypeString) {
        try {
            if (queryTypeString != null) {
                return QueryType.valueOf(queryTypeString.toUpperCase());
            }
        } catch (IllegalArgumentException e) {
            // Invalid query type string.
        }

        return null;
    }
}