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 metadata set between given content types and/or among given contents
046 */
047public class GetCommonMetadataSetAction extends ServiceableAction
048{
049    /** The content type EP */
050    protected ContentTypeExtensionPoint _cTypeEP;
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        _cTypeEP = (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 isEdition = BooleanUtils.toBoolean(request.getParameter("isEdition")); // false by default
073        boolean includeInternal = BooleanUtils.toBoolean(request.getParameter("includeInternal")); // false by default
074        
075        Set<String> requestedCTypeIds = getContentTypes(request);
076        Map<String, Set<String>> cTypesByContent = getContentTypesFromContents(request);
077        if (requestedCTypeIds.isEmpty() && cTypesByContent.keySet().isEmpty())
078        {
079            requestedCTypeIds = getAllAvailablesContentTypes(request, true);
080        }
081        
082        Map<String, Object> result = new HashMap<>();
083        
084        Collection<Map<String, Object>> commonMetadataSetsInfo = getCommonMetadataSetsInfo(cTypesByContent, requestedCTypeIds, isEdition, includeInternal);
085        result.put("metadataSets", commonMetadataSetsInfo);
086        
087        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
088        return EMPTY_MAP;
089    }
090    
091    /**
092     * Retrieves the common metadata labels by name
093     * @param cTypesByContent the content type ids by content
094     * @param cTypeIds the content type ids (not coming from contents)
095     * @param isEdition Is the metadata set for edition ? 
096     * @param includeInternal include internal metadata sets ?
097     * @return The map of metadata set
098     */
099    protected Collection<Map<String, Object>> getCommonMetadataSetsInfo(Map<String, Set<String>> cTypesByContent, Collection<String> cTypeIds, boolean isEdition, boolean includeInternal)
100    {
101        Map<String, Map<String, Object>> commonMetadataSetsInfo = null;
102        
103        for (Set<String> cTypeIdsForCurrentContent : cTypesByContent.values())
104        {
105            List<Map<String, Object>> metadataSetsInfoForCurrentContent = _metadataSetUnion(cTypeIdsForCurrentContent, includeInternal);
106            commonMetadataSetsInfo = _metadataSetIntersection(commonMetadataSetsInfo, metadataSetsInfoForCurrentContent);
107        }
108        
109        for (String cTypeId : cTypeIds)
110        {
111            List<Map<String, Object>> metadataSetsInfoForCurrentCType = _contentTypesHelper.getMetadataSetsInfo(cTypeId, false, includeInternal);
112            commonMetadataSetsInfo = _metadataSetIntersection(commonMetadataSetsInfo, metadataSetsInfoForCurrentCType);
113        }
114        
115        return commonMetadataSetsInfo != null ? commonMetadataSetsInfo.values() : Collections.EMPTY_LIST;
116    }
117    
118    private List<Map<String, Object>> _metadataSetUnion(Set<String> cTypes, boolean includeInternal)
119    {
120        List<Map<String, Object>> metadataSetsInfo = new ArrayList<>();
121        for (String ctype : cTypes)
122        {
123            metadataSetsInfo.addAll(_contentTypesHelper.getMetadataSetsInfo(ctype, false, includeInternal));
124        }
125        return metadataSetsInfo;
126    }
127    
128    private Map<String, Map<String, Object>> _metadataSetIntersection(Map<String, Map<String, Object>> commonMetadataSetsInfo, List<Map<String, Object>> metadataSetsInfo)
129    {
130        Map<String, Map<String, Object>> result = commonMetadataSetsInfo;
131        if (result == null)
132        {
133            result = new HashMap<>();
134            for (Map<String, Object> entry : metadataSetsInfo)
135            {
136                String metadataSetName = (String) entry.get("name");
137                result.put(metadataSetName, entry);
138            }
139        }
140        else
141        {
142            Set<String> metadataSetNames = new HashSet<>();
143            for (Map<String, Object> entry : metadataSetsInfo)
144            {
145                String metadataSetName = (String) entry.get("name");
146                metadataSetNames.add(metadataSetName);
147            }
148            
149            // only retains common metadata (performs a set intersection)
150            result.keySet().retainAll(metadataSetNames);
151        }
152        
153        return result;
154    }
155    
156    /**
157     * Get the content types id to search for
158     * @param request the request
159     * @return the content types
160     */
161    protected Set<String> getContentTypes(Request request)
162    {
163        Set<String> cTypeIds = new HashSet<>();
164        String[] ids = request.getParameterValues("ids");
165        
166        if (ids != null)
167        {
168            for (String id : ids)
169            {
170                if (StringUtils.isNotEmpty(id))
171                {
172                    // id can contains comma (when allOption is selected for example).
173                    for (String idPart : StringUtils.split(id, ','))
174                    {
175                        cTypeIds.add(idPart);
176                    }
177                }
178            }
179        }
180        
181        return cTypeIds;
182    }
183    
184    /**
185     * Get the content types id to search for (by content id)
186     * @param request the request
187     * @return the content types by content
188     */
189    protected Map<String, Set<String>> getContentTypesFromContents(Request request)
190    {
191        Map<String, Set<String>> result = new HashMap<>();
192        
193        String[] contentIds = request.getParameterValues("contentIds");
194        
195        if (contentIds != null)
196        {
197            for (String contentId : contentIds)
198            {
199                if (StringUtils.isNotEmpty(contentId))
200                {
201                    Content content = _resolver.resolveById(contentId);
202                    
203                    Set<String> allContentTypes = Stream.concat(Stream.of(content.getTypes()), Stream.of(content.getMixinTypes()))
204                            .collect(Collectors.toSet());
205                    result.put(contentId, allContentTypes);
206                }
207            }
208        }
209        
210        return result;
211    }
212    
213    /**
214     * Get all the available content types 
215     * @param request the request
216     * @param publicOnly Only the non private content types will be returned
217     * @return all the available content types 
218     */
219    protected Set<String> getAllAvailablesContentTypes (Request request, boolean publicOnly)
220    {
221        Set<String> types = new HashSet<>();
222        
223        for (String id : _cTypeEP.getExtensionsIds())
224        {
225            if (!publicOnly || !_cTypeEP.getExtension(id).isPrivate())
226            {
227                types.add(id);
228            }
229        }
230        
231        return types;
232    }
233}