001/*
002 *  Copyright 2010 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.repository.jcr;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Date;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import javax.jcr.Node;
026import javax.jcr.NodeIterator;
027import javax.jcr.Property;
028import javax.jcr.PropertyIterator;
029import javax.jcr.RepositoryException;
030import javax.jcr.version.OnParentVersionAction;
031import javax.jcr.version.Version;
032import javax.jcr.version.VersionException;
033import javax.jcr.version.VersionHistory;
034import javax.jcr.version.VersionIterator;
035
036import org.ametys.core.group.GroupIdentity;
037import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys;
038import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.plugins.repository.AmetysObject;
041import org.ametys.plugins.repository.AmetysRepositoryException;
042import org.ametys.plugins.repository.ModifiableACLAmetysObject;
043import org.ametys.plugins.repository.UnknownAmetysObjectException;
044import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
045import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
046import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
047import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
048import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject;
049import org.ametys.plugins.repository.version.VersionableAmetysObject;
050
051/**
052 * Default implementation of a {@link JCRAmetysObject}, which is also a {@link VersionableAmetysObject}.
053 * @param <F> the actual type of factory.
054 */
055public class DefaultAmetysObject<F extends DefaultAmetysObjectFactory> extends SimpleAmetysObject<F> implements ModifiableDataAwareVersionableAmetysObject, ModifiableACLAmetysObject
056{
057    
058    /** Properties that are auto-created or protected, which mustn't be copied when copying a node. */
059    protected static final List<String> PROTECTED_PROPERTIES = Arrays.asList("jcr:uuid",
060            "jcr:primaryType", "jcr:predecessors", "jcr:versionHistory", "jcr:baseVersion");
061    
062    // Root JCR Node of this content
063    private Node _baseNode;
064
065    // pointed version, or null if HEAD
066    private Version _versionNode;
067    
068    // Current version, either HEAD or a given version
069    private Node _currentNode;
070    
071    // The version history of the base node
072    private VersionHistory _versionHistory;
073
074    /**
075     * Creates an {@link DefaultAmetysObject}.
076     * @param node the node backing this {@link AmetysObject}
077     * @param parentPath the parentPath in the Ametys hierarchy
078     * @param factory the DefaultAmetysObjectFactory which created the AmetysObject
079     */
080    public DefaultAmetysObject(Node node, String parentPath, F factory)
081    {
082        super(node, parentPath, factory);
083        _baseNode = node;
084        _currentNode = node;
085    }
086    
087    @Override
088    public Node getNode()
089    {
090        return _currentNode;
091    }
092    
093    /**
094     * Returns the JCR node backing this {@link AmetysObject} in the default JCR workspace
095     * @return the JCR node backing this {@link AmetysObject} in the default JCR workspace
096     */
097    public Node getBaseNode ()
098    {
099        return _baseNode;
100    }
101
102    // Versioning capabilities
103    
104    /**
105     * Returns the JCR {@link VersionHistory} of the base node.
106     * @return the JCR {@link VersionHistory} of the base node.
107     * @throws RepositoryException if something wrong occurs retrieving the VersionHistory.
108     */
109    protected VersionHistory getVersionHistory() throws RepositoryException
110    {
111        if (_versionHistory == null)
112        {
113            _versionHistory = _baseNode.getSession().getWorkspace().getVersionManager().getVersionHistory(_baseNode.getPath());
114        }
115        
116        return _versionHistory;
117    }
118    
119    /**
120     * Returns the JCR base version of the node.
121     * @return the JCR base version of the node.
122     * @throws RepositoryException if something wrong occurs retrieving the base version.
123     */
124    protected Version getBaseVersion() throws RepositoryException
125    {
126        return _baseNode.getSession().getWorkspace().getVersionManager().getBaseVersion(_baseNode.getPath());
127    }
128
129    public void checkpoint() throws AmetysRepositoryException
130    {
131        try
132        {
133            getNode().getSession().getWorkspace().getVersionManager().checkpoint(getNode().getPath());
134        }
135        catch (RepositoryException e)
136        {
137            throw new AmetysRepositoryException("Unable to checkpoint", e);
138        }
139    }
140    
141    public void switchToLabel(String label) throws UnknownAmetysObjectException, AmetysRepositoryException
142    {
143        if (label == null)
144        {
145            // back to current version
146            _versionNode = null;
147            _currentNode = _baseNode;
148        }
149        else
150        {
151            try
152            {
153                VersionHistory history = getVersionHistory();
154                _versionNode = history.getVersionByLabel(label);
155                _currentNode = _versionNode.getFrozenNode();
156            }
157            catch (VersionException e)
158            {
159                throw new UnknownAmetysObjectException("There's no label : " + label, e);
160            }
161            catch (RepositoryException e)
162            {
163                throw new AmetysRepositoryException("Unable to switch to label : " + label, e);
164            }
165        }
166    }
167    
168    public void switchToRevision(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException
169    {
170        if (revision == null)
171        {
172            // back to current version
173            _versionNode = null;
174            _currentNode = _baseNode;
175        }
176        else
177        {
178            try
179            {
180                VersionHistory history = getVersionHistory();
181                _versionNode = history.getVersion(revision);
182                _currentNode = _versionNode.getNode("jcr:frozenNode");
183            }
184            catch (VersionException e)
185            {
186                throw new UnknownAmetysObjectException("There's no revision : " + revision, e);
187            }
188            catch (RepositoryException e)
189            {
190                throw new AmetysRepositoryException("Unable to switch to revision : " + revision, e);
191            }
192        }
193    }
194    
195    public void restoreFromLabel(String label) throws UnknownAmetysObjectException, AmetysRepositoryException
196    {
197        try
198        {
199            VersionHistory history = getVersionHistory();
200            Node versionNode = history.getVersionByLabel(label);
201            restoreFromNode(versionNode.getNode("jcr:frozenNode"));
202        }
203        catch (RepositoryException e)
204        {
205            throw new AmetysRepositoryException("Unable to restore from label: " + label, e);
206        }
207    }
208    
209    public void restoreFromRevision(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException
210    {
211        try
212        {
213            VersionHistory history = getVersionHistory();
214            Node versionNode = history.getVersion(revision);
215            restoreFromNode(versionNode.getNode("jcr:frozenNode"));
216        }
217        catch (RepositoryException e)
218        {
219            throw new AmetysRepositoryException("Unable to restore from revision: " + revision, e);
220        }
221    }
222    
223    /**
224     * Restore from a node
225     * @param node The node to restore
226     * @throws RepositoryException If error occurs
227     */
228    protected void restoreFromNode(Node node) throws RepositoryException
229    {
230        // Remove all properties and nodes of the current node (except jcr and OnParentVersion=IGNORE).
231        PropertyIterator propIt = _baseNode.getProperties();
232        while (propIt.hasNext())
233        {
234            Property prop = propIt.nextProperty();
235            String propName = prop.getName();
236            if (!propName.startsWith("jcr:") && prop.getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE)
237            {
238                prop.remove();
239            }
240        }
241        
242        NodeIterator nodeIt = _baseNode.getNodes();
243        while (nodeIt.hasNext())
244        {
245            Node childNode = nodeIt.nextNode();
246            String nodeName = childNode.getName();
247            if (!nodeName.startsWith("jcr:") && childNode.getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE)
248            {
249                childNode.remove();
250            }
251        }
252        
253        // Copy all properties and nodes from the given node (except jcr).
254        PropertyIterator newPropIt = node.getProperties();
255        while (newPropIt.hasNext())
256        {
257            Property newProp = newPropIt.nextProperty();
258            
259            if (!newProp.getName().startsWith("jcr:"))
260            {
261                if (newProp.getDefinition().isMultiple())
262                {
263                    _baseNode.setProperty(newProp.getName(), newProp.getValues());
264                }
265                else
266                {
267                    _baseNode.setProperty(newProp.getName(), newProp.getValue());
268                }
269            }
270        }
271        
272        NodeIterator newNodeIt = node.getNodes();
273        while (newNodeIt.hasNext())
274        {
275            Node newNode = newNodeIt.nextNode();
276            
277            if (!newNode.getName().startsWith("jcr:"))
278            {
279                copyNode(_baseNode, newNode);
280            }
281        }
282    }
283    
284    /**
285     * Copy the source node in parent node
286     * @param parentDest The dest node
287     * @param src The source node to copy
288     * @throws RepositoryException If error occurs
289     */
290    protected void copyNode(Node parentDest, Node src) throws RepositoryException
291    {
292        Node dest;
293        if (parentDest.hasNode(src.getName()))
294        {
295            // case of auto created child
296            dest = parentDest.getNode(src.getName());
297        }
298        else
299        {
300            dest = parentDest.addNode(src.getName(), src.getProperty("jcr:frozenPrimaryType").getString());
301        }
302        
303        PropertyIterator pit = src.getProperties();
304        while (pit.hasNext())
305        {
306            Property p = pit.nextProperty();
307            String name = p.getName();
308            
309            // Tests for protected and/or autocreated properties
310            if (!PROTECTED_PROPERTIES.contains(name) && !name.startsWith("jcr:frozen") && !dest.hasProperty(name))
311            {
312                if (p.getDefinition().isMultiple())
313                {
314                    dest.setProperty(name, p.getValues());
315                }
316                else
317                {
318                    dest.setProperty(name, p.getValue());
319                }
320            }
321        }
322        
323        NodeIterator nit = src.getNodes();
324        while (nit.hasNext())
325        {
326            copyNode(dest, nit.nextNode());
327        }
328    }
329
330    public void addLabel(String label, boolean moveIfPresent) throws AmetysRepositoryException
331    {
332        try
333        {
334            VersionHistory history = getVersionHistory();
335            String versionName;
336
337            if (_versionNode == null)
338            {
339                // not sticked to a particular version
340                versionName = getBaseVersion().getName();
341            }
342            else
343            { 
344                // sticked to label
345                versionName = _versionNode.getName();
346            }
347
348            history.addVersionLabel(versionName, label, moveIfPresent);
349        }
350        catch (RepositoryException e)
351        {
352            throw new AmetysRepositoryException("Unable to add label : " + label, e);
353        }
354    }
355
356    public void removeLabel(String label) throws AmetysRepositoryException
357    {
358        try
359        {
360            VersionHistory history = getVersionHistory();
361            history.removeVersionLabel(label);
362        }
363        catch (RepositoryException ex)
364        {
365            throw new AmetysRepositoryException("Unable to remove label : " + label, ex);
366        }
367    }
368
369    public String[] getAllLabels() throws AmetysRepositoryException
370    {
371        try
372        {
373            return getVersionHistory().getVersionLabels();
374        }
375        catch (RepositoryException e)
376        {
377            throw new AmetysRepositoryException("Unable to retrieve list of labels", e);
378        }
379    }
380
381    public String[] getLabels() throws AmetysRepositoryException
382    {
383        try
384        {
385            Version version = _versionNode;
386
387            if (version == null)
388            {
389                // not sticked to a particular version
390                version = getBaseVersion();
391            }
392
393            return version.getContainingHistory().getVersionLabels(version);
394        }
395        catch (RepositoryException e)
396        {
397            throw new AmetysRepositoryException("Unable to retrieve list of labels for current version", e);
398        }
399    }
400
401
402    public String[] getLabels(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException
403    {
404        try
405        {
406            VersionHistory history = getVersionHistory();
407            Version version = history.getVersion(revision);
408            return history.getVersionLabels(version);
409        }
410        catch (VersionException e)
411        {
412            throw new UnknownAmetysObjectException("There's no revision " + revision, e);
413        }
414        catch (RepositoryException e)
415        {
416            throw new AmetysRepositoryException("Unable to retrieve list of labels for current version", e);
417        }
418    }
419
420    public String getRevision() throws AmetysRepositoryException
421    {
422        if (_versionNode == null)
423        {
424            // Current version
425            return null;
426        }
427
428        try
429        {
430            return _versionNode.getName();
431        }
432        catch (RepositoryException e)
433        {
434            throw new AmetysRepositoryException("Unable to get revision", e);
435        }
436    }
437    
438    @Override
439    public Date getRevisionTimestamp() throws AmetysRepositoryException
440    {
441        if (_versionNode == null)
442        {
443            // Current version
444            return null;
445        }
446
447        try
448        {
449            return _versionNode.getCreated().getTime();
450        }
451        catch (RepositoryException e)
452        {
453            throw new AmetysRepositoryException("Unable to get revision date", e);
454        }
455    }
456    
457    @Override
458    public Date getRevisionTimestamp(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException
459    {
460        try
461        {
462            VersionHistory history = getVersionHistory();
463            Version version = history.getVersion(revision);
464            
465            return version.getCreated().getTime();
466        }
467        catch (VersionException e)
468        {
469            throw new UnknownAmetysObjectException("There's no revision " + revision, e);
470        }
471        catch (RepositoryException e)
472        {
473            throw new AmetysRepositoryException("Unable to get revision date", e);
474        }
475    }
476
477    public String[] getAllRevisions() throws AmetysRepositoryException
478    {
479        try
480        {
481            List<String> revisions = new ArrayList<>();
482            VersionIterator iterator = getVersionHistory().getAllVersions();
483            
484            while (iterator.hasNext())
485            {
486                String name = iterator.nextVersion().getName();
487                if (!"jcr:rootVersion".equals(name))
488                {
489                    revisions.add(name);
490                }
491            }
492
493            return revisions.toArray(new String[revisions.size()]);
494        }
495        catch (RepositoryException ex)
496        {
497            throw new AmetysRepositoryException("Unable to get revisions list", ex);
498        }
499    }
500
501    @Override
502    public ModifiableModelLessDataHolder getUnversionedDataHolder()
503    {
504        try
505        {
506            ModifiableRepositoryData repositoryData = new JCRRepositoryData(_baseNode.getNode("ametys-internal:unversioned"));
507            return new DefaultModifiableModelLessDataHolder(_getFactory().getUnversionedDataTypeExtensionPoint(), repositoryData);
508        }
509        catch (RepositoryException e)
510        {
511            throw new AmetysRepositoryException(e);
512        }
513    }
514    
515    public Map<AnonymousOrAnyConnectedKeys, Set<String>> getProfilesForAnonymousAndAnyConnectedUser()
516    {
517        return ACLJCRAmetysObjectHelper.getProfilesForAnonymousAndAnyConnectedUser(getNode());
518    }
519    
520    public Map<GroupIdentity, Map<UserOrGroup, Set<String>>> getProfilesForGroups(Set<GroupIdentity> groups)
521    {
522        return ACLJCRAmetysObjectHelper.getProfilesForGroups(getNode(), groups);
523    }
524    
525    public Map<UserIdentity, Map<UserOrGroup, Set<String>>> getProfilesForUsers(UserIdentity user)
526    {
527        return ACLJCRAmetysObjectHelper.getProfilesForUsers(getNode(), user);
528    }
529    
530    public void addAllowedProfilesForAnyConnectedUser(Set<String> profileIds)
531    {
532        ACLJCRAmetysObjectHelper.addAllowedProfilesForAnyConnectedUser(getNode(), profileIds);
533    }
534    
535    public void removeAllowedProfilesForAnyConnectedUser(Set<String> profileIds)
536    {
537        ACLJCRAmetysObjectHelper.removeAllowedProfilesForAnyConnectedUser(getNode(), profileIds);
538    }
539    
540    public void addDeniedProfilesForAnyConnectedUser(Set<String> profileIds)
541    {
542        ACLJCRAmetysObjectHelper.addDeniedProfilesForAnyConnectedUser(getNode(), profileIds);
543    }
544    
545    public void removeDeniedProfilesForAnyConnectedUser(Set<String> profileIds)
546    {
547        ACLJCRAmetysObjectHelper.removeDeniedProfilesForAnyConnectedUser(getNode(), profileIds);
548    }
549    
550    public void addAllowedProfilesForAnonymous(Set<String> profileIds)
551    {
552        ACLJCRAmetysObjectHelper.addAllowedProfilesForAnonymous(getNode(), profileIds);
553    }
554    
555    public void removeAllowedProfilesForAnonymous(Set<String> profileIds)
556    {
557        ACLJCRAmetysObjectHelper.removeAllowedProfilesForAnonymous(getNode(), profileIds);
558    }
559    
560    public void addDeniedProfilesForAnonymous(Set<String> profileIds)
561    {
562        ACLJCRAmetysObjectHelper.addDeniedProfilesForAnonymous(getNode(), profileIds);
563    }
564    
565    public void removeDeniedProfilesForAnonymous(Set<String> profileIds)
566    {
567        ACLJCRAmetysObjectHelper.removeDeniedProfilesForAnonymous(getNode(), profileIds);
568    }
569    
570    public void addAllowedUsers(Set<UserIdentity> users, String profileId)
571    {
572        ACLJCRAmetysObjectHelper.addAllowedUsers(users, getNode(), profileId);
573    }
574    
575    public void removeAllowedUsers(Set<UserIdentity> users, String profileId)
576    {
577        ACLJCRAmetysObjectHelper.removeAllowedUsers(users, getNode(), profileId);
578    }
579    
580    public void removeAllowedUsers(Set<UserIdentity> users)
581    {
582        ACLJCRAmetysObjectHelper.removeAllowedUsers(users, getNode());
583    }
584    
585    public void addAllowedGroups(Set<GroupIdentity> groups, String profileId)
586    {
587        ACLJCRAmetysObjectHelper.addAllowedGroups(groups, getNode(), profileId);
588    }
589    
590    public void removeAllowedGroups(Set<GroupIdentity> groups, String profileId)
591    {
592        ACLJCRAmetysObjectHelper.removeAllowedGroups(groups, getNode(), profileId);
593    }
594    
595    public void removeAllowedGroups(Set<GroupIdentity> groups)
596    {
597        ACLJCRAmetysObjectHelper.removeAllowedGroups(groups, getNode());
598    }
599    
600    public void addDeniedUsers(Set<UserIdentity> users, String profileId)
601    {
602        ACLJCRAmetysObjectHelper.addDeniedUsers(users, getNode(), profileId);
603    }
604    
605    public void removeDeniedUsers(Set<UserIdentity> users, String profileId)
606    {
607        ACLJCRAmetysObjectHelper.removeDeniedUsers(users, getNode(), profileId);
608    }
609    
610    public void removeDeniedUsers(Set<UserIdentity> users)
611    {
612        ACLJCRAmetysObjectHelper.removeDeniedUsers(users, getNode());
613    }
614    
615    public void addDeniedGroups(Set<GroupIdentity> groups, String profileId)
616    {
617        ACLJCRAmetysObjectHelper.addDeniedGroups(groups, getNode(), profileId);
618    }
619    
620    public void removeDeniedGroups(Set<GroupIdentity> groups, String profileId)
621    {
622        ACLJCRAmetysObjectHelper.removeDeniedGroups(groups, getNode(), profileId);
623    }
624    
625    public void removeDeniedGroups(Set<GroupIdentity> groups)
626    {
627        ACLJCRAmetysObjectHelper.removeDeniedGroups(groups, getNode());
628    }
629    
630    public boolean isInheritanceDisallowed()
631    {
632        return ACLJCRAmetysObjectHelper.isInheritanceDisallowed(getNode());
633    }
634    
635    public void disallowInheritance(boolean disallow)
636    {
637        ACLJCRAmetysObjectHelper.disallowInheritance(getNode(), disallow);
638    }
639}