Package org.openbel.framework.api

Source Code of org.openbel.framework.api.DefaultKamCacheService$_ThreadFactory

/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program 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.
*
* The OpenBEL Framework 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 the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.api;

import static java.lang.String.*;
import static java.util.concurrent.Executors.*;
import static org.apache.commons.lang.RandomStringUtils.*;
import static org.openbel.framework.api.KamCacheService.LoadStatus.*;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import org.openbel.framework.api.internal.KAMCatalogDao.KamFilter;
import org.openbel.framework.api.internal.KAMCatalogDao.KamInfo;
import org.openbel.framework.common.InvalidArgument;

/**
* Default thread-safe implementation of a {@link KamCacheService}.
* <p>
* <h1>Maps</h1> This implementation maintains three separate maps internally.
* <dl>
* <dt><i>KAM map</i></dt>
* <dd>Maintains a strong reference to a {@link Kam} object by its handle (a
* string)</dd>
* <dt><i>Unfiltered map</i></dt>
* <dd>Maintains a reference to handles available by {@link KamInfo}. This
* allows calls to the interface to return a handle to the KAM held in memory
* when <i>KamFilter</i> is not provided</dd>
* <dt><i>Filtered map</i></dt>
* <dd>Maintains a reference to handles available by {@link KamInfo} and
* {@link KamFilter}. This allows calls to the interface to return a handle to
* the KAM held in memory when <i>KamFilter</i> is provided</dd>
* </dl>
* </p>
* <p>
* <h1>Thread-safety</h1> Access to any of the maps is done under a single read
* lock. Mutating any of the maps is done under a single write lock. Both locks
* are contended for in a fair fashion.
* </p>
* <p>
* <h1>Concurrent Loading</h1> The number of KAMs that can be loaded
* concurrently is controlled by the {@code CONCURRENT_LOAD} constant.
* </p>
*
* @author julianray
*/
public class DefaultKamCacheService implements KamCacheService {
    private static final int CACHE_KEY_LENGTH = 24;
    /*
     * Effectively controls how many KAMs can be loaded concurrently.
     */
    protected static final int CONCURRENT_LOAD = 4;

    // Maps handles to KAMs
    private final Map<String, Kam> kamMap;

    // Maps unfiltered KAMs to handles
    private final Map<KamInfo, String> unfltrdMap;

    // Maps filtered KAMs to handles
    private final Map<FilteredKAMKey, String> fltrdMap;

    /** {@link KAMStore} */
    protected final KAMStore kAMStore;

    private final ReadLock read;
    private final WriteLock write;
    private final ExecutorService execSvc;

    /**
     * Creates a default KAM cache service with a supplied {@link KAMStore}.
     *
     * @param kAMStore {@link KAMStore}
     * @throws InvalidArgument Thrown if {@code kamStore} is null
     */
    public DefaultKamCacheService(KAMStore kAMStore) {
        if (kAMStore == null) {
            throw new InvalidArgument("kamStore", kAMStore);
        }
        this.kAMStore = kAMStore;

        kamMap = new HashMap<String, Kam>();
        unfltrdMap = new HashMap<KamInfo, String>();
        fltrdMap = new HashMap<FilteredKAMKey, String>();

        final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true);
        read = rwlock.readLock();
        write = rwlock.writeLock();

