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