001/* 002 * Copyright 2015 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.contenttype; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027import java.util.stream.Stream; 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.lang.BooleanUtils; 038import org.apache.commons.lang3.StringUtils; 039 040import org.ametys.cms.repository.Content; 041import org.ametys.core.cocoon.JSonReader; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043 044/** 045 * Get the common views between given content types and/or among given contents 046 */ 047public class GetCommonViewsAction extends ServiceableAction 048{ 049 /** The content type EP */ 050 protected ContentTypeExtensionPoint _contentTypeEP; 051 052 /** The content types Helper */ 053 protected ContentTypesHelper _contentTypesHelper; 054 055 /** The ametys object resolver */ 056 protected AmetysObjectResolver _resolver; 057 058 @Override 059 public void service(ServiceManager smanager) throws ServiceException 060 { 061 super.service(smanager); 062 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 063 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 064 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 065 } 066 067 @Override 068 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 069 { 070 Request request = ObjectModelHelper.getRequest(objectModel); 071 072 boolean includeInternals = BooleanUtils.toBoolean(request.getParameter("includeInternals")); // false by default 073 074 Set<String> requestedContentTypeIds = getContentTypes(request); 075 Map<String, Set<String>> contentTypesByContent = getContentTypesFromContents(request); 076 if (requestedContentTypeIds.isEmpty() && contentTypesByContent.keySet().isEmpty()) 077 { 078 requestedContentTypeIds = getAllAvailablesContentTypes(request, true); 079 } 080 081 Map<String, Object> result = new HashMap<>(); 082 083 Collection<Map<String, Object>> commonViewsInfo = getCommonViewsInfo(contentTypesByContent, requestedContentTypeIds, includeInternals); 084 result.put("views", commonViewsInfo); 085 086 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 087 return EMPTY_MAP; 088 } 089 090 /** 091 * Retrieves the common views info by name 092 * @param contentTypesByContent the content type ids by content 093 * @param contentTypeIds the content type ids (not coming from contents) 094 * @param includeInternals <code>true</code> to include internal views, <code>false</code> otherwise 095 * @return The map of views' info 096 */ 097 protected Collection<Map<String, Object>> getCommonViewsInfo(Map<String, Set<String>> contentTypesByContent, Collection<String> contentTypeIds, boolean includeInternals) 098 { 099 Map<String, Map<String, Object>> commonViewsInfo = null; 100 101 for (Set<String> contentTypeIdsForCurrentContent : contentTypesByContent.values()) 102 { 103 List<Map<String, Object>> viewsInfoForCurrentContent = _viewsUnion(contentTypeIdsForCurrentContent, includeInternals); 104 commonViewsInfo = _viewsIntersection(commonViewsInfo, viewsInfoForCurrentContent); 105 } 106 107 for (String contentTypeId : contentTypeIds) 108 { 109 List<Map<String, Object>> viewsInfoForCurrentContentType = _contentTypesHelper.getViewsInfo(contentTypeId, includeInternals); 110 commonViewsInfo = _viewsIntersection(commonViewsInfo, viewsInfoForCurrentContentType); 111 } 112 113 return commonViewsInfo != null ? commonViewsInfo.values() : Collections.EMPTY_LIST; 114 } 115 116 private List<Map<String, Object>> _viewsUnion(Set<String> contentTypes, boolean includeInternals) 117 { 118 List<Map<String, Object>> viewsInfo = new ArrayList<>(); 119 for (String contentType : contentTypes) 120 { 121 viewsInfo.addAll(_contentTypesHelper.getViewsInfo(contentType, includeInternals)); 122 } 123 return viewsInfo; 124 } 125 126 private Map<String, Map<String, Object>> _viewsIntersection(Map<String, Map<String, Object>> commonViewsInfo, List<Map<String, Object>> viewsInfo) 127 { 128 Map<String, Map<String, Object>> result = commonViewsInfo; 129 if (result == null) 130 { 131 result = new HashMap<>(); 132 for (Map<String, Object> entry : viewsInfo) 133 { 134 String viewName = (String) entry.get("name"); 135 result.put(viewName, entry); 136 } 137 } 138 else 139 { 140 Set<String> viewNames = new HashSet<>(); 141 for (Map<String, Object> entry : viewsInfo) 142 { 143 String viewName = (String) entry.get("name"); 144 viewNames.add(viewName); 145 } 146 147 // only retains common metadata (performs a set intersection) 148 result.keySet().retainAll(viewNames); 149 } 150 151 return result; 152 } 153 154 /** 155 * Get the content types id to search for 156 * @param request the request 157 * @return the content types 158 */ 159 protected Set<String> getContentTypes(Request request) 160 { 161 Set<String> contentTypeIds = new HashSet<>(); 162 String[] ids = request.getParameterValues("ids"); 163 164 if (ids != null) 165 { 166 for (String id : ids) 167 { 168 if (StringUtils.isNotEmpty(id)) 169 { 170 // id can contains comma (when allOption is selected for example). 171 for (String idPart : StringUtils.split(id, ',')) 172 { 173 contentTypeIds.add(idPart); 174 } 175 } 176 } 177 } 178 179 return contentTypeIds; 180 } 181 182 /** 183 * Get the content types id to search for (by content id) 184 * @param request the request 185 * @return the content types by content 186 */ 187 protected Map<String, Set<String>> getContentTypesFromContents(Request request) 188 { 189 Map<String, Set<String>> result = new HashMap<>(); 190 191 String[] contentIds = request.getParameterValues("contentIds"); 192 193 if (contentIds != null) 194 { 195 for (String contentId : contentIds) 196 { 197 if (StringUtils.isNotEmpty(contentId)) 198 { 199 Content content = _resolver.resolveById(contentId); 200 201 Set<String> allContentTypes = Stream.concat(Stream.of(content.getTypes()), Stream.of(content.getMixinTypes())) 202 .collect(Collectors.toSet()); 203 result.put(contentId, allContentTypes); 204 } 205 } 206 } 207 208 return result; 209 } 210 211 /** 212 * Get all the available content types 213 * @param request the request 214 * @param publicOnly Only the non private content types will be returned 215 * @return all the available content types 216 */ 217 protected Set<String> getAllAvailablesContentTypes (Request request, boolean publicOnly) 218 { 219 Set<String> types = new HashSet<>(); 220 221 for (String id : _contentTypeEP.getExtensionsIds()) 222 { 223 if (!publicOnly || !_contentTypeEP.getExtension(id).isPrivate()) 224 { 225 types.add(id); 226 } 227 } 228 229 return types; 230 } 231}