Package dk.brics.jwig.server.cache

Source Code of dk.brics.jwig.server.cache.DependencyMap

package dk.brics.jwig.server.cache;

import dk.brics.jwig.WebApp;
import dk.brics.jwig.XMLProducer;
import dk.brics.jwig.persistence.Persistable;
import dk.brics.jwig.server.ThreadContext;
import org.apache.log4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* The dependency map contains a map from objects to cached responses and XMLProducers that depend on them.
*/
public class DependencyMap {

    private Map<Object, Set<CacheObject>> objectPageMap = new ConcurrentHashMap<Object, Set<CacheObject>>();

    private Map<CacheObject, Set<Object>> pageObjectMap = new ConcurrentHashMap<CacheObject, Set<Object>>();

    private volatile int counter;
    private static final Logger log = Logger.getLogger(DependencyMap.class);
    private final Set<CacheTransaction> openTransactions = Collections.synchronizedSet(new HashSet<CacheTransaction>());

    /**
     * Registers the current response object as dependent on the given object.
     */
    public void addResponseDependency(Object p) {
        Object proxy = getObject(p);
        ThreadContext context = ThreadContext.get();
        //The context may be null data is queried from the database by a thread that does not create a page
        if (context == null) {
            return;
        }
        String requestURL = context.getRequestURL();
        CacheObject c = new CacheObject(requestURL);
        XMLProducer producer = context.getProducer();
        if (producer != null) {
            c.setUrl(producer.getHandlerIdentifier());
            c.setHandler(true);
        }
        addDependency(c, proxy);
    }

    /**
     * Registers the given XMLProducer as dependent on the given object.
     */
    public void addDependency(XMLProducer x, Object p) {
        CacheObject o = new CacheObject(x.getHandlerIdentifier());
        addDependency(o, p);
    }

    private void addDependency(CacheObject c, Object object) {
        if (log.isDebugEnabled()) {
            log.debug(c.getUrl() + " depends on " + object);
        }
        if (hasTransaction()) {
            CacheTransaction currentCacheTransaction = getCurrentCacheTransaction();
            if (currentCacheTransaction.isGetMode()) {
                currentCacheTransaction.addDependency(c, object);
            }
        }
    }

    /**
     * All entries that depend on the given object are invalidated in the cache and XMLProducers are recomputed.
     *
     * @param p the object that has been updated
     */
    public synchronized void objectUpdated(Object p) {
        Object proxy = getObject(p);
        if (log.isDebugEnabled()) {
            log.debug("Object updated " + proxy);
        }
        CacheTransaction currentCacheTransaction = null;
        if (hasTransaction()) {
            currentCacheTransaction = getCurrentCacheTransaction();
        }

        if (currentCacheTransaction != null) {
            currentCacheTransaction.objectUpdated(proxy);
        } else {
            instantUpdateObject(p);
        }
    }

    public void pageRemovedFromCache(String url) {
        for (Iterator<Map.Entry<CacheObject, Set<Object>>> it = pageObjectMap.entrySet().iterator(); it.hasNext();) {
            Map.Entry<CacheObject, Set<Object>> entry = it.next();
            CacheObject key = entry.getKey();
            if (url.equals(key.getUrl())) {
                Set<Object> value = entry.getValue();
                it.remove();
                for (Object o : value) {
                    Set<CacheObject> cacheObjects = objectPageMap.get(o);
                    if (cacheObjects != null) {
                        cacheObjects.remove(key);
                    }
                }
            }
        }
    }

