/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.collector.jdbc.binding.provider;
import org.apache.log4j.Logger;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.collector.jdbc.binding.binder.Binder;
import org.helios.collector.jdbc.binding.binder.BinderNotFoundException;
import org.helios.collector.jdbc.binding.binder.IBinder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import javax.management.openmbean.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* <p>Title: BindVariableProviderFactory</p>
* <p>Description: Singleton factory for creating and caching instances of providers and binders.</p>
* <p>Company: Helios Development Group</p>
* @author Whitehead (whitehead.nicholas@gmail.com)
* @version $LastChangedRevision$
* $HeadURL$
* $Id$
* org.helios.collectors.jdbc.binding.BindVariableProviderFactory
*/
//@JMXManagedObject(annotated=true, declared=true)
//@JMXNotifications(notifications={
// @JMXNotification(description="BindVariableProviderFactory JMX Notifications", types={
// @JMXNotificationType(type=BindVariableProviderFactory.VALUE_CHANGED_NOTIFICATION)
// })
//})
// TODO - NEED TO IMPLEMENT NOTIFICATION for OnValueChanged method
public class BindVariableProviderFactory implements ThreadFactory, ApplicationContextAware, IBindVariableProviderListener {
/** */
private static final long serialVersionUID = -4522349786016612658L;
/** Notification type for value changed */
public static final String VALUE_CHANGED_NOTIFICATION = "org.helios.collectors.jdbc.provider.valuechange";
/** The factory singleton instance */
protected static volatile BindVariableProviderFactory factory = null;
/** The singleton lock */
protected static volatile Object lock = new Object();
/** The provider listener execution thread pool */
protected ExecutorService notificationThreadPool = null;
/** Additional classpaths to scannotate for provider and binder classes */
protected Set<URL> classPaths = new HashSet<URL>();
/** A map of provider instances keyed by bind token */
protected Map<String, IBindVariableProvider> providers = new ConcurrentHashMap<String, IBindVariableProvider>();
/** A map of provider classes keyed by bind token key */
protected Map<String, Class<IBindVariableProvider>> providerClasses = new ConcurrentHashMap<String, Class<IBindVariableProvider>>();
/** A map of binder classes keyed by binder shorthand name */
protected Map<String, Class<IBinder>> binderClasses = new ConcurrentHashMap<String, Class<IBinder>>();
/** A map of binder instances keyed by the full binder token */
protected Map<String, IBinder> binders = new ConcurrentHashMap<String, IBinder>();
/** Thread name suffix factory */
protected AtomicInteger serial = new AtomicInteger(0);
/** The spring application context */
protected ApplicationContext appContext = null;
/** The class loader used to load the provider and binder classes */
protected URLClassLoader classLoader = null;
/** The composite type container for bind providers */
protected CompositeType bindProviderType = null;
/** The composite type container for the bind providers */
protected TabularType bindProviderTableType = null;
/** The tabular data instance */
protected TabularDataSupport bindProviderTable = null;
/** The JMX ObjectName for this class */
public static final String FACTORY_OBJECT_NAME = "org.helios.collectors.jdbc:service=BindVariableProviderFactory";
/** The value names in the bindProviderType OpenType */
public static final String[] OpenTypeNames = new String[]{"BindToken", "BindKey", "ProviderValue", "ProviderClass", "BinderClass", "ForceNoBind"};
/** The value descriptions in the bindProviderType OpenType */
public static final String[] OpenTypeDescriptions = new String[]{"The Provider Bind Token", "The Provider Bind Key", "The Provider Current Value", "The Provider Class Name", "The Binder Class Name", "Forces Literal Binding"};
/** The value types in the bindProviderType OpenType */
public static final OpenType[] OpenTypeTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.BOOLEAN};
/** Sequence factory for notifications */
protected AtomicLong notificationSequence = new AtomicLong(0);
/** Static class logger */
protected static final Logger LOG = Logger.getLogger(BindVariableProviderFactory.class);
/**
* Private constructor.
*/
private BindVariableProviderFactory() {
LOG.info("Instantiated BindVariableProviderFactory" );
try {
initComposite();
//this.reflectObject(this);
JMXHelper.getHeliosMBeanServer().registerMBean(this, JMXHelper.objectName(FACTORY_OBJECT_NAME));
} catch (Exception e) {
LOG.warn("Failed to register management interface", e);
}
}
/**
* Singleton accessor
* @return the instance.
*/
public static BindVariableProviderFactory getInstance() {
if(factory==null) {
synchronized(lock) {
if(factory==null) {
factory = new BindVariableProviderFactory();
}
}
}
return factory;
}
/**
* Starts the factory.
* @throws Exception
*/
public void start() throws Exception {
if(notificationThreadPool==null) {
notificationThreadPool = Executors.newCachedThreadPool(this);
}
}
/**
* Initializes the composite type to expose instances of providers. Structure:<ul>
* <li>BindToken Name</li>
* <li>BindToken Key</li>
* <li>Provider Class Name</li>
* <li>Binder Class Name</li>
* <li>Force No Bind</li>
* <li>Value</li>
* </ul>
*/
protected void initComposite() {
try {
bindProviderType = new CompositeType("BindVariableProvider", "Defines an exposed BindVariableProvider", OpenTypeNames, OpenTypeDescriptions, OpenTypeTypes );
bindProviderTableType = new TabularType("BindVariableProviderTable", "Defines a table of exposed BindVariableProviders", bindProviderType, new String[]{"BindToken"});
bindProviderTable = new TabularDataSupport(bindProviderTableType) ;
} catch (Exception e) {
LOG.error("Failed to instantiate composite type", e);
throw new RuntimeException("Failed to instantiate composite type", e);
}
}
/**
* Adds a provider to the bindProvider table.
* @param provider The provider to add.
*/
protected void insert(IBindVariableProvider provider) {
if(provider==null) return;
Object value = provider.getValue();
if(value==null) value = "[null]";
else value = value.toString();
try {
Object[] items = new Object[]{provider.getBindToken(), provider.getBindTokenKey(), value, provider.getClass().getName(), provider.getIBinder().getClass().getName(), provider.isForceNoBind() };
CompositeDataSupport cds = new CompositeDataSupport(bindProviderType, OpenTypeNames, items);
cds.get("BindToken");
bindProviderTable.put(cds);
} catch (OpenDataException e) {
LOG.warn("Failed to create CompositeData item for provider [" + provider.getBindToken() + "]." );
}
}
// /**
// * Scans for all applicable classpaths and then creates an annotation database.
// * @return A Map of all annotations and matching classes.
// * @throws IOException
// */
// protected Map<String,Set<String>> prepareScans() throws IOException {
// Collections.addAll(classPaths, ClasspathUrlFinder.findClassPaths());
// URL[] urlArray = classPaths.toArray(new URL[classPaths.size()]);
// classLoader = new URLClassLoader(urlArray);
// LOG.info("Prepared [" + classPaths.size() + "] URLs to scan for providers and binders");
// LOG.info("Scanning Classpath for Provider Classes");
// annotationDb.setScanClassAnnotations(true);
// annotationDb.setScanFieldAnnotations(false);
// annotationDb.setScanMethodAnnotations(false);
// annotationDb.setScanParameterAnnotations(false);
// annotationDb.scanArchives(urlArray);
// LOG.info("Scan Complete");
// return annotationDb.getAnnotationIndex();
// }
/**
* Locates and registers all bind variable provider classes.
* @param index the annotation map
*/
@SuppressWarnings("unchecked")
protected void scanForProviders(Map<String,Set<String>> index) {
Set<String> classSet = index.get(BindVariableProvider.class.getName());
if(classSet==null) return;
for(String className: classSet) {
if(LOG.isDebugEnabled()) LOG.info("Loading Provider [" + className + "]");
try {
Class<IBindVariableProvider> clazz = (Class<IBindVariableProvider>) Class.forName(className, true, classLoader);
BindVariableProvider bvp = clazz.getAnnotation(BindVariableProvider.class);
String tokenKey = bvp.tokenKey();
Class<?> existingClass = providerClasses.get(tokenKey);
if(existingClass!=null) {
if(!clazz.equals(existingClass)) {
LOG.warn("The provider class [" + clazz.getName() + "] has the same token key as [" + existingClass.getName() + "] but was scanned after it. It will not be added");
}
} else {
providerClasses.put(tokenKey, clazz);
if(LOG.isDebugEnabled()) LOG.info("Loaded Provider [" + className + "] with key [" + tokenKey + "]");
}
} catch (Exception e) {
LOG.error("Failed to load provider class [" + className + "]");
}
}
}
/**
* Locates and registers all binder classes.
* @param index the annotation map
*/
@SuppressWarnings("unchecked")
protected void scanForBinders(Map<String,Set<String>> index) {
Set<String> classSet = index.get(Binder.class.getName());
if(classSet==null) return;
for(String className: classSet) {
if(LOG.isDebugEnabled()) LOG.info("Loading Binder [" + className + "]");
// binderClasses
try {
Class<IBinder> clazz = (Class<IBinder>) Class.forName(className, true, classLoader);
Binder bvp = clazz.getAnnotation(Binder.class);
String name = bvp.name();
Class<?> existingClass = binderClasses.get(name);
if(existingClass!=null) {
if(!clazz.equals(existingClass)) {
LOG.warn("The binder class [" + clazz.getName() + "] has the same shorthand name as [" + existingClass.getName() + "] but was scanned after it. It will not be added");
}
} else {
binderClasses.put(name, clazz);
if(LOG.isDebugEnabled()) LOG.info("Loaded Binder [" + className + "] with key [" + name + "]");
}
} catch (Exception e) {
LOG.error("Failed to load provider class [" + className + "]");
}
}
}
/**
* Returns a provider instance for the passed bind token.
* @param bindToken the bind token from a SQL statement.
* @return a provider
* @throws ProviderNotFoundException
*/
public IBindVariableProvider getProvider(String bindToken) throws ProviderNotFoundException {
if(bindToken==null) throw new ProviderNotFoundException("Bind token was null");
IBindVariableProvider provider = providers.get(bindToken);
if(provider!=null) return provider;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
ProviderToken pt = ProviderToken.parse(bindToken);
Class<IBindVariableProvider> clazz = providerClasses.get(pt.getProviderTypeKey());
if(clazz==null) throw new Exception("No class found for bind token key [" + pt.getProviderTypeKey() + "]");
provider = clazz.newInstance();
provider.setForceNoBind(pt.isForceNoBind());
provider.setBindToken(bindToken);
if(provider instanceof ApplicationContextAware) {
((ApplicationContextAware)provider).setApplicationContext(appContext);
}
if(pt.isProviderConfigDefined()) provider.configureProvider(pt.getProviderConfig());
if(pt.isBinderDefined()) {
IBinder binder = getBinder(pt.getBinderToken());
provider.setBinder(binder);
}
provider.registerListener(this);
providers.put(bindToken, provider);
insert(provider);
return provider;
} catch (Exception e) {
LOG.error("Failed to get provider for bindToken [" + bindToken + "]", e);
throw new ProviderNotFoundException("Failed to get provider for bindToken [" + bindToken + "]", e);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
/**
* Returns the binder class for the passed binderTokenKey
* @param binderTokenKey the binder's token key
* @return the binder class for the passed binderTokenKey
*/
public Class<IBinder> getBinderClass(String binderTokenKey) {
return binderClasses.get(binderTokenKey);
}
/**
* Returns the provider class for the passed providerTokenKey
* @param providerTokenKey the provider's token key
* @return the provider class for the passed providerTokenKey
*/
public Class<IBindVariableProvider> getBindVariableProviderClass(String providerTokenKey) {
return providerClasses.get(providerTokenKey);
}
/**
* Returns an instance of a binder for the passed shorthand name
* @param binderToken the shorthand name and optional config
* @return a binder instance
* @throws BinderNotFoundException
*/
public IBinder getBinder(String binderToken) throws BinderNotFoundException {
String[] frags = binderToken.split(":");
String name = frags[0];
String config = frags.length>1 ? frags[1] : null;
IBinder binder = binders.get(binderToken);
if(binder!=null) return binder;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
Class<IBinder> clazz = binderClasses.get(name);
if(clazz==null) throw new Exception("No class found for shorthand key [" + name + "]");
binder = clazz.newInstance();
if(config!=null) {
binder.configureBinder(config);
}
binders.put(binderToken, binder);
return binder;
} catch (Exception e) {
LOG.error("Failed to get binder for shorthand key [" + name + "]");
throw new BinderNotFoundException("Failed to get binder for shorthand key [" + name + "]");
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
/**
* Asynchronously propagates a provider value change event to all registered listeners.
* @param listeners The listeners to propagate to.
* @param provider The provider that fired the change.
* @param oldObject The old provider value.
* @param newObject The new provider value.
*/
public void fireProviderValueChanged(Set<IBindVariableProviderListener> listeners, IBindVariableProvider provider, Object oldObject, Object newObject) {
ProviderChangeEvent pce = new ProviderChangeEvent(listeners, provider, oldObject, newObject);
this.notificationThreadPool.submit(pce);
}
/**
* @param notificationThreadPool the notificationThreadPool to set
*/
public void setNotificationThreadPool(ExecutorService notificationThreadPool) {
this.notificationThreadPool = notificationThreadPool;
}
/**
* Adds an additional set of URLs to scan for providers and binders.
* @param classPaths the classPath URLs to add.
*/
public void setClassPaths(Set<URL> classPaths) {
if(classPaths!=null) {
this.classPaths.addAll(classPaths);
}
}
/**
* Manually adds a set of bind variable provider classes.
* @param providerClasses the provider Classes to add
*/
public void setProviderClasses(Map<String, Class<IBindVariableProvider>> providerClasses) {
if(providerClasses!=null) {
this.providerClasses.putAll(providerClasses);
}
}
/**
* Manually adds a set of binder classes.
* @param binderClasses the binder Classes to add
*/
public void setBinderClasses(Map<String, Class<IBinder>> binderClasses) {
if(binderClasses!=null) {
this.binderClasses.putAll(binderClasses);
}
}
/**
* Creates threads for the notification thread pool.
* @param r The runnable to be executed by the thread pool.
* @return A new thread.
* @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
*/
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("ProviderChangeNotificationThread-" + serial.incrementAndGet());
return t;
}
/**
* Sets the spring application context
* @param appContext the spring application context
* @throws BeansException
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
this.appContext = appContext;
}
/**
* Manually adds new provider instances with the specified token keys to the factory.
* @param providers A map of provider instances keyed by their unique token key.
*/
public void setProviders(Map<String, IBindVariableProvider> providers) {
if(providers!=null) {
for(Map.Entry<String, IBindVariableProvider> p: providers.entrySet()) {
IBindVariableProvider provider = p.getValue();
if(!this.providers.containsKey(p.getKey().toLowerCase())) {
this.providers.put(p.getKey().toLowerCase(), provider);
} else {
LOG.warn("The provider with class [" + provider.getClass().getName() + "] and Token Key [" + p.getKey() + "] was not registered as a provider with that key was already registered.");
}
}
}
}
/**
* A composite open type accessor that outlines the details of each bind provider in cache.
* @return A bind provider summary table
*/
@ManagedAttribute
public TabularDataSupport getBindProviders() {
return bindProviderTable;
}
/**
* Returns the number of providers in cache
* @return the number of providers in cache
*/
@ManagedAttribute
public int getBindProviderCount() {
return providers.size();
}
/**
* Returns the number of binders in cache.
* @return the number of binders in cache.
*/
@ManagedAttribute
public int getBinderCount() {
return binders.size();
}
/**
* Fired when a provider's value changes.
* @param provider The provider that fired the change
* @param oldValue The old value
* @param newValue The new value
* @see org.helios.collectors.jdbc.binding.provider.IBindVariableProviderListener#onValueChanged(org.helios.collectors.jdbc.binding.provider.IBindVariableProvider, java.lang.Object, java.lang.Object)
*/
public void onValueChanged(IBindVariableProvider provider, Object oldValue, Object newValue) {
// Notification not = new AttributeChangeNotification(this.objectName, notificationSequence.incrementAndGet(), System.currentTimeMillis(), "The value of bind provider [" + provider.getBindToken() + "] changed", provider.getBindToken(), provider.getBindTokenKey(), oldValue, newValue);
// sendNotification(not);
}
}