001/*
002 *  Copyright 2013 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.queriesdirectory.actions;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023import java.util.function.Predicate;
024import java.util.stream.Stream;
025
026import org.apache.avalon.framework.parameters.Parameters;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.acting.ServiceableAction;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.Redirector;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.environment.SourceResolver;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.commons.lang3.tuple.Pair;
036
037import org.ametys.core.cocoon.JSonReader;
038import org.ametys.core.user.CurrentUserProvider;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.plugins.queriesdirectory.Query;
041import org.ametys.plugins.queriesdirectory.Query.QueryProfile;
042import org.ametys.plugins.queriesdirectory.QueryContainer;
043import org.ametys.plugins.queriesdirectory.QueryDAO;
044import org.ametys.plugins.repository.AmetysObjectIterable;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046
047import com.google.common.base.Predicates;
048
049/**
050 * SAX queries
051 *
052 */
053public class GetQueriesAction extends ServiceableAction
054{
055    private CurrentUserProvider _userProvider;
056    private AmetysObjectResolver _resolver;
057    private QueryDAO _queryDAO;
058    
059    
060    @Override
061    public void service(ServiceManager serviceManager) throws ServiceException
062    {
063        super.service(serviceManager);
064        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
065        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
066        _queryDAO = (QueryDAO) serviceManager.lookup(QueryDAO.ROLE);
067    }
068    
069    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
070    {
071        @SuppressWarnings("unchecked")
072        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
073        
074        String profileId = StringUtils.defaultIfEmpty((String) jsParameters.get("profile"), "read_access");
075        
076        @SuppressWarnings("unchecked")
077        List<String> acceptedQueryTypes = (List<String>) jsParameters.get("acceptedQueryTypes");
078        
079        if (!"read_access".equals(profileId) && !"write_access".equals(profileId) && !"right_access".equals(profileId))
080        {
081            throw new IllegalArgumentException("Unexpected profile identifier : " + profileId);
082        }
083        
084        QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase());
085        QueryContainer parentContainer = _getParentContainer(jsParameters);
086        Boolean onlyContainers = (Boolean) jsParameters.getOrDefault("onlyContainers", Boolean.FALSE);
087        Boolean onlyQueries = onlyContainers ? false : (Boolean) jsParameters.getOrDefault("onlyQueries", Boolean.FALSE);
088        Boolean testIfEmptyContainer = (Boolean) jsParameters.getOrDefault("testIfEmptyContainer", Boolean.FALSE);
089        Boolean allDescendants = (Boolean) jsParameters.getOrDefault("allDescendants", Boolean.FALSE);
090        
091        UserIdentity user = _userProvider.getUser();
092        
093        List<Map<String, Object>> nodes = new ArrayList<>();
094        Stream<Query> childQueriesStream = onlyContainers
095                ? Stream.empty()
096                : _getChildQueries(parentContainer, !allDescendants, acceptedQueryTypes, profile, user);
097        childQueriesStream
098                .map(_queryDAO::getQueryProperties)
099                .forEach(nodes::add);
100        
101        Stream<Pair<QueryContainer, AdditionalInfoOnContainer>> childContainersStream = onlyQueries
102                ? Stream.empty()
103                : _getChildQueryContainers(parentContainer, testIfEmptyContainer, acceptedQueryTypes, profile, user);
104        
105        childContainersStream
106                .forEach(p ->
107                {
108                    QueryContainer queryContainer = p.getLeft();
109                    Map<String, Object> properties = _queryDAO.getQueryContainerProperties(queryContainer);
110                    AdditionalInfoOnContainer additionalInfo = p.getRight();
111                    additionalInfo.fillAdditionalInfo(properties);
112                    switch (profile)
113                    {
114                        case WRITE_ACCESS:
115                            if ((boolean) properties.get(QueryDAO.WRITE_ACCESS_PROPERTY) || _queryDAO.hasAnyWritableDescendant(user, queryContainer))
116                            {
117                                nodes.add(properties);
118                            }
119                            break;
120                        case RIGHT_ACCESS:
121                            if ((boolean) properties.get(QueryDAO.EDIT_RIGHTS_ACCESS_PROPERTY) || _queryDAO.hasAnyAssignableDescendant(user, queryContainer))
122                            {
123                                nodes.add(properties);
124                            }
125                            break;
126                        case READ_ACCESS:
127                        default:
128                            if ((boolean) properties.get(QueryDAO.READ_ACCESS_PROPERTY) || _queryDAO.hasAnyReadableDescendant(user, queryContainer))
129                            {
130                                nodes.add(properties);
131                            }
132                            break;
133                    }
134                });
135        
136        Map<String, Object> result = new HashMap<>();
137        result.put("queries", nodes);
138        
139        Request request = ObjectModelHelper.getRequest(objectModel);
140        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
141        
142        return EMPTY_MAP;
143    }
144    
145    private QueryContainer _getParentContainer(Map jsParameters)
146    {
147        Predicate<String> isNotRoot = Predicates.not(Predicates.equalTo(QueryDAO.ROOT_QUERY_CONTAINER_ID));
148        QueryContainer parent = Optional.of("node")
149            .map(jsParameters::get)
150            .filter(String.class::isInstance)
151            .map(String.class::cast)
152            .filter(isNotRoot)
153            .map(_resolver::<QueryContainer>resolveById)
154            .orElse(_queryDAO.getQueriesRootNode()/* root node must exist, so it must be created if it does not exist yet */);
155        return parent;
156    }
157    
158    private Stream<Pair<QueryContainer, AdditionalInfoOnContainer>> _getChildQueryContainers(QueryContainer parentContainer, boolean testIfEmptyContainer, List<String> acceptedQueryTypes, QueryProfile profile, UserIdentity user)
159    {
160        AmetysObjectIterable<QueryContainer> childQueryContainers = _queryDAO.getChildQueryContainers(parentContainer);
161        
162        Stream<QueryContainer> childQueryContainersStream = childQueryContainers.stream();
163        if (testIfEmptyContainer)
164        {
165            // indicates if container do not have (not necessarily direct) query children (i.e. is empty)
166            return childQueryContainersStream
167                    .map(ct -> Pair.of(ct, new AdditionalInfoOnContainer(_hasNoDescendant(ct, acceptedQueryTypes, profile, user))));
168        }
169        else
170        {
171            return childQueryContainersStream
172                    .map(ct -> Pair.of(ct, new AdditionalInfoOnContainer()));
173        }
174    }
175    
176    private Boolean _hasNoDescendant(QueryContainer queryContainer, List<String> acceptedQueryTypes, QueryProfile profile, UserIdentity user)
177    {
178        boolean onlyDirectChildren = false; // in order to test no only direct children, but all descendants
179        Stream<Query> childQueries = _getChildQueries(queryContainer, onlyDirectChildren, acceptedQueryTypes, profile, user);
180        return !childQueries.findAny().isPresent();
181    }
182    
183    private Stream<Query> _getChildQueries(QueryContainer parentContainer, boolean onlyDirectChildren, List<String> acceptedQueryTypes, QueryProfile profile, UserIdentity user)
184    {
185        Stream<Query> childQueries;
186        switch (profile)
187        {
188            case WRITE_ACCESS:
189                childQueries = _queryDAO.getChildQueriesInWriteAccess(parentContainer, onlyDirectChildren, user, acceptedQueryTypes);
190                break;
191            case RIGHT_ACCESS:
192                childQueries = _queryDAO.getChildQueriesInRightAccess(parentContainer, onlyDirectChildren, user, acceptedQueryTypes);
193                break;
194            case READ_ACCESS:
195            default:
196                childQueries = _queryDAO.getChildQueriesInReadAccess(parentContainer, onlyDirectChildren, user, acceptedQueryTypes);
197                break;
198        }
199        
200        return childQueries;
201    }
202    
203    private static class AdditionalInfoOnContainer
204    {
205        private Optional<Boolean> _hasNoDescendant = Optional.empty();
206        
207        AdditionalInfoOnContainer()
208        {
209        }
210        
211        AdditionalInfoOnContainer(boolean hasNoDescendant)
212        {
213            _hasNoDescendant = Optional.of(hasNoDescendant);
214        }
215        
216        void fillAdditionalInfo(Map<String, Object> props)
217        {
218            _hasNoDescendant.ifPresent(b -> props.put("hasNoDescendantQuery", b));
219        }
220    }
221
222}