001/* 002 * Copyright 2017 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.plugins.extraction.component; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.StringJoiner; 026 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.xml.sax.ContentHandler; 034 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.search.Sort; 037import org.ametys.cms.search.Sort.Order; 038import org.ametys.cms.search.cocoon.GroupSearchContent; 039import org.ametys.cms.search.content.ContentSearcherFactory.SimpleContentSearcher; 040import org.ametys.cms.search.query.QuerySyntaxException; 041import org.ametys.cms.search.ui.model.ColumnHelper; 042import org.ametys.cms.search.ui.model.ColumnHelper.Column; 043import org.ametys.core.util.StringUtils; 044import org.ametys.plugins.extraction.ExtractionConstants; 045import org.ametys.plugins.extraction.execution.ExtractionExecutionContext; 046import org.ametys.plugins.extraction.execution.ExtractionExecutionContextHierarchyElement; 047import org.ametys.runtime.model.ModelItem; 048import org.ametys.runtime.model.ViewItemContainer; 049import org.ametys.runtime.model.type.DataContext; 050 051/** 052 * This class represents a query component of the extraction module 053 */ 054public class QueryExtractionComponent extends AbstractGroupExtractionComponent implements TwoStepsExecutingExtractionComponent 055{ 056 private boolean _overrideColumns; 057 private List<ExtractionColumn> _columns = new ArrayList<>(); 058 private boolean _overrideSorts; 059 private Map<String, Order> _sortMap = new LinkedHashMap<>(); 060 private List<Sort> _sorts = new ArrayList<>(); 061 062 private ViewItemContainer _resultItems; 063 064 private ColumnHelper _columnHelper; 065 066 @Override 067 public void service(ServiceManager serviceManager) throws ServiceException 068 { 069 super.service(serviceManager); 070 _columnHelper = (ColumnHelper) serviceManager.lookup(ColumnHelper.ROLE); 071 } 072 073 @Override 074 public void configure(Configuration query) throws ConfigurationException 075 { 076 super.configure(query); 077 078 Configuration columnsConfiguration = query.getChild("columns"); 079 _overrideColumns = columnsConfiguration.getAttributeAsBoolean("override", false); 080 for (Configuration columnConfiguration : columnsConfiguration.getChildren("column")) 081 { 082 ExtractionColumn column = new ExtractionColumn(); 083 column.setFieldPath(columnConfiguration.getValue()); 084 column.setDisplayOptionalName(columnConfiguration.getAttribute("optional", null)); 085 _columns.add(column); 086 } 087 088 Configuration sorts = query.getChild("sorts"); 089 _overrideSorts = sorts.getAttributeAsBoolean("override", false); 090 for (Configuration sort : sorts.getChildren("sort")) 091 { 092 String orderAsString = sort.getAttribute("order", "ASC"); 093 Order order = orderAsString.equalsIgnoreCase("ASC") ? Order.ASC : Order.DESC; 094 String fieldPath = sort.getValue(); 095 _sortMap.put(fieldPath, order); 096 } 097 } 098 099 @Override 100 public void prepareComponentExecution(ExtractionExecutionContext context) throws Exception 101 { 102 super.prepareComponentExecution(context); 103 104 List<String> columnFieldPaths = _getColumnsToDisplay(context); 105 _resultItems = _buildResulItems(columnFieldPaths); 106 107 // Replace attribute separator for sorts coming from configuration 108 for (Map.Entry<String, Order> entry : _sortMap.entrySet()) 109 { 110 String fieldPath = entry.getKey().replaceAll(EXTRACTION_ITEM_PATH_SEPARATOR, ModelItem.ITEM_PATH_SEPARATOR); 111 _sorts.add(new Sort(fieldPath, entry.getValue())); 112 } 113 114 // Sorts can come from configuration or from referenced query 115 for (Sort sort : _sorts) 116 { 117 // get attribute type just to throw an Exception if the field is not available for content types 118 this._getAttributeTypeId(sort.getField(), _contentTypes); 119 } 120 } 121 122 private List<String> _getColumnsToDisplay(ExtractionExecutionContext context) 123 { 124 Map<String, Boolean> displayOptionalColumns = context.getDisplayOptionalColumns(); 125 List<String> columnFieldPaths = new ArrayList<>(); 126 for (ExtractionColumn column : _columns) 127 { 128 String displayOptionalColumnName = column.getDisplayOptionalName(); 129 if (!displayOptionalColumns.containsKey(displayOptionalColumnName) || displayOptionalColumns.get(displayOptionalColumnName)) 130 { 131 columnFieldPaths.add(column.getFieldPath()); 132 } 133 } 134 return columnFieldPaths; 135 } 136 137 private ViewItemContainer _buildResulItems(List<String> columnFieldPaths) 138 { 139 Set<String> contentTypeIds = _contentTypesHelper.getCommonAncestors(_contentTypes); 140 List<Column> columns = _columnHelper.getColumns(columnFieldPaths, contentTypeIds); 141 142 return _columnHelper.createViewFromColumns(contentTypeIds, columns, true); 143 } 144 145 @SuppressWarnings("unchecked") 146 @Override 147 protected void computeReferencedQueryInfos(String refQueryContent) throws QuerySyntaxException 148 { 149 super.computeReferencedQueryInfos(refQueryContent); 150 151 Map<String, Object> contentMap = _jsonUtils.convertJsonToMap(refQueryContent); 152 Map<String, Object> exportParams = (Map<String, Object>) contentMap.get("exportParams"); 153 154 if (!_overrideColumns) 155 { 156 Map<String, Object> values = (Map<String, Object>) exportParams.get("values"); 157 Object columnFieldPathsAsObject = values.get("columns"); 158 159 Collection<String> columnFieldPaths; 160 if (columnFieldPathsAsObject instanceof String) 161 { 162 columnFieldPaths = StringUtils.stringToCollection((String) columnFieldPathsAsObject); 163 } 164 else 165 { 166 columnFieldPaths = (List<String>) columnFieldPathsAsObject; 167 } 168 169 for (String fieldPath : columnFieldPaths) 170 { 171 ExtractionColumn column = new ExtractionColumn(); 172 column.setFieldPath(fieldPath); 173 _columns.add(column); 174 } 175 } 176 177 if (!_overrideSorts) 178 { 179 _sorts.addAll(0, _getQueryFromJSONHelper.getSort(exportParams)); 180 } 181 } 182 183 @Override 184 protected SimpleContentSearcher getContentSearcher() 185 { 186 return super.getContentSearcher().withSort(_sorts); 187 } 188 189 @Override 190 protected void processContents(Iterable<Content> contents, ContentHandler contentHandler, ExtractionExecutionContext context) throws Exception 191 { 192 if (contents.iterator().hasNext()) 193 { 194 XMLUtils.startElement(contentHandler, _tagName); 195 196 GroupSearchContent rootGroup = organizeContentsInGroups(contents, context.getDefaultLocale()); 197 saxGroup(contentHandler, rootGroup, 0, context, _resultItems); 198 199 XMLUtils.endElement(contentHandler, _tagName); 200 } 201 } 202 203 @Override 204 public Iterable<Content> computeFirstLevelResults(ExtractionExecutionContext context) throws Exception 205 { 206 return getContents(context); 207 } 208 209 @Override 210 public void executeFor(ContentHandler contentHandler, Iterable<Content> firstLevelResults, ExtractionExecutionContext context) throws Exception 211 { 212 processContents(firstLevelResults, contentHandler, context); 213 } 214 215 @Override 216 protected void saxContents(ContentHandler contentHandler, ExtractionExecutionContext context, ViewItemContainer resultItems, List<Content> contents) throws Exception 217 { 218 for (int currentContentIndex = 0; currentContentIndex < contents.size(); currentContentIndex++) 219 { 220 if (getLogger().isDebugEnabled()) 221 { 222 getLogger().debug(getLogsPrefix() + "executing content " + (currentContentIndex + 1) + "/" + contents.size()); 223 } 224 225 Content content = contents.get(currentContentIndex); 226 227 AttributesImpl attributes = new AttributesImpl(); 228 attributes.addCDATAAttribute("id", content.getId()); 229 attributes.addCDATAAttribute("name", content.getName()); 230 attributes.addCDATAAttribute("title", content.getTitle(context.getDefaultLocale())); 231 if (content.getLanguage() != null) 232 { 233 attributes.addCDATAAttribute("language", content.getLanguage()); 234 } 235 236 XMLUtils.startElement(contentHandler, "content", attributes); 237 238 DataContext dataContext = DataContext.newInstance() 239 .withLocale(context.getDefaultLocale()) 240 .withEmptyValues(false); 241 content.dataToSAX(contentHandler, resultItems, dataContext); 242 243 ExtractionExecutionContextHierarchyElement currentContext = new ExtractionExecutionContextHierarchyElement(this, Collections.singleton(content)); 244 executeSubComponents(contentHandler, context, currentContext); 245 XMLUtils.endElement(contentHandler, "content"); 246 } 247 } 248 249 @Override 250 public Map<String, Object> getComponentDetailsForTree() 251 { 252 Map<String, Object> details = super.getComponentDetailsForTree(); 253 details.put("tag", ExtractionConstants.QUERY_COMPONENT_TAG); 254 255 @SuppressWarnings("unchecked") 256 Map<String, Object> data = (Map<String, Object>) details.get("data"); 257 258 StringJoiner columns = new StringJoiner(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELIMITER); 259 for (ExtractionColumn column : this.getColumns()) 260 { 261 StringBuilder builder = new StringBuilder(); 262 builder.append(column.getFieldPath()); 263 if (null != column.getDisplayOptionalName()) 264 { 265 builder.append(" (").append(column.getDisplayOptionalName()).append(")"); 266 } 267 columns.add(builder); 268 } 269 data.put("columns", columns.toString()); 270 data.put("overrideColumns", this.overrideColumns()); 271 272 StringJoiner sorts = new StringJoiner(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELIMITER); 273 for (Map.Entry<String, Order> sort : this.getSorts().entrySet()) 274 { 275 StringBuilder builder = new StringBuilder(); 276 builder.append(sort.getKey()).append(" ("); 277 if (Order.DESC.equals(sort.getValue())) 278 { 279 builder.append("DESC"); 280 } 281 else 282 { 283 builder.append("ASC"); 284 } 285 builder.append(")"); 286 sorts.add(builder); 287 } 288 data.put("sorts", sorts.toString()); 289 data.put("overrideSorts", this.overrideSorts()); 290 291 details.put("iconCls", "ametysicon-query-search"); 292 293 return details; 294 } 295 296 @Override 297 protected String getDefaultTagName() 298 { 299 return "query"; 300 } 301 302 @Override 303 protected String getLogsPrefix() 304 { 305 return "Query component '" + _tagName + "': "; 306 } 307 308 /** 309 * the referenced query's columns 310 * @return <code>true</code> if the referenced query's columns are overridden on this component, <code>false</code> otherwise 311 */ 312 public boolean overrideColumns() 313 { 314 return _overrideColumns; 315 } 316 317 /** 318 * Set the boolean to override the referenced query's columns or not 319 * @param overrideColumns <code>true</code> to override columns, <code>false</code> otherwise 320 */ 321 public void setOverrideColumns(boolean overrideColumns) 322 { 323 _overrideColumns = overrideColumns; 324 } 325 326 /** 327 * Retrieves the component columns 328 * @return The component columns 329 */ 330 public List<ExtractionColumn> getColumns() 331 { 332 return _columns; 333 } 334 335 /** 336 * Add columns to the components. Do not manage optional columns' variables 337 * @param fieldPaths Array of columns' field paths to add 338 */ 339 public void addColumns(String... fieldPaths) 340 { 341 for (String fieldPath : fieldPaths) 342 { 343 ExtractionColumn column = new ExtractionColumn(); 344 column.setFieldPath(fieldPath); 345 _columns.add(column); 346 } 347 } 348 349 /** 350 * Add an optional column to the component 351 * @param fieldPath The column's field path 352 * @param displayOptionalColumnName The name of the variable that manage the display of this optional column 353 */ 354 public void addColumn(String fieldPath, String displayOptionalColumnName) 355 { 356 ExtractionColumn column = new ExtractionColumn(); 357 column.setFieldPath(fieldPath); 358 column.setDisplayOptionalName(displayOptionalColumnName); 359 _columns.add(column); 360 } 361 362 /** 363 * the referenced query's sorts 364 * @return <code>true</code> if the referenced query's sorts are overridden on this component, <code>false</code> otherwise 365 */ 366 public boolean overrideSorts() 367 { 368 return _overrideSorts; 369 } 370 371 /** 372 * Set the boolean to override the referenced query's sorts or not 373 * @param overrideSorts <code>true</code> to override sorts, <code>false</code> otherwise 374 */ 375 public void setOverrideSorts(boolean overrideSorts) 376 { 377 _overrideSorts = overrideSorts; 378 } 379 380 /** 381 * Retrieves the component sorts 382 * @return The component sorts 383 */ 384 public Map<String, Order> getSorts() 385 { 386 return _sortMap; 387 } 388 389 /** 390 * Add sort to the component 391 * @param filedPath Field on which apply sort 392 * @param order sort order to apply for field 393 */ 394 public void addSort(String filedPath, Order order) 395 { 396 _sortMap.put(filedPath, order); 397 } 398}