/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.utils.threading; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.Validate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Implementation of double-checked locking for object creation using a {@link ReadWriteLock} * */ public abstract class DoubleCheckedCreator<T> { private static String LOGGER_NAME = DoubleCheckedCreator.class.getName(); private Log logger; /** * Inits and/or returns already initialized logger. <br> * You have to use this method in order to use the logger,<br> * you should not call the private variable directly.<br> * This was done because Tomcat may instantiate all listeners before calling contextInitialized * on any listener.<br> * Note that there is no synchronization here on purpose. The object returned by getLog for a * logger name is<br> * idempotent and getLog itself is thread safe. Eventually all <br> * threads will see an instance level logger variable and calls to getLog will stop. * * @return the log for this class */ protected Log getLogger() { Log l = this.logger; if (l == null) { l = LogFactory.getLog(LOGGER_NAME); this.logger = l; } return l; } private final ReadWriteLock readWriteLock; protected final Lock readLock; protected final Lock writeLock; public DoubleCheckedCreator() { this(new ReentrantReadWriteLock()); } public DoubleCheckedCreator(ReadWriteLock readWriteLock) { Validate.notNull(readWriteLock, "readWriteLock can not be null"); this.readWriteLock = readWriteLock; this.readLock = this.readWriteLock.readLock(); this.writeLock = this.readWriteLock.writeLock(); } /** * @param args Arguments to use when creating the object * @return A newly created object */ protected abstract T create(Object... args); /** * @param args Arguments to use when retrieving the object * @return An existing object if available */ protected abstract T retrieve(Object... args); /** * The default impl returns true if value is null. * * @param value The object to validate * @param args Arguments to use when validating the object * @return true if the object is invalid and should be created, false if not. */ protected boolean invalid(T value, Object... args) { return value == null; } /** * Double checking retrieval/creation of an object * * @param args Optional arguments to pass to {@link #retrieve(Object...)}, {@link * #create(Object...)}, and {@link #invalid(Object, Object...)}. * @return A retrieved or created object. */ public final T get(Object... args) { //Grab a read lock to try retrieving the object this.readLock.lock(); try { //See if the object already exists and is valid final T value = this.retrieve(args); if (!this.invalid(value, args)) { if (getLogger().isDebugEnabled()) { getLogger().debug("Using retrieved (first attempt) Object='" + value + "'"); } return value; } } finally { //Release the read lock this.readLock.unlock(); } //Object must not be valid, switch to a write lock this.writeLock.lock(); try { //Check again if the object exists and is valid T value = this.retrieve(args); if (this.invalid(value, args)) { //Object is not valid, create it value = this.create(args); if (getLogger().isDebugEnabled()) { getLogger().debug("Created new Object='" + value + "'"); } } else if (getLogger().isDebugEnabled()) { getLogger().debug("Using retrieved (second attempt) Object='" + value + "'"); } return value; } finally { //switch back to the read lock this.writeLock.unlock(); } } }