    private synchronized boolean merge(CacheTransaction t) {
        synchronized (ThreadContext.getCache()) {
            //Propagate the changed object to the other threads
            synchronized (openTransactions) {
                for (Object p : t.getUpdatedObjects()) {
                    for (CacheTransaction tran : openTransactions) {
                        if (tran != t) {
                            tran.objectUpdatedInOtherTransaction(p);
                        }
                    }
                }
            }

            //Remove pages from the cache that depend on objects changed in this transaction
            for (Object p : t.getUpdatedObjects()) {
                instantUpdateObject(p);
            }

            //Finally update the dependence map to save the newly generated page.
            // First check that objects we depend on have not changed in concurrent threads.
            Set<Object> dirtyObjects = t.getDirtyObjects();
            if (!dirtyObjects.isEmpty()) {
                dirtyObjects.retainAll(objectPageMap.keySet());
                if (!dirtyObjects.isEmpty()) {
                    log.warn(String.format("Objects changed in other threads while generating the response: %s. This page will not be cached.", dirtyObjects));
                    return false;
                }
            }

            // Merge the object->page and the page->object maps with the transaction
            Map<Object, Set<CacheObject>> tObjectPageMap = t.getObjectPageMap();
            for (Map.Entry<Object, Set<CacheObject>> e : tObjectPageMap.entrySet()) {
                Set<CacheObject> cacheObjects = objectPageMap.get(e.getKey());
                Set<CacheObject> transactionCacheObjects = e.getValue();
                Set<CacheObject> augmentedCacheObjects = new HashSet<CacheObject>();
                for (CacheObject o : transactionCacheObjects) {
                    o = createAugmented(o);
                    augmentedCacheObjects.add(o);
                }
                if (cacheObjects != null) {
                    cacheObjects.addAll(augmentedCacheObjects);
                } else {
                    objectPageMap.put(e.getKey(),augmentedCacheObjects);
                }
            }
            Map<CacheObject, Set<Object>> tPageObjectMap = t.getPageObjectMap();
            for (Map.Entry<CacheObject, Set<Object>> e : tPageObjectMap.entrySet()) {
                CacheObject cacheObject = createAugmented(e.getKey());
                Set<Object> cacheObjects = pageObjectMap.get(cacheObject);
                if (cacheObjects != null) {
                    cacheObjects.addAll(e.getValue());
                } else {
                    pageObjectMap.put(cacheObject, new HashSet<Object>(e.getValue()));
                }
            }
            if (counter++ >= 100) { //Purge maps for each 100 cache transactions/requests
                purge(pageObjectMap);
                purge(objectPageMap);
                counter = 0;
            }
            return true;
        }
    }

    private CacheObject createAugmented(CacheObject o) {
        ThreadContext context = ThreadContext.get();
        if (!o.isHandler() && context.isCacheAugmented()) {
            String url = o.getUrl();
            url = url + "<|>" + WebApp.get().getWebSite().getCacheAugmentationString();
            o = new CacheObject(url);
        }
        return o;
    }

    private void instantUpdateObject(Object p) {
        Set<CacheObject> urls = objectPageMap.get(p);
        if (urls != null) {
            urls = new HashSet<CacheObject>(urls);
            for (CacheObject url : urls) {
                Cache cache = ThreadContext.getCache();
                cache.remove(url.getUrl());
                log.debug("Url removed " + url.getUrl());
                ThreadContext.getSynchronizer().update(url.getUrl());
            }
        }
    }

    public boolean hasTransaction() {
        if (ThreadContext.isInRequestContext()) {
            ThreadContext threadContext = ThreadContext.get();
            return threadContext.getCurrentCacheTransaction() != null;
        } else {
            return false;
        }
    }

    public synchronized CacheTransaction getCurrentCacheTransaction() {
        CacheTransaction currentCacheTransaction = ThreadContext.get().getCurrentCacheTransaction();
        if (currentCacheTransaction == null) {
            throw new InconsistentDependencyException("Cache transaction is not open");
        }
        return currentCacheTransaction;
    }

    public void beginTransaction(boolean getMode) {
        if (hasTransaction()) {
            throw new InconsistentDependencyException("Cache transaction is already opened");
        } else {
            ThreadContext.get().setCurrentCacheTransaction(new CacheTransaction(getMode));
        }
    }

    /**
     * Merges the current cache transaction with the dependency map. Also checks of objects that
     * this page depends on have changed.
     * @return true if it is safe to cache the page
     */
    public boolean mergeTransaction(){
        return merge(getCurrentCacheTransaction());
    }

    public void removeTransaction() {
        ThreadContext.get().setCurrentCacheTransaction(null);
    }

    /**
     * If p is a persistable object, a proxy object is returned. Else the object itself is simply returned.
     */
    private Object getObject(Object p) {
        if (p instanceof Persistable) {
            Persistable persistable = (Persistable) p;
            if (persistable.getId() == null) {
                log.warn("Tried to set up a dependency with a non-persistent persistable", new Exception());
                return null;
            }
            return new ProxyObject(ThreadContext.getWebSite().getQuerier(), (Persistable) p);
        } else {
            return p;
        }
    }

    private <S, T> void purge(Map<S, Set<T>> map) {
        log.info("Purging dependency map");
        Set<S> deadKeys = new HashSet<S>();
        for (Map.Entry<S, Set<T>> e : map.entrySet()) {
            if (e.getValue().isEmpty()) {
                deadKeys.add(e.getKey());
            }
        }
        for (S key : deadKeys) {
            map.remove(key);
        }
        log.info("End purging dependency map");
    }

}
TOP

Related Classes of dk.brics.jwig.server.cache.DependencyMap

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.