/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.synapse.commons.beanstalk.enterprise; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.commons.jmx.MBeanRegistrar; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * An enterprise beanstalk is used to retrieve Enterprise JavaBean (EJB) client stubs. This class * supports stateless and stateful session beans. * Enterprise beanstalks can be configured in synapse.properties file. Parameters of the JNDI * service to be looked up for EJBs should be provided in that configuration. * Enterprise beanstalks cache EJB client stubs to improve efficiency by omitting excessive JNDI * look ups. Cache timeout for both stateless and stateful session beans could be configured * separately. */ public class EnterpriseBeanstalk { private static final Log log = LogFactory.getLog(EnterpriseBeanstalk.class); /** * Name of the beanstalk */ private String name; /** * Properties used while initializing this beanstalk. */ private Properties props; /** * Executor that runs the Cleaner. */ private ScheduledExecutorService scheduler; /** * Cache timeout for stateless session bean stubs. */ private int statelessBeanTimeoutMinutes = 30; /** * Cache timeout for stateful session bean stubs. */ private int statefulBeanTimeoutMinutes = 30; /** * Warn limit for stateless session beans. A warning is generated when more than this many of * stateless bean stubs are cached by this beanstalk. */ private int statelessBeanWarnLimit = Short.MAX_VALUE; /** * Warn limit for stateful session beans. A warning is generated when more than this many of * stateful bean stubs are cached by this beanstalk. */ private int statefulBeanWarnLimit = Short.MAX_VALUE; /** * JNDI context constructed with the properties provided while initializing this beanstalk. */ private InitialContext initialCtx; /** * Stateless session bean stub cache. */ private Map<String, CacheEntry> statelessBeans = new ConcurrentHashMap<String, CacheEntry>(); /** * Stateful session bean stub cache. */ private Map<String, CacheEntry> statefulBeans = new ConcurrentHashMap<String, CacheEntry>(); /** * Constructs a new enterprise beanstalk with the given name and properties. * @param name Name of the enterprise beanstalk. * @param props Configuration properties. This should include properties of the JNDI service * to be looked up for EJBs. * @param scheduler ScheduledExecutorService for cleaning up timed-out stubs. */ public EnterpriseBeanstalk(String name, Properties props, ScheduledExecutorService scheduler) { this.name = name; this.scheduler = scheduler; this.props = props; } /** * Initialize the current beanstalk by creating the JNDI context, registering the MBean etc. */ public void init() { if (log.isDebugEnabled()) { log.debug("Initializing Beanstalk: " + name); } // Initialize the JNDI context. try { initialCtx = new EnterpriseIntitalContext(props); } catch (NamingException e) { log.error("Could not initialize the JNDI context for the Enterprise Beanstalk " + "named '" + name + "'.", e); return; } // Read settings from the provided properties. if (props != null) { String value; value = props.getProperty(EnterpriseBeanstalkConstants.STATELESS_BEANS_TIMEOUT); if (value != null) { statelessBeanTimeoutMinutes = Integer.parseInt(value); } value = props.getProperty(EnterpriseBeanstalkConstants.STATEFUL_BEANS_TIMEOUT); if (value != null) { statefulBeanTimeoutMinutes = Integer.parseInt(value); } value = props.getProperty(EnterpriseBeanstalkConstants.STATELESS_BEANS_WARN_LIMIT); if (value != null) { statelessBeanWarnLimit = Integer.parseInt(value); } value = props.getProperty(EnterpriseBeanstalkConstants.STATEFUL_BEANS_WARN_LIMIT); if (value != null) { statefulBeanWarnLimit = Integer.parseInt(value); } } // Schedule the cleaner that removes expired beans periodically. int minDelay = Math.min(statelessBeanTimeoutMinutes, statefulBeanTimeoutMinutes); scheduler.scheduleWithFixedDelay( new Runnable() { public void run() { removeExpiredBeans(); } }, minDelay, minDelay, TimeUnit.MINUTES); // Register the MBean for this beanstalk. MBeanRegistrar.getInstance().registerMBean(new EnterpriseBeanstalkView(this), EnterpriseBeanstalkConstants.BEANSTALK_MBEAN_CATEGORY_NAME, name); if (log.isDebugEnabled()) { log.debug("Successfully initialized Beanstalk: " + name); } } /** * Destroys the beanstalk by un-registering MBeans and cleaning up other resources. */ public void destroy() { MBeanRegistrar.getInstance().unRegisterMBean( EnterpriseBeanstalkConstants.BEANSTALK_MBEAN_CATEGORY_NAME, name); } /** * Returns the name of the beanstalk. * * @return Name of the beanstalk. */ public String getName() { return name; } /** * Returns a client stub for the EJB with the given class name, bean id and jndi name. If the * stub is already available in the cache, it is retrieved from there. Otherwise, a JNDI lookup * is performed with the given JNDI name. * * @param className Fully qualified name of the remote interface of the session bean. * @param sessionId Session id for stateful beans. null for stateless ones. * @param jndiName JNDI name of the EJB. null could be used if the bean is already available in * the cache. * @return Retrieved EJB client stub. null if the EJB is not found. */ public Object getEnterpriseBean(String className, String sessionId, String jndiName) { return sessionId == null ? findEjb(statelessBeans, className, sessionId, jndiName, statelessBeanWarnLimit) : findEjb(statefulBeans, className, sessionId, jndiName, statefulBeanWarnLimit); } /** * Remove the specified client stub from the cache. If the stub is not removed using this * method it will be automatically removed from the beanstalk after it times out. * @param className Fully qualified name of the remote interface of the session bean * @param sessionId Session id for stateful session beans, null for stateless ones. * @return EJB client stub that was removed from the beanstalk. */ public Object removeEnterpriseBean(String className, String sessionId) { return (sessionId == null) ? statelessBeans.remove(getMapKey(className, sessionId)) : statefulBeans.remove(getMapKey(className, sessionId)); } /** * Removes expired stateless and stateful bean stubs from the cache. Timeouts are configurable. */ public void removeExpiredBeans() { removeExpiredBeansFromMap(statelessBeans, statelessBeanTimeoutMinutes); removeExpiredBeansFromMap(statefulBeans, statefulBeanTimeoutMinutes); } /** * Retrieves an EJB client stub from the cache, looks up in the JNDI service if it is not * available in the cache. If the bean is found from the JNDI lookup, it is added to the cache. * * @param map Cache to search the stub in. * @param className Fully qualified name of the remote interface of the session bean. * @param sessionId Session id for stateful beans. null for stateless ones. * @param jndiName JNDI name of the EJB. null could be used if the bean is already available in * the cache. * @param warnLimit If this many of stubs are already available in the cache, a warning is * generated before adding a new stub to the cache. * @return Retrieved EJB client stub. null if the EJB is not found. */ private Object findEjb(Map<String, CacheEntry> map, String className, String sessionId, String jndiName, int warnLimit) { CacheEntry entry = map.get(getMapKey(className, sessionId)); if (entry == null && jndiName != null) { synchronized (this) { entry = map.get(getMapKey(className, sessionId)); if (entry == null) { Object ejb = lookupInJndi(jndiName); if (ejb != null) { map.put(getMapKey(className, sessionId), entry = new CacheEntry(ejb)); int size = map.size(); if (size > warnLimit) { String type = sessionId == null ? "stateless" : "stateful"; log.warn("Warn limit reached for " + type + " beans. Currently there " + "are " + size + " " + type + " EJB stubs cached in '" + name + "' " + "beanstalk."); } } } } } if (entry == null) { return null; } else { entry.setLastAccessTime(System.currentTimeMillis()); return entry.getBean(); } } /** * Looks up the given resource in the JNDI service. * @param jndiName JNDI name of the resource (EJB). * @return Resource retrieved from the JNDI lookup. null if no resource is found. */ private Object lookupInJndi(String jndiName) { try { return initialCtx.lookup(jndiName); } catch (NamingException ex) { log.error("Lookup failed for JNDI name: " + jndiName, ex); return null; } } /** * Remove stubs from the given map that have not been used for a time period longer than the * provided timeout. * @param map Bean stub cache. * @param timeoutInMinutes Expiry timeout. */ private void removeExpiredBeansFromMap(Map<String, CacheEntry> map, int timeoutInMinutes) { Iterator<Map.Entry<String, CacheEntry>> itr = map.entrySet().iterator(); while (itr.hasNext()) { Map.Entry<String, CacheEntry> mapEntry = itr.next(); if (System.currentTimeMillis() - mapEntry.getValue().getLastAccessTime() > timeoutInMinutes * 60L * 1000L) { if (log.isDebugEnabled()) { log.debug("Removing the timed-out EJB stub with key '" + mapEntry.getKey() + "', from '" + name + "' beanstalk cache."); } itr.remove(); } } } /** * Derives the map key for this session bean. * @param className Fully qualified name of the remote interface of the EJB. * @param sessionId Session Id for stateful beans, null for stateless ones. * @return Map key derived from the given parameters. */ private String getMapKey(String className, String sessionId) { return sessionId == null ? className : className + "-" + sessionId; } Map<String, CacheEntry> getStatelessBeans() { return statelessBeans; } Map<String, CacheEntry> getStatefulBeans() { return statefulBeans; } }