/*
* CacheManager.java
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This work 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004-2006 Per Cederberg. All rights reserved.
*/
package org.liquidsite.core.content;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import org.liquidsite.util.log.Log;
/**
* A content cache manager. This class will be used to cache objects
* upon retrieval from a caching content manager. Only a single
* instance exists, in order to easily guarantee cache consistency.
* This class also synchronizes any changes to the cache content.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
class CacheManager {
/**
* The class logger.
*/
private static final Log LOG = new Log(CacheManager.class);
/**
* The one and only cache manager instance.
*/
private static final CacheManager INSTANCE = new CacheManager();
/**
* The cache manager instance.
*
* @return the cache manager instance
*/
public static CacheManager getInstance() {
return INSTANCE;
}
/**
* The domain cache. This is a map of all domains in the system.
* The domains are indexed by their names.
*/
private HashMap domains = new HashMap();
/**
* The domain host cache. This is a map of all domains in the
* system, indexed by all the host names.
*/
private HashMap domainHosts = new HashMap();
/**
* The site cache. This is a map of site arrays, containing the
* highest revision of each one (no work revisions). It may
* contain sites that are currently offline. The site arrays are
* indexed by their domain name.
*/
private HashMap sites = new HashMap();
/**
* The content parent identifier cache. This is a map containing
* content parent identifiers, indexed by the content identifiers.
* Only content objects where the work and the highest revision
* correlates will be added to this cache.
*/
private HashMap parents = new HashMap();
/**
* The content object cache. This is a map containing some of the
* content objects, indexed by the content identifiers. Only
* content objects where the work and the highest revision
* correlates and being in a limited set of categories will be
* added to this cache.
*/
private HashMap contents = new HashMap();
/**
* The permission list cache. This is a map of domain and content
* permission lists. It is indexed by either the domain name or
* the content identifier. If the permission list is empty for a
* specified domain or content object, a null value is stored in
* this map.
*/
private HashMap permissions = new HashMap();
/**
* Creates a new content cache manager.
*/
private CacheManager() {
// No further initializations needed
}
/**
* Checks if a specified object is present in the cache. Only
* objects that can be cached and that is not present will return
* true. Non-cacheable objects will always return false. This
* method is suitable to use before calling add() to avoid costs
* of cache maintenance and object synchronization.
*
* @param obj the object to check
*
* @return true if the object is cacheable but not cached, or
* false otherwise
*/
public boolean isCached(PersistentObject obj) {
Domain domain;
Content content;
PermissionList perms;
Object key;
if (obj instanceof Domain) {
domain = (Domain) obj;
return domains.containsKey(domain.getName());
} else if (obj instanceof Content) {
content = (Content) obj;
if (content.isLatestRevision() && content.isPublishedRevision()) {
key = new Integer(content.getId());
if (parents.containsKey(key)) {
if (content instanceof ContentTemplate) {
return contents.containsKey(key);
} else {
return true;
}
}
}
} else if (obj instanceof PermissionList) {
perms = (PermissionList) obj;
if (perms.getContentId() == 0) {
key = perms.getDomainName();
} else {
key = new Integer(perms.getContentId());
}
return permissions.containsKey(key);
}
return false;
}
/**
* Adds a persistent object to the cache.
*
* @param obj the object to add
*/
public synchronized void add(PersistentObject obj) {
Domain domain;
ArrayList hosts;
DomainHost host;
Content content;
PermissionList perms;
Object key;
if (obj instanceof Domain) {
domain = (Domain) obj;
domains.put(domain.getName(), obj);
hosts = domain.getHosts();
for (int i = 0; i < hosts.size(); i++) {
host = (DomainHost) hosts.get(i);
domainHosts.put(host.getName(), domain);
LOG.trace("cached host " + host.getName());
}
LOG.trace("cached domain " + domain.getName());
} else if (obj instanceof Content) {
content = (Content) obj;
if (content instanceof ContentSite) {
sites.remove(content.getDomainName());
LOG.trace("uncached site list for " +
content.getDomainName());
}
if (content.isLatestRevision() && content.isPublishedRevision()) {
key = new Integer(content.getId());
parents.put(key, new Integer(content.getParentId()));
LOG.trace("cached content parent for " + key);
if (content instanceof ContentTemplate) {
contents.put(key, content);
LOG.trace("cached content object " + key);
}
}
} else if (obj instanceof PermissionList) {
perms = (PermissionList) obj;
if (perms.getContentId() == 0) {
key = perms.getDomainName();
} else {
key = new Integer(perms.getContentId());
}
if (perms.isEmpty()) {
perms = null;
}
permissions.put(key, perms);
LOG.trace("cached permission list for " + key);
}
}
/**
* Adds a set of persistent objects to the cache. The objects
* will be added individually. That is, the array itself will not
* be stored in the cache.
*
* @param objs the objects to add
*/
public void addAll(PersistentObject[] objs) {
for (int i = 0; i < objs.length; i++) {
if (!isCached(objs[i])) {
add(objs[i]);
}
}
}
/**
* Adds an array of content sites to the cache.
*
* @param domain the domain to use for retrieval
* @param content the array of content sites
*/
public synchronized void addSites(Domain domain,
ContentSite[] content) {
addAll(content);
sites.put(domain.getName(), content);
LOG.trace("cached site list for " + domain.getName());
}
/**
* Removes a specified persistent object from the cache.
*
* @param obj the object to remove
*/
public synchronized void remove(PersistentObject obj) {
Domain domain;
Content content;
PermissionList perms;
Iterator iter;
if (obj instanceof Domain) {
domain = (Domain) obj;
domains.remove(domain.getName());
iter = domainHosts.keySet().iterator();
while (iter.hasNext()) {
if (domain.equals(domainHosts.get(iter.next()))) {
iter.remove();
}
}
sites.remove(domain.getName());
parents.clear();
contents.clear();
permissions.clear();
LOG.trace("uncached domain " + domain.getName());
} else if (obj instanceof Content) {
content = (Content) obj;
if (obj instanceof ContentSite) {
sites.remove(content.getDomainName());
LOG.trace("uncached site list for " +
content.getDomainName());
}
parents.remove(new Integer(content.getId()));
LOG.trace("uncached content parent for " + content.getId());
if (obj instanceof ContentTemplate) {
contents.remove(new Integer(content.getId()));
LOG.trace("uncached content object " + content.getId());
}
permissions.remove(new Integer(content.getId()));
LOG.trace("uncached permission list for " + content.getId());
} else if (obj instanceof PermissionList) {
perms = (PermissionList) obj;
if (perms.getContentId() == 0) {
permissions.remove(perms.getDomainName());
LOG.trace("uncached permission list for " +
perms.getDomainName());
} else {
permissions.remove(new Integer(perms.getContentId()));
LOG.trace("uncached permission list for " +
perms.getContentId());
}
}
}
/**
* Removes all persistent objects from the cache. This is a
* complete cache flush and should be avoided.
*/
public synchronized void removeAll() {
domains.clear();
domainHosts.clear();
sites.clear();
parents.clear();
contents.clear();
permissions.clear();
LOG.trace("cleared all caches");
}
/**
* Returns a collection of all domains in the cache.
*
* @return the collection of domains in the cache
*/
public Collection getAllDomains() {
return domains.values();
}
/**
* Returns a domain from the cache.
*
* @param name the domain name
*
* @return the domain found, or
* null if not present in the cache
*/
public Domain getDomain(String name) {
return (Domain) domains.get(name);
}
/**
* Returns a host domain from the cache.
*
* @param name the host name
*
* @return the domain found, or
* null if not present in the cache
*/
public Domain getHostDomain(String name) {
Domain domain;
domain = (Domain) domainHosts.get(name);
if (domain == null) {
LOG.trace("cache miss on host " + name);
} else {
LOG.trace("cache hit on host " + name);
}
return domain;
}
/**
* Returns all sites in a domain.
*
* @param domain the domain
*
* @return the array of sites in the domain, or
* null if not present in the cache
*/
public ContentSite[] getSites(Domain domain) {
return (ContentSite[]) sites.get(domain.getName());
}
/**
* Returns a content object from the cache.
*
* @param id the content identifier
*
* @return the content object, or
* null if not present in the cache
*/
public Content getContent(int id) {
Content content;
content = (Content) contents.get(new Integer(id));
if (content == null) {
LOG.trace("cache miss on content object " + id);
} else {
LOG.trace("cache hit on content object " + id);
}
return content;
}
/**
* Returns a domain permission list from the cache.
*
* @param domain the domain
*
* @return the permission list for the domain, or
* null if not present in the cache
*/
public PermissionList getPermissions(Domain domain) {
PermissionList perms;
if (domain == null) {
return null;
}
perms = (PermissionList) permissions.get(domain.getName());
if (!permissions.containsKey(domain.getName())) {
LOG.trace("cache miss on permission list for " +
domain.getName());
return null;
} else if (perms == null) {
LOG.trace("cache hit on empty permission list for " +
domain.getName());
return new PermissionList(domain.getContentManager(), domain);
} else {
LOG.trace("cache hit on permission list for " +
domain.getName());
return perms;
}
}
/**
* Returns a content permission list from the cache. If the object
* has no permissions either an empty list or the inherited
* permission list will be returned.
*
* @param content the content object
* @param inherit the search inherited permissions flag
*
* @return the permission list for the domain, or
* null if not present in the cache
*/
public PermissionList getPermissions(Content content, boolean inherit) {
PermissionList perms;
Object key;
if (content == null) {
return null;
}
key = new Integer(content.getId());
perms = (PermissionList) permissions.get(key);
if (inherit) {
while (perms == null && permissions.containsKey(key)) {
key = parents.get(key);
if (key == null) {
LOG.trace("cache miss on permission list for " +
content.getId() + " due to uncached parents");
return null;
} else if (key instanceof Integer
&& ((Integer) key).intValue() == 0) {
return getPermissions(getDomain(content.getDomainName()));
}
perms = (PermissionList) permissions.get(key);
}
}
if (!permissions.containsKey(key)) {
LOG.trace("cache miss on permission list for " +
content.getId());
return null;
} else if (perms == null) {
LOG.trace("cache hit on empty permission list for " +
content.getId());
return new PermissionList(content.getContentManager(), content);
} else {
LOG.trace("cache hit on permission list for " +
content.getId());
return perms;
}
}
}