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.Date;
019import java.util.GregorianCalendar;
020import java.util.HashSet;
021import java.util.Set;
022
023import javax.jcr.Node;
024import javax.jcr.NodeIterator;
025import javax.jcr.RepositoryException;
026
027import org.apache.commons.collections.CollectionUtils;
028import org.apache.commons.lang.StringUtils;
029
030import org.ametys.core.group.GroupIdentity;
031import org.ametys.core.user.UserIdentity;
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.repository.RepositoryConstants;
035import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
036import org.ametys.runtime.i18n.I18nizableText;
037
038/**
039 * Class representing a query, backed by a JCR node.<br>
040 */
041public class Query extends DefaultAmetysObject<QueryFactory>
042{
043    /** Property name for query title */
044    public static final String TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":label";
045    /** Property name for query type */
046    public static final String TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":type";
047    /** Property name for query description */
048    public static final String DESCRIPTION = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":description";
049    /** Property name for query content */
050    public static final String CONTENT = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":content";
051    /** Property name for query author */
052    public static final String AUTHOR = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":author";
053    /** Property name for query last modification date */
054    public static final String LASTMODIFICATIONDATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":lastModificationDate";
055
056    /** Property name for query visibility */
057    public static final String VISIBILITY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":visibility";
058    /** Property name for query read access */
059    public static final String PROFILE_READ_ACCESS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":read-access";
060    /** Property name for query write access */
061    public static final String PROFILE_WRITE_ACCESS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":write-access";
062    /** Property name for query groups */
063    public static final String GROUPS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":groups";
064    /** Property name for query users */
065    public static final String USERS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":users";
066    
067    /**
068     * Visibility of a Query
069     */
070    public enum Visibility
071    {
072        /** Private visibility. */
073        PRIVATE,
074        /** Shared visibility. */
075        SHARED,
076        /** Public visibility. */
077        PUBLIC;
078        
079        @Override
080        public String toString()
081        {
082            return name().toLowerCase();
083        }
084    }
085    
086    /**
087     * Type of a Query
088     */
089    public enum Type
090    {
091        /** Simple type. */
092        SIMPLE,
093        /** Default type. */
094        ADVANCED,
095        /** Script type. */
096        SCRIPT;
097        
098        @Override
099        public String toString()
100        {
101            return name().toLowerCase();
102        }
103    }
104    
105    /**
106     * Rights profiles
107     */
108    public enum QueryProfile
109    {
110        /** Read access */
111        READ_ACCESS,
112        /** Write access */
113        WRITE_ACCESS;
114        
115        @Override
116        public String toString()
117        {
118            return name().toLowerCase();
119        }
120        
121        /**
122         * Query profile title getter.
123         * @return The title of the profile
124         */
125        public I18nizableText getTitle()
126        {
127            switch (this)
128            {
129                case READ_ACCESS: 
130                    return new I18nizableText("plugin.queries-directory", "PLUGINS_QUERIESDIRECTORY_RIGHT_PROFILE_READ_TITLE");
131                case WRITE_ACCESS: 
132                    return new I18nizableText("plugin.queries-directory", "PLUGINS_QUERIESDIRECTORY_RIGHT_PROFILE_WRITE_TITLE");
133                default: 
134                    return null;
135            }
136        }
137
138        /**
139         * Query profile description getter.
140         * @return The description of the profile
141         */
142        public I18nizableText getDescription()
143        {
144            switch (this)
145            {
146                case READ_ACCESS: 
147                    return new I18nizableText("plugin.queries-directory", "PLUGINS_QUERIESDIRECTORY_RIGHT_PROFILE_READ_DESCRIPTION");
148                case WRITE_ACCESS: 
149                    return new I18nizableText("plugin.queries-directory", "PLUGINS_QUERIESDIRECTORY_RIGHT_PROFILE_READ_DESCRIPTION");
150                default: 
151                    return null;
152            }
153        }
154    }
155    
156    /**
157     * Creates an {@link Query}.
158     * @param node the node backing this {@link AmetysObject}
159     * @param parentPath the parentPath in the Ametys hierarchy
160     * @param factory the DefaultAmetysObjectFactory which created the AmetysObject
161     */
162    public Query(Node node, String parentPath, QueryFactory factory)
163    {
164        super(node, parentPath, factory);
165    }
166    
167    
168    /**
169     * Set the title of this query.
170     * @param title the description
171     */
172    public void setTitle (String title)
173    {
174        try
175        {
176            getNode().setProperty(TITLE, title != null ? title : StringUtils.EMPTY);
177        }
178        catch (RepositoryException e)
179        {
180            throw new AmetysRepositoryException("Error while setting title property.", e);
181        }
182    }
183    
184    /**
185     * Set the type of this query.
186     * @param type the description
187     */
188    public void setType (String type)
189    {
190        try
191        {
192            getNode().setProperty(TYPE, type != null ? type : StringUtils.EMPTY);
193        }
194        catch (RepositoryException e)
195        {
196            throw new AmetysRepositoryException("Error while setting type property.", e);
197        }
198    }
199    
200    /**
201     * Set the description of this query.
202     * @param description the description
203     */
204    public void setDescription (String description)
205    {
206        try
207        {
208            getNode().setProperty(DESCRIPTION, description != null ? description : StringUtils.EMPTY);
209        }
210        catch (RepositoryException e)
211        {
212            throw new AmetysRepositoryException("Error while setting description property.", e);
213        }
214    }
215    
216    /**
217     * Set the content of this query.
218     * @param content the content
219     */
220    public void setContent (String content)
221    {
222        try
223        {
224            getNode().setProperty(CONTENT, content != null ? content : StringUtils.EMPTY);
225        }
226        catch (RepositoryException e)
227        {
228            throw new AmetysRepositoryException("Error while setting content property.", e);
229        }
230    }
231    
232    /**
233     * Set the author of this query.
234     * @param author the author
235     */
236    public void setAuthor(UserIdentity author)
237    {
238        try
239        {
240            Node authorNode = _getOrCreateNode(getNode(), AUTHOR, "ametys:user");
241            authorNode.setProperty("ametys:login", author.getLogin());
242            authorNode.setProperty("ametys:population", author.getPopulationId());
243        }
244        catch (RepositoryException e)
245        {
246            throw new AmetysRepositoryException("Error setting the author property.", e);
247        }
248    }
249    
250    /**
251     * Set the date of the last modification.
252     * @param lastModificationDate the last modification date to set.
253     */
254    public void setLastModificationDate(Date lastModificationDate)
255    {
256        try
257        {
258            GregorianCalendar cal = new GregorianCalendar();
259            cal.setTime(lastModificationDate);
260            
261            getNode().setProperty(LASTMODIFICATIONDATE, cal);
262        }
263        catch (RepositoryException e)
264        {
265            throw new AmetysRepositoryException("Error setting the last modification date property.", e);
266        }
267    }
268    
269    /**
270     * Set the query visibility
271     * @param visibility the visibility
272     */
273    public void setVisibility (Visibility visibility)
274    {
275        try
276        {
277            getNode().setProperty(VISIBILITY, visibility.name());
278        }
279        catch (RepositoryException e)
280        {
281            throw new AmetysRepositoryException("Error while setting title property.", e);
282        }
283    }
284    
285    /**
286     * Get the title of the query
287     * @return The title
288     */
289    public String getTitle()
290    {        
291        try
292        {
293            if (getNode().hasProperty(TITLE))
294            {
295                return getNode().getProperty(TITLE).getValue().getString();
296            }
297            else
298            {
299                return StringUtils.EMPTY;
300            }
301        }
302        catch (RepositoryException e)
303        {
304            throw new AmetysRepositoryException("Error while getting title property.", e);
305        }
306    }
307    
308    /**
309     * Get the type of the query
310     * @return The type
311     */
312    public String getType()
313    {        
314        try
315        {
316            if (getNode().hasProperty(TYPE))
317            {
318                return getNode().getProperty(TYPE).getValue().getString();
319            }
320            else
321            {
322                return StringUtils.EMPTY;
323            }
324        }
325        catch (RepositoryException e)
326        {
327            throw new AmetysRepositoryException("Error while getting type property.", e);
328        }
329    }
330    
331    
332    /**
333     * Get the description of the query
334     * @return The description
335     */
336    public String getDescription()
337    {
338        try
339        {
340            if (getNode().hasProperty(DESCRIPTION))
341            {
342                return getNode().getProperty(DESCRIPTION).getValue().getString();
343            }
344            else
345            {
346                return StringUtils.EMPTY;
347            }
348        }
349        catch (RepositoryException e)
350        {
351            throw new AmetysRepositoryException("Error while getting description property.", e);
352        }
353    }
354    
355    /**
356     * Get the content of the query
357     * @return The content
358     */
359    public String getContent()
360    {
361        try
362        {
363            if (getNode().hasProperty(CONTENT))
364            {
365                return getNode().getProperty(CONTENT).getValue().getString();
366            }
367            else
368            {
369                return StringUtils.EMPTY;
370            }
371        }
372        catch (RepositoryException e)
373        {
374            throw new AmetysRepositoryException("Error while getting content property.", e);
375        }
376    }
377    
378    
379    /**
380     * Get the author of the query
381     * @return The author
382     */
383    public UserIdentity getAuthor ()
384    {
385        try
386        {
387            Node authorNode = getNode().getNode(AUTHOR);
388            return new UserIdentity(authorNode.getProperty("ametys:login").getString(), authorNode.getProperty("ametys:population").getString());
389        }
390        catch (RepositoryException e)
391        {
392            throw new AmetysRepositoryException("Error while getting author property.", e);
393        }
394    }
395    
396    /**
397     * Get the visibility of the query
398     * @return The visibility
399     */
400    public Visibility getVisibility()
401    {
402        try
403        {
404            return Visibility.valueOf(getNode().getProperty(VISIBILITY).getValue().getString());
405        }
406        catch (RepositoryException e)
407        {
408            throw new AmetysRepositoryException("Error while getting visibility property.", e);
409        }
410    }
411    
412    /**
413     * Get the granted users
414     * @param profile teh query profile
415     * @return the logins of granted users
416     */
417    public Set<UserIdentity> getGrantedUsers(QueryProfile profile)
418    {
419        try
420        {
421            String profileNodeName = null;
422                    
423            switch (profile)
424            {
425                case READ_ACCESS:
426                    profileNodeName = PROFILE_READ_ACCESS;
427                    break;
428                case WRITE_ACCESS:
429                    profileNodeName = PROFILE_WRITE_ACCESS;
430                    break;
431                default:
432                    throw new AmetysRepositoryException("Unexisting Query profile : " + profile);
433            }
434            
435            Node profileNode = _getOrCreateNode(getNode(), profileNodeName, "nt:unstructured");
436            Set<UserIdentity> grantedUsers = new HashSet<>();
437            
438            grantedUsers.add(this.getAuthor());
439            
440            if (profileNode.hasNode(USERS))
441            {
442                NodeIterator it = profileNode.getNodes(USERS);
443                while (it.hasNext())
444                {
445                    Node next = (Node) it.next();
446                    grantedUsers.add(new UserIdentity(next.getProperty("ametys:login").getString(), next.getProperty("ametys:population").getString()));
447                }
448            }
449            
450            return grantedUsers;
451        }
452        catch (RepositoryException e)
453        {
454            throw new AmetysRepositoryException("Error while getting grantedUsers property.", e);
455        }
456    }
457    
458    /**
459     * Get the granted groups
460     * @param profile the query profile
461     * @return the granted groups
462     */
463    public Set<GroupIdentity> getGrantedGroups(QueryProfile profile)
464    {
465        try
466        {
467            String profileNodeName = null;
468                    
469            switch (profile)
470            {
471                case READ_ACCESS:
472                    profileNodeName = PROFILE_READ_ACCESS;
473                    break;
474                case WRITE_ACCESS:
475                    profileNodeName = PROFILE_WRITE_ACCESS;
476                    break;
477                default:
478                    throw new AmetysRepositoryException("Unexisting Query profile : " + profile);
479            }
480            
481            Node profileNode = _getOrCreateNode(getNode(), profileNodeName, "nt:unstructured");
482            Set<GroupIdentity> grantedGroups = new HashSet<>();
483            
484            if (profileNode.hasNode(GROUPS))
485            {
486                NodeIterator it = profileNode.getNodes(GROUPS);
487                while (it.hasNext())
488                {
489                    Node next = (Node) it.next();
490                    grantedGroups.add(new GroupIdentity(next.getProperty("ametys:groupId").getString(), next.getProperty("ametys:groupDirectory").getString()));
491                }
492            }
493            
494            return grantedGroups;
495        }
496        catch (RepositoryException e)
497        {
498            throw new AmetysRepositoryException("Error while getting grantedGroups property.", e);
499        }
500    }
501    
502    /**
503     * Determines if an user has READ access to this query.
504     * @param user The user
505     * @return <code>true</code> if the user has read access, <code>false</code> otherwise
506     */
507    public boolean canRead(UserIdentity user)
508    {
509        Visibility visibility = getVisibility();
510        
511        switch (visibility)
512        {
513            case PRIVATE:
514                return getAuthor().equals(user);
515                
516            case SHARED:
517                return getGrantedUsers(QueryProfile.READ_ACCESS).contains(user) || CollectionUtils.containsAny(getGrantedGroups(QueryProfile.READ_ACCESS), _getFactory()._getGroupManager().getUserGroups(user)) || canWrite(user);
518
519            case PUBLIC:
520            default:
521                return true;
522        }
523    }
524    
525    /**
526     * Determines if an user has WRITE access to this query.
527     * @param user The user
528     * @return <code>true</code> if the user has write access, <code>false</code> otherwise
529     */
530    public boolean canWrite(UserIdentity user)
531    {
532        Visibility visibility = getVisibility();
533        
534        switch (visibility)
535        {
536            case PRIVATE:
537                return getAuthor().equals(user);
538                
539            case SHARED:
540                return getGrantedUsers(QueryProfile.WRITE_ACCESS).contains(user) || CollectionUtils.containsAny(getGrantedGroups(QueryProfile.WRITE_ACCESS), _getFactory()._getGroupManager().getUserGroups(user));
541
542            case PUBLIC:
543            default:
544                return true;
545        }
546    }
547    
548    /**
549     * Set the granted users
550     * @param profile the query profile
551     * @param users the granted users
552     */
553    public void setGrantedUsers (QueryProfile profile, Set<UserIdentity> users)
554    {
555        try
556        {
557            String profileNodeName = null;
558            
559            switch (profile)
560            {
561                case READ_ACCESS:
562                    profileNodeName = PROFILE_READ_ACCESS;
563                    break;
564                case WRITE_ACCESS:
565                    profileNodeName = PROFILE_WRITE_ACCESS;
566                    break;
567                default:
568                    throw new AmetysRepositoryException("Unexisting query profile : " + profile);
569            }
570            
571            Node profileNode = _getOrCreateNode(getNode(), profileNodeName, "nt:unstructured");
572            
573            NodeIterator it = profileNode.getNodes(USERS);
574            while (it.hasNext())
575            {
576                Node next = (Node) it.next();
577                next.remove();
578            }
579            
580            UserIdentity author = this.getAuthor();
581            users.remove(author);
582            
583            for (UserIdentity userIdentity : users)
584            {
585                Node userNode = profileNode.addNode(USERS, "ametys:user");
586                userNode.setProperty("ametys:login", userIdentity.getLogin());
587                userNode.setProperty("ametys:population", userIdentity.getPopulationId());
588            }
589        }
590        catch (RepositoryException e)
591        {
592            throw new AmetysRepositoryException("Error while setting users property.", e);
593        }
594    }
595    
596    /**
597     * Set the granted groups
598     * @param profile the query profile
599     * @param groups the granted groups
600     */
601    public void setGrantedGroups(QueryProfile profile, Set<GroupIdentity> groups)
602    {
603        try
604        {
605            String profileNodeName = null;
606            
607            switch (profile)
608            {
609                case READ_ACCESS:
610                    profileNodeName = PROFILE_READ_ACCESS;
611                    break;
612                case WRITE_ACCESS:
613                    profileNodeName = PROFILE_WRITE_ACCESS;
614                    break;
615                default:
616                    throw new AmetysRepositoryException("Unexisting query profile : " + profile);
617            }
618            
619            Node profileNode = _getOrCreateNode(getNode(), profileNodeName, "nt:unstructured");
620            
621            NodeIterator it = profileNode.getNodes(GROUPS);
622            while (it.hasNext())
623            {
624                Node next = (Node) it.next();
625                next.remove();
626            }
627            
628            for (GroupIdentity groupIdentity : groups)
629            {
630                Node groupNode = profileNode.addNode(GROUPS, "ametys:group");
631                groupNode.setProperty("ametys:groupId", groupIdentity.getId());
632                groupNode.setProperty("ametys:groupDirectory", groupIdentity.getDirectoryId());
633            }
634        }
635        catch (RepositoryException e)
636        {
637            throw new AmetysRepositoryException("Error while setting groups property.", e);
638        }
639    }
640    
641    private Node _getOrCreateNode(Node parent, String name, String type) throws RepositoryException
642    {
643        Node node = null;
644        
645        if (parent.hasNode(name))
646        {
647            node = parent.getNode(name);
648        }
649        else if (type != null)
650        {
651            node = parent.addNode(name, type);
652        }
653        else
654        {
655            node = parent.addNode(name);
656        }
657        
658        return node;
659    }
660    
661    /**
662     * Get the date of the last modification of the query
663     * @return The date
664     */
665    public Date getLastModificationDate()
666    {
667        try
668        {
669            Date value = null;
670            
671            if (getNode().hasProperty(LASTMODIFICATIONDATE))
672            {
673                return getNode().getProperty(LASTMODIFICATIONDATE).getValue().getDate().getTime();
674            }
675            
676            return value;
677        }
678        catch (RepositoryException e)
679        {
680            throw new AmetysRepositoryException("Error getting the last modification date property.", e);
681        }
682    }
683}