Project: Possom
/* Copyright (2012) Schibsted ASA
 *   This file is part of Possom. 
 * 
 *   Possom is free software: you can redistribute it and/or modify 
 *   it under the terms of the GNU Lesser General Public License as published by 
 *   the Free Software Foundation, either version 3 of the License, or 
 *   (at your option) any later version. 
 * 
 *   Possom is distributed in the hope that it will be useful, 
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 *   GNU Lesser General Public License for more details. 
 * 
 *   You should have received a copy of the GNU Lesser General Public License 
 *   along with Possom.  If not, see <http://www.gnu.org/licenses/>. 
 * 
 */
 
package no.sesat.search.site; 
import java.util.Map; 
import java.util.concurrent.locks.ReentrantReadWriteLock; 
import org.apache.log4j.Logger; 
 
/** Abstract implementation of a SiteKeyedFactory to help with
 * the INSTANCES map and locking pattern typically used around it using ReentrantReadWriteLocks. 
 * 
 * @version $Id$ 
 */
 
public abstract class AbstractSiteKeyedFactory implements SiteKeyedFactory{ 
 
    // Constants ----------------------------------------------------- 
 
    private static final Logger LOG = Logger.getLogger(AbstractSiteKeyedFactory.class); 
 
    private static final String ERR_DOC_BUILDER_CREATION = " failed to construct new factory instance for "
 
    // Attributes ---------------------------------------------------- 
 
    // Static -------------------------------------------------------- 
 
    /** Handles the locking pattern around the INSTANCES map that's done with a ReentrantReadWriteLock.
     * @see http://permalink.gmane.org/gmane.comp.java.sesat.kernel.devel/228 
     * 
     * @param <T> the type of SiteKeyedFactory that can be constructed. 
     * @param site the site this factory will answer to. 
     * @param instances the map of factories of type T already in existence. 
     * @param instancesLock the lock used around the instances map 
     * @param constructor the wrapper around the constructor used to create factories of type T. 
     * @return the singleton instance of factory of type T related to the given site. 
     */
 
    protected static final <T extends AbstractSiteKeyedFactory> T instanceOf
            final Site site, 
            final Map<Site,T> instances, 
            final ReentrantReadWriteLock instancesLock, 
            final FactoryConstructor<T> constructor){ 
 
        try { 
            instancesLock.readLock().lock(); 
            if (!instances.containsKey(site)) { 
                // It is not possible to upgrade a read lock... 
                instancesLock.readLock().unlock(); 
                instancesLock.writeLock().lock(); 
                try { 
                    // ...so check the condition again. 
                    if (!instances.containsKey(site)) { 
                        instances.put(site, constructor.construct()); 
                    } 
                } catch (SiteKeyedFactoryInstantiationException e) { 
                    LOG.error(constructor + ERR_DOC_BUILDER_CREATION + site, e); 
                } finally { 
                    // Downgrade to a read lock. 
                    instancesLock.readLock().lock(); 
                    instancesLock.writeLock().unlock(); 
                } 
            } 
            return instances.get(site); 
        } finally { 
            // Finally release the read lock. 
            instancesLock.readLock().unlock(); 
        } 
    } 
 
    // Constructors -------------------------------------------------- 
 
    protected AbstractSiteKeyedFactory() { 
    } 
 
    // Public -------------------------------------------------------- 
 
    // Package protected --------------------------------------------- 
 
    // Protected ----------------------------------------------------- 
 
    // Private ------------------------------------------------------- 
 
    // Inner classes ------------------------------------------------- 
 
    /** Wraps a constructor so that it may be delegated to from the instanceOf method.
     * 
     * @param <T> the type of SiteKeyedFactory that can be constructed. 
     */
 
    protected interface FactoryConstructor<T extends AbstractSiteKeyedFactory>{ 
        /** Wraps a constructor so that it may be delegated to from the instanceOf method.
         * 
         * @return the new SiteKeyedFactory instance 
         * @throws no.sesat.search.site.SiteKeyedFactoryInstantiationException any failure in construction. 
         */
 
        T construct() throws SiteKeyedFactoryInstantiationException; 
    } 
}