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;
017
018import java.util.Optional;
019import java.util.Set;
020
021import javax.jcr.RepositoryException;
022
023import org.apache.jackrabbit.util.ISO9075;
024
025import org.ametys.core.group.GroupIdentity;
026import org.ametys.core.user.UserIdentity;
027import org.ametys.plugins.repository.AmetysRepositoryException;
028
029/**
030 * Helper for manipulating {@link Query}
031 *
032 */
033public final class QueryHelper
034{
035    private QueryHelper()
036    {
037        // Private
038    }
039    
040    /**
041     * Creates the XPath query to get all query containers
042     * @param queryContainer The {@link QueryContainer}, defining the context from which getting children 
043     * @return The XPath query
044     */
045    static String getXPathForQueryContainers(QueryContainer queryContainer)
046    {
047        return _getXPathQuery(queryContainer, true, ObjectToReturn.QUERY_CONTAINER, Optional.empty(), Optional.empty(), null);
048    }
049    
050    /**
051     * Creates the XPath query to get all queries for administrator
052     * @param queryContainer The {@link QueryContainer}, defining the context from which getting children 
053     * @param onlyDirectChildren <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path
054     * @param type The query type
055     * @return The XPath query
056     */
057    static String getXPathForQueriesForAdministrator(QueryContainer queryContainer, boolean onlyDirectChildren, Optional<String> type)
058    {
059        return _getXPathQuery(queryContainer, onlyDirectChildren, ObjectToReturn.QUERY, Optional.empty(), type, null);
060    }
061    
062    /**
063     * Creates the XPath query to get all queries in READ access
064     * @param queryContainer The {@link QueryContainer}, defining the context from which getting children 
065     * @param onlyDirectChildren <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path
066     * @param visibility The user and its groups for checking visibility
067     * @param type The query type
068     * @return The XPath query
069     */
070    static String getXPathForQueriesInReadAccess(QueryContainer queryContainer, boolean onlyDirectChildren, Visibility visibility, Optional<String> type)
071    {
072        return _getXPathQuery(queryContainer, onlyDirectChildren, ObjectToReturn.QUERY, Optional.of(visibility), type, true);
073    }
074    
075    /**
076     * Creates the XPath query to get all queries in WRITE access
077     * @param queryContainer The {@link QueryContainer}, defining the context from which getting children 
078     * @param onlyDirectChildren <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path
079     * @param visibility The user and its groups for checking visibility
080     * @param type The query type
081     * @return The XPath query
082     */
083    static String getXPathForQueriesInWriteAccess(QueryContainer queryContainer, boolean onlyDirectChildren, Visibility visibility, Optional<String> type)
084    {
085        return _getXPathQuery(queryContainer, onlyDirectChildren, ObjectToReturn.QUERY, Optional.of(visibility), type, false);
086    }
087    
088    private static StringBuilder _getParentPath(QueryContainer queryContainer)
089    {
090        try
091        {
092            StringBuilder parentPath = new StringBuilder("/jcr:root")
093                    .append(ISO9075.encodePath(queryContainer.getNode().getPath()));
094            return parentPath;
095        }
096        catch (RepositoryException e)
097        {
098            throw new AmetysRepositoryException(e);
099        }
100    }
101    
102    private static String _getXPathQuery(QueryContainer queryContainer, boolean onlyDirectChildren, ObjectToReturn objectToReturn, Optional<Visibility> visibility, Optional<String> type, Boolean readAccess)
103    {
104        StringBuilder parentPath = _getParentPath(queryContainer);
105        
106        final String slashOrDoubleSlash = onlyDirectChildren ? "/" : "//";
107        StringBuilder query = parentPath
108                .append(slashOrDoubleSlash)
109                .append("element(*, ")
110                .append(objectToReturn.toNodetype())
111                .append(")");
112        
113        if (visibility.isPresent() || type.isPresent())
114        {
115            query.append('[');
116        }
117        
118        type.ifPresent(t -> query.append("@").append(Query.TYPE).append("='").append(t).append("'"));
119        
120        if (visibility.isPresent())
121        {
122            type.ifPresent(t -> query.append(" and ("));
123            
124            _appendPublicPredicate(query);
125            
126            UserIdentity user = visibility.get().getUser();
127            String login = user.getLogin();
128            String populationId = user.getPopulationId();
129            _appendPrivatePredicate(query, login, populationId);
130            
131            Set<GroupIdentity> groups = visibility.get().getGroups();
132            _appendSharedPredicate(query, login, populationId, groups, readAccess);
133            
134            type.ifPresent(t -> query.append(')'));
135        }
136        
137        
138        if (visibility.isPresent() || type.isPresent())
139        {
140            query.append(']');
141        }
142        
143        return query.toString();
144    }
145    
146    private static void _appendPublicPredicate(StringBuilder query)
147    {
148        query.append("(@").append(Query.VISIBILITY).append("='").append(Query.Visibility.PUBLIC.name())
149             .append("') or ");
150    }
151    
152    private static void _appendPrivatePredicate(StringBuilder query, String login, String populationId)
153    {
154        query.append("(@").append(Query.VISIBILITY).append("='").append(Query.Visibility.PRIVATE.name())
155             .append("' and ");
156        _appendUserCondition(query, login, populationId);
157        query.append(") or ");
158    }
159    
160    private static void _appendSharedPredicate(StringBuilder query, String login, String populationId, Set<GroupIdentity> groups, boolean readAccess)
161    {
162        query.append("(@").append(Query.VISIBILITY).append("='").append(Query.Visibility.SHARED.name())
163             .append("' and (");
164        query.append("(");
165        _appendUserCondition(query, login, populationId);
166        query.append(") or ");
167        
168        if (readAccess)
169        {
170            _appendUserAccessCondition(query, login, populationId, Query.PROFILE_READ_ACCESS);
171            query.append(" or ");
172        }
173        _appendUserAccessCondition(query, login, populationId, Query.PROFILE_WRITE_ACCESS);
174        
175        _appendGroupsCondition(query, groups, readAccess);
176        query.append("))");
177    }
178    
179    private static void _appendUserCondition(StringBuilder query, String login, String populationId)
180    {
181        query.append('@').append(Query.AUTHOR).append("/ametys:login='").append(login)
182             .append("' and @").append(Query.AUTHOR).append("/ametys:population='").append(populationId)
183             .append('\'');
184    }
185    
186    private static void _appendGroupsCondition(StringBuilder query, Set<GroupIdentity> groups, boolean readAccess)
187    {
188        for (GroupIdentity group : groups)
189        {
190            String groupId = group.getId();
191            String groupDirectory = group.getDirectoryId();
192            
193            if (readAccess)
194            {
195                query.append(" or ");
196                _appendGroupAccessCondition(query, groupId, groupDirectory, Query.PROFILE_READ_ACCESS);
197            }
198            query.append(" or ");
199            _appendGroupAccessCondition(query, groupId, groupDirectory, Query.PROFILE_WRITE_ACCESS);
200        }
201    }
202    
203    private static void _appendUserAccessCondition(StringBuilder query, String login, String populationId, String accessNodeName)
204    {
205        query.append("(").append(accessNodeName).append("/").append(Query.USERS).append("/ametys:login='").append(login)
206             .append("' and ").append(accessNodeName).append("/").append(Query.USERS).append("/ametys:population='").append(populationId)
207             .append("')");
208    }
209    
210    private static void _appendGroupAccessCondition(StringBuilder query, String groupId, String groupDirectory, String accessNodeName)
211    {
212        query.append("(").append(accessNodeName).append("/").append(Query.GROUPS).append("/ametys:groupId='").append(groupId)
213             .append("' and ").append(accessNodeName).append("/").append(Query.GROUPS).append("/ametys:groupDirectory='").append(groupDirectory)
214             .append("')");
215    }
216    
217    /**
218     * The user and its groups for checking visibility
219     */
220    public static final class Visibility
221    {
222        private UserIdentity _user;
223        private Set<GroupIdentity> _groups;
224        
225        private Visibility(UserIdentity user, Set<GroupIdentity> groups)
226        {
227            _user = user;
228            _groups = groups;
229        }
230        
231        /**
232         * Creates a new {@link Visibility}
233         * @param user The user
234         * @param groups The user groups
235         * @return the {@link Visibility} wrapper
236         */
237        public static Visibility of(UserIdentity user, Set<GroupIdentity> groups)
238        {
239            return new Visibility(user, groups);
240        }
241        
242        UserIdentity getUser()
243        {
244            return _user;
245        }
246        
247        Set<GroupIdentity> getGroups()
248        {
249            return _groups;
250        }
251    }
252    
253    private static enum ObjectToReturn
254    {
255        QUERY,
256        QUERY_CONTAINER;
257        
258        private String _nodetype;
259        
260        static
261        {
262            QUERY._nodetype = QueryFactory.QUERY_NODETYPE;
263            QUERY_CONTAINER._nodetype = QueryContainerFactory.QUERY_CONTAINER_NODETYPE;
264        }
265        
266        String toNodetype()
267        {
268            return _nodetype;
269        }
270    }
271}