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