001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.solr; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029 030import org.apache.avalon.framework.container.ContainerUtil; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.cocoon.environment.Request; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.cms.contenttype.ContentTypesHelper; 037import org.ametys.cms.repository.Content; 038import org.ametys.cms.search.SearchResults; 039import org.ametys.cms.search.Sort; 040import org.ametys.cms.search.cocoon.SearchAction; 041import org.ametys.cms.search.query.AndQuery; 042import org.ametys.cms.search.query.Query; 043import org.ametys.cms.search.query.WorkflowStepQuery; 044import org.ametys.cms.search.solr.CriteriaSearchUIModelWrapper.Column; 045import org.ametys.cms.search.ui.model.SearchUICriterion; 046import org.ametys.cms.search.ui.model.SearchUIModel; 047import org.ametys.core.util.AvalonLoggerAdapter; 048 049import com.google.common.primitives.Ints; 050 051/** 052 * Execute a solr query with custom columns and facets. 053 */ 054public class SolrQuerySearchAction extends SearchAction 055{ 056 /** The content types helper. */ 057 protected ContentTypesHelper _contentTypesHelper; 058 059 /** The searcher */ 060// protected Searcher _searcher; 061 062// protected ContentSearcherFactory _contentSearcher; 063 064 @Override 065 public void service(ServiceManager serviceManager) throws ServiceException 066 { 067 super.service(serviceManager); 068 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 069// _searcher = (Searcher) serviceManager.lookup(Searcher.ROLE); 070// _contentSearcher = (ContentSearcherFactory) serviceManager.lookup(ContentSearcherFactory.ROLE); 071 } 072 073 @SuppressWarnings("unchecked") 074 @Override 075 protected void doSearch(Request request, SearchUIModel model, int offset, int maxResults, Map<String, Object> jsParameters, Map<String, Object> contextualParameters) throws Exception 076 { 077 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 078 079 List<Query> queriesInFinalQuery = new ArrayList<>(); 080 queriesInFinalQuery.add(() -> (String) values.get("query")); 081 082 String columnsStr = StringUtils.defaultString((String) values.get("columns")); 083 084 String facetObj = StringUtils.defaultString((String) values.get("facets")); 085 Collection<String> facets = Arrays.asList(StringUtils.split(facetObj, ", ")).stream().map(s -> s.replaceAll("\\.", "/")).collect(Collectors.toList()); 086 087 Map<String, List<String>> facetValues = (Map<String, List<String>>) jsParameters.get("facetValues"); 088 if (facetValues == null) 089 { 090 facetValues = Collections.emptyMap(); 091 } 092 093 String sortInfo = (String) jsParameters.get("sort"); 094 String groupInfo = (String) jsParameters.get("group"); 095 List<Sort> sort = getSort(sortInfo, groupInfo); 096 097 Set<String> contentTypes = Collections.emptySet(); 098 099 Object cTypesObj = values.get("contentTypes"); 100 String baseContentType = null; 101 if (cTypesObj != null && cTypesObj instanceof List<?>) 102 { 103 contentTypes = new HashSet<>((List<String>) cTypesObj); 104 baseContentType = _contentTypesHelper.getCommonAncestor(contentTypes); 105 106 Query cTypeQuery = _queryBuilder.createContentTypeOrMixinQuery(null, contentTypes, true); 107 108 queriesInFinalQuery.add(0, cTypeQuery); 109 } 110 111 Object wfStepsObj = values.get("workflowSteps"); 112 if (wfStepsObj != null && wfStepsObj instanceof List<?>) 113 { 114 Set<Integer> wfSteps = new HashSet<>(); 115 for (String wfStepObj : (List<String>) wfStepsObj) 116 { 117 if (StringUtils.isNotEmpty(wfStepObj)) 118 { 119 wfSteps.add(Integer.parseInt(wfStepObj)); 120 } 121 } 122 123 if (!wfSteps.isEmpty()) 124 { 125 WorkflowStepQuery wfStepQuery = new WorkflowStepQuery(Ints.toArray(wfSteps)); 126 127 queriesInFinalQuery.add(0, wfStepQuery); 128 } 129 } 130 131 CriteriaSearchUIModelWrapper modelWrapper = new CriteriaSearchUIModelWrapper(model, manager, _context, new AvalonLoggerAdapter(getLogger())); 132 ContainerUtil.service(modelWrapper, manager); 133 134 facets = modelWrapper.setFacetedCriteria(baseContentType, facets, contextualParameters); 135 modelWrapper.setResultColumns(baseContentType, getColumns(columnsStr), contextualParameters); 136 137 Query query = new AndQuery(queriesInFinalQuery); 138 139 SearchResults<Content> results = _searcherFactory.create(contentTypes) 140 .withSort(sort) 141 .withFacets(facets) 142 .withLimits(offset, maxResults) 143 .searchWithFacets(query, facetValues); 144// AmetysObjectIterable<Content> contents = _searcher.search(solrQuery, sort, begin, maxResults); 145// SearchResults results = new LocalSearchResults(contents); 146 147 request.setAttribute(SEARCH_RESULTS, results); 148 request.setAttribute(SEARCH_MODEL, modelWrapper); 149 150 Map<String, SearchUICriterion> criteria = model.getCriteria(contextualParameters); 151 String lang = _queryBuilder.getCriteriaLanguage(criteria, null, values, contextualParameters); 152 if (StringUtils.isNotEmpty(lang)) 153 { 154 request.setAttribute(SEARCH_LOCALE, new Locale(lang)); 155 } 156 } 157 158 /** 159 * From a string representing columns, returns the list of column ids with their (optional) labels. 160 * @param columnsStr The columns as a string 161 * @return the list of column ids with their (optional) labels. 162 */ 163 public static List<Column> getColumns(String columnsStr) 164 { 165 return _getColumns(Stream.of(StringUtils.split(columnsStr, ','))); 166 } 167 /** 168 * From a list of string representing columns, returns the list of column ids with their (optional) labels. 169 * @param columns The columns 170 * @return the list of column ids with their (optional) labels. 171 */ 172 public static List<Column> getColumns(List<String> columns) 173 { 174 return _getColumns(columns.stream()); 175 } 176 177 private static List<Column> _getColumns(Stream<String> columns) 178 { 179 // in StringUtils.split, adjacent separators are treated as one separator, so col cannot be empty 180 // but it still can be whitespaces only, just ignore them silently 181 return columns.filter(StringUtils::isNotBlank) 182 .map(SolrQuerySearchAction::_leftTrim) // because we do not want a column named " as " to be split then, the "as" should be considered as the column id 183 .map(col -> col.split("(?i) AS ", 2)) 184 .map(arr -> 185 { 186 // col is never empty, so arr.length cannot be 0 187 String colId = arr[0].trim().replace('.', '/'); 188 if (arr.length == 2) 189 { 190 return new Column(colId, arr[1].trim()); 191 } 192 else 193 { 194 return new Column(colId, null); 195 } 196 }) 197 .distinct() 198 .collect(Collectors.toList()); 199 } 200 201 private static String _leftTrim(String s) 202 { 203 return s.replaceAll("^\\s+", ""); 204 } 205}