/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package javax.management;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.jboss.mx.util.ObjectNamePatternHelper;
import org.jboss.mx.util.ObjectNamePatternHelper.PropertyPattern;
import org.jboss.mx.util.Serialization;
/**
* Object name represents the MBean reference.
*
* @see javax.management.MBeanServer
*
* @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
* @author <a href="mailto:trevor@protocool.com">Trevor Squires</a>.
* @author <a href="mailto:adrian.brock@happeningtimes.com">Adrian Brock</a>.
* @version $Revision: 1.12 $
*
* <p><b>Revisions:</b>
* <p><b>20020521 Adrian Brock:</b>
* <ul>
* <li>Allow *,* in the hashtable properties to signify a property pattern
* </ul>
* <p><b>20020710 Adrian Brock:</b>
* <ul>
* <li> Serialization
* </ul>
*/
public class ObjectName
implements java.io.Serializable, QueryExp
{
// Attributes ----------------------------------------------------
private transient boolean hasPattern = false;
private transient boolean hasDomainPattern = false;
private transient boolean hasPropertyPattern = false;
private transient Hashtable propertiesHash = null;
private transient String domain = null;
private transient String kProps = null;
private transient String ckProps = null;
private transient int hash;
private transient PropertyPattern propertyPattern;
// Static --------------------------------------------------------
private static final long serialVersionUID;
private static final ObjectStreamField[] serialPersistentFields;
static
{
switch (Serialization.version)
{
case Serialization.V1R0:
serialVersionUID = -5467795090068647408L;
serialPersistentFields = new ObjectStreamField[]
{
new ObjectStreamField("domain", String.class),
new ObjectStreamField("propertyList", Hashtable.class),
new ObjectStreamField("propertyListString", String.class),
new ObjectStreamField("canonicalName" , String.class),
new ObjectStreamField("pattern" , Boolean.TYPE),
new ObjectStreamField("propertyPattern" , Boolean.TYPE)
};
break;
default:
serialVersionUID = 1081892073854801359L;
serialPersistentFields = new ObjectStreamField[0];
}
}
/**
* Return an instance of an ObjectName
*
* @todo Review: return common object names?
* @param name a string representation of the object name
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null name
*/
public static ObjectName getInstance(String name)
throws MalformedObjectNameException
{
return new ObjectName(name);
}
/**
* Return an instance of an ObjectName
*
* @todo Review: return common object names?
* @param domain the domain of the object name
* @param key the key of the single property
* @param value the value of the single property
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null parameter
*/
public static ObjectName getInstance(String domain, String key, String value)
throws MalformedObjectNameException
{
return new ObjectName(domain, key, value);
}
/**
* Return an instance of an ObjectName
*
* @todo Review: return common object names?
* @param domain the domain of the object name
* @param table of hashtable for key property pairs
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null parameter
*/
public static ObjectName getInstance(String domain, Hashtable table)
throws MalformedObjectNameException
{
return new ObjectName(domain, table);
}
// Constructors --------------------------------------------------
/**
*
* @param String name a string representation of the object name
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null name
*/
public ObjectName(String name) throws MalformedObjectNameException
{
init(name);
}
/**
* Construct a new ObjectName
*
* @param domain the domain of the object name
* @param key the key of the single property
* @param value the value of the single property
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null parameter
*/
public ObjectName(String domain, String key, String value)
throws MalformedObjectNameException
{
initDomain(domain);
if (null == key)
throw new NullPointerException("properties key cannot be null");
if (null == value)
throw new NullPointerException("properties value cannot be null");
Hashtable ptable = new Hashtable();
ptable.put(key, value);
initProperties(ptable);
this.kProps = key + "=" + value;
}
/**
* Construct a new ObjectName
*
* @param domain the domain of the object name
* @param table of hashtable for key property pairs
* @exception MalformedObjectNameException for an invalid object name
* @exception NullPointerException for a null parameter
*/
public ObjectName(String domain, Hashtable table) throws MalformedObjectNameException
{
if (table == null)
throw new NullPointerException("null table");
if (table.size() < 1)
throw new MalformedObjectNameException("empty table");
initDomain(domain);
initProperties((Hashtable) table.clone());
this.kProps = ckProps;
}
// Public ------------------------------------------------------
public boolean equals(Object object)
{
if (object == this)
return true;
if (object == null)
return false;
if (object instanceof ObjectName)
{
ObjectName oname = (ObjectName) object;
return (oname.hash == hash && domain.equals(oname.domain) &&
ckProps.equals(oname.ckProps));
}
return false;
}
public int hashCode()
{
return hash;
}
public String toString()
{
return this.domain + ":" + kProps;
}
public boolean isPattern()
{
return hasPattern;
}
public String getCanonicalName()
{
return this.domain + ":" + ckProps;
}
public String getDomain()
{
return domain;
}
public String getKeyProperty(String property)
{
return (String) propertiesHash.get(property);
}
public Hashtable getKeyPropertyList()
{
return (Hashtable) propertiesHash.clone();
}
public String getKeyPropertyListString()
{
return kProps;
}
public String getCanonicalKeyPropertyListString()
{
return ckProps;
}
public boolean isPropertyPattern()
{
return hasPropertyPattern;
}
public boolean isDomainPattern()
{
return hasDomainPattern;
}
// QueryExp Implementation -------------------------------------
public boolean apply(ObjectName name)
throws BadStringOperationException,
BadBinaryOpValueExpException,
BadAttributeValueExpException,
InvalidApplicationException
{
if (name.isPattern())
return false;
if (this == name)
return true;
if (ObjectNamePatternHelper.patternMatch(name.getDomain(), this.getDomain()))
{
if (propertyPattern == null)
propertyPattern = new PropertyPattern(this);
return propertyPattern.patternMatch(name);
}
return false;
}
public void setMBeanServer(MBeanServer server)
{
QueryEval.server.set(server);
}
// Private -----------------------------------------------------
/**
* constructs an object name from a string
*/
private void init(String name) throws MalformedObjectNameException
{
if (name == null)
throw new NullPointerException("null name");
if (name.length() == 0)
name = "*:*";
int domainSep = name.indexOf(':');
if (-1 == domainSep)
throw new MalformedObjectNameException("missing domain");
initDomain(name.substring(0, domainSep));
initProperties(name.substring(domainSep + 1));
}
/**
* checks for domain patterns and illegal characters
*/
private void initDomain(String dstring) throws MalformedObjectNameException
{
if (null == dstring)
{
throw new MalformedObjectNameException("null domain");
}
if (isIllegalDomain(dstring))
{
throw new MalformedObjectNameException("domain contains illegal characters");
}
if (dstring.indexOf('*') > -1 || dstring.indexOf('?') > -1)
{
this.hasPattern = true;
this.hasDomainPattern = true;
}
this.domain = dstring;
}
/**
* takes the properties string and breaks it up into key/value pairs for
* insertion into a newly created hashtable.
*
* minimal validation is performed so that it doesn't blow up when
* constructing the kvp strings.
*
* checks for duplicate keys
*
* detects property patterns
*
*/
private void initProperties(String properties) throws MalformedObjectNameException
{
if (null == properties || properties.length() < 1)
{
throw new MalformedObjectNameException("null or empty properties");
}
// The StringTokenizer below hides malformations such as ',,' in the
// properties string or ',' as the first or last character.
// Rather than asking for tokens and building a state machine I'll
// just manually check for those 3 scenarios.
if (properties.startsWith(",") || properties.endsWith(",") || properties.indexOf(",,") != -1)
{
throw new MalformedObjectNameException("empty key/value pair in properties string");
}
Hashtable ptable = new Hashtable();
StringTokenizer tokenizer = new StringTokenizer(properties, ",");
while (tokenizer.hasMoreTokens())
{
String chunk = tokenizer.nextToken();
if (chunk.equals("*"))
{
this.hasPropertyPattern = true;
this.hasPattern = true;
continue;
}
int keylen = chunk.length();
int eqpos = chunk.indexOf('=');
// test below: as in '=value' or 'key=' so that our substrings don't blow up
if (eqpos < 1 || (keylen == eqpos + 1))
{
throw new MalformedObjectNameException("malformed key/value pair: " + chunk);
}
String key = chunk.substring(0, eqpos);
if (ptable.containsKey(key))
{
throw new MalformedObjectNameException("duplicate key: " + key);
}
ptable.put(key, chunk.substring(eqpos + 1, keylen));
}
initProperties(ptable);
this.kProps = properties;
}
/**
* validates incoming properties hashtable
*
* builds canonical string
*
* precomputes the hashcode
*/
private void initProperties(Hashtable properties) throws MalformedObjectNameException
{
if (null == properties || (!this.hasPropertyPattern && properties.size() < 1))
{
throw new MalformedObjectNameException("null or empty properties");
}
Iterator it = properties.keySet().iterator();
ArrayList list = new ArrayList();
while (it.hasNext())
{
String key = null;
try
{
key = (String) it.next();
}
catch (ClassCastException e)
{
throw new MalformedObjectNameException("key is not a string");
}
String val = null;
try
{
val = (String) properties.get(key);
}
catch (ClassCastException e)
{
throw new MalformedObjectNameException("value is not a string");
}
if (key.equals("*") && val.equals("*"))
{
it.remove();
this.hasPropertyPattern = true;
this.hasPattern = true;
continue;
}
if (isIllegalKeyOrValue(key) || isIllegalKeyOrValue(val))
{
throw new MalformedObjectNameException("malformed key/value pair: " + key + "=" + val);
}
list.add(new String(key + "=" + val));
}
Collections.sort(list);
StringBuffer strBuffer = new StringBuffer();
it = list.iterator();
while (it.hasNext())
{
strBuffer.append(it.next());
if (it.hasNext())
{
strBuffer.append(',');
}
}
if (this.hasPropertyPattern)
{
if (properties.size() > 0)
{
strBuffer.append(",*");
}
else
{
strBuffer.append("*");
}
}
this.propertiesHash = properties;
this.ckProps = strBuffer.toString();
this.hash = getCanonicalName().hashCode();
}
/**
* returns true if the key or value string is zero length or contains illegal characters
*/
private boolean isIllegalKeyOrValue(String keyOrValue)
{
char[] chars = keyOrValue.toCharArray();
if (chars.length == 0)
{
return true;
}
for (int i = 0; i < chars.length; i++)
{
switch (chars[i])
{
case ':':
case ',':
case '=':
case '*':
case '?':
return true;
}
}
return false;
}
/**
* returns true if the domain contains illegal characters
*/
private boolean isIllegalDomain(String dom)
{
char[] chars = dom.toCharArray();
for (int i = 0; i < chars.length; i++)
{
switch (chars[i])
{
case ':':
case ',':
case '=':
return true;
}
}
return false;
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException
{
switch (Serialization.version)
{
case Serialization.V1R0:
ObjectInputStream.GetField getField = ois.readFields();
try
{
String name = (String) getField.get("canonicalName", null);
if (name == null)
throw new StreamCorruptedException("No canonical name for jmx1.0?");
init(name);
}
catch (MalformedObjectNameException e)
{
throw new StreamCorruptedException(e.toString());
}
break;
default:
try
{
init((String) ois.readObject());
}
catch (MalformedObjectNameException e)
{
throw new StreamCorruptedException(e.toString());
}
}
}
private void writeObject(ObjectOutputStream oos)
throws IOException
{
switch (Serialization.version)
{
case Serialization.V1R0:
ObjectOutputStream.PutField putField = oos.putFields();
putField.put("domain", domain);
putField.put("propertyList", propertiesHash);
putField.put("propertyListString", ckProps);
putField.put("canonicalName", domain + ":" + ckProps);
putField.put("pattern", hasPattern);
putField.put("propertyPattern", hasPropertyPattern);
oos.writeFields();
break;
default:
oos.writeObject(domain + ":" + ckProps);
}
}
}