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.json.JSONArray; 038import org.opencms.json.JSONObject; 039import org.opencms.jsp.search.config.CmsSearchConfiguration; 040import org.opencms.jsp.search.config.I_CmsSearchConfiguration; 041import org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser; 042import org.opencms.jsp.search.config.parser.CmsSimpleSearchConfigurationParser; 043import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigParserUtils; 044import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean; 045import org.opencms.jsp.search.controller.CmsSearchController; 046import org.opencms.jsp.search.controller.I_CmsSearchControllerCommon; 047import org.opencms.jsp.search.controller.I_CmsSearchControllerMain; 048import org.opencms.jsp.search.result.CmsSearchResultWrapper; 049import org.opencms.jsp.search.result.I_CmsSearchResultWrapper; 050import org.opencms.jsp.util.CmsJspElFunctions; 051import org.opencms.jsp.util.CmsJspJsonWrapper; 052import org.opencms.main.CmsException; 053import org.opencms.main.CmsIllegalArgumentException; 054import org.opencms.main.CmsLog; 055import org.opencms.main.OpenCms; 056import org.opencms.search.CmsSearchException; 057import org.opencms.search.CmsSearchResource; 058import org.opencms.search.fields.CmsSearchField; 059import org.opencms.search.solr.CmsSolrIndex; 060import org.opencms.search.solr.CmsSolrQuery; 061import org.opencms.search.solr.CmsSolrResultList; 062import org.opencms.util.CmsRequestUtil; 063import org.opencms.util.CmsUUID; 064 065import java.util.HashSet; 066import java.util.List; 067import java.util.Map; 068import java.util.Set; 069 070import javax.servlet.jsp.JspException; 071 072import org.apache.commons.logging.Log; 073 074/** 075 * This tag is used to easily create a search form for a Solr search within a JSP.<p> 076 */ 077public class CmsJspTagSimpleSearch extends CmsJspScopedVarBodyTagSuport implements I_CmsCollectorPublishListProvider { 078 079 /** Default number of items which are checked for change for the "This page" publish dialog. */ 080 public static final int DEFAULT_CONTENTINFO_ROWS = 600; 081 082 /** The log object for this class. */ 083 private static final Log LOG = CmsLog.getLog(CmsJspTagSimpleSearch.class); 084 085 /** The serial version id. */ 086 private static final long serialVersionUID = -12197069109672022L; 087 088 /** Number of entries for which content info should be added to allow correct relations in "This page" publish dialog. */ 089 private Integer m_addContentInfoForEntries; 090 091 /** The "configFile" tag attribute. */ 092 private Object m_configFile; 093 094 /** The "configString" tag attribute. */ 095 private String m_configString; 096 097 /** The "additionalConfigs" tag attribute. */ 098 private Object m_additionalConfigs; 099 100 /** The search index that should be used . 101 * It will either be the configured index, or "Solr Offline" / "Solr Online" depending on the project. 102 * */ 103 private CmsSolrIndex m_index; 104 105 /** Search controller keeping all the config and state from the search. */ 106 private I_CmsSearchControllerMain m_searchController; 107 108 /** 109 * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 110 */ 111 @SuppressWarnings("javadoc") 112 public static Set<CmsResource> getPublishResourcesInternal(CmsObject cms, I_CmsContentLoadCollectorInfo info) 113 throws CmsException { 114 115 CmsSolrIndex solrOnline = OpenCms.getSearchManager().getIndexSolr(CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE); 116 CmsSolrIndex solrOffline = OpenCms.getSearchManager().getIndexSolr(CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE); 117 Set<CmsResource> result = new HashSet<CmsResource>(); 118 try { 119 Map<String, String[]> searchParams = CmsRequestUtil.createParameterMap( 120 info.getCollectorParams(), 121 true, 122 null); 123 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 124 // adjust the CmsObject to prevent unintended filtering of resources 125 CmsSolrResultList offlineResults = solrOffline.search( 126 CmsPublishListHelper.adjustCmsObject(cms, false), 127 new CmsSolrQuery(null, searchParams), 128 true); 129 Set<String> offlineIds = new HashSet<String>(offlineResults.size()); 130 for (CmsSearchResource offlineResult : offlineResults) { 131 offlineIds.add(offlineResult.getField(CmsSearchField.FIELD_ID)); 132 } 133 for (String id : offlineIds) { 134 CmsResource resource = cms.readResource(new CmsUUID(id)); 135 if (!(resource.getState().isUnchanged())) { 136 result.add(resource); 137 } 138 } 139 CmsSolrResultList onlineResults = solrOnline.search( 140 CmsPublishListHelper.adjustCmsObject(cms, true), 141 new CmsSolrQuery(null, searchParams), 142 true); 143 Set<String> deletedIds = new HashSet<String>(onlineResults.size()); 144 for (CmsSearchResource onlineResult : onlineResults) { 145 String uuid = onlineResult.getField(CmsSearchField.FIELD_ID); 146 if (!offlineIds.contains(uuid)) { 147 deletedIds.add(uuid); 148 } 149 } 150 for (String uuid : deletedIds) { 151 CmsResource resource = cms.readResource(new CmsUUID(uuid), CmsResourceFilter.ALL); 152 if (!(resource.getState().isUnchanged())) { 153 result.add(resource); 154 } 155 } 156 } catch (CmsSearchException e) { 157 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 158 } 159 return result; 160 } 161 162 /** 163 * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag() 164 */ 165 @Override 166 public int doEndTag() throws JspException { 167 168 release(); 169 return super.doEndTag(); 170 } 171 172 /** 173 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 174 */ 175 @Override 176 public int doStartTag() throws JspException, CmsIllegalArgumentException { 177 178 CmsFlexController controller = CmsFlexController.getController(pageContext.getRequest()); 179 CmsObject cms = controller.getCmsObject(); 180 181 try { 182 I_CmsSearchConfiguration config = null; 183 CmsResource resource = CmsJspElFunctions.convertRawResource(cms, m_configFile); 184 CmsConfigurationBean configBean = CmsConfigParserUtils.parseListConfiguration(cms, resource); 185 config = new CmsSearchConfiguration( 186 new CmsSimpleSearchConfigurationParser(cms, configBean, m_configString), 187 cms); 188 if (null != m_additionalConfigs) { 189 if (m_additionalConfigs instanceof CmsJspJsonWrapper) { 190 m_additionalConfigs = ((CmsJspJsonWrapper)m_additionalConfigs).getJson(); 191 } 192 if (m_additionalConfigs instanceof JSONArray) { 193 JSONArray addConfig = (JSONArray)m_additionalConfigs; 194 if (addConfig.length() > 0) { 195 for (int i = 0; i < addConfig.length(); i++) { 196 Object c = addConfig.get(i); 197 CmsJSONSearchConfigurationParser parser = (c instanceof JSONObject) 198 ? new CmsJSONSearchConfigurationParser((JSONObject)c) 199 : new CmsJSONSearchConfigurationParser(c.toString()); 200 I_CmsSearchConfiguration conf = new CmsSearchConfiguration(parser, cms); 201 config.extend(conf); 202 } 203 } 204 } else if (m_additionalConfigs instanceof JSONObject) { 205 CmsJSONSearchConfigurationParser parser = new CmsJSONSearchConfigurationParser( 206 (JSONObject)m_additionalConfigs); 207 I_CmsSearchConfiguration conf = new CmsSearchConfiguration(parser, cms); 208 config.extend(conf); 209 } else if (m_additionalConfigs instanceof List) { 210 for (Object c : (List<?>)m_additionalConfigs) { 211 CmsJSONSearchConfigurationParser parser = (c instanceof JSONObject) 212 ? new CmsJSONSearchConfigurationParser((JSONObject)c) 213 : new CmsJSONSearchConfigurationParser(c.toString()); 214 I_CmsSearchConfiguration conf = new CmsSearchConfiguration(parser, cms); 215 config.extend(conf); 216 } 217 } else { 218 // We assume we have a string. 219 String c = m_additionalConfigs.toString().trim(); 220 if (!c.isEmpty()) { 221 if (c.startsWith("[")) { 222 // We must have a JSON array 223 JSONArray addConfig = new JSONArray(c); 224 if (addConfig.length() > 0) { 225 for (int i = 0; i < addConfig.length(); i++) { 226 CmsJSONSearchConfigurationParser parser = new CmsJSONSearchConfigurationParser( 227 addConfig.getJSONObject(i)); 228 I_CmsSearchConfiguration conf = new CmsSearchConfiguration(parser, cms); 229 config.extend(conf); 230 } 231 } 232 } else { 233 // We must have a single JSON Object 234 CmsJSONSearchConfigurationParser parser = new CmsJSONSearchConfigurationParser(c); 235 I_CmsSearchConfiguration conf = new CmsSearchConfiguration(parser, cms); 236 config.extend(conf); 237 } 238 } 239 } 240 } 241 m_searchController = new CmsSearchController(config); 242 243 String indexName = m_searchController.getCommon().getConfig().getSolrIndex(); 244 // try to use configured index 245 if ((indexName != null) && !indexName.trim().isEmpty()) { 246 m_index = OpenCms.getSearchManager().getIndexSolr(indexName); 247 } 248 // if not successful, use the following default 249 if (m_index == null) { 250 m_index = OpenCms.getSearchManager().getIndexSolr( 251 cms.getRequestContext().getCurrentProject().isOnlineProject() 252 ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE 253 : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE); 254 } 255 256 storeAttribute(getVar(), getSearchResults(cms)); 257 258 } catch (Exception e) { // CmsException | UnsupportedEncodingException | JSONException 259 LOG.error(e.getLocalizedMessage(), e); 260 controller.setThrowable(e, cms.getRequestContext().getUri()); 261 throw new JspException(e); 262 } 263 264 if (!cms.getRequestContext().getCurrentProject().isOnlineProject() 265 && (m_addContentInfoForEntries != null) 266 && (CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE.equals( 267 m_searchController.getCommon().getConfig().getSolrIndex()))) { 268 CmsSolrQuery query = new CmsSolrQuery(); 269 m_searchController.addQueryParts(query, cms); 270 query.setStart(Integer.valueOf(0)); 271 query.setRows(m_addContentInfoForEntries); 272 query.setFields(CmsSearchField.FIELD_ID); 273 query.setFacet(false); 274 CmsContentLoadCollectorInfo info = new CmsContentLoadCollectorInfo(); 275 info.setCollectorClass(this.getClass().getName()); 276 // Somehow the normal toString() does add '+' for spaces, but keeps "real" '+' 277 // so we cannot reconstruct the correct query again. 278 // Using toQueryString() puts '+' for spaces as well, but escapes the "real" '+' 279 // so we can "repair" the query. 280 // The method adds '?' as first character, what we do not need. 281 String queryString = query.toQueryString(); 282 if (queryString.length() > 0) { 283 // Cut the leading '?' and put correct spaces in place 284 queryString = queryString.substring(1).replace('+', ' '); 285 } 286 info.setCollectorParams(queryString); 287 info.setId((new CmsUUID()).getStringValue()); 288 if (CmsJspTagEditable.getDirectEditProvider(pageContext) != null) { 289 try { 290 CmsJspTagEditable.getDirectEditProvider(pageContext).insertDirectEditListMetadata( 291 pageContext, 292 info); 293 } catch (JspException e) { 294 LOG.error("Could not write content info.", e); 295 } 296 } 297 } 298 return EVAL_BODY_INCLUDE; 299 } 300 301 /** 302 * Returns the value of the specified configuration file (given via the tag's "configFile" attribute).<p> 303 * 304 * @return the config file 305 */ 306 public Object getConfigFile() { 307 308 return m_configFile; 309 } 310 311 /** 312 * Returns the "configString".<p> 313 * 314 * @return the "configString" 315 */ 316 public String getConfigString() { 317 318 return m_configString; 319 } 320 321 /** 322 * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 323 */ 324 public Set<CmsResource> getPublishResources(CmsObject cms, I_CmsContentLoadCollectorInfo info) throws CmsException { 325 326 return getPublishResourcesInternal(cms, info); 327 } 328 329 /** 330 * @see javax.servlet.jsp.tagext.Tag#release() 331 */ 332 @Override 333 public void release() { 334 335 m_configFile = null; 336 m_configString = null; 337 m_searchController = null; 338 m_index = null; 339 m_addContentInfoForEntries = null; 340 super.release(); 341 } 342 343 /** Setter for "addContentInfo", indicating if content information should be added. 344 * @param doAddInfo The value of the "addContentInfo" attribute of the tag 345 */ 346 public void setAddContentInfo(final Boolean doAddInfo) { 347 348 if ((doAddInfo != null) && doAddInfo.booleanValue() && (null == m_addContentInfoForEntries)) { 349 m_addContentInfoForEntries = Integer.valueOf(DEFAULT_CONTENTINFO_ROWS); 350 } 351 } 352 353 /** Setter for the "additionalConfigs". 354 * @param additionalConfigs The "additionalConfigs". 355 */ 356 public void setAdditionalConfigs(final Object additionalConfigs) { 357 358 m_additionalConfigs = additionalConfigs; 359 } 360 361 /** Setter for the configuration file. 362 * @param fileName Name of the configuration file to use for the search. 363 */ 364 public void setConfigFile(String fileName) { 365 366 m_configFile = fileName; 367 } 368 369 /** Setter for the "configString". 370 * @param configString The "configString". 371 */ 372 public void setConfigString(final String configString) { 373 374 m_configString = configString; 375 } 376 377 /** Setter for "contentInfoMaxItems". 378 * @param maxItems number of items to maximally check for alterations. 379 */ 380 public void setContentInfoMaxItems(Integer maxItems) { 381 382 if (null != maxItems) { 383 m_addContentInfoForEntries = maxItems; 384 } 385 } 386 387 /** 388 * Here the search query is composed and executed. 389 * The result is wrapped in an easily usable form. 390 * It is exposed to the JSP via the tag's "var" attribute.<p> 391 * 392 * @param cms the cms context 393 * 394 * @return The result object exposed via the tag's attribute "var". 395 */ 396 private I_CmsSearchResultWrapper getSearchResults(CmsObject cms) { 397 398 // The second parameter is just ignored - so it does not matter 399 m_searchController.updateFromRequestParameters(pageContext.getRequest().getParameterMap(), false); 400 I_CmsSearchControllerCommon common = m_searchController.getCommon(); 401 // Do not search for empty query, if configured 402 if (common.getState().getQuery().isEmpty() 403 && (!common.getConfig().getIgnoreQueryParam() && !common.getConfig().getSearchForEmptyQueryParam())) { 404 return new CmsSearchResultWrapper(m_searchController, null, null, cms, null); 405 } 406 Map<String, String[]> queryParams = null; 407 boolean isEditMode = CmsJspTagEditable.isEditableRequest(pageContext.getRequest()); 408 if (isEditMode) { 409 String params = ""; 410 if (common.getConfig().getIgnoreReleaseDate()) { 411 params += "&fq=released:[* TO *]"; 412 } 413 if (common.getConfig().getIgnoreExpirationDate()) { 414 params += "&fq=expired:[* TO *]"; 415 } 416 if (!params.isEmpty()) { 417 queryParams = CmsRequestUtil.createParameterMap(params.substring(1)); 418 } 419 } 420 CmsSolrQuery query = new CmsSolrQuery(null, queryParams); 421 m_searchController.addQueryParts(query, cms); 422 try { 423 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 424 // also set resource filter to allow for returning unreleased/expired resources if necessary. 425 CmsSolrResultList solrResultList = m_index.search( 426 cms, 427 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. 428 true, 429 null, 430 false, 431 isEditMode ? CmsResourceFilter.IGNORE_EXPIRATION : null, 432 m_searchController.getCommon().getConfig().getMaxReturnedResults()); 433 return new CmsSearchResultWrapper(m_searchController, solrResultList, query, cms, null); 434 } catch (CmsSearchException e) { 435 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 436 return new CmsSearchResultWrapper(m_searchController, null, query, cms, e); 437 } 438 } 439}