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}