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