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, 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.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.CmsUser; 035import org.opencms.gwt.Messages; 036import org.opencms.lock.CmsLockActionRecord.LockChange; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsLog; 039import org.opencms.util.CmsFileUtil; 040 041import java.io.Closeable; 042import java.util.List; 043import java.util.Map; 044 045import org.apache.commons.logging.Log; 046 047import com.google.common.collect.Maps; 048 049/** 050 * Locking utility class.<p> 051 */ 052public final class CmsLockUtil { 053 054 /** Helper to handle the lock reports together with the files. */ 055 public static final class LockedFile implements AutoCloseable { 056 057 /** The cms object used for locking, unlocking and encoding determination. */ 058 private CmsObject m_cms; 059 /** The file that was read (cached file for getFile). */ 060 private CmsFile m_file; 061 /** The lock action record from locking the file. */ 062 private CmsLockActionRecord m_lockRecord; 063 /** Flag, indicating if the file was newly created. */ 064 private boolean m_new; 065 /** The resource that was locked. */ 066 private CmsResource m_res; 067 068 /** Private constructor. 069 * @param cms the cms user context. 070 * @param resource the resource to lock and read. 071 * @throws CmsException thrown if locking fails. 072 */ 073 private LockedFile(CmsObject cms, CmsResource resource) 074 throws CmsException { 075 076 m_lockRecord = CmsLockUtil.ensureLock(cms, resource); 077 m_res = resource; 078 m_new = false; 079 m_cms = cms; 080 } 081 082 /** 083 * Lock and read a file. 084 * @param cms the cms user context. 085 * @param resource the resource to lock and read. 086 * @return the read file with the lock action record. 087 * @throws CmsException thrown if locking fails 088 */ 089 public static LockedFile lockResource(CmsObject cms, CmsResource resource) throws CmsException { 090 091 return new LockedFile(cms, resource); 092 } 093 094 /** 095 * @see java.lang.AutoCloseable#close() 096 */ 097 public void close() throws Exception { 098 099 tryUnlock(); 100 101 } 102 103 /** 104 * Returns the encoding used for the file. 105 * 106 * @see CmsFileUtil#getEncoding(CmsObject, CmsResource) 107 * 108 * @return the encoding used for the file. 109 */ 110 public String getEncoding() { 111 112 return CmsFileUtil.getEncoding(m_cms, m_res); 113 114 } 115 116 /** Returns the file, or null if reading fails. 117 * @return the file, or null if reading fails. 118 */ 119 @SuppressWarnings("synthetic-access") 120 public CmsFile getFile() { 121 122 if ((null == m_file) && m_res.isFile()) { 123 try { 124 m_file = m_cms.readFile(m_res); 125 } catch (CmsException e) { 126 LOG.error(e.getLocalizedMessage(), e); 127 } 128 } 129 return m_file; 130 } 131 132 /** Returns the lock action record. 133 * @return the lock action record. 134 */ 135 public CmsLockActionRecord getLockActionRecord() { 136 137 return m_lockRecord; 138 } 139 140 /** 141 * Returns a flag, indicating if the file is newly created. 142 * @return flag, indicating if the file is newly created. 143 */ 144 public boolean isCreated() { 145 146 return m_new; 147 } 148 149 /** 150 * Set the flag, indicating if the file was newly created. 151 * @param isNew flag, indicating if the file was newly created. 152 */ 153 public void setCreated(boolean isNew) { 154 155 m_new = isNew; 156 157 } 158 159 /** 160 * Unlocks the resource if it was not formerly locked.<p> 161 * 162 * @return <code>true</code> in case the resource was unlocked 163 */ 164 public boolean tryUnlock() { 165 166 if (!m_lockRecord.getChange().equals(LockChange.unchanged) || m_new) { 167 try { 168 m_cms.unlockResource(m_res); 169 return true; 170 } catch (CmsException e) { 171 // this will happen in case a parent folder is still locked, can be ignored 172 } 173 174 } 175 return false; 176 } 177 } 178 179 /** Logger instance for this class. */ 180 private static final Log LOG = CmsLog.getLog(CmsLockUtil.class); 181 182 /** 183 * Hidden constructor. 184 */ 185 private CmsLockUtil() { 186 187 // Hide constructor for util class 188 } 189 190 /** 191 * Static helper method to lock a resource.<p> 192 * 193 * @param cms the CMS context to use 194 * @param resource the resource to lock 195 * @return the action that was taken 196 * 197 * @throws CmsException if something goes wrong 198 */ 199 public static CmsLockActionRecord ensureLock(CmsObject cms, CmsResource resource) throws CmsException { 200 201 return ensureLock(cms, resource, false); 202 } 203 204 /** 205 * Static helper method to lock a resource.<p> 206 * 207 * @param cms the CMS context to use 208 * @param resource the resource to lock 209 * @return the action that was taken 210 * @param shallow true if we only need a shallow lock 211 * 212 * @throws CmsException if something goes wrong 213 */ 214 public static CmsLockActionRecord ensureLock(CmsObject cms, CmsResource resource, boolean shallow) 215 throws CmsException { 216 217 LockChange change = LockChange.unchanged; 218 if (!shallow) { 219 List<CmsResource> blockingResources = cms.getBlockingLockedResources(resource); 220 if ((blockingResources != null) && !blockingResources.isEmpty()) { 221 throw new CmsException( 222 Messages.get().container( 223 Messages.ERR_RESOURCE_HAS_BLOCKING_LOCKED_CHILDREN_1, 224 cms.getSitePath(resource))); 225 } 226 } 227 CmsUser user = cms.getRequestContext().getCurrentUser(); 228 CmsLock lock = cms.getLock(resource); 229 if (!lock.isOwnedBy(user)) { 230 if (shallow) { 231 cms.lockResourceShallow(resource); 232 } else { 233 cms.lockResourceTemporary(resource); 234 } 235 change = LockChange.locked; 236 lock = cms.getLock(resource); 237 } else if (!lock.isOwnedInProjectBy(user, cms.getRequestContext().getCurrentProject())) { 238 cms.changeLock(resource); 239 change = LockChange.changed; 240 lock = cms.getLock(resource); 241 } 242 return new CmsLockActionRecord(lock, change); 243 } 244 245 /** 246 * Tries to unlock the given resource.<p> 247 * Will ignore any failure.<p> 248 * 249 * @param cms the cms context 250 * @param resource the resource to unlock 251 */ 252 public static void tryUnlock(CmsObject cms, CmsResource resource) { 253 254 try { 255 cms.unlockResource(resource); 256 } catch (CmsException e) { 257 LOG.debug("Unable to unlock " + resource.getRootPath(), e); 258 } 259 } 260 261 /** 262 * Utility method for locking and unlocking a set of resources conveniently with the try-with syntax 263 * from Java 1.7.<p> 264 * 265 * This method locks a set of resources and returns a Closeable instance that will unlock the locked resources 266 * when its close() method is called. 267 * 268 * @param cms the CMS context 269 * @param shallow true if we only want shallow locks 270 * @param resources the resources to lock 271 * 272 * @return the Closeable used to unlock the resources 273 * @throws Exception if something goes wrong 274 */ 275 public static AutoCloseable withLockedResources(final CmsObject cms, boolean shallow, CmsResource... resources) 276 throws Exception { 277 278 final Map<CmsResource, CmsLockActionRecord> lockMap = Maps.newHashMap(); 279 Closeable result = new Closeable() { 280 281 @SuppressWarnings("synthetic-access") 282 public void close() { 283 284 for (Map.Entry<CmsResource, CmsLockActionRecord> entry : lockMap.entrySet()) { 285 if (entry.getValue().getChange() == LockChange.locked) { 286 CmsResource resourceToUnlock = entry.getKey(); 287 // the resource may have been moved, so we read it again to get the correct path 288 try { 289 resourceToUnlock = cms.readResource(entry.getKey().getStructureId(), CmsResourceFilter.ALL); 290 } catch (CmsException e) { 291 LOG.error(e.getLocalizedMessage(), e); 292 } 293 try { 294 cms.unlockResource(resourceToUnlock); 295 } catch (CmsException e) { 296 LOG.warn(e.getLocalizedMessage(), e); 297 } 298 } 299 300 } 301 } 302 }; 303 try { 304 for (CmsResource resource : resources) { 305 CmsLockActionRecord record = ensureLock(cms, resource, shallow); 306 lockMap.put(resource, record); 307 } 308 } catch (CmsException e) { 309 result.close(); 310 throw e; 311 } 312 return result; 313 } 314 315 /** 316 * Utility method for locking and unlocking a set of resources conveniently with the try-with syntax 317 * from Java 1.7.<p> 318 * 319 * This method locks a set of resources and returns a Closeable instance that will unlock the locked resources 320 * when its close() method is called. 321 * 322 * @param cms the CMS context 323 * @param resources the resources to lock 324 * 325 * @return the Closeable used to unlock the resources 326 * @throws Exception if something goes wrong 327 */ 328 public static AutoCloseable withLockedResources(final CmsObject cms, CmsResource... resources) throws Exception { 329 330 return withLockedResources(cms, false, resources); 331 } 332 333}