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; 028 029import org.apache.avalon.framework.container.ContainerUtil; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.environment.Request; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.contenttype.ContentTypesHelper; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.search.QueryBuilder; 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.QuerySyntaxException; 044import org.ametys.cms.search.query.WorkflowStepQuery; 045import org.ametys.cms.search.ui.model.ColumnHelper; 046import org.ametys.cms.search.ui.model.ColumnHelper.Column; 047import org.ametys.cms.search.ui.model.SearchUICriterion; 048import org.ametys.cms.search.ui.model.SearchUIModel; 049import org.ametys.core.util.AvalonLoggerAdapter; 050 051import com.google.common.primitives.Ints; 052 053/** 054 * Execute a solr query with custom columns and facets. 055 */ 056public class SolrQuerySearchAction extends SearchAction 057{ 058 /** The content types helper. */ 059 protected ContentTypesHelper _contentTypesHelper; 060 061 /** The helper for columns */ 062 protected ColumnHelper _columnHelper; 063 064 /** The searcher */ 065// protected Searcher _searcher; 066 067// protected ContentSearcherFactory _contentSearcher; 068 069 @Override 070 public void service(ServiceManager serviceManager) throws ServiceException 071 { 072 super.service(serviceManager); 073 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 074 _columnHelper = (ColumnHelper) manager.lookup(ColumnHelper.ROLE); 075// _searcher = (Searcher) serviceManager.lookup(Searcher.ROLE); 076// _contentSearcher = (ContentSearcherFactory) serviceManager.lookup(ContentSearcherFactory.ROLE); 077 } 078 079 @SuppressWarnings("unchecked") 080 @Override 081 protected void doSearch(Request request, SearchUIModel model, int offset, int maxResults, Map<String, Object> jsParameters, Map<String, Object> contextualParameters) throws Exception 082 { 083 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 084 085 String baseQuery = (String) values.get("query"); 086 087 String columnsStr = StringUtils.defaultString((String) values.get("columns")); 088 089 String facetObj = StringUtils.defaultString((String) values.get("facets")); 090 Collection<String> facets = Arrays.asList(StringUtils.split(facetObj, ", ")).stream().map(s -> s.replaceAll("\\.", "/")).collect(Collectors.toList()); 091 092 Map<String, List<String>> facetValues = (Map<String, List<String>>) jsParameters.get("facetValues"); 093 if (facetValues == null) 094 { 095 facetValues = Collections.emptyMap(); 096 } 097 098 String sortInfo = (String) jsParameters.get("sort"); 099 String groupInfo = (String) jsParameters.get("group"); 100 List<Sort> sort = getSort(sortInfo, groupInfo); 101 102 Set<String> contentTypeIds = getContentTypes(jsParameters); 103 Set<String> baseContentTypeIds = _contentTypesHelper.getCommonAncestors(contentTypeIds); 104 105 Object wfStepsObj = values.get("workflowSteps"); 106 Set<Integer> wfSteps = new HashSet<>(); 107 if (wfStepsObj != null && wfStepsObj instanceof List<?>) 108 { 109 for (String wfStepObj : (List<String>) wfStepsObj) 110 { 111 if (StringUtils.isNotEmpty(wfStepObj)) 112 { 113 wfSteps.add(Integer.parseInt(wfStepObj)); 114 } 115 } 116 } 117 118 CriteriaSearchUIModelWrapper modelWrapper = new CriteriaSearchUIModelWrapper(model, manager, _context, new AvalonLoggerAdapter(getLogger())); 119 ContainerUtil.service(modelWrapper, manager); 120 121 facets = modelWrapper.setFacetedCriteria(baseContentTypeIds, facets, contextualParameters); 122 Collection<Column> columns = _getColumns(columnsStr, baseContentTypeIds); 123 modelWrapper.setResultColumns(baseContentTypeIds, columns, contextualParameters); 124 125 String queryStr = buildQuery(_queryBuilder, baseQuery, Collections.EMPTY_SET/*content types or mixin query will be handled by the SimpleContentSearcher*/, wfSteps); 126 127 Map<String, SearchUICriterion> criteria = model.getCriteria(contextualParameters); 128 String lang = _queryBuilder.getCriteriaLanguage(criteria, null, values, contextualParameters); 129 if (StringUtils.isNotEmpty(lang)) 130 { 131 request.setAttribute(SEARCH_LOCALE, new Locale(lang)); 132 } 133 134 SearchResults<Content> results = _searcherFactory.create(contentTypeIds) 135 .withSort(sort) 136 .withFacets(facets) 137 .withLimits(offset, maxResults) 138 .searchWithFacets(queryStr, facetValues); 139// AmetysObjectIterable<Content> contents = _searcher.search(solrQuery, sort, begin, maxResults); 140// SearchResults results = new LocalSearchResults(contents); 141 142 request.setAttribute(SEARCH_RESULTS, results); 143 request.setAttribute(SEARCH_MODEL, modelWrapper); 144 } 145 146 private Collection<Column> _getColumns(String columnsStr, Set<String> baseContentTypeIds) 147 { 148 return _columnHelper.getColumns(columnsStr, baseContentTypeIds); 149 } 150 151 /** 152 * Gets the content types from JS parameters when using 'search-ui.solr' model 153 * @param jsParameters The JS parameters 154 * @return The content types 155 */ 156 @SuppressWarnings("unchecked") 157 public static Set<String> getContentTypes(Map<String, Object> jsParameters) 158 { 159 Map<String, Object> values = (Map<String, Object>) jsParameters.get("values"); 160 Object cTypesObj = values.get("contentTypes"); 161 if (cTypesObj != null && cTypesObj instanceof List<?>) 162 { 163 return new HashSet<>((List<String>) cTypesObj); 164 } 165 return Set.of(); 166 } 167 168 /** 169 * Build a Solr query string from inputs coming from a SolrQuerySearch (query, content types, workflow steps) 170 * @param queryBuilder The {@link QueryBuilder} component 171 * @param baseQuery The main query 172 * @param contentTypesOrMixins The content types -or mixins- (can be empty) 173 * @param wfSteps The workflow steps (can be empty) 174 * @return The built query 175 * @throws QuerySyntaxException If a query syntax is invalid 176 */ 177 public static String buildQuery(QueryBuilder queryBuilder, String baseQuery, Set<String> contentTypesOrMixins, Set<Integer> wfSteps) throws QuerySyntaxException 178 { 179 // Base query 180 List<Query> queriesInFinalQuery = new ArrayList<>(); 181 queriesInFinalQuery.add(() -> baseQuery); 182 183 // Content types 184 if (!contentTypesOrMixins.isEmpty()) 185 { 186 Query cTypeQuery = queryBuilder.createContentTypeOrMixinQuery(null, contentTypesOrMixins, true); 187 queriesInFinalQuery.add(0, cTypeQuery); 188 } 189 190 // Workflow steps 191 if (!wfSteps.isEmpty()) 192 { 193 WorkflowStepQuery wfStepQuery = new WorkflowStepQuery(Ints.toArray(wfSteps)); 194 queriesInFinalQuery.add(0, wfStepQuery); 195 } 196 197 Query query = new AndQuery(queriesInFinalQuery); 198 return query.build(); 199 } 200}