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.controller.CmsSearchController; 041import org.opencms.jsp.search.controller.I_CmsSearchControllerCommon; 042import org.opencms.jsp.search.controller.I_CmsSearchControllerMain; 043import org.opencms.jsp.search.result.CmsSearchResultWrapper; 044import org.opencms.jsp.search.result.I_CmsSearchResultWrapper; 045import org.opencms.jsp.util.CmsJspElFunctions; 046import org.opencms.main.CmsException; 047import org.opencms.main.CmsIllegalArgumentException; 048import org.opencms.main.CmsLog; 049import org.opencms.main.OpenCms; 050import org.opencms.search.CmsSearchException; 051import org.opencms.search.CmsSearchResource; 052import org.opencms.search.fields.CmsSearchField; 053import org.opencms.search.solr.CmsSolrIndex; 054import org.opencms.search.solr.CmsSolrQuery; 055import org.opencms.search.solr.CmsSolrResultList; 056import org.opencms.ui.apps.lists.CmsListManager; 057import org.opencms.ui.apps.lists.CmsListManager.ListConfigurationBean; 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 = 200; 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 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 112 // adjust the CmsObject to prevent unintended filtering of resources 113 CmsSolrResultList offlineResults = solrOffline.search( 114 CmsPublishListHelper.adjustCmsObject(cms, false), 115 new CmsSolrQuery(null, CmsRequestUtil.createParameterMap(info.getCollectorParams())), 116 true); 117 Set<String> offlineIds = new HashSet<String>(offlineResults.size()); 118 for (CmsSearchResource offlineResult : offlineResults) { 119 offlineIds.add(offlineResult.getField(CmsSearchField.FIELD_ID)); 120 } 121 for (String id : offlineIds) { 122 CmsResource resource = cms.readResource(new CmsUUID(id)); 123 if (!(resource.getState().isUnchanged())) { 124 result.add(resource); 125 } 126 } 127 CmsSolrResultList onlineResults = solrOnline.search( 128 CmsPublishListHelper.adjustCmsObject(cms, true), 129 new CmsSolrQuery(null, CmsRequestUtil.createParameterMap(info.getCollectorParams())), 130 true); 131 Set<String> deletedIds = new HashSet<String>(onlineResults.size()); 132 for (CmsSearchResource onlineResult : onlineResults) { 133 String uuid = onlineResult.getField(CmsSearchField.FIELD_ID); 134 if (!offlineIds.contains(uuid)) { 135 deletedIds.add(uuid); 136 } 137 } 138 for (String uuid : deletedIds) { 139 CmsResource resource = cms.readResource(new CmsUUID(uuid)); 140 if (!(resource.getState().isUnchanged())) { 141 result.add(resource); 142 } 143 } 144 } catch (CmsSearchException e) { 145 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 146 } 147 return result; 148 } 149 150 /** 151 * @see javax.servlet.jsp.tagext.BodyTagSupport#doEndTag() 152 */ 153 @Override 154 public int doEndTag() throws JspException { 155 156 release(); 157 return super.doEndTag(); 158 } 159 160 /** 161 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 162 */ 163 @Override 164 public int doStartTag() throws JspException, CmsIllegalArgumentException { 165 166 CmsFlexController controller = CmsFlexController.getController(pageContext.getRequest()); 167 CmsObject cms = controller.getCmsObject(); 168 169 try { 170 I_CmsSearchConfiguration config = null; 171 CmsResource resource = CmsJspElFunctions.convertRawResource(cms, m_configFile); 172 ListConfigurationBean configBean = CmsListManager.parseListConfiguration(cms, resource); 173 config = new CmsSearchConfiguration( 174 new CmsSimpleSearchConfigurationParser(cms, configBean, m_configString)); 175 m_searchController = new CmsSearchController(config); 176 177 String indexName = m_searchController.getCommon().getConfig().getSolrIndex(); 178 // try to use configured index 179 if ((indexName != null) && !indexName.trim().isEmpty()) { 180 m_index = OpenCms.getSearchManager().getIndexSolr(indexName); 181 } 182 // if not successful, use the following default 183 if (m_index == null) { 184 m_index = OpenCms.getSearchManager().getIndexSolr( 185 cms.getRequestContext().getCurrentProject().isOnlineProject() 186 ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE 187 : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE); 188 } 189 190 storeAttribute(getVar(), getSearchResults(cms)); 191 192 } catch (Exception e) { // CmsException | UnsupportedEncodingException | JSONException 193 LOG.error(e.getLocalizedMessage(), e); 194 controller.setThrowable(e, cms.getRequestContext().getUri()); 195 throw new JspException(e); 196 } 197 if (!cms.getRequestContext().getCurrentProject().isOnlineProject() 198 && (null == m_searchController.getCommon().getConfig().getSolrIndex()) 199 && (null != m_addContentInfoForEntries)) { 200 CmsSolrQuery query = new CmsSolrQuery(); 201 m_searchController.addQueryParts(query, cms); 202 query.setStart(Integer.valueOf(0)); 203 query.setRows(m_addContentInfoForEntries); 204 CmsContentLoadCollectorInfo info = new CmsContentLoadCollectorInfo(); 205 info.setCollectorClass(this.getClass().getName()); 206 info.setCollectorParams(query.getQuery()); 207 info.setId((new CmsUUID()).getStringValue()); 208 if (CmsJspTagEditable.getDirectEditProvider(pageContext) != null) { 209 try { 210 CmsJspTagEditable.getDirectEditProvider(pageContext).insertDirectEditListMetadata( 211 pageContext, 212 info); 213 } catch (JspException e) { 214 LOG.error("Could not write content info.", e); 215 } 216 } 217 } 218 return EVAL_BODY_INCLUDE; 219 } 220 221 /** 222 * Returns the value of the specified configuration file (given via the tag's "configFile" attribute).<p> 223 * 224 * @return the config file 225 */ 226 public Object getConfigFile() { 227 228 return m_configFile; 229 } 230 231 /** 232 * Returns the "configString".<p> 233 * 234 * @return the "configString" 235 */ 236 public String getConfigString() { 237 238 return m_configString; 239 } 240 241 /** 242 * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo) 243 */ 244 public Set<CmsResource> getPublishResources(CmsObject cms, I_CmsContentLoadCollectorInfo info) throws CmsException { 245 246 return getPublishResourcesInternal(cms, info); 247 } 248 249 /** 250 * @see javax.servlet.jsp.tagext.Tag#release() 251 */ 252 @Override 253 public void release() { 254 255 m_configFile = null; 256 m_configString = null; 257 m_searchController = null; 258 m_index = null; 259 super.release(); 260 } 261 262 /** Setter for "addContentInfo", indicating if content information should be added. 263 * @param doAddInfo The value of the "addContentInfo" attribute of the tag 264 */ 265 public void setAddContentInfo(final Boolean doAddInfo) { 266 267 if ((null != doAddInfo) && doAddInfo.booleanValue() && (null != m_addContentInfoForEntries)) { 268 m_addContentInfoForEntries = Integer.valueOf(DEFAULT_CONTENTINFO_ROWS); 269 } 270 } 271 272 /** Setter for the configuration file. 273 * @param fileName Name of the configuration file to use for the search. 274 */ 275 public void setConfigFile(String fileName) { 276 277 m_configFile = fileName; 278 } 279 280 /** Setter for the "configString". 281 * @param configString The "configString". 282 */ 283 public void setConfigString(final String configString) { 284 285 m_configString = configString; 286 } 287 288 /** Setter for "contentInfoMaxItems". 289 * @param maxItems number of items to maximally check for alterations. 290 */ 291 public void setContentInfoMaxItems(Integer maxItems) { 292 293 if (null != maxItems) { 294 m_addContentInfoForEntries = maxItems; 295 } 296 } 297 298 /** 299 * Here the search query is composed and executed. 300 * The result is wrapped in an easily usable form. 301 * It is exposed to the JSP via the tag's "var" attribute.<p> 302 * 303 * @param cms the cms context 304 * 305 * @return The result object exposed via the tag's attribute "var". 306 */ 307 private I_CmsSearchResultWrapper getSearchResults(CmsObject cms) { 308 309 // The second parameter is just ignored - so it does not matter 310 m_searchController.updateFromRequestParameters(pageContext.getRequest().getParameterMap(), false); 311 I_CmsSearchControllerCommon common = m_searchController.getCommon(); 312 // Do not search for empty query, if configured 313 if (common.getState().getQuery().isEmpty() 314 && (!common.getConfig().getIgnoreQueryParam() && !common.getConfig().getSearchForEmptyQueryParam())) { 315 return new CmsSearchResultWrapper(m_searchController, null, null, cms, null); 316 } 317 Map<String, String[]> queryParams = null; 318 boolean isEditMode = CmsJspTagEditable.isEditableRequest(pageContext.getRequest()); 319 if (isEditMode) { 320 String params = ""; 321 if (common.getConfig().getIgnoreReleaseDate()) { 322 params += "&fq=released:[* TO *]"; 323 } 324 if (common.getConfig().getIgnoreExpirationDate()) { 325 params += "&fq=expired:[* TO *]"; 326 } 327 if (!params.isEmpty()) { 328 queryParams = CmsRequestUtil.createParameterMap(params.substring(1)); 329 } 330 } 331 CmsSolrQuery query = new CmsSolrQuery(null, queryParams); 332 m_searchController.addQueryParts(query, cms); 333 try { 334 // use "complicated" constructor to allow more than 50 results -> set ignoreMaxResults to true 335 // also set resource filter to allow for returning unreleased/expired resources if necessary. 336 CmsSolrResultList solrResultList = m_index.search( 337 cms, 338 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. 339 true, 340 isEditMode ? CmsResourceFilter.IGNORE_EXPIRATION : null); 341 return new CmsSearchResultWrapper(m_searchController, solrResultList, query, cms, null); 342 } catch (CmsSearchException e) { 343 LOG.warn(Messages.get().getBundle().key(Messages.LOG_TAG_SEARCH_SEARCH_FAILED_0), e); 344 return new CmsSearchResultWrapper(m_searchController, null, query, cms, e); 345 } 346 } 347}