de.fme.alfresco.repo.jscript.PagedSearch.java Source code

Java tutorial

Introduction

Here is the source code for de.fme.alfresco.repo.jscript.PagedSearch.java

Source

/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco 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 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package de.fme.alfresco.repo.jscript;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.jscript.ScriptPagingNodes;
import org.alfresco.repo.jscript.Search;
import org.alfresco.repo.jscript.ValueConverter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;

/**
 * Paged Search component for use by the ScriptService. Better paging version of Search.java/search in javascript
 * <p>
 * Provides access to Lucene search facilities including saved search objects. The results
 * from a search are returned as an array (collection) of scriptable Node wrapper objects.
 * <p>
 * The object is added to the root of the model to provide syntax such as:
 * <code>var results = search.luceneSearch(statement);</code>
 * and
 * <code>var results = search.savedSearch(node);</code>
 * 
 * @author Rasmus Melgaard, Novem-IT
 */
public class PagedSearch extends Search {
    private static Log logger = LogFactory.getLog(PagedSearch.class);

    /**
     * Execute a query based on the supplied search definition object.
     * 
     * Search object is defined in JavaScript thus:
     * <pre>
     * search
     * {
     *    query: string,          mandatory, in appropriate format and encoded for the given language
     *    store: string,          optional, defaults to 'workspace://SpacesStore'
     *    language: string,       optional, one of: lucene, xpath, jcr-xpath, fts-alfresco - defaults to 'lucene'
     *    templates: [],          optional, Array of query language template objects (see below) - if supported by the language 
     *    sort: [],               optional, Array of sort column objects (see below) - if supported by the language
     *    page: object,           optional, paging information object (see below) - if supported by the language
     *    namespace: string,      optional, the default namespace for properties
     *    defaultField: string,   optional, the default field for query elements when not explicit in the query
     *    onerror: string         optional, result on error - one of: exception, no-results - defaults to 'exception'
     * }
     * 
     * sort
     * {
     *    column: string,         mandatory, sort column in appropriate format for the language
     *    ascending: boolean      optional, defaults to false
     * }
     * 
     * page
     * {
     *    maxItems: int,          optional, max number of items to return in result set
     *    pageSize: int,        optional, max number of items to return in PagingResult
     *    skipCount: int          optional, number of items to skip over before returning results
     * }
     * 
     * template
     * {
     *    field: string,          mandatory, custom field name for the template
     *    template: string        mandatory, query template replacement for the template
     * }
     * 
     * Note that only some query languages support custom query templates, such as 'fts-alfresco'. 
     * See the following documentation for more details:
     * {@link http://wiki.alfresco.com/wiki/Full_Text_Search_Query_Syntax#Templates}
     * </pre>
     * 
     * @param search    Search definition object as above
     * 
     * @return Array of ScriptNode results
     */
    public ScriptPagingNodes pagedQuery(Object search) {
        ScriptPagingNodes results = null;

        if (search instanceof Serializable) {
            Serializable obj = new ValueConverter().convertValueForRepo((Serializable) search);
            if (obj instanceof Map) {
                Map<Serializable, Serializable> def = (Map<Serializable, Serializable>) obj;

                // test for mandatory values
                String query = (String) def.get("query");
                if (query == null || query.length() == 0) {
                    throw new AlfrescoRuntimeException("Failed to search: Missing mandatory 'query' value.");
                }

                // collect optional values
                String store = (String) def.get("store");
                String language = (String) def.get("language");
                List<Map<Serializable, Serializable>> sort = (List<Map<Serializable, Serializable>>) def
                        .get("sort");
                Map<Serializable, Serializable> page = (Map<Serializable, Serializable>) def.get("page");
                String namespace = (String) def.get("namespace");
                String onerror = (String) def.get("onerror");
                String defaultField = (String) def.get("defaultField");

                // extract supplied values

                // sorting columns
                SortColumn[] sortColumns = null;
                if (sort != null) {
                    sortColumns = new SortColumn[sort.size()];
                    int index = 0;
                    for (Map<Serializable, Serializable> column : sort) {
                        String strCol = (String) column.get("column");
                        if (strCol == null || strCol.length() == 0) {
                            throw new AlfrescoRuntimeException(
                                    "Failed to search: Missing mandatory 'sort: column' value.");
                        }
                        Boolean boolAsc = (Boolean) column.get("ascending");
                        boolean ascending = (boolAsc != null ? boolAsc.booleanValue() : false);
                        sortColumns[index++] = new SortColumn(strCol, ascending);
                    }
                }

                // paging settings
                int maxResults = -1;
                int skipResults = 0;
                int pageSizes = -1;
                if (page != null) {
                    if (page.get("maxItems") != null) {
                        Object maxItems = page.get("maxItems");
                        if (maxItems instanceof Number) {
                            maxResults = ((Number) maxItems).intValue();
                        } else if (maxItems instanceof String) {
                            // try and convert to int (which it what it should be!)
                            maxResults = Integer.parseInt((String) maxItems);
                        }
                    }
                    if (page.get("skipCount") != null) {
                        Object skipCount = page.get("skipCount");
                        if (skipCount instanceof Number) {
                            skipResults = ((Number) page.get("skipCount")).intValue();
                        } else if (skipCount instanceof String) {
                            skipResults = Integer.parseInt((String) skipCount);
                        }
                    }
                    if (page.get("pageSize") != null) {
                        Object pageSize = page.get("pageSize");
                        if (pageSize instanceof Number) {
                            pageSizes = ((Number) page.get("pageSize")).intValue();
                        } else if (pageSize instanceof String) {
                            pageSizes = Integer.parseInt((String) pageSize);
                        }
                    }
                }

                // query templates
                Map<String, String> queryTemplates = null;
                List<Map<Serializable, Serializable>> templates = (List<Map<Serializable, Serializable>>) def
                        .get("templates");
                if (templates != null) {
                    queryTemplates = new HashMap<String, String>(templates.size(), 1.0f);

                    for (Map<Serializable, Serializable> template : templates) {
                        String field = (String) template.get("field");
                        if (field == null || field.length() == 0) {
                            throw new AlfrescoRuntimeException(
                                    "Failed to search: Missing mandatory 'template: field' value.");
                        }
                        String t = (String) template.get("template");
                        if (t == null || t.length() == 0) {
                            throw new AlfrescoRuntimeException(
                                    "Failed to search: Missing mandatory 'template: template' value.");
                        }
                        queryTemplates.put(field, t);
                    }
                }

                SearchParameters sp = new SearchParameters();
                sp.addStore(store != null ? new StoreRef(store) : this.storeRef);
                sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE);
                sp.setQuery(query);
                if (defaultField != null) {
                    sp.setDefaultFieldName(defaultField);
                }
                if (namespace != null) {
                    sp.setNamespace(namespace);
                }
                if (maxResults > 0) {
                    sp.setLimit(maxResults);
                    sp.setLimitBy(LimitBy.FINAL_SIZE);
                }
                if (skipResults > 0) {
                    sp.setSkipCount(skipResults);
                }
                if (sort != null) {
                    for (SortColumn sd : sortColumns) {
                        sp.addSort(sd.column, sd.asc);
                    }
                }
                if (queryTemplates != null) {
                    for (String field : queryTemplates.keySet()) {
                        sp.addQueryTemplate(field, queryTemplates.get(field));
                    }
                }

                // error handling opions
                boolean exceptionOnError = true;
                if (onerror != null) {
                    if (onerror.equals("exception")) {
                        // default value, do nothing
                    } else if (onerror.equals("no-results")) {
                        exceptionOnError = false;
                    } else {
                        throw new AlfrescoRuntimeException(
                                "Failed to search: Unknown value supplied for 'onerror': " + onerror);
                    }
                }

                // execute search based on search definition
                results = pagedQuery(sp, pageSizes, exceptionOnError);
            }
        }

