com.concursive.connect.web.modules.search.utils.SearchUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.concursive.connect.web.modules.search.utils.SearchUtils.java

Source

/*
 * ConcourseConnect
 * Copyright 2009 Concursive Corporation
 * http://www.concursive.com
 *
 * This file is part of ConcourseConnect, an open source social business
 * software and community platform.
 *
 * Concursive ConcourseConnect is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, version 3 of the License.
 *
 * Under the terms of the GNU Affero General Public License you must release the
 * complete source code for any application that uses any part of ConcourseConnect
 * (system header files and libraries used by the operating system are excluded).
 * These terms must be included in any work that has ConcourseConnect components.
 * If you are developing and distributing open source applications under the
 * GNU Affero General Public License, then you are free to use ConcourseConnect
 * under the GNU Affero General Public License.
 *
 * If you are deploying a web site in which users interact with any portion of
 * ConcourseConnect over a network, the complete source code changes must be made
 * available.  For example, include a link to the source archive directly from
 * your web site.
 *
 * For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
 * products, and do not license and distribute their source code under the GNU
 * Affero General Public License, Concursive provides a flexible commercial
 * license.
 *
 * To anyone in doubt, we recommend the commercial license. Our commercial license
 * is competitively priced and will eliminate any confusion about how
 * ConcourseConnect can be used and distributed.
 *
 * ConcourseConnect 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 Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ConcourseConnect.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Attribution Notice: ConcourseConnect is an Original Work of software created
 * by Concursive Corporation
 */
package com.concursive.connect.web.modules.search.utils;

import com.concursive.commons.text.StringUtils;
import com.concursive.connect.indexer.IIndexerSearch;
import com.concursive.connect.indexer.IndexerFactory;
import com.concursive.connect.web.modules.common.social.geotagging.utils.LocationBean;
import com.concursive.connect.web.modules.common.social.geotagging.utils.LocationUtils;
import com.concursive.connect.web.modules.search.beans.SearchBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * Utilities to work with search strings and Lucene queries
 *
 * @author matt rajkowski
 * @created June 11, 2004
 */
public class SearchUtils {

    private static Log LOG = LogFactory.getLog(SearchUtils.class);
    private final static String DOUBLE_QUOTE = "\"";
    private final static String WHITESPACE_AND_QUOTES = " \t\r\n\"";
    private final static String QUOTES_ONLY = "\"";

    /**
     * Extracts the keywords into tokens, and then either concats them with AND
     * if all words are required, or leaves the tokens alone
     *
     * @param searchText Description of the Parameter
     * @param allWords   Description of the Parameter
     * @return Description of the Return Value
     */
    public static String parseSearchText(String searchText, boolean allWords) {
        StringBuffer sb = new StringBuffer();
        boolean returnTokens = true;
        String currentDelims = WHITESPACE_AND_QUOTES;
        StringTokenizer parser = new StringTokenizer(searchText, currentDelims, returnTokens);
        String token = null;
        boolean spacer = false;
        while (parser.hasMoreTokens()) {
            token = parser.nextToken(currentDelims);
            if (!isDoubleQuote(token)) {
                if (hasText(token)) {
                    String gotToken = token.trim().toLowerCase();
                    if ("and".equals(gotToken) || "or".equals(gotToken) || "not".equals(gotToken)) {
                        if (sb.length() > 0) {
                            sb.append(" ");
                        }
                        sb.append(gotToken.toUpperCase());
                        spacer = true;
                    } else {
                        if (spacer) {
                            if (sb.length() > 0) {
                                sb.append(" ");
                            }
                            spacer = false;
                        } else {
                            if (sb.length() > 0) {
                                if (allWords) {
                                    sb.append(" AND ");
                                } else {
                                    sb.append(" ");
                                }
                            }
                        }
                        if (gotToken.indexOf(" ") > -1) {
                            sb.append("\"").append(gotToken).append("\"");
                        } else {
                            sb.append(gotToken);
                        }
                    }
                }
            } else {
                currentDelims = flipDelimiters(currentDelims);
            }
        }
        return sb.toString();
    }

