/*
* Copyright 2002-2004 Greg Hinkle
*
* 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.mc4j.ems.impl.jmx.connection.bean.attribute;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mc4j.ems.connection.EmsException;
import org.mc4j.ems.connection.bean.attribute.AttributeChangeEvent;
import org.mc4j.ems.connection.bean.attribute.AttributeChangeListener;
import org.mc4j.ems.connection.bean.attribute.EmsAttribute;
import org.mc4j.ems.impl.jmx.connection.bean.DMBean;
import org.mc4j.ems.store.Value;
import org.mc4j.ems.store.ValueHistory;
import org.mc4j.ems.store.CompleteValueHistory;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ReflectionException;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import java.io.NotSerializableException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Collections;
/**
* @author Greg Hinkle (ghinkle@users.sourceforge.net), Apr 4, 2005
* @version $Revision: 1.3 $($Author: ghinkl $ / $Date: 2006/05/22 02:38:53 $)
*/
public class DAttribute implements EmsAttribute {
private static Log log = LogFactory.getLog(DAttribute.class);
protected MBeanAttributeInfo info;
protected DMBean bean;
protected boolean loaded;
protected boolean supportedType = true;
protected Set<AttributeChangeListener> changeListeners;
protected long lastRetrieved;
protected Object currentValue;
protected ValueHistory valueHistory;
protected LinkedList<Throwable> failures;
public DAttribute(MBeanAttributeInfo info, DMBean bean) {
this.info = info;
this.bean = bean;
init();
}
/**
* Initializes internal storage settings for the value history
*/
protected void init() {
if (Boolean.valueOf(getControlProperty(CONTROL_ATTRIBUTE_HISTORY,"true"))) {
String historyClass = getControlProperty(CONTROL_ATTRIBUTE_HISTORY_CLASS, null);
if (historyClass != null) {
try {
this.valueHistory = (ValueHistory) Class.forName(historyClass).newInstance();
} catch (InstantiationException e) {
throw new EmsException("Could not instantiate value history class",e);
} catch (IllegalAccessException e) {
throw new EmsException("Could not access value history class",e);
} catch (ClassNotFoundException e) {
throw new EmsException("Configured ValueHistory class not found",e);
}
} else {
this.valueHistory = new CompleteValueHistory(Integer.valueOf(getControlProperty(CONTROL_ATTRIBUTE_HISTORY_DEPTH,"1")));
}
}
}
private String getControlProperty(String property,String defaultValue) {
return bean.getConnectionProvider().getConnectionSettings().getControlProperties().getProperty(property,defaultValue);
}
// TODO GH: Should you be able to register for a certain frequency? and then be guaranteed that the
// notifications won't be faster than that? Then the requests could be grouped as well
public synchronized void registerAttributeChangeListener(AttributeChangeListener listener) {
if (changeListeners == null)
changeListeners = new HashSet<AttributeChangeListener>();
changeListeners.add(listener);
}
public Object getValue() {
if (!loaded)
refresh();
return this.currentValue;
}
public int getValueSize() {
if (valueHistory.getHistorySize() == 0) {
return 0;
} else {
// return com.vladium.utils.ObjectProfiler.sizeof(getValue());
return 0;
}
}
/**
* Set the attribute on the server
*
* @param newValue The value to be set
* @throws Exception
*/
public void setValue(Object newValue) throws Exception {
try {
MBeanServer server = bean.getConnectionProvider().getMBeanServer();
server.setAttribute(bean.getObjectName(),
new Attribute(getName(), newValue));
alterValue(newValue);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
refresh();
}
/**
* Alters the internally stored value of this attribute. Does not update the
* server value. Is intended for mass load of attribute data via the MBean.
*
* @param newValue
*/
public void alterValue(Object newValue) {
if ((newValue != null && !newValue.equals(currentValue)) || (newValue == null && currentValue != null)) {
if (changeListeners != null && changeListeners.size() > 0) {
AttributeChangeEvent event = new AttributeChangeEvent(this, currentValue, newValue);
for (AttributeChangeListener listener : changeListeners) {
listener.attributeChange(event);
}
}
// This old way stored attribute values if they were under a certain size
// if (com.vladium.utils.ObjectProfiler.sizeof(newValue) < 200)
if (newValue instanceof Number)
valueHistory.addValue(new Value(newValue, System.currentTimeMillis()));
currentValue = newValue;
}
}
protected boolean storeHistory(Object value) {
if (value instanceof Number)
return true;
// TODO GH: Store statistics, possibly certain open mbean types
return false;
}
/**
* Updates the local value of this mbean from the server
* <p/>
* TODO we should not update to null on failure, but retain the last known
*/
public synchronized Object refresh() {
loaded = true;
Object newValue = null;
try {
MBeanServer server = bean.getConnectionProvider().getMBeanServer();
newValue = server.getAttribute(bean.getObjectName(), getName());
} catch (ReflectionException e) {
supportedType = false;
registerFailure(e);
throw new EmsException("Could not load attribute value " + e.toString(),e);
} catch (InstanceNotFoundException e) {
registerFailure(e);
throw new EmsException("Could not load attribute value, bean instance not found " + bean.getObjectName().toString(),e);
} catch (MBeanException e) {
registerFailure(e);
Throwable t = e.getTargetException();
if (t != null)
throw new EmsException("Could not load attribute value, target bean threw exception " + t.getLocalizedMessage(),t);
else
throw new EmsException("Could not load attribute value " + e.getLocalizedMessage(), e);
} catch (AttributeNotFoundException e) {
registerFailure(e);
throw new EmsException("Could not load attribute value, attribute [" + getName() + "] not found",e);
} catch(UndeclaredThrowableException e) {
if (e.getUndeclaredThrowable() instanceof InvocationTargetException) {
Throwable t = e.getCause();
if (t.getCause() instanceof NotSerializableException) {
supportedType = false;
registerFailure(t.getCause());
throw new EmsException("Could not load attribute value " + t.getLocalizedMessage(),t.getCause());
} else
throw new EmsException("Could not load attribute value " + t.getLocalizedMessage(),t);
}
throw new EmsException("Could not load attribute value " + e.getLocalizedMessage(),e);
} catch (RuntimeException re) {
supportedType = false;
// TODO GH: Figure this one out
// Getting weblogic.management.NoAccessRuntimeException on wl9
registerFailure(re);
throw new EmsException("Could not load attribute value " + re.getLocalizedMessage(),re);
} catch (NoClassDefFoundError ncdfe) {
supportedType = false;
registerFailure(ncdfe);
throw new EmsException("Could not load attribute value " + ncdfe.getLocalizedMessage(),ncdfe);
} catch (Throwable t) {
throw new EmsException("Could not load attribute value " + t.getLocalizedMessage(),t);
}
alterValue(newValue);
return newValue;
}
/**
* TODO GH: Should this be a list of failure objects that has more info or
* perhaps a custom exception with the info? (timestamp, bean name, attribute name)
* TODO GH: Should this be all failures, retrieval failures, what about set failures?
* TODO GH: Should this be genericised for the server proxy objects?
*
* @return failures of interaction with server related to this attribute
*/
public List<Throwable> getFailures() {
return Collections.unmodifiableList(failures);
}
protected void registerFailure(Throwable t) {
if (failures == null)
failures = new LinkedList<Throwable>();
failures.add(t);
// Bounding this list to make sure memory doesn't grow
if (failures.size() > 2)
failures.removeFirst();
log.warn("Attribute access failure " + t.getLocalizedMessage(),t);
}
public org.mc4j.ems.store.ValueHistory getValueHistory() {
return valueHistory;
}
public String getName() {
return info.getName();
}
public String getType() {
return info.getType();
}
private static final Set<Class> NUMERIC_TYPES = new HashSet();
static {
NUMERIC_TYPES.add(Short.TYPE);
NUMERIC_TYPES.add(Short.class);
NUMERIC_TYPES.add(Integer.TYPE);
NUMERIC_TYPES.add(Integer.class);
NUMERIC_TYPES.add(Long.TYPE);
NUMERIC_TYPES.add(Long.class);
NUMERIC_TYPES.add(Float.TYPE);
NUMERIC_TYPES.add(Float.class);
NUMERIC_TYPES.add(Double.TYPE);
NUMERIC_TYPES.add(Double.class);
NUMERIC_TYPES.add(BigInteger.class);
NUMERIC_TYPES.add(BigDecimal.class);
}
private static final Map<String, Class> TYPES = new HashMap();
static {
TYPES.put(Boolean.TYPE.getName(), Boolean.TYPE);
TYPES.put(Character.TYPE.getName(), Character.TYPE);
TYPES.put(Byte.TYPE.getName(), Byte.TYPE);
TYPES.put(Short.TYPE.getName(), Short.TYPE);
TYPES.put(Integer.TYPE.getName(), Integer.TYPE);
TYPES.put(Long.TYPE.getName(), Long.TYPE);
TYPES.put(Float.TYPE.getName(), Float.TYPE);
TYPES.put(Double.TYPE.getName(), Double.TYPE);
TYPES.put(Void.TYPE.getName(), Void.TYPE);
}
public Class getTypeClass() {
if (TYPES.containsKey(getType())) {
return (Class) TYPES.get(getType());
} else {
// TODO: Switch to using ConnectionProvider.getClassloader(), oh and implement that too
try {
return Class.forName(getType(), true, getClass().getClassLoader());
} catch (ClassNotFoundException e) {
return null; // TODO: Unkown type, how to handle?
}
}
}
public boolean isNumericType() {
return NUMERIC_TYPES.contains(getTypeClass());
}
public String getDescription() {
return info.getDescription();
}
public boolean isWritable() {
return info.isWritable();
}
public boolean isReadable() {
return info.isReadable();
}
public boolean isSupportedType() {
return supportedType;
}
public void setSupportedType(boolean supportedType) {
this.supportedType = supportedType;
}
public int compareTo(Object o) {
DAttribute otherAttribute = (DAttribute) o;
return this.getName().compareTo(
otherAttribute.getName());
}
}