001/*
002 *  Copyright 2017 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;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.parameters.Parameters;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.acting.ServiceableAction;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Redirector;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.environment.SourceResolver;
033import org.apache.commons.io.IOUtils;
034import org.apache.commons.lang.StringUtils;
035import org.apache.excalibur.source.Source;
036
037import org.ametys.cms.content.ContentHelper;
038import org.ametys.cms.contenttype.ContentType;
039import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
040import org.ametys.cms.contenttype.ContentTypesHelper;
041import org.ametys.cms.repository.Content;
042import org.ametys.core.cocoon.JSonReader;
043import org.ametys.plugins.repository.AmetysObjectIterable;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.runtime.i18n.I18nizableText;
046
047/**
048 * Action for getting information about the hierarchy of a reference table
049 */
050public class GetHierarchicalReferenceTablesAction extends ServiceableAction
051{
052    /** The extension point for content types */
053    protected ContentTypeExtensionPoint _contentTypeEP;
054    /** The resolver for Ametys Objects */
055    protected AmetysObjectResolver _ametysResolver;
056    /** The helper component for hierarchical reference tables */
057    protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTablesHelper;
058    /** Content types helper */
059    protected ContentTypesHelper _cTypeHelper;
060    /** The content helper */
061    protected ContentHelper _contentHelper;
062    /** The source resolver */
063    protected org.apache.excalibur.source.SourceResolver _srcResolver;
064
065    @Override
066    public void service(ServiceManager smanager) throws ServiceException
067    {
068        _srcResolver = (org.apache.excalibur.source.SourceResolver) smanager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);
069        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
070        _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE);
071        _ametysResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
072        _hierarchicalReferenceTablesHelper = (HierarchicalReferenceTablesHelper) smanager.lookup(HierarchicalReferenceTablesHelper.ROLE);
073        _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
074    }
075    
076    @Override
077    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
078    {
079        Map<String, Object> result = new HashMap<>();
080        Request request = ObjectModelHelper.getRequest(objectModel);
081        String contextPath = request.getContextPath();
082        
083        @SuppressWarnings("unchecked")
084        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
085        
086        String contentId = jsParameters != null ? (String) jsParameters.get("contentId") : request.getParameter("contentId");
087        String leafContentTypeId = jsParameters != null ? (String) jsParameters.get("leafContentType") : request.getParameter("leafContentType");
088        ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId);
089
090        if (leafContentType == null)
091        {
092            getLogger().warn(String.format("Unknown content type id '%s'", leafContentTypeId));
093            return EMPTY_MAP;
094        }
095        
096        boolean checkSelection = jsParameters != null ? jsParameters.get("checkSelection") != null && (Boolean) jsParameters.get("checkSelection") : false;
097        ContentType topLevelContentType = _hierarchicalReferenceTablesHelper.getTopLevelType(leafContentType);
098        List<Map<String, Object>> children = new ArrayList<>();
099        
100        try (AmetysObjectIterable<Content> contents = _getChildren(contentId, topLevelContentType, leafContentType))
101        {
102            for (Content child : contents)
103            {
104                children.add(_contentToJson(child, false, topLevelContentType, leafContentType, checkSelection, contextPath));
105            }
106        }
107        
108        if ("root".equals(contentId) && _hierarchicalReferenceTablesHelper.supportCandidates(leafContentType))
109        {
110            // Add root for candidates
111            children.add(_rootCandidateToJson(leafContentType, checkSelection));
112        }
113
114        result.put("children", children);
115        
116        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
117        return EMPTY_MAP;
118    }
119    
120    private AmetysObjectIterable<Content> _getChildren(String parentId, ContentType topLevelContentType, ContentType leafContentType)
121    {
122        if ("root".equals(parentId))
123        {
124            return _hierarchicalReferenceTablesHelper.getRootChildren(topLevelContentType, true);
125        }
126        else if ("rootCandidate".equals(parentId))
127        {
128            return _hierarchicalReferenceTablesHelper.getCandidates(leafContentType.getId());
129        }
130        else
131        {
132            return _hierarchicalReferenceTablesHelper.getDirectChildren((Content) _ametysResolver.resolveById(parentId));
133        }
134    }
135    
136    private Map<String, Object> _contentToJson(Content content, boolean full, ContentType topContentType, ContentType leafContentType, boolean checkSelection, String contextPath)
137    {
138        Map<String, Object> map = new HashMap<>();
139        
140        map.put("contentId", content.getId());
141        map.put("contenttypesIds", content.getTypes());
142        map.put("name", content.getName()); 
143        map.put("title", _contentHelper.getTitle(content));
144        map.put("lang", content.getLanguage()); 
145
146        map.put("iconGlyph", _cTypeHelper.getIconGlyph(content));
147        map.put("iconDecorator", _cTypeHelper.getIconDecorator(content));
148        map.put("iconSmall", _cTypeHelper.getSmallIcon(content));
149        map.put("iconMedium", _cTypeHelper.getMediumIcon(content));
150        map.put("iconLarge", _cTypeHelper.getLargeIcon(content));
151        map.put("tooltip", _getTooltip(content));
152        
153        if (checkSelection)
154        {
155            // Check if the node if selectable
156            map.put("disabled", !_cTypeHelper.isInstanceOf(content, leafContentType.getId()));
157        }
158        
159        map.put("leaf", _isLeaf(content, topContentType.getId(), leafContentType.getId()));
160        map.put("isSimple", leafContentType.isSimple());
161        map.put("isCandidate", _hierarchicalReferenceTablesHelper.isCandidate(content));
162        map.put("areCandidatesAllowed", _hierarchicalReferenceTablesHelper.supportCandidates(leafContentType));
163        map.put("mixin", content.getMixinTypes());
164        map.put("type", "content");
165        
166        AmetysObjectIterable<Content> children = _hierarchicalReferenceTablesHelper.getDirectChildren(content);
167        if (children.getSize() == 0)
168        {
169            // No child
170            map.put("children", new ArrayList());
171        }
172        else if (full)
173        {
174            List<Map<String, Object>> childrenInfos = new ArrayList<>();
175            for (Content child : children)
176            {
177                childrenInfos.add(_contentToJson(child, false, topContentType, leafContentType, checkSelection, contextPath));
178            }
179            map.put("children", childrenInfos);
180        }
181        
182        return map;
183    }
184    
185    private String _getTooltip(Content content)
186    {
187        if (_cTypeHelper.getView("tooltip", content.getTypes(), content.getMixinTypes()) != null)
188        {
189            String uri = _contentHelper.getContentHtmlViewUrl(content, "tooltip");
190            Source source = null;
191            try
192            {
193                source = _srcResolver.resolveURI(uri);
194                try (InputStream is = source.getInputStream())
195                {
196                    String tooltip = IOUtils.toString(is, "utf-8");
197                    
198                    // Lets's keep only inside <body>
199                    tooltip = StringUtils.substringAfter(tooltip, "<body");
200                    tooltip = StringUtils.substringAfter(tooltip, ">");
201                    tooltip = StringUtils.substringBefore(tooltip, "</body>");
202                    
203                    return tooltip;
204                }
205            }
206            catch (IOException e)
207            {
208                getLogger().error("Failed to get tooltip view for content " + content.getId(), e);
209            }
210            finally 
211            {
212                _srcResolver.release(source);
213            }
214        }
215        
216        return StringUtils.EMPTY;
217    }
218    
219    private Map<String, Object> _rootCandidateToJson(ContentType leafContentType, boolean checkSelection)
220    {
221        ContentType candidateType = _contentTypeEP.getExtension(HierarchicalReferenceTablesHelper.CANDIDATE_CONTENT_TYPE);
222        Map<String, Object> infos = _cTypeHelper.getContentTypeProperties(candidateType);
223        
224        infos.put("contentId", "rootCandidate");
225        infos.put("contenttypesIds", new String[] {leafContentType.getId()});
226        infos.put("name", "rootCandidate"); 
227        infos.put("title", new I18nizableText("plugin.cms", "PLUGINS_CMS_HIERARCHICAL_REFERENCE_TABLES_CANDIDATES_LABEL"));
228        infos.put("leaf", false);
229        infos.put("type", "root-candidate");
230        
231        if (checkSelection)
232        {
233            // Check if the node if selectable
234            infos.put("disabled", true);
235        }
236        
237        AmetysObjectIterable<Content> candidates = _hierarchicalReferenceTablesHelper.getCandidates(leafContentType.getId());
238        if (candidates.getSize() == 0)
239        {
240            // No child
241            infos.put("children", new ArrayList());
242        }
243        
244        return infos;
245    }
246    
247    private boolean _isLeaf(Content content, String topContentTypeId, String leafContentTypeId)
248    {
249        if (_cTypeHelper.isInstanceOf(content, leafContentTypeId))
250        {
251            // a child can be a leaf, or it can be a hierarchy with only one content type, referencing itself as parent
252            // hierarchy with only one content type, referencing itself as parent
253            return !topContentTypeId.equals(leafContentTypeId);
254        }
255        else
256        {
257            return false;
258        }
259    }
260}