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