    /**
     * Description of the Method
     *
     * @param searchText Description of the Parameter
     * @return Description of the Return Value
     */
    public static ArrayList<String> parseSearchTerms(String searchText) {
        ArrayList<String> terms = new ArrayList<String>();
        StringBuffer sb = new StringBuffer();
        boolean returnTokens = true;
        String currentDelims = WHITESPACE_AND_QUOTES;
        StringTokenizer parser = new StringTokenizer(searchText, currentDelims, returnTokens);
        String token = null;
        while (parser.hasMoreTokens()) {
            token = parser.nextToken(currentDelims);
            if (!isDoubleQuote(token)) {
                if (hasText(token)) {
                    String gotToken = token.trim().toLowerCase();
                    if ("and".equals(gotToken) || "or".equals(gotToken) || "not".equals(gotToken)) {
                    } else {
                        if (sb.length() > 0) {
                            sb.append(" ");
                        }
                        sb.append(gotToken);
                        terms.add(sb.toString());
                        sb.setLength(0);
                    }
                }
            } else {
                currentDelims = flipDelimiters(currentDelims);
            }
        }
        return terms;
    }

    /**
     * Description of the Method
     *
     * @param text Description of the Parameter
     * @return Description of the Return Value
     */
    private static boolean hasText(String text) {
        return (text != null && !text.trim().equals(""));
    }

    /**
     * Gets the doubleQuote attribute of the SearchUtils object
     *
     * @param text Description of the Parameter
     * @return The doubleQuote value
     */
    private static boolean isDoubleQuote(String text) {
        return text.equals(DOUBLE_QUOTE);
    }

    /**
     * Description of the Method
     *
     * @param delims Description of the Parameter
     * @return Description of the Return Value
     */
    private static String flipDelimiters(String delims) {
        String result = null;
        if (delims.equals(WHITESPACE_AND_QUOTES)) {
            result = QUOTES_ONLY;
        } else {
            result = WHITESPACE_AND_QUOTES;
        }
        return result;
    }

    public static synchronized IIndexerSearch retrieveSearcher(int indexType) throws IOException {
        // Get the shared searcher
        return IndexerFactory.getInstance().getIndexerService().getIndexerSearch(indexType);
    }

    /**
     * Generates the Lucene Query for finding public listings, user-public listings, and
     * user-member listings
     *
     * @param search
     * @param userId
     * @return
     */
    public static String generateProjectQueryString(SearchBean search, int userId, int instanceId,
            String projectList) {
        LOG.info("Search Query: " + search.getQuery() + " (" + search.getLocation() + ")");
        // The search portal is being used
        String locationTerm = search.getParsedLocation();

        // if search location is 5-digit number, then find the city and state to expand the search,
        // but boost the zip code
        if (StringUtils.hasText(locationTerm)) {
            if (StringUtils.isNumber(locationTerm) && locationTerm.length() == 5) {
                LocationBean location = LocationUtils.findLocationByZipCode(locationTerm);
                if (location != null) {
                    locationTerm = "\"" + locationTerm + "\"^30 OR (\"" + location.getCity() + "\"^29 AND "
                            + location.getState() + "^28)";
                }
            } else {
                locationTerm = "\"" + locationTerm + "\"^30";
            }
        }

        // Optimize the terms
        StringBuffer titleValues = new StringBuffer();
        StringBuffer keywordValues = new StringBuffer();
        StringBuffer termValues = new StringBuffer();

        // Find the phrase as-is in quotes for exactness, in the title
        titleValues.append("\"").append(search.getParsedQuery()).append("\"^25");

        // Find matches in the keywords
        keywordValues.append("\"").append(search.getParsedQuery()).append("\"^24");

        // Exact description match
        termValues.append("\"").append(search.getParsedQuery()).append("\"^16");

        // Find the words in the phrase
        ArrayList<String> terms = search.getTerms();

        int titleCount = 23;
        int keywordCount = 19;
        int count = 15;

        for (String term : terms) {
            if (titleValues.length() > 0) {
                titleValues.append(" OR ");
            }
            if (keywordValues.length() > 0) {
                keywordValues.append(" OR ");
            }
            if (termValues.length() > 0) {
                termValues.append(" OR ");
            }
            if (count < 5) {
                count = 5;
            }
            titleValues.append(term).append("^").append(titleCount);
            --titleCount;
            keywordValues.append(term).append("^").append(count);
            --keywordCount;
            termValues.append(term).append("^").append(count);
            --count;
        }
        // Find wildcards for the words in the phrase
        count = 4;
        for (String term : terms) {
            if (termValues.length() > 0) {
                termValues.append(" OR ");
            }
            if (count < 1) {
                count = 1;
            }
            termValues.append(term).append("*").append("^").append(count);
            --count;
        }

        String thisQuery = "(approved:1) " + (instanceId > -1 ? "AND (instanceId:" + instanceId + ") " : "")
                + "AND (" + "(guests:1) " + (userId > 0 ? "OR (participants:1) " : "")
                + (StringUtils.hasText(projectList) ? "OR " + "(projectId:(" + projectList + ")) " : "") + ") "
                + "AND (closed:0) " + "AND (website:0) "
                + (search.getProjectId() > -1 ? "AND (projectId:" + search.getProjectId() + ") " : "")
                + (StringUtils.hasText(search.getQuery())
                        ? "AND (title:(" + titleValues.toString() + ") OR (keywords:(" + keywordValues.toString()
                                + ")) OR (" + termValues.toString() + ")) "
                        : "")
                + (StringUtils.hasText(locationTerm) ? "AND (location:(" + locationTerm + ")) " : "");

        LOG.debug("Built Query: " + thisQuery);

        return thisQuery;
    }

