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.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;
031
032import org.ametys.cms.content.ContentHelper;
033import org.ametys.cms.contenttype.ContentType;
034import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
035import org.ametys.cms.contenttype.ContentTypesHelper;
036import org.ametys.cms.contenttype.MetadataDefinition;
037import org.ametys.cms.repository.Content;
038import org.ametys.cms.repository.ContentQueryHelper;
039import org.ametys.cms.repository.ContentTypeExpression;
040import org.ametys.core.cocoon.JSonReader;
041import org.ametys.plugins.repository.AmetysObjectIterable;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.EmptyIterable;
044import org.ametys.plugins.repository.query.SortCriteria;
045import org.ametys.plugins.repository.query.expression.AndExpression;
046import org.ametys.plugins.repository.query.expression.Expression;
047import org.ametys.plugins.repository.query.expression.Expression.Operator;
048import org.ametys.plugins.repository.query.expression.MetadataExpression;
049import org.ametys.plugins.repository.query.expression.NotExpression;
050import org.ametys.plugins.repository.query.expression.OrExpression;
051import org.ametys.plugins.repository.query.expression.StringExpression;
052
053/**
054 * Action for getting information about the hierarchy of a reference table
055 */
056public class GetHierarchicalReferenceTablesAction extends ServiceableAction
057{
058    /** The extension point for content types */
059    protected ContentTypeExtensionPoint _contentTypeEP;
060    /** The resolver for Ametys Objects */
061    protected AmetysObjectResolver _resolver;
062    /** The helper component for hierarchical reference tables */
063    protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTablesHelper;
064    /** Content types helper */
065    protected ContentTypesHelper _cTypeHelper;
066    /** The content helper */
067    protected ContentHelper _contentHelper;
068
069    @Override
070    public void service(ServiceManager smanager) throws ServiceException
071    {
072        super.service(smanager);
073        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
074        _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE);
075        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
076        _hierarchicalReferenceTablesHelper = (HierarchicalReferenceTablesHelper) smanager.lookup(HierarchicalReferenceTablesHelper.ROLE);
077        _cTypeHelper = (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        Map<String, Object> result = new HashMap<>();
084        Request request = ObjectModelHelper.getRequest(objectModel);
085        
086        @SuppressWarnings("unchecked")
087        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
088        
089        String contentId = jsParameters != null ? (String) jsParameters.get("contentId") : request.getParameter("contentId");
090        String leafContentTypeId = jsParameters != null ? (String) jsParameters.get("leafContentType") : request.getParameter("leafContentType");
091        ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId);
092        
093        if (leafContentType == null)
094        {
095            getLogger().warn(String.format("Unknown content type id '%s'", leafContentTypeId));
096            return EMPTY_MAP;
097        }
098        
099        ContentType topLevelContentType = _hierarchicalReferenceTablesHelper.getTopLevelType(leafContentType);
100        
101        try (AmetysObjectIterable<Content> contents = "root".equals(contentId) ? _getRootChildren(topLevelContentType) : _getChildContents(contentId))
102        {
103            List<Map<String, Object>> children = new ArrayList<>();
104            for (Content child : contents)
105            {
106                children.add(_contentToJson(child, false, topLevelContentType, leafContentType));
107            }
108            result.put("children", children);
109        }
110        
111        
112        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
113        return EMPTY_MAP;
114    }
115    
116    private AmetysObjectIterable<Content> _getRootChildren(ContentType rootContentType)
117    {
118        Expression expr = new ContentTypeExpression(Operator.EQ, rootContentType.getId());
119        MetadataDefinition parentMetadata = rootContentType.getParentMetadata();
120        
121        if (parentMetadata != null && rootContentType.getId().equals(parentMetadata.getContentType()))
122        {
123            // even if it is the top level type, parentMetadata can be not null if it references itself as parent
124            expr = new AndExpression(expr, new NotExpression(new MetadataExpression(parentMetadata.getName())));
125        }
126        
127        //element(*, ametys:content)[@ametys-internal:contentType = 'foo.contentType' and not(@ametys:pointingMetadata)]
128        
129        String query = ContentQueryHelper.getContentXPathQuery(expr);
130        return _resolver.query(query);
131    }
132    
133    private AmetysObjectIterable<Content> _getChildContents(String contentId)
134    {
135        Content refTableEntry = _resolver.resolveById(contentId);
136        List<ContentType> childContentTypes = _hierarchicalReferenceTablesHelper.getChildContentTypes(refTableEntry);
137        
138        if (!childContentTypes.isEmpty())
139        {
140            List<Expression> exprs = new ArrayList<>();
141            for (ContentType childContentType : childContentTypes)
142            {
143                MetadataDefinition pointingMetadata = childContentType.getParentMetadata();
144                String pointingMetadataName = pointingMetadata.getName();
145                
146                // //element(*, ametys:content)[@ametys-internal:contentType = 'foo.child.contentType' and @ametys:pointingMetadata = 'contentId']
147                Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, childContentType.getId());
148                Expression parentExpr = new StringExpression(pointingMetadataName, Operator.EQ, contentId);
149                
150                exprs.add(new AndExpression(cTypeExpr, parentExpr));
151            }
152            
153            Expression finalExpr = new OrExpression(exprs.toArray(new Expression[exprs.size()]));
154            
155            SortCriteria sortCriteria = new SortCriteria();
156            sortCriteria.addCriterion("title", true, true);
157            String xPathQuery = ContentQueryHelper.getContentXPathQuery(finalExpr, sortCriteria);
158            
159            return _resolver.query(xPathQuery);
160        }
161        
162        return new EmptyIterable<>();
163    }
164    
165    private Map<String, Object> _contentToJson(Content content, boolean full, ContentType topContentType, ContentType leafContentType)
166    {
167        Map<String, Object> map = new HashMap<>();
168        
169        map.put("contentId", content.getId());
170        map.put("contenttypesIds", content.getTypes());
171        map.put("name", content.getName()); 
172        map.put("title", _contentHelper.getTitle(content));
173        map.put("lang", content.getLanguage()); 
174
175        map.put("iconGlyph", _cTypeHelper.getIconGlyph(content));
176        map.put("iconDecorator", _cTypeHelper.getIconDecorator(content));
177        map.put("iconSmall", _cTypeHelper.getSmallIcon(content));
178        map.put("iconMedium", _cTypeHelper.getMediumIcon(content));
179        map.put("iconLarge", _cTypeHelper.getLargeIcon(content));
180        
181        map.put("canBeChecked", _cTypeHelper.isInstanceOf(content, leafContentType.getId()));
182        
183        map.put("leaf", _isLeaf(content, topContentType.getId(), leafContentType.getId()));
184        
185        AmetysObjectIterable<Content> children = _getChildContents(content.getId());
186        if (children.getSize() == 0)
187        {
188            // No child
189            map.put("children", new ArrayList());
190        }
191        else if (full)
192        {
193            List<Map<String, Object>> childrenInfos = new ArrayList<>();
194            for (Content child : children)
195            {
196                childrenInfos.add(_contentToJson(child, false, topContentType, leafContentType));
197            }
198            map.put("children", childrenInfos);
199        }
200        
201        return map;
202    }
203    
204    private boolean _isLeaf(Content content, String topContentTypeId, String leafContentTypeId)
205    {
206        if (_cTypeHelper.isInstanceOf(content, leafContentTypeId))
207        {
208            // a child can be a leaf, or it can be a hierarchy with only one content type, referencing itself as parent
209            // hierarchy with only one content type, referencing itself as parent
210            return !topContentTypeId.equals(leafContentTypeId);
211        }
212        else
213        {
214            return false;
215        }
216    }
217}