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