    /**
     * Generates the Lucene Query for finding public data, user-public data, and
     * user-member data
     *
     * @param search
     * @param userId
     * @param instanceId
     * @param projectListings
     * @return
     * @throws SQLException
     */
    public static String generateDataQueryString(SearchBean search, int userId, int instanceId,
            String projectListings) throws SQLException {
        // Generate the string
        return (StringUtils.hasText(search.getQuery()) ? "(" + search.getParsedQuery() + ") AND " : "")
                + (instanceId > -1 ? "(instanceId:" + instanceId + ") AND " : "") + "(" + "("
                + "((guests:1) AND (membership:0)) "
                + (userId > 0 ? " OR ((participants:1) AND (membership:0)) " : "") + ") "
                + (StringUtils.hasText(projectListings) ? "OR " + "(projectId:(" + projectListings + "))" : "")
                + ") ";
    }

    public static String generateValidProjects(Connection db, int userId, int specificProjectId)
            throws SQLException {
        return generateValidProjects(db, userId, specificProjectId, -1);
    }

    /**
     * Generates a list of projects that the user has access to
     *
     * @param db
     * @param userId
     * @param specificProjectId
     * @param specificCategoryId
     * @return
     * @throws SQLException
     */
    public static String generateValidProjects(Connection db, int userId, int specificProjectId,
            int specificCategoryId) throws SQLException {
        if (userId < 1) {
            return "";
        }
        // @todo get ids from user cache
        // @update cache everytime a user is added or removed from a project team
        // get the projects for the user
        // get the project permissions for each project
        // if user has access to the data, then add to query
        StringBuffer projectList = new StringBuffer();
        PreparedStatement pst = db
                .prepareStatement("SELECT project_id " + "FROM project_team " + "WHERE user_id = ? "
                        + "AND status IS NULL " + (specificProjectId > -1 ? "AND project_id = ? " : "")
                        + (specificCategoryId > -1
                                ? "AND project_id IN (SELECT project_id FROM projects WHERE category_id = ?) "
                                : ""));
        int i = 0;
        pst.setInt(++i, userId);
        if (specificProjectId > -1) {
            pst.setInt(++i, specificProjectId);
        }
        if (specificCategoryId > -1) {
            pst.setInt(++i, specificCategoryId);
        }
        ResultSet rs = pst.executeQuery();
        while (rs.next()) {
            int projectId = rs.getInt("project_id");
            // these projects override the lower access projects
            if (projectList.length() > 0) {
                projectList.append(" OR ");
            }
            projectList.append(projectId);
        }
        rs.close();
        pst.close();
        return projectList.toString();
    }
}