001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp; 029 030import org.opencms.ade.publish.CmsPublishListHelper; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.collectors.I_CmsCollectorPublishListProvider; 035import org.opencms.flex.CmsFlexController; 036import org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo; 037import org.opencms.jsp.search.config.CmsSearchConfiguration; 038import org.opencms.jsp.search.config.I_CmsSearchConfiguration; 039import org.opencms.jsp.search.config.parser.CmsSimpleSearchConfigurationParser; 040import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigParserUtils; 041import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean; 042import org.opencms.jsp.search.controller.CmsSearchController; 043import org.opencms.jsp.search.controller.I_CmsSearchControllerCommon; 044import org.opencms.jsp.search.controller.I_CmsSearchControllerMain; 045import org.opencms.jsp.search.result.CmsSearchResultWrapper; 046import org.opencms.jsp.search.result.I_CmsSearchResultWrapper; 047import org.opencms.jsp.util.CmsJspElFunctions; 048import org.opencms.main.CmsException; 049import org.opencms.main.CmsIllegalArgumentException; 050import org.opencms.main.CmsLog; 051import org.opencms.main.OpenCms; 052import org.opencms.search.CmsSearchException; 053import org.opencms.search.CmsSearchResource; 054import org.opencms.search.fields.CmsSearchField; 055import org.opencms.search.solr.CmsSolrIndex; 056import org.opencms.search.solr.CmsSolrQuery; 057import org.opencms.search.solr.CmsSolrResultList; 058import org.opencms.util.CmsRequestUtil; 059import org.opencms.util.CmsUUID; 060 061import java.util.HashSet; 062import java.util.Map; 063import java.util.Set; 064 065import javax.servlet.jsp.JspException; 066 067import org.apache.commons.logging.Log; 068 069/** 070 * This tag is used to easily create a search form for a Solr search within a JSP.<p> 071 */ 072public class CmsJspTagSimpleSearch extends CmsJspScopedVarBodyTagSuport implements I_CmsCollectorPublishListProvider { 073 074 /** Default number of items which are checked for change for the "This page" publish dialog. */ 075 public static final int DEFAULT_CONTENTINFO_ROWS = 600; 076 077 /** The log object for this class. */ 078 private static final Log LOG = CmsLog.getLog(CmsJspTagSimpleSearch.class); 079 080 /** The serial version id. */ 081 private static final long serialVersionUID = -12197069109672022L; 082 083 /** Number of entries for which content info should be added to allow correct relations in "This page" publish dialog. */ 084 private Integer m_addContentInfoForEntries; 085 086 /** The "configFile" tag attribute. */ 087 private Object m_configFile; 088 089 /** The "configString" tag attribute. */ 090 private String m_configString; 091 092 /** The search index that should be used . 093 * It will either be the configured index, or "Solr Offline" / "Solr Online" depending on the project. 094 * */ 095 private CmsSolrIndex m_index; 096 097 /** Search controller keeping all the config and state from the search. */ 098 private I_CmsSearchControllerMain m_searchController; 099 100 /** 101 * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 102 */ 103 @SuppressWarnings("javadoc") 104 public static Set<CmsResource> getPublishResourcesInternal(CmsObject cms, I_CmsContentLoadCollectorInfo info) 105 throws CmsException { 106 107 CmsSolrIndex solrOnline = OpenCms.getSearchManager().getIndexSolr(CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE); 108 CmsSolrIndex solrOffline = OpenCms.getSearchManager().getIndexSolr(CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE); 109 Set<CmsResource> result = new HashSet<CmsResource>(); 110 try { 111 Map<String, String[]> searchParams = CmsRequestUtil.createParameterMap( 112 info.getCollectorParams(), 113 true, 114 null); 115 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 116 // adjust the CmsObject to prevent unintended filtering of resources 117 CmsSolrResultList offlineResults = solrOffline.search( 118 CmsPublishListHelper.adjustCmsObject(cms, false), 119 new CmsSolrQuery(null, searchParams), 120 true); 121 Set<String> offlineIds = new HashSet<String>(offlineResults.size()); 122 for (CmsSearchResource offlineResult : offlineResults) { 123 offlineIds.add(offlineResult.getField(CmsSearchField.FIELD_ID)); 124 } 125 for (String id : offlineIds) { 126 CmsResource resource = cms.readResource(new CmsUUID(id)); 127 if (!(resource.getState().isUnchanged())) { 128 result.add(resource); 129 } 130 } 131 CmsSolrResultList onlineResults = solrOnline.search( 132 CmsPublishListHelper.adjustCmsObject(cms, true), 133 new CmsSolrQuery(null, searchParams), 134 true); 135 Set<String> deletedIds = new HashSet<String>(onlineResults.size()); 136 for (CmsSearchResource onlineResult : onlineResults) { 137 String uuid = onlineResult.getField(CmsSearchField.FIELD_ID); 138 if (!offlineIds.contains(uuid)) { 139 deletedIds.add(uuid); 140 } 141 } 142 for (String uuid : deletedIds) { 143 CmsResource resource = cms.readResource(new CmsUUID(uuid), CmsResourceFilter.ALL); 144 if (!(resource.getState().isUnchanged())) { 145 result.add(resource); 146 } 147 } 148 } catch (CmsSearchException e) { 149 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 150 } 151 return result; 152 } 153 154 /** 155 * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag() 156 */ 157 @Override 158 public int doEndTag() throws JspException { 159 160 release(); 161 return super.doEndTag(); 162 } 163 164 /** 165 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 166 */ 167 @Override 168 public int doStartTag() throws JspException, CmsIllegalArgumentException { 169 170 CmsFlexController controller = CmsFlexController.getController(pageContext.getRequest()); 171 CmsObject cms = controller.getCmsObject(); 172 173 try { 174 I_CmsSearchConfiguration config = null; 175 CmsResource resource = CmsJspElFunctions.convertRawResource(cms, m_configFile); 176 CmsConfigurationBean configBean = CmsConfigParserUtils.parseListConfiguration(cms, resource); 177 config = new CmsSearchConfiguration( 178 new CmsSimpleSearchConfigurationParser(cms, configBean, m_configString), 179 cms); 180 m_searchController = new CmsSearchController(config); 181 182 String indexName = m_searchController.getCommon().getConfig().getSolrIndex(); 183 // try to use configured index 184 if ((indexName != null) && !indexName.trim().isEmpty()) { 185 m_index = OpenCms.getSearchManager().getIndexSolr(indexName); 186 } 187 // if not successful, use the following default 188 if (m_index == null) { 189 m_index = OpenCms.getSearchManager().getIndexSolr( 190 cms.getRequestContext().getCurrentProject().isOnlineProject() 191 ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE 192 : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE); 193 } 194 195 storeAttribute(getVar(), getSearchResults(cms)); 196 197 } catch (Exception e) { // CmsException | UnsupportedEncodingException | JSONException 198 LOG.error(e.getLocalizedMessage(), e); 199 controller.setThrowable(e, cms.getRequestContext().getUri()); 200 throw new JspException(e); 201 } 202 203 if (!cms.getRequestContext().getCurrentProject().isOnlineProject() 204 && (m_addContentInfoForEntries != null) 205 && (CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE.equals( 206 m_searchController.getCommon().getConfig().getSolrIndex()))) { 207 CmsSolrQuery query = new CmsSolrQuery(); 208 m_searchController.addQueryParts(query, cms); 209 query.setStart(Integer.valueOf(0)); 210 query.setRows(m_addContentInfoForEntries); 211 query.setFields(CmsSearchField.FIELD_ID); 212 query.setFacet(false); 213 CmsContentLoadCollectorInfo info = new CmsContentLoadCollectorInfo(); 214 info.setCollectorClass(this.getClass().getName()); 215 // Somehow the normal toString() does add '+' for spaces, but keeps "real" '+' 216 // so we cannot reconstruct the correct query again. 217 // Using toQueryString() puts '+' for spaces as well, but escapes the "real" '+' 218 // so we can "repair" the query. 219 // The method adds '?' as first character, what we do not need. 220 String queryString = query.toQueryString(); 221 if (queryString.length() > 0) { 222 // Cut the leading '?' and put correct spaces in place 223 queryString = queryString.substring(1).replace('+', ' '); 224 } 225 info.setCollectorParams(queryString); 226 info.setId((new CmsUUID()).getStringValue()); 227 if (CmsJspTagEditable.getDirectEditProvider(pageContext) != null) { 228 try { 229 CmsJspTagEditable.getDirectEditProvider(pageContext).insertDirectEditListMetadata( 230 pageContext, 231 info); 232 } catch (JspException e) { 233 LOG.error("Could not write content info.", e); 234 } 235 } 236 } 237 return EVAL_BODY_INCLUDE; 238 } 239 240 /** 241 * Returns the value of the specified configuration file (given via the tag's "configFile" attribute).<p> 242 * 243 * @return the config file 244 */ 245 public Object getConfigFile() { 246 247 return m_configFile; 248 } 249 250 /** 251 * Returns the "configString".<p> 252 * 253 * @return the "configString" 254 */ 255 public String getConfigString() { 256 257 return m_configString; 258 } 259 260 /** 261 * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 262 */ 263 public Set<CmsResource> getPublishResources(CmsObject cms, I_CmsContentLoadCollectorInfo info) throws CmsException { 264 265 return getPublishResourcesInternal(cms, info); 266 } 267 268 /** 269 * @see javax.servlet.jsp.tagext.Tag#release() 270 */ 271 @Override 272 public void release() { 273 274 m_configFile = null; 275 m_configString = null; 276 m_searchController = null; 277 m_index = null; 278 m_addContentInfoForEntries = null; 279 super.release(); 280 } 281 282 /** Setter for "addContentInfo", indicating if content information should be added. 283 * @param doAddInfo The value of the "addContentInfo" attribute of the tag 284 */ 285 public void setAddContentInfo(final Boolean doAddInfo) { 286 287 if ((doAddInfo != null) && doAddInfo.booleanValue() && (null == m_addContentInfoForEntries)) { 288 m_addContentInfoForEntries = Integer.valueOf(DEFAULT_CONTENTINFO_ROWS); 289 } 290 } 291 292 /** Setter for the configuration file. 293 * @param fileName Name of the configuration file to use for the search. 294 */ 295 public void setConfigFile(String fileName) { 296 297 m_configFile = fileName; 298 } 299 300 /** Setter for the "configString". 301 * @param configString The "configString". 302 */ 303 public void setConfigString(final String configString) { 304 305 m_configString = configString; 306 } 307 308 /** Setter for "contentInfoMaxItems". 309 * @param maxItems number of items to maximally check for alterations. 310 */ 311 public void setContentInfoMaxItems(Integer maxItems) { 312 313 if (null != maxItems) { 314 m_addContentInfoForEntries = maxItems; 315 } 316 } 317 318 /** 319 * Here the search query is composed and executed. 320 * The result is wrapped in an easily usable form. 321 * It is exposed to the JSP via the tag's "var" attribute.<p> 322 * 323 * @param cms the cms context 324 * 325 * @return The result object exposed via the tag's attribute "var". 326 */ 327 private I_CmsSearchResultWrapper getSearchResults(CmsObject cms) { 328 329 // The second parameter is just ignored - so it does not matter 330 m_searchController.updateFromRequestParameters(pageContext.getRequest().getParameterMap(), false); 331 I_CmsSearchControllerCommon common = m_searchController.getCommon(); 332 // Do not search for empty query, if configured 333 if (common.getState().getQuery().isEmpty() 334 && (!common.getConfig().getIgnoreQueryParam() && !common.getConfig().getSearchForEmptyQueryParam())) { 335 return new CmsSearchResultWrapper(m_searchController, null, null, cms, null); 336 } 337 Map<String, String[]> queryParams = null; 338 boolean isEditMode = CmsJspTagEditable.isEditableRequest(pageContext.getRequest()); 339 if (isEditMode) { 340 String params = ""; 341 if (common.getConfig().getIgnoreReleaseDate()) { 342 params += "&fq=released:[* TO *]"; 343 } 344 if (common.getConfig().getIgnoreExpirationDate()) { 345 params += "&fq=expired:[* TO *]"; 346 } 347 if (!params.isEmpty()) { 348 queryParams = CmsRequestUtil.createParameterMap(params.substring(1)); 349 } 350 } 351 CmsSolrQuery query = new CmsSolrQuery(null, queryParams); 352 m_searchController.addQueryParts(query, cms); 353 try { 354 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 355 // also set resource filter to allow for returning unreleased/expired resources if necessary. 356 CmsSolrResultList solrResultList = m_index.search( 357 cms, 358 query.clone(), // use a clone of the query, since the search function manipulates the query (removes highlighting parts), but we want to keep the original one. 359 true, 360 null, 361 false, 362 isEditMode ? CmsResourceFilter.IGNORE_EXPIRATION : null, 363 m_searchController.getCommon().getConfig().getMaxReturnedResults()); 364 return new CmsSearchResultWrapper(m_searchController, solrResultList, query, cms, null); 365 } catch (CmsSearchException e) { 366 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 367 return new CmsSearchResultWrapper(m_searchController, null, query, cms, e); 368 } 369 } 370}