001/* 002 * Copyright 2018 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.content.referencetable.search; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.LinkedHashMap; 022import java.util.LinkedHashSet; 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.parameters.Parameters; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.acting.ServiceableAction; 033import org.apache.cocoon.environment.ObjectModelHelper; 034import org.apache.cocoon.environment.Redirector; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.SourceResolver; 037import org.apache.commons.lang3.ArrayUtils; 038import org.apache.commons.lang3.StringUtils; 039 040import org.ametys.cms.content.ContentHelper; 041import org.ametys.cms.contenttype.ContentType; 042import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 043import org.ametys.cms.contenttype.ContentTypesHelper; 044import org.ametys.cms.data.type.ModelItemTypeConstants; 045import org.ametys.cms.languages.Language; 046import org.ametys.cms.languages.LanguagesManager; 047import org.ametys.cms.repository.Content; 048import org.ametys.cms.repository.WorkflowAwareContent; 049import org.ametys.core.cocoon.JSonReader; 050import org.ametys.core.util.DateUtils; 051import org.ametys.core.util.JSONUtils; 052import org.ametys.core.util.ServerCommHelper; 053import org.ametys.plugins.core.user.UserHelper; 054import org.ametys.plugins.repository.AmetysObjectResolver; 055import org.ametys.plugins.workflow.support.WorkflowProvider; 056import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 057import org.ametys.runtime.i18n.I18nizableText; 058 059import com.opensymphony.workflow.loader.StepDescriptor; 060import com.opensymphony.workflow.loader.WorkflowDescriptor; 061 062/** 063 * Action for getting information about the referencing contents of a content 064 * 065 */ 066public class GetReferencingContentsAction extends ServiceableAction 067{ 068 /** The extension point for content types */ 069 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 070 /** Content types helper */ 071 protected ContentTypesHelper _contentTypesHelper; 072 /** The content helper */ 073 protected ContentHelper _contentHelper; 074 /** Server comm helper */ 075 protected ServerCommHelper _serverCommHelper; 076 /** Language Manager */ 077 protected LanguagesManager _languagesManager; 078 /** Workflow provider */ 079 protected WorkflowProvider _workflowProvider; 080 /** User helper */ 081 protected UserHelper _userHelper; 082 /** The ametys object resolver */ 083 protected AmetysObjectResolver _resolver; 084 /** JSON Utils */ 085 protected JSONUtils _jsonUtils; 086 087 @Override 088 public void service(ServiceManager smanager) throws ServiceException 089 { 090 super.service(smanager); 091 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 092 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 093 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 094 _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE); 095 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 096 _languagesManager = (LanguagesManager) smanager.lookup(LanguagesManager.ROLE); 097 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 098 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 099 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 100 } 101 102 @Override 103 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 104 { 105 Map<String, Object> result = new HashMap<>(); 106 Request request = ObjectModelHelper.getRequest(objectModel); 107 108 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 109 110 String contentId = (String) jsParameters.get("contentId"); 111 112 int begin = jsParameters.containsKey("start") ? (Integer) jsParameters.get("start") : 0; // Index of search 113 int offset = jsParameters.containsKey("limit") ? (Integer) jsParameters.get("limit") : Integer.MAX_VALUE; // Number of results to SAX 114 115 Set<String> filteredCTypes = _getFilteredContentTypes(jsParameters); 116 117 String lang = (String) jsParameters.get("lang"); 118 Locale defaultLocale = StringUtils.isNotEmpty(lang) ? new Locale(lang) : null; 119 120 Set<Content> referencingContents = new LinkedHashSet<>(); 121 List<Map<String, Object>> referencingContentsJson = new ArrayList<>(); 122 123 int index = 0; 124 referencingContents.addAll(getReferencingContents(contentId, true, filteredCTypes)); 125 126 for (Content content : referencingContents) 127 { 128 if (index >= begin && index < begin + offset) 129 { 130 referencingContentsJson.add(getContentData(content, defaultLocale)); 131 } 132 133 index++; 134 } 135 136 result.put("contents", referencingContentsJson); 137 result.put("total", referencingContents.size()); 138 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 139 return EMPTY_MAP; 140 } 141 142 @SuppressWarnings("unchecked") 143 private Set<String> _getFilteredContentTypes(Map<String, Object> jsParameters) 144 { 145 String jsonFilter = (String) jsParameters.get("filter"); 146 if (!StringUtils.isEmpty(jsonFilter)) 147 { 148 List<Object> filters = _jsonUtils.convertJsonToList(jsonFilter); 149 150 return (Set<String>) filters.stream() 151 .filter(Map.class::isInstance) 152 .map(Map.class::cast) 153 .filter(filter -> "contentTypes".equals(filter.get("property"))) 154 .map(filter -> filter.get("value")) 155 .filter(List.class::isInstance) 156 .map(List.class::cast) 157 .flatMap(List::stream) 158 .collect(Collectors.toSet()); 159 } 160 else 161 { 162 return Set.of(); 163 } 164 } 165 166 /** 167 * Get the contents referencing the entry of a reference table 168 * @param contentId The id of the entry 169 * @param excludeContentOfSameType true to exclude the contents of the same reference table 170 * @param filteredCTypes The filtered content types. If empty no filter on content types should be applied 171 * @return The referencing contents 172 */ 173 protected Collection<Content> getReferencingContents (String contentId, boolean excludeContentOfSameType, Set<String> filteredCTypes) 174 { 175 Content content = _resolver.resolveById(contentId); 176 177 Collection<Content> referencingContents = content.getReferencingContents(); 178 179 if (excludeContentOfSameType || !filteredCTypes.isEmpty()) 180 { 181 return referencingContents.stream() 182 .filter(refContent -> !excludeContentOfSameType || _isNotSameType(content, refContent)) 183 .filter(refContent -> filteredCTypes.isEmpty() || _filterContentType(refContent, filteredCTypes)) 184 .collect(Collectors.toCollection(LinkedHashSet::new)); 185 } 186 else 187 { 188 return referencingContents; 189 } 190 } 191 192 private boolean _filterContentType(Content refContent, Set<String> filteredCTypes) 193 { 194 for (String cType : filteredCTypes) 195 { 196 if (_contentTypesHelper.isInstanceOf(refContent, cType)) 197 { 198 return true; 199 } 200 } 201 202 return false; 203 } 204 205 private boolean _isNotSameType(Content content, Content refContent) 206 { 207 String[] ctypes = content.getTypes(); 208 String[] refCTypes = refContent.getTypes(); 209 210 for (String ctype : ctypes) 211 { 212 if (ArrayUtils.contains(refCTypes, ctype)) 213 { 214 return false; 215 } 216 } 217 218 return true; 219 } 220 221 /** 222 * Get the JSON representation of content 223 * @param content The content 224 * @param defaultLocale The default locale 225 * @return The content data 226 */ 227 protected Map<String, Object> getContentData(Content content, Locale defaultLocale) 228 { 229 Map<String, Object> contentData = new HashMap<>(); 230 231 contentData.put("id", content.getId()); 232 contentData.put("name", content.getName()); 233 234 if (_contentHelper.isMultilingual(content) && ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(content.getType(Content.ATTRIBUTE_TITLE).getId())) 235 { 236 contentData.put("title", _contentHelper.getTitleVariants(content)); 237 } 238 else 239 { 240 contentData.put("title", _contentHelper.getTitle(content)); 241 } 242 243 contentData.put("language", _language2json(content)); 244 contentData.put("contentTypes", _contentTypes2json(content)); 245 contentData.put("iconGlyph", _contentTypesHelper.getIconGlyph(content)); 246 contentData.put("iconDecorator", _contentTypesHelper.getIconDecorator(content)); 247 contentData.put("smallIcon", _contentTypesHelper.getSmallIcon(content)); 248 contentData.put("mediumIcon", _contentTypesHelper.getMediumIcon(content)); 249 contentData.put("largeIcon", _contentTypesHelper.getLargeIcon(content)); 250 contentData.put("isSimple", _contentHelper.isSimple(content)); 251 contentData.put("lastModified", DateUtils.zonedDateTimeToString(content.getLastModified())); 252 contentData.put("creationDate", DateUtils.zonedDateTimeToString(content.getCreationDate())); 253 contentData.put("creator", _userHelper.user2json(content.getCreator(), true)); 254 contentData.put("contributor", _userHelper.user2json(content.getLastContributor(), true)); 255 contentData.put("workflowStep", _workflowStep2json(content)); 256 257 return contentData; 258 } 259 260 private List<I18nizableText> _contentTypes2json(Content content) 261 { 262 List<I18nizableText> cTypes = new ArrayList<>(); 263 for (String cTypeId : content.getTypes()) 264 { 265 ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); 266 if (cType != null) 267 { 268 cTypes.add(cType.getLabel()); 269 } 270 else if (getLogger().isWarnEnabled()) 271 { 272 getLogger().warn(String.format("Trying to get the label for an unknown content type : '%s'.", cTypeId)); 273 } 274 } 275 276 return cTypes; 277 } 278 279 private Map<String, Object> _language2json(Content content) 280 { 281 Map<String, Object> infos = new LinkedHashMap<>(); 282 283 String languageCode = content.getLanguage(); 284 infos.put("code", languageCode); 285 286 if (languageCode != null) 287 { 288 Language language = _languagesManager.getLanguage(languageCode); 289 if (language != null) 290 { 291 infos.put("icon", language.getSmallIcon()); 292 infos.put("label", language.getLabel()); 293 } 294 } 295 296 return infos; 297 } 298 299 private Map<String, Object> _workflowStep2json(Content content) 300 { 301 Map<String, Object> workflowInfos = new LinkedHashMap<>(); 302 303 if (content instanceof WorkflowAwareContent) 304 { 305 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 306 int currentStepId = Math.toIntExact(waContent.getCurrentStepId()); 307 308 StepDescriptor stepDescriptor = _getStepDescriptor(waContent, currentStepId); 309 310 if (stepDescriptor != null) 311 { 312 I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName()); 313 314 workflowInfos.put("stepId", currentStepId); 315 workflowInfos.put("name", workflowStepName); 316 317 String[] icons = new String[] {"small", "medium", "large"}; 318 for (String icon : icons) 319 { 320 if ("application".equals(workflowStepName.getCatalogue())) 321 { 322 workflowInfos.put(icon + "Icon", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-" + icon + ".png"); 323 } 324 else 325 { 326 String pluginName = workflowStepName.getCatalogue().substring("plugin.".length()); 327 workflowInfos.put(icon + "Icon", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-" + icon + ".png"); 328 } 329 } 330 } 331 } 332 333 return workflowInfos; 334 } 335 336 private StepDescriptor _getStepDescriptor(WorkflowAwareContent content, int stepId) 337 { 338 long workflowId = content.getWorkflowId(); 339 340 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 341 342 String workflowName = workflow.getWorkflowName(workflowId); 343 WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName); 344 345 if (workflowDescriptor != null) 346 { 347 StepDescriptor stepDescriptor = workflowDescriptor.getStep(stepId); 348 if (stepDescriptor != null) 349 { 350 return stepDescriptor; 351 } 352 else if (getLogger().isWarnEnabled()) 353 { 354 getLogger().warn("Unknown step id '" + stepId + "' for workflow for name : " + workflowName); 355 } 356 } 357 else if (getLogger().isWarnEnabled()) 358 { 359 getLogger().warn("Unknown workflow for name : " + workflowName); 360 } 361 362 return null; 363 } 364}