        if (results == null) {
            results = new ScriptPagingNodes(Context.getCurrentContext().newArray(getScope(), new Object[0]), false,
                    0, 0);
        }

        return results;
    }

    /**
     * Execute the query
     * 
     * Removes any duplicates that may be present (ID search can cause duplicates -
     * it is better to remove them here)
     * 
     * @param store         StoreRef to search against - null for default configured store
     * @param search        Lucene search to execute
     * @param sort          Columns to sort by
     * @param language      Search language to use e.g. SearchService.LANGUAGE_LUCENE
     * @param maxResults    Maximum results to return if > 0
     * @param skipResults   Results to skip in the result set
     * 
     * @return Array of Node objects
     */
    protected ScriptPagingNodes pagedQuery(String store, String search, SortColumn[] sort, String language,
            int maxResults, int skipResults, int pageSize) {
        SearchParameters sp = new SearchParameters();
        sp.addStore(store != null ? new StoreRef(store) : this.storeRef);
        sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE);
        sp.setQuery(search);
        if (maxResults > 0) {
            sp.setLimit(maxResults);
            sp.setLimitBy(LimitBy.FINAL_SIZE);
        }
        if (skipResults > 0) {
            sp.setSkipCount(skipResults);
        }
        if (sort != null) {
            for (SortColumn sd : sort) {
                sp.addSort(sd.column, sd.asc);
            }
        }

        return pagedQuery(sp, pageSize, true);
    }

    /**
     * Execute the query and only count the rpotential results
     * 
     * Removes any duplicates that may be present (ID search can cause duplicates -
     * it is better to remove them here)
     * 
     * @param store         StoreRef to search against - null for default configured store
     * @param search        Lucene search to execute
     * @param sort          Columns to sort by
     * @param language      Search language to use e.g. SearchService.LANGUAGE_LUCENE
     * @param maxResults    Maximum results to return if > 0
     * @param skipResults   Results to skip in the result set
     * 
     * @return Array of Node objects
     */
    public ScriptPagingNodes countQuery(String store, String search, String language) {
        SearchParameters sp = new SearchParameters();
        sp.addStore(store != null ? new StoreRef(store) : this.storeRef);
        sp.setLanguage(language != null ? language : SearchService.LANGUAGE_LUCENE);
        sp.setQuery(search);

        sp.setMaxItems(0); //do count
        sp.setLimitBy(LimitBy.UNLIMITED); //all

        return pagedQuery(sp, 50, true);
    }

    /**
     * Execute the query
     * 
     * Removes any duplicates that may be present (ID search can cause duplicates -
     * it is better to remove them here)
     * 
     * @param sp                SearchParameters describing the search to execute.
     * @param exceptionOnError  True to throw a runtime exception on error, false to return empty resultset
     * 
     * @return Array of Node objects
     */
    protected ScriptPagingNodes pagedQuery(SearchParameters sp, int pageSize, boolean exceptionOnError) {
        Collection<ScriptNode> set = null;

        if (logger.isDebugEnabled())
            logger.debug("query=" + sp.getQuery() + " limit="
                    + (sp.getLimitBy() != LimitBy.UNLIMITED ? sp.getLimit() : "none") + " skipCount="
                    + sp.getSkipCount() + " pageSize=" + pageSize + " maxItems=" + sp.getMaxItems());
        long length = 0;
        // perform the search against the repo
        ResultSet results = null;
        try {
            results = this.services.getSearchService().query(sp);

            if (results.length() != 0) {
                length = results.length();
                NodeService nodeService = this.services.getNodeService();
                int page = 0 < pageSize ? pageSize : results.length();
                set = new LinkedHashSet<ScriptNode>(page, 1.0f);
                for (ResultSetRow row : results) {
                    NodeRef nodeRef = row.getNodeRef();
                    if (nodeService.exists(nodeRef)) {
                        set.add(new ScriptNode(nodeRef, this.services, getScope()));
                        page--;
                        if (1 > page) {
                            break;
                        }
                    }
                }

            } else {
                length = results.getNumberFound();
            }
        } catch (Throwable err) {
            if (exceptionOnError) {
                throw new AlfrescoRuntimeException("Failed to execute search: " + sp.getQuery(), err);
            } else if (logger.isDebugEnabled()) {
                logger.debug("Failed to execute search: " + sp.getQuery(), err);
            }
        } finally {
            if (results != null) {
                results.close();
            }
        }
        int fullLength = Integer.MAX_VALUE < length ? Integer.MAX_VALUE : (int) length; // + sp.getSkipCount();

        if (null == set) {
            return new ScriptPagingNodes(Context.getCurrentContext().newArray(getScope(), new Object[0]),
                    Boolean.FALSE, fullLength, fullLength);
        } else {

            if (logger.isDebugEnabled())
                logger.debug("result length=" + length + " fullLength=" + fullLength + " skipCount="
                        + sp.getSkipCount() + " set.size()=" + set.size());

            return new ScriptPagingNodes(
                    Context.getCurrentContext().newArray(getScope(), set.toArray(new Object[(set.size())])),
                    length > set.size(), fullLength, fullLength);
        }

    }

}