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