/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.core.commons.services.lock.pessimistic; import java.util.Date; import java.util.List; import javax.persistence.LockModeType; import org.olat.core.commons.persistence.DB; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Description:<br> * implementation for pessimistic locking.<br> * Do not use this class directly. please use Syncer or Locker via CoordinatorManager! * * <P> * Initial Date: 25.10.2007 <br> * @author Felix Jost, http://www.goodsolutions.ch */ @Service("lockManager") public class PessimisticLockManager implements InitializingBean { private static final OLog log = Tracing.createLoggerFor(PessimisticLockManager.class); private final String ASSET_INSERT_LOCK = "SYS_plock_global"; private boolean initDone = false; private final DB dbInstance; @Autowired private PessimisticLockManager(DB dbInstance) { this.dbInstance = dbInstance; } @Override public void afterPropertiesSet() throws Exception { // make sure that the resource (= row in our table) to lock the creation of new assets exists PLock gLock = findPLock(ASSET_INSERT_LOCK); if (gLock == null) { // need to create it gLock = createPLock(ASSET_INSERT_LOCK); savePLock(gLock); } dbInstance.intermediateCommit(); initDone = true; } private PLock findPLock(String asset) { List<PLock> res = dbInstance.getCurrentEntityManager() .createNamedQuery("loadByPLockByAsset", PLock.class) .setParameter("asset", asset) .setLockMode(LockModeType.PESSIMISTIC_WRITE) .setHint("javax.persistence.lock.timeout", new Integer(30000)) .getResultList(); if (res.size() == 0) { return null; } else { return res.get(0); } } private PLock createPLock(String asset) { PLockImpl lock = new PLockImpl(); lock.setAsset(asset); lock.setCreationDate(new Date()); return lock; } private void savePLock(PLock plock) { dbInstance.saveObject(plock); } /** * do not use this class directly. please use Syncer or Locker via CoordinatorManager! * @param asset * @return */ public PLock findOrPersistPLock(String asset) { if (!initDone) throw new AssertException("init not called yet - make sure the ClusterModule is enabled in your olat.local.properties file"); boolean debug = log.isDebug(); if (debug) { log.debug("findOrPersistPLock START asset="+asset); } PLock plock = findPLock(asset); if (debug) { if (plock==null) { log.debug("findOrPersistPLock PLock not found"); } else { log.debug("findOrPersistPLock found and locked PLock: "+plock); } } // if not found, persist it. if (plock == null ) { // synchronize the findOrCreate by using the special row with the global-lock-asset // locks the global lock - which is only used to sync creation of new resource entries, so that those can later be locked. findPLock(ASSET_INSERT_LOCK); if (debug) { log.debug("findOrPersistPLock global insert lock locked"); } // need to read again within the protected region plock = findPLock(asset); if (plock == null) { if (debug) { log.debug("findOrPersistPLock creating new plock: "+asset); } plock = createPLock(asset); if (debug) { log.debug("findOrPersistPLock created new plock: "+asset); } savePLock(plock); if (debug) { log.debug("findOrPersistPLock saved new plock: "+asset); } } // else plock got created by another thread in the meantime // some notes: // takes advantage of the fact that the select for update blocks a transaction when a lock is already acquired. // // since we have concurrent access here, we could have many threads which try to create // the entry to later lock upon. // we therefore could // a) lock on a olat-wide lock // or b) catch the exception - and continue, since we know that the row already exists // even c) start a new connection and set serializable isolation level.. // in a cluster, each vm syncs via synchronized() first, so that there is only one concurrent access to the PLockManager from one node at a given time. // -> we have maximal num-of-cluster concurrent accesses, e.g. 3-5 // a: performance, should only occur once for a resource: the first time a lock for a certain resource is accessed. // b) is the transaction still safe to continue? what about hibernate first level cache etc. hibernate docs says in general we'd need to close the session. // -> go for solution a. } // else found return plock; } }