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}