        execSvc = newFixedThreadPool(CONCURRENT_LOAD, new _ThreadFactory());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String cacheKam(String kamName, Kam kam) {

        if (kam == null) {
            throw new InvalidArgument("kam", kam);
        }

        if (kamName == null) {
            kamName = kam.getKamInfo().getName();
            if (kamName == null) {
                throw new InvalidArgument("no KAM name available");
            }
        }

        read.lock();
        try {
            for (Entry<String, Kam> entry : kamMap.entrySet()) {
                final Kam value = entry.getValue();
                if (kamName.equals(value.getKamInfo().getName())) {
                    return entry.getKey();
                }
            }
        } finally {
            read.unlock();
        }

        // Generate a key and cache it
        String key = generateCacheKey();
        write.lock();
        try {
            while (kamMap.containsKey(key)) {
                key = generateCacheKey();
            }
            kamMap.put(key, kam);
        } finally {
            write.unlock();
        }
        return key;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Kam getKam(String handleOrName) {
        Kam ret;
        read.lock();
        try {
            ret = kamMap.get(handleOrName);
            if (ret != null) {
                return ret;
            }

            // fallback to retrieval by KAM name
            for (final Kam k : kamMap.values()) {
                if (k.getKamInfo().getName().equals(handleOrName)) {
                    return k;
                }
            }
            return null;
        } finally {
            read.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String loadKam(KamInfo ki, KamFilter kf)
            throws KamCacheServiceException {

        if (ki == null) {
            throw new InvalidArgument("KamInfo required");
        }

        String ret;
        Callable<String> callable;
        if (kf != null) {
            // Check the fltrdMap cache first
            FilteredKAMKey key = new FilteredKAMKey(ki, kf);
            read.lock();
            try {
                ret = fltrdMap.get(key);
            } finally {
                read.unlock();
            }
            if (ret != null) {
                // Cache hit
                return ret;
            }
            // Cache miss, create a callable to defer loading
            callable = new CacheCallable(ki, kf);
        } else {
            read.lock();
            try {
                ret = unfltrdMap.get(ki);
            } finally {
                read.unlock();
            }
            if (ret != null) {
                // Cache hit
                return ret;
            }
            // Cache miss, create a callable to defer loading
            callable = new CacheCallable(ki, null);
        }

        // Block, waiting for KAM to load
        try {
            ret = execSvc.submit(callable).get();
        } catch (ExecutionException e) {
            throw new KamCacheServiceException(e);
        } catch (InterruptedException e) {
            throw new KamCacheServiceException(e);
        }
        return ret;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public LoadKAMResult loadKamWithResult(KamInfo ki, KamFilter kf)
            throws KamCacheServiceException {

        if (ki == null) {
            throw new InvalidArgument("KamInfo required");
        }

        String handle;
        Callable<String> callable;
        if (kf != null) {
            // Check the fltrdMap cache first
            FilteredKAMKey key = new FilteredKAMKey(ki, kf);
            read.lock();
            try {
                handle = fltrdMap.get(key);
            } finally {
                read.unlock();
            }
            if (handle != null) {
                // Cache hit
                LoadKAMResult ret = new LoadKAMResult(handle, LOADED);
                return ret;
            }
            // Cache miss, create a callable to defer loading
            callable = new CacheCallable(ki, kf);
        } else {
            read.lock();
            try {
                handle = unfltrdMap.get(ki);
            } finally {
                read.unlock();
            }
            if (handle != null) {
                // Cache hit
                LoadKAMResult ret = new LoadKAMResult(handle, LOADED);
                return ret;
            }
            // Cache miss, create a callable to defer loading
            callable = new CacheCallable(ki, null);
        }

        // Submit and return
        execSvc.submit(callable);

        LoadKAMResult ret = new LoadKAMResult(null, LOADING);
        return ret;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void releaseKam(String handle) {
        if (handle == null) {
            throw new InvalidArgument("handle", handle);
        }

        // Purge any cache entries
        purgeHandle(handle);
    }

    /**
     * Removes any cached entries for this KAM handle.
     * <p>
     * This method will block obtaining a write lock on the cache.
     * </p>
     *
     * @param handle String
     */
    private void purgeHandle(String handle) {
        write.lock();
        try {
            kamMap.remove(handle);

            Set<Entry<FilteredKAMKey, String>> entries = fltrdMap.entrySet();
            Iterator<Entry<FilteredKAMKey, String>> iter = entries.iterator();
            while (iter.hasNext()) {
                Entry<FilteredKAMKey, String> next = iter.next();
                if (handle.equals(next.getValue())) {
                    iter.remove();
                }
            }

            Set<Entry<KamInfo, String>> entries2 = unfltrdMap.entrySet();
            Iterator<Entry<KamInfo, String>> iter2 = entries2.iterator();
            while (iter2.hasNext()) {
                Entry<KamInfo, String> next = iter2.next();
                if (handle.equals(next.getValue())) {
                    iter2.remove();
                }
            }
        } finally {
            write.unlock();
        }
    }

    /**
     * Generates a random string to be used as a cache key.
     *
     * @return {@link String}
     */
    protected final static String generateCacheKey() {
        return randomAlphabetic(CACHE_KEY_LENGTH);
    }

    private class CacheCallable implements Callable<String> {
        private KamInfo ki;
        private KamFilter kf;

        /**
         * Callable task that loads a KAM and returns its handle.
         *
         * @param ki {@link KamInfo}; must be non-null
         * @param kf {@link KamFilter}; may be null
         */
        CacheCallable(final KamInfo ki, final KamFilter kf) {
            if (ki == null) {
                throw new InvalidArgument("ki cannot be null");
            }
            this.ki = ki;
            this.kf = kf;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String call() throws KAMStoreException {
            String ret = null;

            if (kf != null) {
                // Load request is for a filtered KAM.
                FilteredKAMKey key = new FilteredKAMKey(ki, kf);
                read.lock();
                try {
                    ret = fltrdMap.get(key);
                } finally {
                    read.unlock();
                }
                if (ret != null) {
                    return ret;
                }

                Kam k = kAMStore.getKam(ki, kf);

                write.lock();
                try {
                    // check again - now that we're alone, just you and I
                    ret = fltrdMap.get(key);
                    if (ret != null) {
                        return ret;
                    }

                    // cache the filtered KAM
                    ret = generateCacheKey();
                    kamMap.put(ret, k);
                    fltrdMap.put(key, ret);
                } finally {
                    write.unlock();
                }
            } else {
                // Load request is for an unfiltered KAM.
                read.lock();
                try {
                    ret = unfltrdMap.get(ki);
                } finally {
                    read.unlock();
                }
                if (ret != null) {
                    return ret;
                }

                // get an unfiltered KAM
                Kam k = kAMStore.getKam(ki);

                write.lock();
                try {
                    // check again - now that we're alone, just you and I
                    ret = unfltrdMap.get(ki);
                    if (ret != null) {
                        return ret;
                    }

                    // cache the unfiltered KAM
                    ret = generateCacheKey();
                    kamMap.put(ret, k);
                    unfltrdMap.put(ki, ret);
                } finally {
                    write.unlock();
                }
            }
            return ret;
        }
    }

    private static class FilteredKAMKey {
        final int hash;
        final KamInfo info;
        final KamFilter fltr;

        /**
         * Filtered KAM key.
         *
         * @param info {@link KamInfo} assumed non-null
         * @param fltr {@link KamFilter} assumed non-null
         */
        FilteredKAMKey(KamInfo info, KamFilter fltr) {
            this.info = info;
            this.fltr = fltr;

            final int prime = 31;
            int hash2 = prime;

            hash2 *= prime;
            hash2 += info.hashCode();

            hash2 *= prime;
            hash2 += fltr.hashCode();

            this.hash = hash2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return hash;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (!(obj instanceof FilteredKAMKey)) return false;
            FilteredKAMKey f = (FilteredKAMKey) obj;

            // info is non-null by contract
            if (!info.equals(f.info)) return false;
            // fltr is non-null by contract
            if (!fltr.equals(f.fltr)) return false;
            return true;
        }

    }

    /**
     * Creates daemon threads with names {@code kam-cache-thread-%d}.
     */
    private class _ThreadFactory implements ThreadFactory {
        int thread_num = 0;

        /**
         * {@inheritDoc}
         */
        @Override
        public Thread newThread(Runnable r) {
            final Thread t = new Thread(r);
            t.setName(format("kam-cache-thread-%d", thread_num++));
            t.setDaemon(true);
            return t;
        }

    }
}
TOP

Related Classes of org.openbel.framework.api.DefaultKamCacheService$_ThreadFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.