/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006, JBoss Inc.
*/
package org.jboss.internal.soa.esb.services.registry;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.collections.map.LRUMap;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.Service;
import org.jboss.soa.esb.addressing.EPR;
import org.jboss.soa.esb.common.Configuration;
import org.jboss.soa.esb.common.Environment;
import org.jboss.soa.esb.services.registry.AbstractRegistryInterceptor;
import org.jboss.soa.esb.services.registry.RegistryException;
import org.jboss.soa.esb.services.registry.ServiceNotFoundException;
/**
* Caching registry interceptor.
*
* @author <a href='mailto:Kevin.Conner@jboss.com'>Kevin Conner</a>
*/
public class CachingRegistryInterceptor extends AbstractRegistryInterceptor
{
/**
* The logger for the registry cache
*/
private static final Logger LOGGER = Logger.getLogger(CachingRegistryInterceptor.class) ;
/**
* The default service cache size.
*/
private static final int DEFAULT_MAX_CACHE_SIZE = 100 ;
/**
* The default cache validity period.
*/
private static final long DEFAULT_VALIDITY_PERIOD = Environment.DEFAULT_REGISTRY_CACHE_LIFE_MILLIS_VALUE ;
/**
* The maximum number of services stored in the cache.
*/
private static final int MAX_CACHE_SIZE ;
/**
* The validity period of the entry.
*/
private static final long VALIDITY_PERIOD ;
/**
* The LRU map for the cached services.
*/
private final LRUMap serviceInfoMap = new LRUMap(MAX_CACHE_SIZE) ;
/**
* Find all Services assigned to the Red Hat/JBossESB organization.
* @return Collection of Strings containing the service names.
* @throws RegistryException
*/
public List<String> findAllServices() throws RegistryException
{
// Do not cache, go direct to the registry
return getRegistry().findAllServices() ;
}
/**
* Find all services that belong to the supplied category.
*
* @param serviceCategoryName - name of the category to which the service belongs.
* @return Collection of Strings containing the service names
* @throws RegistryException
*/
public List<String> findServices(final String category)
throws RegistryException
{
// Do not cache, go direct to the registry
return getRegistry().findServices(category) ;
}
/**
* Returns the first EPR in the list that belong to a specific category and service combination.
*
* @param serviceCategoryName - name of the category to which the service belongs.
* @param serviceName - name of the service to which the EPS belongs.
* @return EPR.
* @throws RegistryException
*/
public EPR findEPR(final String category, final String name)
throws RegistryException, ServiceNotFoundException
{
final Service service = new Service(category, name) ;
final ConcurrentMap<EPR, AtomicLong> eprs = getEPRs(service) ;
final Iterator<EPR> eprIter = eprs.keySet().iterator() ;
if (eprIter.hasNext())
{
return eprIter.next() ;
}
else
{
return null;
}
}
/**
* Finds all the EPRs that belong to a specific category and service combination.
*
* @param serviceCategoryName - name of the category to which the service belongs.
* @param serviceName - name of the service to which the EPS belongs.
* @return Collection of EPRs.
* @throws RegistryException
*/
public List<EPR> findEPRs(final String category, final String name)
throws RegistryException, ServiceNotFoundException
{
final Service service = new Service(category, name) ;
final ConcurrentMap<EPR, AtomicLong> eprs = getEPRs(service) ;
return Arrays.asList(eprs.keySet().toArray(new EPR[0])) ;
}
/**
* Registers an EPR under the specified category and service. If the specified service does
* not exist, it will be created at the same time.
*
* @param serviceCategoryName - name of the category to which the service belongs.
* @param serviceName - name of the service to which the EPS belongs.
* @param serviceDescription - human readable description of the service,
* only used when it the service does not yet exist.
* @param epr - the EndPointReference (EPR) that needs to be registered.
* @param eprDescription - human readable description of the EPR
* @throws RegistryException
*/
public void registerEPR(final String category, final String name,
final String serviceDescription, final EPR epr, final String eprDescription)
throws RegistryException
{
final Service service = new Service(category, name) ;
final ServiceInfo serviceInfo = getServiceInfo(service) ;
if (serviceInfo != null)
{
serviceInfo.acquireWriteLock() ;
}
try
{
getRegistry().registerEPR(category, name, serviceDescription, epr, eprDescription) ;
if (serviceInfo != null)
{
final ConcurrentMap<EPR, AtomicLong> eprs = serviceInfo.getEPRs() ;
if (eprs != null)
{
addEPR(eprs, epr) ;
}
}
}
finally
{
if (serviceInfo != null)
{
serviceInfo.releaseWriteLock() ;
}
}
}
/**
* Removes an EPR from the Registry.
* @param serviceCategoryName - name of the category to which the service belongs.
* @param serviceName - name of the service to which the EPS belongs.
* @param epr - the EndPointReference (EPR) that needs to be unregistered.
* @throws RegistryException
*/
public void unRegisterEPR(final String category, final String name,
final EPR epr) throws RegistryException, ServiceNotFoundException
{
final Service service = new Service(category, name) ;
final ServiceInfo serviceInfo = getServiceInfo(service) ;
if (serviceInfo != null)
{
serviceInfo.acquireWriteLock() ;
}
try
{
getRegistry().unRegisterEPR(category, name, epr) ;
if (serviceInfo != null)
{
final ConcurrentMap<EPR, AtomicLong> eprs = serviceInfo.getEPRs() ;
if (eprs != null)
{
final AtomicLong count = eprs.get(epr) ;
if (count != null)
{
if (count.decrementAndGet() == 0)
{
eprs.remove(epr) ;
}
}
}
}
}
finally
{
if (serviceInfo != null)
{
serviceInfo.releaseWriteLock() ;
}
}
}
/**
* Removes a service from the Registry along with all the ServiceBindings underneath it.
*
* @param category - name of the service category, for example 'transformation'.
* @param serviceName - name of the service, for example 'smooks'.
* @throws RegistryException
*/
public void unRegisterService(final String category, final String name)
throws RegistryException, ServiceNotFoundException
{
final Service service = new Service(category, name) ;
final ServiceInfo serviceInfo = getServiceInfo(service) ;
if (serviceInfo != null)
{
serviceInfo.acquireWriteLock() ;
}
try
{
getRegistry().unRegisterService(category, name) ;
removeServiceInfo(service) ;
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Cache removing service " + service) ;
}
}
finally
{
if (serviceInfo != null)
{
serviceInfo.releaseWriteLock() ;
}
}
}
/**
* Get the service information if it is still valid.
* @param service The service information.
* @return The service information or null
*/
private synchronized ServiceInfo getServiceInfo(final Service service)
{
final ServiceInfo serviceInfo = (ServiceInfo)serviceInfoMap.get(service) ;
if (serviceInfo != null)
{
if (serviceInfo.isValid())
{
return serviceInfo ;
}
removeServiceInfo(service) ;
}
return null ;
}
/**
* Create new service information or return current information if present.
* @param service The service information.
* @return The service information
*/
private synchronized ServiceInfo createServiceInfo(final Service service)
{
final ServiceInfo serviceInfo = new ServiceInfo() ;
final ServiceInfo origServiceInfo = (ServiceInfo)serviceInfoMap.put(service, serviceInfo) ;
if ((origServiceInfo != null) && origServiceInfo.isValid())
{
serviceInfoMap.put(service, origServiceInfo) ;
return origServiceInfo ;
}
else
{
return serviceInfo ;
}
}
/**
* Remove the service information from map.
* @param service The service information
*/
private synchronized void removeServiceInfo(final Service service)
{
serviceInfoMap.remove(service) ;
}
/**
* Get the EPRs assocaited with the service, updating the cache if necessary
* @param service The service to query.
* @return The map of EPRs.
* @throws RegistryException For errors accessing the registry delegate.
* @throws ServiceNotFoundException If the service is not in the registry.
*/
private ConcurrentMap<EPR, AtomicLong> getEPRs(final Service service)
throws RegistryException, ServiceNotFoundException
{
final ServiceInfo serviceInfo = getServiceInfo(service) ;
if (serviceInfo != null)
{
serviceInfo.acquireReadLock() ;
try
{
final ConcurrentMap<EPR, AtomicLong> eprs = serviceInfo.getEPRs() ;
if (eprs != null)
{
return eprs ;
}
}
finally
{
serviceInfo.releaseReadLock() ;
}
}
final ServiceInfo newServiceInfo = createServiceInfo(service) ;
newServiceInfo.acquireWriteLock() ;
try
{
final ConcurrentMap<EPR, AtomicLong> eprs = newServiceInfo.getEPRs() ;
if (eprs != null)
{
return eprs ;
}
else
{
final List<EPR> currentEPRs = getRegistry().findEPRs(service.getCategory(), service.getName()) ;
final ConcurrentMap<EPR, AtomicLong> newEPRs = new ConcurrentHashMap<EPR, AtomicLong>() ;
for(EPR epr: currentEPRs)
{
addEPR(newEPRs, epr) ;
}
newServiceInfo.setEPRs(newEPRs) ;
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Cache reloaded for service " + service) ;
}
return newEPRs ;
}
}
finally
{
newServiceInfo.releaseWriteLock() ;
}
}
/**
* Add an EPR entry into the map.
* @param eprs The current map.
* @param epr The epr to add.
*/
private static void addEPR(final ConcurrentMap<EPR, AtomicLong> eprs, final EPR epr)
{
final AtomicLong newCount = new AtomicLong(1) ;
final AtomicLong count = eprs.putIfAbsent(epr, newCount) ;
if (count != null)
{
count.incrementAndGet() ;
}
}
/**
* Class representing the service information
* @author kevin
*/
private static class ServiceInfo
{
private final long expiryTime ;
private ConcurrentMap<EPR, AtomicLong> eprs ;
private ReadWriteLock lock = new ReentrantReadWriteLock() ;
private ServiceInfo()
{
if (VALIDITY_PERIOD > 0)
{
expiryTime = System.currentTimeMillis() + VALIDITY_PERIOD ;
}
else
{
expiryTime = Long.MAX_VALUE ;
}
}
boolean isValid()
{
return System.currentTimeMillis() < expiryTime ;
}
ConcurrentMap<EPR, AtomicLong> getEPRs()
{
return eprs ;
}
void setEPRs(final ConcurrentMap<EPR, AtomicLong> eprs)
{
this.eprs = eprs ;
}
void acquireWriteLock()
{
lock.writeLock().lock() ;
}
void releaseWriteLock()
{
lock.writeLock().unlock() ;
}
void acquireReadLock()
{
lock.readLock().lock() ;
}
void releaseReadLock()
{
lock.readLock().unlock() ;
}
}
static
{
final String maxCacheSizeVal = Configuration.getRegistryCacheMaxSize() ;
int maxCacheSize = DEFAULT_MAX_CACHE_SIZE ;
if (maxCacheSizeVal != null)
{
try
{
maxCacheSize = Integer.parseInt(maxCacheSizeVal) ;
}
catch (final NumberFormatException nfe)
{
// fallback to default
LOGGER.warn("Failed to parse maximum cache size, falling back to default", nfe) ;
}
}
final String validityPeriodVal = Configuration.getRegistryCacheValidityPeriod() ;
long validityPeriod = DEFAULT_VALIDITY_PERIOD ;
if (validityPeriodVal != null)
{
try
{
validityPeriod = Long.parseLong(validityPeriodVal) ;
}
catch (final NumberFormatException nfe)
{
// fallback to default
LOGGER.warn("Failed to parse validity period, falling back to default", nfe) ;
}
}
VALIDITY_PERIOD = validityPeriod ;
MAX_CACHE_SIZE = maxCacheSize ;
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Registry cache validity period: " + VALIDITY_PERIOD) ;
LOGGER.debug("Registry cache maximum size: " + MAX_CACHE_SIZE) ;
}
}
}