/*
* Copyright 2008 the original author or authors.
*
* Licensed 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.rioproject.impl.watch;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.impl.jmx.GenericMBeanInvoker;
import org.rioproject.watch.WatchDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* The WatchInjector provides support for declarative Watch management, by
* taking a {@link org.rioproject.watch.WatchDescriptor} and creating
* {@link SamplingWatch} instances which are then registered for
* an instantiated service.
*
* <p>If the watch already exists in the bean's
* {@link WatchRegistry}, the watch will not be added.
*
* <p>The bean must declare a Java bean getter method that has as it's return
* type one of the following:
*
* <ul>
* <li>Supported primitive types: int.class, long.class, float.class, double.class
* <li>Supported types: Integer.class, Long.class, Lloat.class, Double.class
* </ul>
*
* The read property return type is verified prior to watch creation.
* For example, given a bean called Foo, which declares a method called
* <tt>getCount()</tt> :
*
* <pre>
* public class Foo {
* ...
* public long getCount() {
* return(value);
* }
* ...
* }
* </pre>
*
* Finally, in the OperationalString, Watch declaration is accomplished using
* the embedded the <Monitor> element, for example:
* <pre>
* <SLA ID="backlog" Low="100" High="500">
* <PolicyHandler type="scaling" max="10"
* lowerDampener="3000" upperDampener="3000"/>
* <b><Monitor name="entryCounter" property="count" period="10000"/></b>
* </SLA>
* </pre>
*
* This declaration creates a {@link SamplingWatch} with a name
* of <tt>entryCounter</tt>, which adds the value returned from the
* <tt>getCount()</tt> method every 10 seconds.
*
* @author Dennis Reedy
*/
public class WatchInjector {
private Object impl;
private PropertyDescriptor[] pds;
private ServiceBeanContext context;
/** Collection of created Watch objects */
private final List<Watch> createdWatches = new ArrayList<Watch>();
private final List<Thread> mbeanCheckThreads = new ArrayList<Thread>();
static final String COMPONENT = "org.rioproject.watch.WatchInjector";
static final Logger logger = LoggerFactory.getLogger(COMPONENT);
public WatchInjector(Object impl, ServiceBeanContext context)
throws IntrospectionException {
if(impl ==null)
throw new IllegalArgumentException("impl is null");
if(context == null)
throw new IllegalArgumentException("context is null");
this.impl = impl;
this.context = context;
Class aClass = impl.getClass();
BeanInfo bi = Introspector.getBeanInfo(aClass);
pds = bi.getPropertyDescriptors();
}
/**
* Add a WatchDescriptor, creating the Watch
*
* @param wDesc The WatchDescriptor to add, must not be null
*
* @return The created Watch, or <code>null</code> if it could not be
* created. If the watch already exists, the current Watch wil be returned
* @throws Exception if there are problems getting the configuration from
* the context
*/
public Watch inject(WatchDescriptor wDesc) throws Exception {
if(wDesc ==null)
throw new IllegalArgumentException("wDesc is null");
Watch watch = checkForExistingWatch(wDesc.getName());
if(watch!=null)
return(watch);
if(wDesc.getObjectName()!=null) {
watch = createWatch(wDesc, impl, context.getConfiguration());
if(watch!=null) {
context.getWatchRegistry().register(watch);
createdWatches.add(watch);
}
} else {
boolean propertyMatch = false;
String propertyName = wDesc.getProperty();
for (PropertyDescriptor pd : pds) {
String propName = pd.getName();
if (propName.equals(propertyName)) {
propertyMatch = true;
Method accessor = pd.getReadMethod();
if (accessor != null) {
watch = createWatch(wDesc, impl, context.getConfiguration());
if(watch!=null) {
context.getWatchRegistry().register(watch);
createdWatches.add(watch);
}
} else {
logger.warn("WatchDescriptor [{}], with declared propertyName [{}], " +
"matched, no readMethod found on target object [{}]",
wDesc.toString(), propertyName, impl.getClass().getName());
}
}
}
if(!propertyMatch) {
logger.warn("WatchDescriptor [{}], with declared propertyName [{}] not found on target object [{}]",
wDesc.toString(), propertyName, impl.getClass().getName());
}
}
if(watch!=null)
((SamplingWatch)watch).start();
return(watch);
}
/**
* Add a WatchDescriptor, creating the Watch
*
* @param wDesc The WatchDescriptor to add, must not be null
* @param bean The target bean to poll data from
* @param accessor Method to get data from, must not be null
*
* @return The created Watch, or <code>null</code> if it could not be
* created. If the watch already exists, the current Watch wil be returned
* @throws Exception if there are problems getting the configuration from
* the context
*/
public Watch inject(WatchDescriptor wDesc,
Object bean,
Method accessor) throws Exception {
if(wDesc ==null)
throw new IllegalArgumentException("wDesc is null");
if(accessor ==null)
throw new IllegalArgumentException("accessor is null");
Watch watch = checkForExistingWatch(wDesc.getName());
if(watch!=null)
return(watch);
watch = createWatch(wDesc, bean, accessor, context.getConfiguration());
if(watch!=null) {
context.getWatchRegistry().register(watch);
createdWatches.add(watch);
}
if(watch!=null)
((SamplingWatch)watch).start();
return(watch);
}
/**
* Check to see if a Watch of the provided name exists
*
* @param watchName The Watch name to check
*
* @return The matching Watch or null if not found
*/
private Watch checkForExistingWatch(String watchName) {
if(context.getWatchRegistry().findWatch(watchName)!=null) {
if(logger.isDebugEnabled())
logger.debug("WatchDescriptor for Watch [{}], found on target object's WatchRegistry [{}]",
watchName, impl.getClass().getName()+"]");
/* return the first matching Watch, which in generally every
* case is exactly what is required */
return(context.getWatchRegistry().findWatch(watchName));
}
return null;
}
public void terminate() {
impl = null;
context = null;
pds = null;
createdWatches.clear();
Thread[] threads =
mbeanCheckThreads.toArray(new Thread[mbeanCheckThreads.size()]);
for(Thread t : threads) {
if(t.isAlive())
t.interrupt();
}
}
protected Watch createWatch(WatchDescriptor wDesc,
Object bean,
Configuration config) throws Exception {
return createWatch(wDesc, bean, null, config);
}
protected Watch createWatch(WatchDescriptor wDesc,
Object bean,
Method accessor,
Configuration config) throws Exception {
SamplingWatch watch = null;
if(wDesc.getObjectName()!=null) {
if(wDesc.getMBeanServerConnection()==null) {
logger.warn("Cannot create Watch to monitor " +
"MBean [{}] without " +
"an MBeanServerConnection. You are using Java " +
"version [{}], " +
"that does either not support the JMX Attach API " +
"or the MBeanServerConnection could not be " +
"obtained for the external service.",
wDesc.getObjectName(), System.getProperty("java.version"));
return null;
}
Thread t = new MBeanVerification(wDesc, config);
t.start();
mbeanCheckThreads.add(t);
} else {
watch = new SamplingWatch(wDesc.getName(), config);
watch.setBean(bean);
watch.setAccessor(accessor);
watch.setProperty(wDesc.getProperty());
watch.setPeriod(wDesc.getPeriod());
}
return(watch);
}
/**
* Get all created Watch instances
*
* @return All watches that have been created and registered
* by this utility. If there are no watches created and registered by this
* utility a zero-length array is returned. A new array is allocated each
* time
*/
private Watch[] getWatches() {
Watch[] watches = new Watch[createdWatches.size()];
for(int i=0; i< watches.length; i++)
watches[i] = createdWatches.get(i);
return(watches);
}
/**
* Get all created Watch names
*
* @return The names of all watches that have been created and registered
* by this utility. If there are no watches created and registered by
* this utility a zero-length array is returned. A new array is
* allocated each time
*/
public String[] getWatchNames() {
String[] names = new String[createdWatches.size()];
for(int i = 0; i < names.length; i++)
names[i] = (createdWatches.get(i)).getId();
return (names);
}
/**
* If this utility created a Watch with the provided name, return the Watch.
* Otherwise return null
*
* @param name Te name to look for
*
* @return A Watch instance or null if not created
*/
private Watch getWatch(String name) {
Watch watch = null;
Watch[] watches = getWatches();
for (Watch watche : watches) {
if (watche.getId().equals(name)) {
watch = watche;
break;
}
}
return(watch);
}
/**
* Modify an injected Watch
*
* @param wDesc The WatchDescriptor to modify, must not be null
*
* @throws ConfigurationException if there are errors reading the
* configuration
*/
public void modify(WatchDescriptor wDesc) throws ConfigurationException {
if(wDesc == null)
throw new IllegalArgumentException("wDesc is null");
Watch watch = getWatch(wDesc.getName());
if(watch == null) {
if(logger.isDebugEnabled())
logger.debug("Unable to modify Watch [{}], not created by the WatchInjector", wDesc.getName());
return;
}
SamplingWatch sWatch = (SamplingWatch)watch;
if(wDesc.getPeriod()!=sWatch.getPeriod()) {
sWatch.setPeriod(wDesc.getPeriod());
} else if(!(wDesc.getProperty().equals(sWatch.getProperty()))) {
sWatch.setProperty(wDesc.getProperty());
}
}
/**
* Unregister and remove an injected Watch
*
* @param wDesc The WatchDescriptor to remove, must not be null
*/
public void remove(WatchDescriptor wDesc) {
if(wDesc == null)
throw new IllegalArgumentException("wDesc is null");
Watch watch = getWatch(wDesc.getName());
if(watch==null) {
logger.warn("Unable to remove Watch [{}], not created by the WatchInjector", wDesc.getName());
return;
}
if(createdWatches.remove(watch))
context.getWatchRegistry().deregister(watch);
}
/**
* Unregister and remove an injected Watch
*
* @param name The Watch name (id) to remove, must not be null
*/
public void remove(String name) {
if(name == null)
throw new IllegalArgumentException("name is null");
Watch watch = getWatch(name);
if(watch==null) {
logger.warn("Unable to remove Watch [{}], not created by the WatchInjector", name);
return;
}
if(createdWatches.remove(watch))
context.getWatchRegistry().deregister(watch);
}
class MBeanVerification extends Thread {
WatchDescriptor wDesc;
ObjectName oName;
Configuration config;
boolean alive = true;
MBeanVerification(WatchDescriptor wDesc, Configuration config) {
this.wDesc = wDesc;
this.config = config;
}
@Override
public void interrupt() {
alive = false;
super.interrupt();
}
public void run() {
try {
boolean proceed = false;
oName = new ObjectName(wDesc.getObjectName());
int iterations = 0;
while(alive) {
proceed = wDesc.getMBeanServerConnection().isRegistered(oName);
if(!proceed) {
try {
if(iterations++%5==0)
logger.info("Waiting for MBean [{}] to become available before injecting " +
"watch and associated SLA monitoring", wDesc.getObjectName());
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
} else {
break;
}
}
if(proceed) {
createAndRegisterWatch();
logger.info("MBean [{}] is available, injecting watch [{}] and associated SLA monitoring",
wDesc.getObjectName(), wDesc.getName());
}
} catch (MalformedObjectNameException e) {
logger.warn("The value ["+wDesc.getObjectName()+"] cannot be " +
"used to create an ObjectName. Please verify the " +
"format of the value and retry. The service will " +
"continue to execute, but the monitor you have " +
"requested to be created to observe this MBean can " +
"not be created",
e);
} catch (IOException e) {
logger.warn("A connection exception has occurred communicating " +
"to the attached MBeanServer for the exec'd service. " +
"The service will continue to execute, " +
"but the monitor you have requested to be created to " +
"observe this MBean can not be created",
e);
} finally {
mbeanCheckThreads.remove(Thread.currentThread());
}
}
void createAndRegisterWatch() {
SamplingWatch watch = new SamplingWatch(wDesc.getName(), config);
GenericMBeanInvoker mbi = new GenericMBeanInvoker();
mbi.setObjectName(oName);
mbi.setAttribute(wDesc.getAttribute());
mbi.setMBeanServerConnection(wDesc.getMBeanServerConnection());
watch.setBean(mbi);
watch.setProperty(GenericMBeanInvoker.GETTER);
watch.setPeriod(wDesc.getPeriod());
watch.start();
context.getWatchRegistry().register(watch);
createdWatches.add(watch);
}
}
}