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        Optional<String> queryType = Optional.of("queryType")
076                                             .map(jsParameters::get)
077                                             .filter(String.class::isInstance)
078                                             .map(String.class::cast);
079        
080        if (!"read_access".equals(profileId) && !"write_access".equals(profileId) && !"right_access".equals(profileId))
081        {
082            throw new IllegalArgumentException("Unexpected profile identifier : " + profileId);
083        }
084        
085        QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase());
086        QueryContainer parentContainer = _getParentContainer(jsParameters);
087        Boolean onlyContainers = (Boolean) jsParameters.getOrDefault("onlyContainers", Boolean.FALSE);
088        Boolean onlyQueries = onlyContainers ? false : (Boolean) jsParameters.getOrDefault("onlyQueries", Boolean.FALSE);
089        Boolean testIfEmptyContainer = (Boolean) jsParameters.getOrDefault("testIfEmptyContainer", Boolean.FALSE);
090        Boolean allDescendants = (Boolean) jsParameters.getOrDefault("allDescendants", Boolean.FALSE);
091        
092        UserIdentity user = _userProvider.getUser();
093        
094        List<Map<String, Object>> nodes = new ArrayList<>();
095        Stream<Query> childQueriesStream = onlyContainers
096                ? Stream.empty()
097                : _getChildQueries(parentContainer, !allDescendants, queryType, profile, user);
098        childQueriesStream
099                .map(_queryDAO::getQueryProperties)
100                .forEach(nodes::add);
101        
102        Stream<Pair<QueryContainer, AdditionalInfoOnContainer>> childContainersStream = onlyQueries
103                ? Stream.empty()
104                : _getChildQueryContainers(parentContainer, testIfEmptyContainer, queryType, profile, user);
105        
106        childContainersStream
107                .forEach(p ->
108                {
109                    QueryContainer queryContainer = p.getLeft();
110                    Map<String, Object> properties = _queryDAO.getQueryContainerProperties(queryContainer );
111                    AdditionalInfoOnContainer additionalInfo = p.getRight();
112                    additionalInfo.fillAdditionalInfo(properties);
113                    switch (profile)
114                    {
115                        case WRITE_ACCESS:
116                            if ((boolean) properties.get(QueryDAO.WRITE_ACCESS_PROPERTY) || _queryDAO.hasAnyWritableDescendant(user, queryContainer))
117                            {
118                                nodes.add(properties);
119                            }
120                            break;
121                        case RIGHT_ACCESS:
122                            if ((boolean) properties.get(QueryDAO.EDIT_RIGHTS_ACCESS_PROPERTY) || _queryDAO.hasAnyAssignableDescendant(user, queryContainer))
123                            {
124                                nodes.add(properties);
125                            }
126                            break;
127                        case READ_ACCESS:
128                        default:
129                            if ((boolean) properties.get(QueryDAO.READ_ACCESS_PROPERTY) || _queryDAO.hasAnyReadableDescendant(user, queryContainer))
130                            {
131                                nodes.add(properties);
132                            }
133                            break;
134                    }
135                });
136        
137        Map<String, Object> result = new HashMap<>();
138        result.put("queries", nodes);
139        
140        Request request = ObjectModelHelper.getRequest(objectModel);
141        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
142        
143        return EMPTY_MAP;
144    }
145    
146    private QueryContainer _getParentContainer(Map jsParameters)
147    {
148        Predicate<String> isNotRoot = Predicates.not(Predicates.equalTo(QueryDAO.ROOT_QUERY_CONTAINER_ID));
149        QueryContainer parent = Optional.of("node")
150            .map(jsParameters::get)
151            .filter(String.class::isInstance)
152            .map(String.class::cast)
153            .filter(isNotRoot)
154            .map(_resolver::<QueryContainer>resolveById)
155            .orElse(_queryDAO.getQueriesRootNode()/* root node must exist, so it must be created if it does not exist yet */);
156        return parent;
157    }
158    
159    private Stream<Pair<QueryContainer, AdditionalInfoOnContainer>> _getChildQueryContainers(QueryContainer parentContainer, boolean testIfEmptyContainer, Optional<String> queryType, QueryProfile profile, UserIdentity user)
160    {
161        AmetysObjectIterable<QueryContainer> childQueryContainers = _queryDAO.getChildQueryContainers(parentContainer);
162        
163        Stream<QueryContainer> childQueryContainersStream = childQueryContainers.stream();
164        if (testIfEmptyContainer)
165        {
166            // indicates if container do not have (not necessarily direct) query children (i.e. is empty)
167            return childQueryContainersStream
168                    .map(ct -> Pair.of(ct, new AdditionalInfoOnContainer(_hasNoDescendant(ct, queryType, profile, user))));
169        }
170        else
171        {
172            return childQueryContainersStream
173                    .map(ct -> Pair.of(ct, new AdditionalInfoOnContainer()));
174        }
175    }
176    
177    private Boolean _hasNoDescendant(QueryContainer queryContainer, Optional<String> queryType, QueryProfile profile, UserIdentity user)
178    {
179        boolean onlyDirectChildren = false; // in order to test no only direct children, but all descendants
180        Stream<Query> childQueries = _getChildQueries(queryContainer, onlyDirectChildren, queryType, profile, user);
181        return !childQueries.findAny().isPresent();
182    }
183    
184    private Stream<Query> _getChildQueries(QueryContainer parentContainer, boolean onlyDirectChildren, Optional<String> queryType, QueryProfile profile, UserIdentity user)
185    {
186        Stream<Query> childQueries;
187        switch (profile)
188        {
189            case WRITE_ACCESS:
190                childQueries = _queryDAO.getChildQueriesInWriteAccess(parentContainer, onlyDirectChildren, user, queryType);
191                break;
192            case RIGHT_ACCESS:
193                childQueries = _queryDAO.getChildQueriesInRightAccess(parentContainer, onlyDirectChildren, user, queryType);
194                break;
195            case READ_ACCESS:
196            default:
197                childQueries = _queryDAO.getChildQueriesInReadAccess(parentContainer, onlyDirectChildren, user, queryType);
198                break;
199        }
200        
201        return childQueries;
202    }
203    
204    private static class AdditionalInfoOnContainer
205    {
206        private Optional<Boolean> _hasNoDescendant = Optional.empty();
207        
208        AdditionalInfoOnContainer()
209        {
210        }
211        
212        AdditionalInfoOnContainer(boolean hasNoDescendant)
213        {
214            _hasNoDescendant = Optional.of(hasNoDescendant);
215        }
216        
217        void fillAdditionalInfo(Map<String, Object> props)
218        {
219            _hasNoDescendant.ifPresent(b -> props.put("hasNoDescendantQuery", b));
220        }
221    }
222
223}