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}