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