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.plugins.contentstree.ui;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.parameters.Parameters;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.cocoon.acting.ServiceableAction;
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.environment.Redirector;
029import org.apache.cocoon.environment.Request;
030import org.apache.cocoon.environment.SourceResolver;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.cms.contenttype.ContentConstants;
034import org.ametys.cms.contenttype.ContentType;
035import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
036import org.ametys.cms.contenttype.ContentTypesHelper;
037import org.ametys.cms.contenttype.MetadataDefinition;
038import org.ametys.cms.contenttype.MetadataDefinitionHolder;
039import org.ametys.cms.contenttype.MetadataType;
040import org.ametys.cms.contenttype.RepeaterDefinition;
041import org.ametys.cms.repository.Content;
042import org.ametys.core.cocoon.JSonReader;
043import org.ametys.plugins.contentstree.MetadataTreeConfigurationElementsChild;
044import org.ametys.plugins.contentstree.TreeConfiguration;
045import org.ametys.plugins.contentstree.TreeConfigurationContentType;
046import org.ametys.plugins.contentstree.TreeConfigurationElements;
047import org.ametys.plugins.contentstree.TreeConfigurationElementsChild;
048import org.ametys.plugins.contentstree.TreeExtensionPoint;
049import org.ametys.plugins.repository.AmetysObject;
050import org.ametys.plugins.repository.AmetysObjectResolver;
051import org.ametys.plugins.repository.metadata.CompositeMetadata;
052
053/** 
054 * Get the node information to build the tree
055 * Put it in the request attribute to use the json reader
056 *
057 */
058public class GetTreeNodeAction extends ServiceableAction
059{
060    /** The ametys object resolver instance */
061    protected AmetysObjectResolver _ametysResolver;
062    /** The tree configuration EP instance */
063    protected TreeExtensionPoint _treeExtensionPoint;
064    /** The content type EP instance */
065    protected ContentTypeExtensionPoint _contentTypesEP;
066    /** The content types helper instance */
067    protected ContentTypesHelper _contentTypesHelper;
068
069    @Override
070    public void service(ServiceManager smanager) throws ServiceException
071    {
072        super.service(smanager);
073        
074        _ametysResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
075        _treeExtensionPoint = (TreeExtensionPoint) smanager.lookup(TreeExtensionPoint.ROLE);
076        _contentTypesEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
077        _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
078    }
079    
080    @Override
081    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
082    {
083        @SuppressWarnings("unchecked")
084        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
085        
086        TreeConfiguration treeConfiguration = _getTreeConfiguration((String) jsParameters.get("tree"));
087        Content parentContent = _getParentContent((String) jsParameters.get("contentId"));
088        
089        Map<String, Object> result = contentToJSON(parentContent, treeConfiguration);
090        
091        Request request = ObjectModelHelper.getRequest(objectModel);
092        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
093        
094        return EMPTY_MAP;
095    }
096    
097    private TreeConfiguration _getTreeConfiguration(String treeId)
098    {
099        if (StringUtils.isBlank(treeId))
100        {
101            throw new IllegalArgumentException("The tree information cannot be obtain, because 'tree' is null");
102        }
103
104        TreeConfiguration treeConfiguration = _treeExtensionPoint.getExtension(treeId);
105        if (treeConfiguration == null)
106        {
107            throw new IllegalArgumentException("There is no tree configuration for '" + treeId + "'");
108        }
109        return treeConfiguration;
110    }
111
112    private Content _getParentContent(String parentId) throws IllegalArgumentException
113    {
114        if (StringUtils.isBlank(parentId))
115        {
116            throw new IllegalArgumentException("The tree information cannot be obtain, because 'node' is null"); 
117        }
118        
119        try
120        {
121            return _ametysResolver.resolveById(parentId);
122        }
123        catch (Exception e)
124        {
125            throw new IllegalArgumentException("The tree configuration cannot be used on an object that is not a content: " + parentId, e);
126        }
127
128    }
129 
130   /**
131    * Gets the content to JSON format
132    * @param content The content
133    * @param treeConfiguration The configuration of the tree
134    * @return The content properties
135    */
136    protected Map<String, Object> contentToJSON(Content content, TreeConfiguration treeConfiguration)
137    {
138        Map<String, Object> infos = new HashMap<>();
139        
140        Map<String, List<Content>> childrenContent = _getChildrendContent(content, treeConfiguration);
141
142        List<Map<String, Object>> childrenInfos = new ArrayList<>();
143        infos.put("children", childrenInfos);
144        
145        for (String metadataName : childrenContent.keySet())
146        {
147            for (Content childContent : childrenContent.get(metadataName))
148            {
149                Map<String, Object> childInfo = new HashMap<>();
150                childrenInfos.add(childInfo);
151                
152                childInfo.put("contentId", childContent.getId());
153                childInfo.put("metadataPath", metadataName);
154                childInfo.put("contenttypesIds", childContent.getTypes());
155                childInfo.put("name", childContent.getName()); 
156                childInfo.put("title", childContent.getTitle());
157                childInfo.put("lang", childContent.getLanguage()); 
158    
159                childInfo.put("iconGlyph", _contentTypesHelper.getIconGlyph(childContent));
160                childInfo.put("iconDecorator", _contentTypesHelper.getIconDecorator(childContent));
161                childInfo.put("iconSmall", _contentTypesHelper.getSmallIcon(childContent));
162                childInfo.put("iconMedium", _contentTypesHelper.getMediumIcon(childContent));
163                childInfo.put("iconLarge", _contentTypesHelper.getLargeIcon(childContent));
164    
165                childInfo.putAll(getAdditionalContentInfos(childContent));
166                
167                Map<String, List<Content>> grandchildrenContent = _getChildrendContent(childContent, treeConfiguration);
168                if (grandchildrenContent.size() == 0)
169                {
170                    List<Map<String, Object>> subinfos = new ArrayList<>();
171                    childInfo.put("children", subinfos);
172                }
173            }
174        }
175
176        return infos;
177    }
178
179    /**
180     * Get the additional infos on content
181     * @param content The content
182     * @return The additionnal infos in a Map
183     */
184    protected Map<String, Object> getAdditionalContentInfos (Content content)
185    {
186        return new HashMap<>();
187    }
188    
189    private Map<String, List<Content>> _getChildrendContent(Content parentContent, TreeConfiguration treeConfiguration)
190    {
191        Map<String, List<Content>> childrenContent = new HashMap<>();
192        
193        // For each content type of the content
194        for (String contentTypeId : parentContent.getTypes())
195        {
196            // Loop over all possible elements for the tree
197            for (TreeConfigurationElements treeConfigurationElements : treeConfiguration.getElements())
198            {
199                // Check for a match between the element and the content type of the content
200                for (TreeConfigurationContentType treeConfigurationContentType : treeConfigurationElements.getContentTypesConfiguration())
201                {
202                    if (treeConfigurationContentType.getContentTypesIds().contains(contentTypeId))
203                    {
204                        ContentType contentType = _contentTypesEP.getExtension(contentTypeId);
205                        
206                        // Add all required children for this element
207                        for (TreeConfigurationElementsChild treeConfigurationElementsChild : treeConfigurationElements.getChildren())
208                        {
209                            if (treeConfigurationElementsChild instanceof MetadataTreeConfigurationElementsChild)
210                            {
211                                // Get the metadata
212                                Map<String, List<Content>> contents = _handleMetadataTreeConfigurationElementsChild(contentType, parentContent.getMetadataHolder(), (MetadataTreeConfigurationElementsChild) treeConfigurationElementsChild, treeConfiguration);
213                                _merge(childrenContent, contents);
214                            }
215                            else
216                            {
217                                throw new IllegalArgumentException("The child configuration element class <" + treeConfigurationElementsChild + "> is not supported in tree '" + treeConfiguration.getId() + "'");
218                            }
219                        }
220                    }
221                }
222                
223            }
224        }
225        
226        return childrenContent;
227    }
228
229    private void _merge(Map<String, List<Content>> childrenContent, Map<String, List<Content>> contents)
230    {
231        for (String key : contents.keySet())
232        {
233            if (!childrenContent.containsKey(key))
234            {
235                childrenContent.put(key, new ArrayList<Content>());
236            }
237            
238            List<Content> contentsList = childrenContent.get(key);
239            contentsList.addAll(contents.get(key));
240        }
241    }
242
243    private Map<String, List<Content>> _handleMetadataTreeConfigurationElementsChild(ContentType contentType, CompositeMetadata metadataHolder, MetadataTreeConfigurationElementsChild metadataTreeConfigurationElementsChild, TreeConfiguration treeConfiguration)
244    {
245        Map<String, List<Content>> childrenContent = new HashMap<>();
246        
247        String metadataId = metadataTreeConfigurationElementsChild.getId();
248
249        try
250        {
251            Map<String, List<Content>> contents = _handleMetadata(contentType, metadataHolder, metadataId, "");
252            _merge(childrenContent, contents);
253        }
254        catch (Exception e)
255        {
256            throw new IllegalArgumentException("An error occured on the tree configuration '" + treeConfiguration.getId() + "' getting for metadata '" + metadataId + "' on content type '" + contentType.getId() + "'", e);  
257        }
258
259        return childrenContent;
260    }
261
262    private Map<String, List<Content>> _handleMetadata(MetadataDefinitionHolder metadataDefHolder, CompositeMetadata metadataHolder, String metadataId, String absoluteMetadataName)
263    {
264        Map<String, List<Content>> childrenContent = new HashMap<>();
265
266        String metadataName = StringUtils.substringBefore(metadataId, ContentConstants.METADATA_PATH_SEPARATOR);
267        
268        MetadataDefinition subMetadataDef = metadataDefHolder.getMetadataDefinition(metadataName);
269        if (subMetadataDef == null)
270        {
271            throw new IllegalArgumentException("No metadata definition for " + metadataName);
272        }
273        
274        
275        if (metadataHolder.hasMetadata(metadataName))
276        {
277            if (subMetadataDef instanceof RepeaterDefinition)
278            {
279                CompositeMetadata metadata = metadataHolder.getCompositeMetadata(subMetadataDef.getName());
280                for (String entryName : metadata.getMetadataNames())
281                {
282                    CompositeMetadata entry = metadata.getCompositeMetadata(entryName);
283                    String subMetadataId = StringUtils.substringAfter(metadataId, ContentConstants.METADATA_PATH_SEPARATOR);
284                    
285                    Map<String, List<Content>> contents = _handleMetadata(subMetadataDef, entry, subMetadataId, absoluteMetadataName + metadataName + ContentConstants.METADATA_PATH_SEPARATOR + entryName + ContentConstants.METADATA_PATH_SEPARATOR);
286                    _merge(childrenContent, contents);
287                }
288            }
289            else if (subMetadataDef.getType() == MetadataType.COMPOSITE)
290            {
291                CompositeMetadata metadata = metadataHolder.getCompositeMetadata(subMetadataDef.getName());
292                String subMetadataId = StringUtils.substringAfter(metadataId, ContentConstants.METADATA_PATH_SEPARATOR);
293                
294                Map<String, List<Content>> contents = _handleMetadata(subMetadataDef, metadata, subMetadataId, absoluteMetadataName + metadataName + ContentConstants.METADATA_PATH_SEPARATOR);
295                _merge(childrenContent, contents);
296            }
297            else if (subMetadataDef.getType() == MetadataType.CONTENT)
298            {
299                String[] contentIds = metadataHolder.getStringArray(metadataName);
300                for (String contentId : contentIds)
301                {
302                    Content refContent = _ametysResolver.resolveById(contentId);
303                    
304                    String key = absoluteMetadataName + metadataName;
305                    if (!childrenContent.containsKey(key))
306                    {
307                        childrenContent.put(key, new ArrayList<Content>());
308                    }
309                    childrenContent.get(key).add(refContent);
310                }
311            }
312            else if (subMetadataDef.getType() == MetadataType.SUB_CONTENT)
313            {
314                for (AmetysObject ametysObject : metadataHolder.getObjectCollection(metadataName).getChildren())
315                {
316                    if (ametysObject instanceof Content)
317                    {
318                        Content subContent = (Content) ametysObject;
319
320                        String key = absoluteMetadataName + metadataName;
321                        if (!childrenContent.containsKey(key))
322                        {
323                            childrenContent.put(key, new ArrayList<Content>());
324                        }
325                        childrenContent.get(key).add(subContent);
326                    }
327                }
328            }
329            else 
330            {
331                throw new IllegalArgumentException("The metadata definition for " + absoluteMetadataName + metadataName + " is not a content");
332            }
333        }
334        
335        return childrenContent;
336    }
337}