001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.lock;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsUser;
033import org.opencms.util.CmsUUID;
034
035/**
036 * Represents the lock state of a VFS resource.<p>
037 *
038 * The lock state is combination of how, by whom and in which project
039 * a resource is currently locked.<p>
040 *
041 * @since 6.0.0
042 *
043 * @see org.opencms.file.CmsObject#getLock(org.opencms.file.CmsResource)
044 * @see org.opencms.lock.CmsLockManager
045 */
046public class CmsLock implements Comparable<CmsLock> {
047
048    /** The shared null lock object. */
049    private static final CmsLock NULL_LOCK = new CmsLock(
050        "",
051        CmsUUID.getNullUUID(),
052        new CmsProject(),
053        CmsLockType.UNLOCKED);
054
055    /** The project where the resource is locked. */
056    private CmsProject m_project;
057
058    /** The related lock. */
059    private CmsLock m_relatedLock;
060
061    /** The name of the locked resource. */
062    private String m_resourceName;
063
064    /** Indicates how the resource is locked. */
065    private CmsLockType m_type;
066
067    /** The ID of the user who locked the resource. */
068    private CmsUUID m_userId;
069
070    /**
071     * Constructor for a new Cms lock.<p>
072     *
073     * @param resourceName the full resource name including the site root
074     * @param userId the ID of the user who locked the resource
075     * @param project the project where the resource is locked
076     * @param type flag indicating how the resource is locked
077     */
078    public CmsLock(String resourceName, CmsUUID userId, CmsProject project, CmsLockType type) {
079
080        m_resourceName = resourceName;
081        m_userId = userId;
082        m_project = project;
083        m_type = type;
084    }
085
086    /**
087     * Returns the shared Null CmsLock.<p>
088     *
089     * @return the shared Null CmsLock
090     */
091    public static CmsLock getNullLock() {
092
093        return CmsLock.NULL_LOCK;
094    }
095
096    /**
097     * @see java.lang.Comparable#compareTo(java.lang.Object)
098     */
099    public int compareTo(CmsLock other) {
100
101        return m_resourceName.compareTo(other.m_resourceName);
102    }
103
104    /**
105     * Compares this lock to the specified object.<p>
106     *
107     * @param obj the object to compare to
108     * @return true if and only if member values of this CmsLock are the same with the compared CmsLock
109     */
110    @Override
111    public boolean equals(Object obj) {
112
113        if (obj == this) {
114            return true;
115        }
116        if (obj instanceof CmsLock) {
117            CmsLock other = (CmsLock)obj;
118            return other.m_resourceName.equals(m_resourceName)
119                && other.m_userId.equals(m_userId)
120                && other.m_project.equals(m_project)
121                && other.m_type.equals(m_type);
122        }
123        return false;
124    }
125
126    /**
127     * Returns the edition lock.<p>
128     *
129     * @return the edition lock
130     */
131    public CmsLock getEditionLock() {
132
133        if (isSystemLock()) {
134            return getRelatedLock();
135        }
136        return this;
137    }
138
139    /**
140     * Returns the project where the resource is currently locked.<p>
141     *
142     * @return the project where the resource is currently locked
143     */
144    public CmsProject getProject() {
145
146        return m_project;
147    }
148
149    /**
150     * Returns the ID of the project where the resource is currently locked.<p>
151     *
152     * @return the ID of the project
153     */
154    public CmsUUID getProjectId() {
155
156        return m_project.getUuid();
157    }
158
159    /**
160     * Returns the name of the locked resource.<p>
161     *
162     * @return the name of the locked resource
163     */
164    public String getResourceName() {
165
166        return m_resourceName;
167    }
168
169    /**
170     * Returns the system lock.<p>
171     *
172     * @return the system lock
173     */
174    public CmsLock getSystemLock() {
175
176        if (!isSystemLock()) {
177            return getRelatedLock();
178        }
179        return this;
180    }
181
182    /**
183     * Returns the type about how the resource is locked.<p>
184     *
185     * @return the type of the lock
186     */
187    public CmsLockType getType() {
188
189        return m_type;
190    }
191
192    /**
193     * Returns the ID of the user who currently locked the resource.<p>
194     *
195     * @return the ID of the user
196     */
197    public CmsUUID getUserId() {
198
199        return m_userId;
200    }
201
202    /**
203     * @see java.lang.Object#hashCode()
204     */
205    @Override
206    public int hashCode() {
207
208        return m_project.hashCode() + m_resourceName.hashCode() + m_userId.hashCode() + m_type.hashCode();
209    }
210
211    /**
212     * Returns <code>true</code> if this is an directly inherited lock.<p>
213     *
214     * @return <code>true</code> if this is an directly inherited lock
215     */
216    public boolean isDirectlyInherited() {
217
218        return m_type.isDirectlyInherited();
219    }
220
221    /**
222     * Returns <code>true</code> if this is an exclusive, temporary exclusive, or
223     * directly inherited lock, and the given user is the owner of this lock.<p>
224     *
225     * @param user the user to compare to the owner of this lock
226     *
227     * @return <code>true</code> if this is an exclusive, temporary exclusive, or
228     *      directly inherited lock, and the given user is the owner of this lock
229     */
230    public boolean isDirectlyOwnedBy(CmsUser user) {
231
232        return (isExclusive() || isDirectlyInherited()) && isOwnedBy(user);
233    }
234
235    /**
236     * Returns <code>true</code> if this is an exclusive, temporary exclusive, or
237     * directly inherited lock, and the current user is the owner of this lock,
238     * checking also the project of the lock.<p>
239     *
240     * @param cms the CMS context to check
241     *
242     * @return <code>true</code> if this is an exclusive, temporary exclusive, or
243     *      directly inherited lock, and the current user is the owner of this lock
244     */
245    public boolean isDirectlyOwnedInProjectBy(CmsObject cms) {
246
247        return (isExclusive() || isDirectlyInherited())
248            && isOwnedInProjectBy(
249                cms.getRequestContext().getCurrentUser(),
250                cms.getRequestContext().getCurrentProject());
251    }
252
253    /**
254     * Returns <code>true</code> if this is an exclusive, temporary exclusive, or
255     * directly inherited lock, and the given user is the owner of this lock,
256     * checking also the project of the lock.<p>
257     *
258     * @param user the user to compare to the owner of this lock
259     * @param project the project to compare to the project of this lock
260     *
261     * @return <code>true</code> if this is an exclusive, temporary exclusive, or
262     *      directly inherited lock, and the given user is the owner of this lock
263     */
264    public boolean isDirectlyOwnedInProjectBy(CmsUser user, CmsProject project) {
265
266        return (isExclusive() || isDirectlyInherited()) && isOwnedInProjectBy(user, project);
267    }
268
269    /**
270     * Returns <code>true</code> if this is an exclusive (or temporary exclusive) lock.<p>
271     *
272     * @return <code>true</code> if this is an exclusive (or temporary exclusive) lock
273     */
274    public boolean isExclusive() {
275
276        return m_type.isExclusive();
277    }
278
279    /**
280     * Returns <code>true</code> if this is an exclusive (or temporary exclusive) lock,
281     * and the given user is the owner of this lock.<p>
282     *
283     * @param user the user to compare to the owner of this lock
284     *
285     * @return <code>true</code> if this is an exclusive (or temporary exclusive) lock,
286     *      and the given user is the owner of this lock
287     */
288    public boolean isExclusiveOwnedBy(CmsUser user) {
289
290        return isExclusive() && isOwnedBy(user);
291    }
292
293    /**
294     * Returns <code>true</code> if this is an exclusive (or temporary exclusive) lock,
295     * and the given user is the owner and the given project is the project of this lock.<p>
296     *
297     * @param user the user to compare to the owner of this lock
298     * @param project the project to compare to the project of this lock
299     *
300     * @return <code>true</code> if this is an exclusive (or temporary exclusive) lock,
301     *      and the given user is the owner and the given project is the project of this lock
302     */
303    public boolean isExclusiveOwnedInProjectBy(CmsUser user, CmsProject project) {
304
305        return isExclusive() && isOwnedInProjectBy(user, project);
306    }
307
308    /**
309     * Returns <code>true</code> if this is an inherited lock, which may either be directly or shared inherited.<p>
310     *
311     * @return <code>true</code> if this is an inherited lock, which may either be directly or shared inherited
312     */
313    public boolean isInherited() {
314
315        return m_type.isInherited();
316    }
317
318    /**
319     * Returns <code>true</code> if the given project is the project of this lock.<p>
320     *
321     * @param project the project to compare to the project of this lock
322     *
323     * @return <code>true</code> if the given project is the project of this lock
324     */
325    public boolean isInProject(CmsProject project) {
326
327        return m_project.equals(project);
328    }
329
330    /**
331     * Checks if a resource can be locked by a user.<p>
332     *
333     * The resource is not lockable if it already has a lock of type {@link CmsLockType#PUBLISH}.<p>
334     *
335     * The resource is lockable either
336     * - if it is currently unlocked
337     * - if it has a lock of another type set and the user is the lock owner
338     *
339     * @param user the user to test lockeability for
340     *
341     * @return <code>true</code> if this lock blocks any operation on the locked resource until it is unlocked
342     */
343    public boolean isLockableBy(CmsUser user) {
344
345        if (getSystemLock().isPublish()) {
346            return false;
347        }
348        if (getEditionLock().isUnlocked() && getSystemLock().isUnlocked()) {
349            return true;
350        }
351        return getEditionLock().isOwnedBy(user);
352    }
353
354    /**
355     * Returns <code>true</code> if this lock is the <code>NULL</code> lock which can
356     * be obtained by {@link #getNullLock()}.<p>
357     *
358     * Only for the <code>NULL</code> lock, {@link #isUnlocked()} is <code>true</code>.<p>
359     *
360     * @return <code>true</code> if this lock is the <code>NULL</code> lock
361     */
362    public boolean isNullLock() {
363
364        return isUnlocked();
365    }
366
367    /**
368     * Returns <code>true</code> if the given user is the owner of this lock.<p>
369     *
370     * @param user the user to compare to the owner of this lock
371     *
372     * @return <code>true</code> if the given user is the owner of this lock
373     */
374    public boolean isOwnedBy(CmsUser user) {
375
376        return m_userId.equals(user.getId());
377    }
378
379    /**
380     * Returns <code>true</code> if the given user is the owner of this lock,
381     * and this lock belongs to the given project.<p>
382     *
383     * @param user the user to compare to the owner of this lock
384     * @param project the project to compare to the project of this lock
385     *
386     * @return <code>true</code> if the given user is the owner of this lock,
387     *      and this lock belongs to the given project
388     */
389    public boolean isOwnedInProjectBy(CmsUser user, CmsProject project) {
390
391        return isOwnedBy(user) && isInProject(project);
392    }
393
394    /**
395     * Returns <code>true</code> if this is a persistent lock that should be saved when the systems shuts down.<p>
396     *
397     * @return <code>true</code> if this is a persistent lock that should be saved when the systems shuts down
398     */
399    public boolean isPersistent() {
400
401        return m_type.isPersistent();
402    }
403
404    /**
405     * Returns <code>true</code> if this is a publish lock.<p>
406     *
407     * @return <code>true</code> if this is a publish lock
408     */
409    public boolean isPublish() {
410
411        return m_type.isPublish();
412    }
413
414    /**
415     * Returns <code>true</code> if this is a shared lock.<p>
416     *
417     * @return <code>true</code> if this is a shared lock
418     */
419    public boolean isShared() {
420
421        return m_type.isShared();
422    }
423
424    /**
425     * Returns <code>true</code> if this is a system (2nd level) lock.<p>
426     *
427     * @return <code>true</code> if this is a system (2nd level) lock
428     */
429    public boolean isSystemLock() {
430
431        return m_type.isSystem();
432    }
433
434    /**
435     * Returns <code>true</code> if this is a temporary lock.<p>
436     *
437     * @return <code>true</code> if this is a temporary lock
438     */
439    public boolean isTemporary() {
440
441        return m_type.isTemporary();
442    }
443
444    /**
445     * Returns <code>true</code> if this lock is in fact unlocked.<p>
446     *
447     * Only if this is <code>true</code>, the result lock is equal to the <code>NULL</code> lock,
448     * which can be obtained by {@link #getNullLock()}.<p>
449     *
450     * @return <code>true</code> if this lock is in fact unlocked
451     */
452    public boolean isUnlocked() {
453
454        return m_type.isUnlocked();
455    }
456
457    /**
458     * Builds a string representation of the current state.<p>
459     *
460     * @see java.lang.Object#toString()
461     */
462    @Override
463    public String toString() {
464
465        StringBuffer buf = new StringBuffer();
466
467        buf.append("[CmsLock: resource: ");
468        buf.append(getResourceName());
469        buf.append(", type: ");
470        buf.append(getType());
471        buf.append(", project: ");
472        buf.append(getProjectId());
473        buf.append(", user: ");
474        buf.append(getUserId());
475        if (getRelatedLock() != null) {
476            buf.append(", related lock: ");
477            buf.append(getRelatedLock().getType());
478        }
479        buf.append("]");
480
481        return buf.toString();
482    }
483
484    /**
485     * @see java.lang.Object#clone()
486     */
487    @Override
488    protected Object clone() {
489
490        CmsLock lock = new CmsLock(m_resourceName, m_userId, m_project, m_type);
491        if ((m_relatedLock != null) && !m_relatedLock.isNullLock()) {
492            lock.setRelatedLock(
493                new CmsLock(
494                    m_relatedLock.m_resourceName,
495                    m_relatedLock.m_userId,
496                    m_relatedLock.m_project,
497                    m_relatedLock.m_type));
498        }
499        return lock;
500    }
501
502    /**
503     * Returns the related Lock.<p>
504     *
505     * @return the related Lock
506     */
507    protected CmsLock getRelatedLock() {
508
509        if (m_relatedLock == null) {
510            CmsLockType type;
511            if (isSystemLock()) {
512                type = CmsLockType.UNLOCKED;
513            } else {
514                type = CmsLockType.SYSTEM_UNLOCKED;
515            }
516            CmsLock lock = new CmsLock(getResourceName(), getUserId(), getProject(), type);
517            lock.setRelatedLock(this);
518            if (isUnlocked()) {
519                // prevent the null lock gets modified
520                return lock;
521            }
522            m_relatedLock = lock;
523        }
524        return m_relatedLock;
525    }
526
527    /**
528     * Sets the related Lock.<p>
529     *
530     * @param relatedLock the related Lock to set
531     */
532    protected void setRelatedLock(CmsLock relatedLock) {
533
534        if (this == NULL_LOCK) {
535            throw new RuntimeException("null lock");
536        }
537        if ((relatedLock == null) || relatedLock.isUnlocked()) {
538            m_relatedLock = null;
539        } else {
540            m_relatedLock = relatedLock;
541            m_relatedLock.m_relatedLock = this;
542        }
543    }
544}