001/*
002 *  Copyright 2012 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 javax.jcr.Node;
019import javax.jcr.PathNotFoundException;
020import javax.jcr.RepositoryException;
021import javax.jcr.Value;
022import javax.jcr.lock.Lock;
023import javax.jcr.lock.LockManager;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.core.user.CurrentUserProvider;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.plugins.repository.AmetysObject;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.RepositoryConstants;
036import org.ametys.plugins.repository.lock.UnlockHelper;
037
038/**
039 * Component that provides methods for lock management on {@link JCRAmetysObject}s.
040 */
041public class LockComponent extends AbstractLogEnabled implements Serviceable, Component
042{
043    
044    /** The avalon component role. */
045    public static final String ROLE = LockComponent.class.getName();
046    
047    /** The unlock helper. */
048    protected UnlockHelper _unlockHelper;
049    
050    /** The current user provider. */
051    protected CurrentUserProvider _currentUserProvider;
052    
053    @Override
054    public void service(ServiceManager manager) throws ServiceException
055    {
056        _unlockHelper = (UnlockHelper) manager.lookup(UnlockHelper.ROLE);
057        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
058    }
059    
060    /**
061     * Register a locked content for automatic unlocking
062     * @param object the locked {@link AmetysObject}
063     */
064    public void addLockedContent(AmetysObject object)
065    {
066        _unlockHelper.scheduleUnlocking(object);
067    }
068    
069    /**
070     * Unregister a locked content for automatic unlocking
071     * @param object the locked {@link AmetysObject}
072     */
073    public void removeLockedContent(AmetysObject object)
074    {
075        _unlockHelper.cancelUnlocking(object);
076    }
077    
078    /**
079     * Provides the current user.
080     * @return the current user.
081     */
082    public UserIdentity getCurrentUser()
083    {
084        return _currentUserProvider.getUser();
085    }
086    
087    /**
088     * Lock a {@link JCRAmetysObject}.
089     * @param object the object to lock.
090     * @throws AmetysRepositoryException if an error occurs.
091     */
092    public void lock(JCRAmetysObject object) throws AmetysRepositoryException
093    {
094        try
095        {
096            Node node = getNode(object);
097            
098            if (!node.isCheckedOut())
099            {
100                throw new AmetysRepositoryException("Unable to lock a checked-in Node");
101            }
102            
103            LockManager lockManager = node.getSession().getWorkspace().getLockManager();
104            
105            Lock lock = lockManager.lock(node.getPath(), false, false, Long.MAX_VALUE, null);
106            node.setProperty(RepositoryConstants.METADATA_LOCKTOKEN, lock.getLockToken());
107            
108            UserIdentity currentUser = getCurrentUser();
109            if (currentUser != null)
110            {
111                node.setProperty(RepositoryConstants.METADATA_LOCKOWNER, UserIdentity.userIdentityToString(currentUser));
112            }
113            node.getSession().save();
114
115            // On détache immédiatement le lock de la session, pour permettre
116            // les utilisations concurrentes
117            lockManager.removeLockToken(lock.getLockToken());
118
119            addLockedContent(object);
120        }
121        catch (RepositoryException ex)
122        {
123            throw new AmetysRepositoryException("Unable to lock object " + object, ex);
124        }
125    }
126    
127    /**
128     * Unlock a {@link JCRAmetysObject}.
129     * @param object the object to unlock.
130     * @throws AmetysRepositoryException if an error occurs.
131     */
132    public void unlock(JCRAmetysObject object) throws AmetysRepositoryException
133    {
134        try
135        {
136            Node node = getNode(object);
137            
138            LockManager lockManager = node.getSession().getWorkspace().getLockManager();
139            
140            Lock lock = lockManager.getLock(node.getPath());
141            Node lockHolder = lock.getNode();
142            String lockToken = lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString();
143            
144            lockManager.addLockToken(lockToken);
145
146            lockManager.unlock(node.getPath());
147            
148            // Remove residual properties
149            node.setProperty(RepositoryConstants.METADATA_LOCKTOKEN, (Value) null);
150            node.setProperty(RepositoryConstants.METADATA_LOCKOWNER, (Value) null);
151            node.getSession().save();
152            
153            removeLockedContent(object);
154        }
155        catch (RepositoryException ex)
156        {
157            throw new AmetysRepositoryException("Unable to unlock object " + object, ex);
158        }
159    }
160    
161    /**
162     * Sets a lock token on the current session for the given {@link JCRAmetysObject}
163     * @param object the object for which the lock token is set.
164     * @throws AmetysRepositoryException if an error occurs
165     */
166    public void setLockTokenOnCurrentSession(JCRAmetysObject object) throws AmetysRepositoryException
167    {
168        try
169        {
170            Node node = getNode(object);
171            if (node.isLocked())
172            {
173                LockManager lockManager = node.getSession().getWorkspace().getLockManager();
174                
175                Lock lock = lockManager.getLock(node.getPath());
176                Node lockHolder = lock.getNode();
177                String lockToken = lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString();
178                
179                lockManager.addLockToken(lockToken);
180            }
181        }
182        catch (RepositoryException ex)
183        {
184            throw new AmetysRepositoryException("Unable to add the lock token on object " + object, ex);
185        }
186    }
187    
188    /**
189     * Test if a {@link JCRAmetysObject} is locked.
190     * @param object the object to test.
191     * @return true if the object is locked, false otherwise.
192     * @throws AmetysRepositoryException if an error occurs.
193     */
194    public boolean isLocked(JCRAmetysObject object) throws AmetysRepositoryException
195    {
196        try
197        {
198            return getNode(object).isLocked();
199        }
200        catch (RepositoryException ex)
201        {
202            throw new AmetysRepositoryException("Unable to get lock status on object " + object, ex);
203        }
204    }
205    
206    /**
207     * Get the lock owner of a {@link JCRAmetysObject}.
208     * @param object the object of which to get the lock owner.
209     * @return the lock owner or null if there is no lock owner.
210     * @throws AmetysRepositoryException if an error occurs.
211     */
212    public UserIdentity getLockOwner(JCRAmetysObject object) throws AmetysRepositoryException
213    {
214        try
215        {
216            String userIdentity = getNode(object).getProperty(RepositoryConstants.METADATA_LOCKOWNER).getString();
217            return UserIdentity.stringToUserIdentity(userIdentity);
218        }
219        catch (PathNotFoundException e)
220        {
221            // No lock owner
222            return null;
223        }
224        catch (RepositoryException ex)
225        {
226            throw new AmetysRepositoryException("Unable to get lock owner on object " + object, ex);
227        }
228    }
229    
230    /**
231     * Get an object's base node.
232     * @param object the object which node to get.
233     * @return the object's base node.
234     */
235    protected Node getNode(JCRAmetysObject object)
236    {
237        return object instanceof DefaultAmetysObject defaultAmetysObject
238                ? defaultAmetysObject.getBaseNode()
239                : object.getNode();
240    }
241    
242}