/* * 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.actions; import com.concursive.commons.text.StringUtils; import com.concursive.commons.web.mvc.actions.ActionContext; import com.concursive.connect.Constants; import com.concursive.connect.cms.portal.dao.DashboardPage; import com.concursive.connect.cms.portal.dao.DashboardTemplateList; import com.concursive.connect.cms.portal.utils.DashboardUtils; import com.concursive.connect.indexer.*; import com.concursive.connect.web.controller.actions.GenericAction; import com.concursive.connect.web.modules.profile.dao.PermissionList; import com.concursive.connect.web.modules.profile.dao.Project; import com.concursive.connect.web.modules.profile.dao.ProjectCategoryList; import com.concursive.connect.web.modules.profile.utils.ProjectUtils; import com.concursive.connect.web.modules.search.beans.SearchBean; import com.concursive.connect.web.modules.search.utils.SearchUtils; import com.concursive.connect.web.portal.PortletManager; import com.concursive.connect.web.utils.PagedListInfo; import javax.servlet.http.Cookie; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; /** * Actions for working with the search page * * @author matt rajkowski * @version $Id$ * @created May 27, 2004 */ public final class Search extends GenericAction { /** * This command will reindex all data * * @param context Description of the Parameter * @return Description of the Return Value */ public synchronized String executeCommandIndex(ActionContext context) { setMaximized(context); if (!getUser(context).getAccessAdmin()) { return "PermissionError"; } if (!hasMatchingFormToken(context)) { return "TokenError"; } Connection db = null; try { // Use the configured indexer IIndexerService index = IndexerFactory.getInstance().getIndexerService(); // Create an indexer context IndexerContext indexerContext = new IndexerContext(getApplicationPrefs(context)); indexerContext.setIndexType(Constants.INDEXER_FULL); // Retrieve a persistent database connection db = getConnection(context, true); // Reindex the whole database LOG.info("Reindexing the whole database"); index.reindexAllData(indexerContext, db); } catch (Exception e) { context.getRequest().setAttribute("Error", e); return ("SystemError"); } finally { freeConnection(context, db); } return "IndexOK"; } public synchronized String executeCommandIndexProjects(ActionContext context) { if (!getUser(context).getAccessAdmin()) { return "PermissionError"; } Connection db = null; try { // Use the configured indexer IIndexerService index = IndexerFactory.getInstance().getIndexerService(); // Create an indexer context IndexerContext indexerContext = new IndexerContext(getApplicationPrefs(context)); indexerContext.setIndexType(Constants.INDEXER_DIRECTORY); // Retrieve a persistent database connection db = getConnection(context, true); // Reindex the whole database LOG.info("Reindexing the directory database"); index.reindexAllData(indexerContext, db); } catch (Exception e) { context.getRequest().setAttribute("Error", e); return ("SystemError"); } finally { freeConnection(context, db); } return "IndexOK"; } /** * This command applies permissions and projects to the query string and * provides this resulting hits * * @param context Description of the Parameter * @return Description of the Return Value */ public String executeCommandDefault(ActionContext context) { /* if (getUser(context).getId() < 0) { return "PermissionError"; } */ setMaximized(context); SearchBean search = (SearchBean) context.getFormBean(); PagedListInfo searchBeanInfo = this.getPagedListInfo(context, "searchBeanInfo"); searchBeanInfo.setLink(context, ctx(context) + "/search"); Connection db = null; try { search.parseQuery(); if (!search.isValid()) { return "SearchResultsERROR"; } // Save the location in a cookie if (StringUtils.hasText(search.getLocation())) { Cookie locationCookie = new Cookie(Constants.COOKIE_USER_SEARCH_LOCATION, search.getLocation()); locationCookie.setPath("/"); // 21 day cookie locationCookie.setMaxAge(21 * 24 * 60 * 60); context.getResponse().addCookie(locationCookie); } else { // Cleanup the cookie Cookie userCookie = new Cookie(Constants.COOKIE_USER_SEARCH_LOCATION, ""); userCookie.setPath("/"); userCookie.setMaxAge(0); context.getResponse().addCookie(userCookie); } // Perform the search... // get the core Indexer IIndexerService indexer = IndexerFactory.getInstance().getIndexerService(); IIndexerSearch searcher = indexer.getIndexerSearch(Constants.INDEXER_FULL); db = getConnection(context); long start = System.currentTimeMillis(); // Create a query string based on the user input and compare with project access if (search.getScope() != SearchBean.THIS) { search.setProjectId(-1); } if (search.getProjectId() > -1) { Project thisProject = retrieveAuthorizedProject(search.getProjectId(), context); context.getRequest().setAttribute("project", thisProject); search.setProjectId(thisProject.getId()); } // Base the search on project categories ProjectCategoryList categories = new ProjectCategoryList(); categories.setEnabled(true); categories.setTopLevelOnly(true); // Limit the categories to the ones that are available to the user if (!getUser(context).isLoggedIn()) { categories.setSensitive(Constants.FALSE); } categories.buildList(db); // Pass the query string and let the portlet customize it DashboardPage page = null; // Try the specific one first if (search.getCategoryId() > -1) { page = DashboardUtils.loadDashboardPage(DashboardTemplateList.TYPE_SEARCH, categories.getValueFromId(search.getCategoryId())); } // Default to the "All" page if (page == null) { page = DashboardUtils.loadDashboardPage(DashboardTemplateList.TYPE_SEARCH, "All"); } if (page != null) { context.getRequest().setAttribute("dashboardPage", page); // Determine if the directory specific indexer is ready to be used IIndexerSearch projectSearcher = null; if ("true".equals(context.getServletContext().getAttribute(Constants.DIRECTORY_INDEX_INITIALIZED))) { // Search public projects only LOG.debug("Using directory index..."); projectSearcher = indexer.getIndexerSearch(Constants.INDEXER_DIRECTORY); } else { // Use the full index because the directory hasn't loaded LOG.debug("Using full index..."); projectSearcher = indexer.getIndexerSearch(Constants.INDEXER_FULL); } // Retrieve the user's allowed projects String projectListings = SearchUtils.generateValidProjects(db, getUserId(context), -1); // Generate a valid project title query string String queryString = SearchUtils.generateProjectQueryString(search, getUserId(context), getInstance(context).getId(), projectListings); // Generate a valid data query string String dataQueryString = SearchUtils.generateDataQueryString(search, getUserId(context), getInstance(context).getId(), projectListings); // provide other common items to the portlets context.getRequest().setAttribute("searchBean", search); context.getRequest().setAttribute("projectCategoryList", categories); // data searcher context.getRequest().setAttribute("searcher", searcher); context.getRequest().setAttribute("dataQueryString", dataQueryString); // project searcher context.getRequest().setAttribute("projectSearcher", projectSearcher); context.getRequest().setAttribute("baseQueryString", queryString); boolean isAction = PortletManager.processPage(context, db, page); // Show duration long end = System.currentTimeMillis(); long duration = end - start; context.getRequest().setAttribute("duration", duration); if (System.getProperty("DEBUG") != null) { System.out.println("Duration: " + duration + " ms"); } return "SearchResultsPortalOK"; } else { // The search portal IS NOT being used... so the search results are of mixed content String queryString = null; if (search.getProjectId() > -1) { queryString = "(" + buildProjectList(search, db, getUserId(context), search.getProjectId()) + ") AND (" + search.getParsedQuery() + ")"; } else { queryString = "(" + buildProjectList(search, db, getUserId(context), -1) + ") AND (" + search.getParsedQuery() + ")"; } // Embed the various values in the query object... String tmpItemsPerPage = context.getRequest().getParameter("items"); if (tmpItemsPerPage != null) { searchBeanInfo.setItemsPerPage(tmpItemsPerPage); } if ("true".equals(context.getRequest().getParameter("auto-populate"))) { searchBeanInfo.setCurrentOffset(0); } // Execute the query IndexerQueryResultList hits = new IndexerQueryResultList(queryString); hits.setPagedListInfo(searchBeanInfo); searcher.search(hits); context.getRequest().setAttribute("hits", hits); //System.out.println("Found " + hits.size() + " document(s) that matched query '" + queryString + "':"); //context.getRequest().setAttribute("queryString", queryString); // Get unique project id's from hits /* ArrayList<String> projectIdList = new ArrayList<String>(); for (int i = 0; i < hits.size(); i++) { Document document = hits.doc(i); String projectId = document.get("projectId"); if (projectId != null && !projectIdList.contains(projectId)) { projectIdList.add(projectId); } } context.getRequest().setAttribute("projectIdList", projectIdList); */ // Show duration long end = System.currentTimeMillis(); long duration = end - start; context.getRequest().setAttribute("duration", duration); if (System.getProperty("DEBUG") != null) { System.out.println("Duration: " + duration + " ms"); } } } catch (Exception e) { context.getRequest().setAttribute("Error", e); return "SearchResultsERROR"; } finally { freeConnection(context, db); } return "SearchResultsOK"; } /** * Include private data that the user has access to * * @param db Description of the Parameter * @param userId Description of the Parameter * @param search Description of the Parameter * @param specificProjectId Description of the Parameter * @return Description of the Return Value * @throws SQLException Description of the Exception */ private String buildProjectList(SearchBean search, Connection db, int userId, int specificProjectId) throws SQLException { HashMap<Integer, Integer> projectList = new HashMap<Integer, Integer>(); if (search.getSection() != SearchBean.WEBSITE) { PreparedStatement pst = null; ResultSet rs = null; if (userId > 0) { // get the projects for the user // get the project permissions for each project // if user has access to the data, then add to query pst = db.prepareStatement( "SELECT project_id, userlevel " + "FROM project_team " + "WHERE user_id = ? " + "AND status IS NULL " + (specificProjectId > -1 ? "AND project_id = ? " : "")); int i = 0; pst.setInt(++i, userId); if (specificProjectId > -1) { pst.setInt(++i, specificProjectId); } rs = pst.executeQuery(); while (rs.next()) { int projectId = rs.getInt("project_id"); int roleId = rs.getInt("userlevel"); // these projects override the lower access projects projectList.put(projectId, roleId); } rs.close(); pst.close(); } } // build query string StringBuffer projectBuffer = new StringBuffer(); // scan for permissions for (Integer projectId : projectList.keySet()) { StringBuffer permissionBuffer = new StringBuffer(); Integer roleId = projectList.get(projectId); // for each project check the available user permissions Project cachedProject = ProjectUtils.loadProject(projectId); PermissionList permissionList = cachedProject.getPermissions(); if (search.getSection() == SearchBean.DETAILS || search.getSection() == SearchBean.UNDEFINED) { // Check for project permissions if (permissionList.getAccessLevel("project-details-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:project"); } } if (search.getSection() == SearchBean.NEWS || search.getSection() == SearchBean.UNDEFINED) { // Check for news permissions if (permissionList.getAccessLevel("project-news-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } // current, archived, unreleased // check for status permissions if (permissionList.getAccessLevel("project-news-view-unreleased") >= roleId) { permissionBuffer.append("type:news"); } else { // take into account a date range [20030101 TO 20040101] SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); permissionBuffer.append("(type:news AND newsStatus:2 AND newsDate:[20030101 TO " + formatter.format(new Date()) + "])"); } } } if (search.getSection() == SearchBean.WIKI || search.getSection() == SearchBean.UNDEFINED) { // Check for wiki permissions if (permissionList.getAccessLevel("project-wiki-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:wiki"); } } if (search.getSection() == SearchBean.DISCUSSION || search.getSection() == SearchBean.UNDEFINED) { // Check for issue category permissions if (permissionList.getAccessLevel("project-discussion-forums-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:issuecategory"); } // Check for issue permissions and issue reply permissions if (permissionList.getAccessLevel("project-discussion-topics-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:issue OR type:issuereply"); } } if (search.getSection() == SearchBean.DOCUMENTS || search.getSection() == SearchBean.UNDEFINED) { // Check for file item permissions if (permissionList.getAccessLevel("project-documents-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:file"); } } if (search.getSection() == SearchBean.LISTS || search.getSection() == SearchBean.UNDEFINED) { // Check for task category permissions and task permissions if (permissionList.getAccessLevel("project-lists-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:listcategory OR type:list"); } } if (search.getSection() == SearchBean.TICKETS || search.getSection() == SearchBean.UNDEFINED) { // Check for ticket permissions if (permissionList.getAccessLevel("project-tickets-view") >= roleId) { if (permissionList.getAccessLevel("project-tickets-other") >= roleId) { // Can access all tickets in this project if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:ticket"); } else { // Can access only tickets that the user created in this project if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("(type:ticket AND enteredBy:" + userId + ")"); } } } if (search.getSection() == SearchBean.PLAN || search.getSection() == SearchBean.UNDEFINED) { // Check for requirement permissions if (permissionList.getAccessLevel("project-plan-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:outline"); // Check for assignment folder permissions permissionBuffer.append(" OR "); permissionBuffer.append("type:activityfolder"); // Check for assignment permissions permissionBuffer.append(" OR "); permissionBuffer.append("type:activity"); // Check for assignment note permissions permissionBuffer.append(" OR "); permissionBuffer.append("type:activitynote"); } } if (search.getSection() == SearchBean.ADS || search.getSection() == SearchBean.UNDEFINED) { // Check for ad. permissions if (permissionList.getAccessLevel("project-ads-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:ads"); } } if (search.getSection() == SearchBean.CLASSIFIEDS || search.getSection() == SearchBean.UNDEFINED) { // Check for classified permissions if (permissionList.getAccessLevel("project-classifieds-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:classifieds"); } } if (search.getSection() == SearchBean.REVIEWS || search.getSection() == SearchBean.UNDEFINED) { // Check for review permissions if (permissionList.getAccessLevel("project-reviews-view") >= roleId) { if (permissionBuffer.length() > 0) { permissionBuffer.append(" OR "); } permissionBuffer.append("type:reviews"); } } // append this project and the user's permissions to the query string if (permissionBuffer.length() > 0) { if (projectBuffer.length() > 0) { projectBuffer.append(" OR "); } projectBuffer.append("(projectId:" + projectId + " AND (" + permissionBuffer.toString() + ")) "); } // debugging if (System.getProperty("DEBUG") != null) { if (permissionBuffer.length() == 0) { System.out.println("NO PERMISSIONS FOR PROJECT: " + projectId); } } } // If the portal is enabled, then include web page results too. if ((search.getSection() == SearchBean.WEBSITE && specificProjectId == -1) || (search.getSection() == SearchBean.UNDEFINED && specificProjectId == -1)) { StringBuffer portalBuffer = new StringBuffer(); PreparedStatement pst = db.prepareStatement( "SELECT project_id " + "FROM projects " + "WHERE approvaldate IS NOT NULL " + "AND portal = ? "); int i = 0; pst.setBoolean(++i, true); ResultSet rs = pst.executeQuery(); int portalCount = 0; while (rs.next()) { ++portalCount; if (portalCount == 1) { portalBuffer.append("(("); } if (portalCount > 1) { portalBuffer.append(" OR "); } int portalProjectId = rs.getInt("project_id"); portalBuffer.append("projectId:" + portalProjectId); } rs.close(); pst.close(); // TODO: Make sure the news is enabled... if (portalBuffer.length() > 0) { // ((projectId:X OR projectId:Y ... portalBuffer.append(") AND "); // ((projectId:X OR projectId:Y) AND ... if (projectBuffer.length() > 0) { projectBuffer.append(" OR "); } SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); projectBuffer.append(portalBuffer.toString() + "type:news AND newsPortal:true AND newsDate:[20030101 TO " + formatter.format(new java.util.Date()) + "])"); } } // user does not have any projects, so lock them into a non-existent project // for security if (projectBuffer.length() == 0) { return "projectId:-1"; } else { return projectBuffer.toString(); } } /** * Description of the Method * * @param context Description of the Parameter * @return Description of the Return Value */ public String executeCommandTips(ActionContext context) { return "TipsOK"; } }