summaryrefslogtreecommitdiff
path: root/qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java')
-rw-r--r--qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java601
1 files changed, 601 insertions, 0 deletions
diff --git a/qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java b/qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java
new file mode 100644
index 0000000000..469939a52f
--- /dev/null
+++ b/qpid/java/management/agent/src/main/java/org/apache/qpid/agent/binding/ClassBinding.java
@@ -0,0 +1,601 @@
+/*
+ *
+ * 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.qpid.agent.binding;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.qpid.agent.annotations.QMFEvent;
+import org.apache.qpid.agent.annotations.QMFObject;
+import org.apache.qpid.agent.annotations.QMFProperty;
+import org.apache.qpid.agent.annotations.QMFSeeAlso;
+import org.apache.qpid.agent.annotations.QMFType;
+import org.apache.qpid.agent.annotations.QMFHide;
+import org.apache.qpid.transport.codec.Decoder;
+import org.apache.qpid.transport.codec.Encoder;
+
+/**
+ * Binding information from a custom java class to a QMF schema
+ */
+public class ClassBinding implements TypeBinding
+{
+ private static final Log log = LogFactory.getLog(ClassBinding.class);
+
+ private static enum MethodType
+ {
+ READ_ONLY, READ_WRITE, METHOD, IGNORE
+ }
+
+ protected boolean exposeBehaviour = true;
+ protected String pkg;
+ protected BindingContext bctx;
+ protected String name;
+ protected ArrayList<PropertyBinding> properties = new ArrayList<PropertyBinding>();
+ protected ArrayList<MethodBinding> methods = new ArrayList<MethodBinding>();
+ protected Map<String, MethodBinding> methodsByName = new HashMap<String, MethodBinding>();
+ protected Class javaClass;
+ protected short kind = 1;
+ protected byte hash[] = null;
+ protected ClassBinding superType = null;
+
+ public ClassBinding(String pkg, String name, Class cls,
+ boolean exposeBehaviour, BindingContext bctx)
+ {
+ this.pkg = pkg;
+ this.name = name;
+ this.bctx = bctx;
+ this.javaClass = cls;
+ this.exposeBehaviour = exposeBehaviour;
+ }
+
+ protected MethodType classify(Class<?> cls, Method m)
+ {
+ String name = m.getName();
+ MethodType returnValue = MethodType.METHOD;
+ String propPrefixes[] =
+ { "get", "is" };
+ for (String prefix : propPrefixes)
+ {
+ if (name.startsWith(prefix) && m.getParameterTypes().length == 0)
+ {
+ try
+ {
+ Class<?> type = m.getReturnType();
+ Method setter = cls.getMethod("set"
+ + name.substring(prefix.length()), type);
+ returnValue = MethodType.READ_WRITE;
+ } catch (NoSuchMethodException e)
+ {
+ returnValue = MethodType.READ_ONLY;
+ }
+ break;
+ }
+ }
+ return returnValue;
+ }
+
+ protected String property(Method m)
+ {
+ String name = m.getName();
+ String propPrefixes[] =
+ { "get", "is" };
+ for (String prefix : propPrefixes)
+ {
+ if (name.startsWith(prefix) && m.getParameterTypes().length == 0)
+ {
+ String sfx = name.substring(prefix.length());
+ return Character.toLowerCase(sfx.charAt(0)) + sfx.substring(1);
+ }
+ }
+ // If we got here, it is n invalid property
+ throw new IllegalArgumentException("" + m);
+ }
+
+ protected ArrayList<Method> getMethods(Class cls)
+ {
+ ArrayList returnValue = new ArrayList();
+ ArrayList nameList = new ArrayList();
+ if ((cls != null) && (!cls.equals(Object.class)))
+ {
+ for (Method m : cls.getDeclaredMethods())
+ {
+ if (m.getAnnotation(QMFHide.class) == null)
+ // && (!Modifier.isAbstract(m.getModifiers())))
+ {
+ returnValue.add(m);
+ nameList.add(m.getName());
+ }
+ }
+ // Look at the superclass, if it is also a
+ // QMF object then stop.
+ if (!this.hasQMFSupertype(cls))
+ {
+ for (Method m : this.getMethods(cls.getSuperclass()))
+ {
+ if (!nameList.contains(m.getName()))
+ {
+ returnValue.add(m);
+ nameList.add(m.getName());
+ }
+ }
+ }
+ }
+ return returnValue;
+ }
+
+ protected boolean hasQMFSupertype(Class cls)
+ {
+ boolean returnValue = false;
+ Class superType = cls.getSuperclass();
+ if (superType != null)
+ {
+ if ((superType.getAnnotation(QMFObject.class) != null)
+ || (superType.getAnnotation(QMFType.class) != null)
+ || (superType.getAnnotation(QMFSeeAlso.class) != null)
+ || (superType.getAnnotation(QMFEvent.class) != null))
+ {
+ returnValue = true;
+ }
+ }
+ return returnValue;
+ }
+
+ protected boolean isOptional(Method m, TypeBinding type)
+ {
+ boolean returnValue = false;
+ // Look for the annotaiton first
+ QMFProperty ann = m.getAnnotation(QMFProperty.class);
+ if (ann != null)
+ {
+ returnValue = ann.optional();
+ } else
+ {
+ returnValue = type.optionalDefault();
+ }
+ return returnValue;
+ }
+
+ public ClassBinding parse()
+ {
+ log.debug(String.format(
+ "Parsing class binding '%s' for package '%s' from class %s",
+ name, pkg, javaClass.getName()));
+ for (Method m : this.getMethods(javaClass))
+ {
+ String mname = m.getName();
+ Class<?> type = m.getReturnType();
+ switch (classify(javaClass, m))
+ {
+ case READ_ONLY:
+ TypeBinding tb = bctx.getTypeBinding(type);
+ boolean optional = isOptional(m, tb);
+ properties.add(new PropertyBinding(property(m), tb,
+ PropertyBinding.READ_ONLY, optional));
+ break;
+ case READ_WRITE:
+ TypeBinding tbnd = bctx.getTypeBinding(type);
+ boolean opt = isOptional(m, tbnd);
+ properties.add(new PropertyBinding(property(m), tbnd,
+ PropertyBinding.READ_WRITE, opt));
+ break;
+ case METHOD:
+ // Only expose methods if told to
+ if (exposeBehaviour)
+ {
+ List<ParameterBinding> params = new ArrayList<ParameterBinding>();
+ int arg = 0;
+ for (Class pcls : m.getParameterTypes())
+ {
+ params.add(new ParameterBinding("arg" + arg++, bctx
+ .getTypeBinding(pcls), true, false));
+ }
+ if (type != void.class)
+ {
+ params.add(new ParameterBinding("result", bctx
+ .getTypeBinding(type), false, true));
+ }
+ methods.add(new MethodBinding(mname, params));
+ }
+ break;
+ case IGNORE:
+ break;
+ }
+ }
+ for (MethodBinding m : methods)
+ {
+ methodsByName.put(m.getName(), m);
+ }
+ QMFEvent eventAnnotation = (QMFEvent) javaClass
+ .getAnnotation(QMFEvent.class);
+ if (eventAnnotation != null)
+ {
+ kind = 2; // Event Type
+ }
+ // if (this.hasQMFSupertype(javaClass)) {
+ if ((javaClass.getSuperclass() != Object.class)
+ && (javaClass.getSuperclass() != null))
+ {
+ superType = bctx.register(javaClass.getSuperclass());
+ }
+ return this;
+ }
+
+ public String getPackage()
+ {
+ return pkg;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public List<PropertyBinding> getProperties()
+ {
+ return properties;
+ }
+
+ public List<PropertyBinding> getAllProperties()
+ {
+ if (this.superType == null)
+ {
+ return properties;
+ } else
+ {
+ List<PropertyBinding> newList = new ArrayList<PropertyBinding>(
+ properties);
+ for (PropertyBinding p : superType.getAllProperties())
+ {
+ if (!newList.contains(p))
+ {
+ newList.add(p);
+ }
+ }
+ return newList;
+ }
+ }
+
+ public List<MethodBinding> getMethods()
+ {
+ return methods;
+ }
+
+ public MethodBinding getMethod(String name)
+ {
+ return methodsByName.get(name);
+ }
+
+ // Use this format
+ // bytes value
+ // 0-3 package name
+ // 4-7 class name
+ // 8-11 property signature hash
+ // 12-15 method signature hash
+ // FIXME: Hash codes seem to mess things up
+ public byte[] getSchemaHash()
+ {
+ if (null == hash)
+ {
+ hash = new byte[16];
+ StringBuilder blder = new StringBuilder();
+ int packageHash = pkg.hashCode();
+ int classHash = name.hashCode();
+ int propertyHash = 0;
+ int methodHash = 0;
+ for (PropertyBinding p : properties)
+ {
+ blder.append(p.getName()).append(":").append(
+ p.getType().getCode()).append(":")
+ .append(p.getAccess()).append(":").append(
+ p.isOptional());
+ }
+ propertyHash = blder.toString().hashCode();
+ blder = new StringBuilder();
+ for (MethodBinding m : methods)
+ {
+ blder.append(m.getName());
+ for (ParameterBinding p : m.getParameters())
+ {
+ String direction = p.isIn() ? "in" : "out";
+ blder.append(":").append(p.getName()).append(":").append(
+ direction).append(":")
+ .append(p.getType().getCode());
+ }
+ }
+ methodHash = blder.toString().hashCode();
+ hash[0] = (byte) (packageHash >> 24);
+ hash[1] = (byte) (packageHash >> 16);
+ hash[2] = (byte) (packageHash >> 8);
+ hash[3] = (byte) (packageHash);
+ hash[4] = (byte) (classHash >> 24);
+ hash[5] = (byte) (classHash >> 16);
+ hash[6] = (byte) (classHash >> 8);
+ hash[7] = (byte) (classHash);
+ hash[8] = (byte) (propertyHash >> 24);
+ hash[9] = (byte) (propertyHash >> 16);
+ hash[10] = (byte) (propertyHash >> 8);
+ hash[11] = (byte) (propertyHash);
+ hash[12] = (byte) (methodHash >> 24);
+ hash[13] = (byte) (methodHash >> 16);
+ hash[14] = (byte) (methodHash >> 8);
+ hash[15] = (byte) (methodHash);
+ }
+ return hash;
+ }
+
+ public void encode(Encoder enc)
+ {
+ log.debug(String.format("encoding %s %s with superclass %s", this
+ .getRefClass(), this.getRefPackage(), superType));
+ enc.writeUint8(kind); // kind
+ enc.writeStr8(pkg);
+ enc.writeStr8(name);
+ enc.writeBin128(this.getSchemaHash()); // schema hash
+ // Send true (1) if we have a super-type
+ //if (superType == null)
+ //{
+ // enc.writeUint8((short) 0);
+ //} else
+ //{
+ // enc.writeUint8((short) 1);
+ //}
+ enc.writeUint16(properties.size());
+ // Events do not have the method size sent
+ if (kind == 1)
+ {
+ enc.writeUint16(0);
+ enc.writeUint16(methods.size());
+ }
+ // Add the super type information if we have it
+ //if (superType != null)
+ //{
+ // enc.writeStr8(superType.pkg);
+ // enc.writeStr8(superType.name);
+ // enc.writeBin128(superType.getSchemaHash()); // schema hash
+ //}
+ for (PropertyBinding p : properties)
+ {
+ log.trace("encoding property " + p.getName());
+ p.encode(enc);
+ }
+ for (MethodBinding m : methods)
+ {
+ m.encode(enc);
+ }
+ }
+
+ // Type Binding functions
+ public short getCode()
+ {
+ return (short) 20;
+ }
+
+ public Class<?> getJavaClass()
+ {
+ return javaClass;
+ }
+
+ public Object decode(Decoder dec)
+ {
+ // FIXME This only works with POJOs
+ short typeCode = dec.readUint8();
+ log.trace("Type code: " + typeCode);
+ if (typeCode == 20)
+ {
+ String packageName = dec.readStr8();
+ String className = dec.readStr8();
+ log
+ .debug(String
+ .format(
+ "Decoding an object for package %s class %s with bindings for %s %s",
+ packageName, className, this.pkg, this.name));
+ byte schemaHash[] = dec.readBin128();
+ // Check to see that this is me, and not a subclass
+ if (packageName.equals(this.pkg) && className.equals(this.name))
+ {
+ return decodeWithNoHeaders(dec);
+ } else
+ {
+ ClassBinding mcls = bctx
+ .getClassBinding(packageName, className);
+ return mcls.decodeWithNoHeaders(dec);
+ }
+ } else
+ {
+ TypeBinding tb = QMFTypeBinding.getType(typeCode);
+ return tb.decode(dec);
+ }
+ }
+
+ protected Object decodeWithNoHeaders(Decoder dec)
+ {
+ Object instance = null;
+ try
+ {
+ log.trace("Creating a new instance of " + this.javaClass.getName());
+ instance = this.javaClass.newInstance();
+ } catch (Exception e)
+ {
+ log.error("Could not instantiate object of class"
+ + this.javaClass.getName());
+ throw new BindingException(e);
+ }
+ List<String> excludes = this.processPresenceMasks(dec);
+ for (PropertyBinding p : getAllProperties())
+ {
+ if (!excludes.contains(p.getName()))
+ {
+ Object value = p.getType().decode(dec);
+ BindingUtils.set(p, value, instance);
+ }
+ }
+ return instance;
+ }
+
+ protected List<String> processPresenceMasks(Decoder dec)
+ {
+ List<String> excludes = new ArrayList<String>();
+ short bit = 0;
+ short mask = 0;
+ for (PropertyBinding prop : properties)
+ {
+ if (prop.isOptional())
+ {
+ if (bit == 0)
+ {
+ mask = dec.readUint8();
+ bit = 1;
+ }
+ if ((mask & bit) == 0)
+ {
+ log.trace("Going in exclude " + prop.getName());
+ excludes.add(prop.getName());
+ }
+ bit *= 2;
+ if (bit == 256)
+ {
+ bit = 0;
+ }
+ }
+ }
+ return excludes;
+ }
+
+ public void encode(Encoder enc, Object value)
+ {
+ // if the object is null, assume this is the
+ // correct class
+ if (value == null || (value.getClass().equals(this.javaClass)))
+ {
+ String pkg = getPackage();
+ String cls = getName();
+ log.debug(String.format("Encoding class %s:%s", pkg, cls));
+ enc.writeUint8(this.getCode());
+ enc.writeStr8(pkg);
+ enc.writeStr8(cls);
+ enc.writeBin128(this.getSchemaHash());
+ short bit = 0;
+ short mask = 0;
+ if (value != null)
+ {
+ // Encode the property presence masks first.
+ // if this is not an event
+ if (!isEvent())
+ {
+ for (PropertyBinding p : getAllProperties())
+ {
+ if (p.isOptional())
+ {
+ Object pValue = BindingUtils.get(p, value);
+ if (bit == 0)
+ bit = 1;
+ if (pValue != null)
+ {
+ mask |= bit;
+ }
+ if (bit == 128)
+ {
+ enc.writeUint8(mask);
+ bit = 0;
+ mask = 0;
+ } else
+ {
+ bit = (short) (bit << 1);
+ }
+ }
+ }
+ if (bit != 0)
+ {
+ enc.writeUint8(mask);
+ }
+ }
+ // Now put the actual properties
+ for (PropertyBinding p : getAllProperties())
+ {
+ Object pValue = BindingUtils.get(p, value);
+ if (!p.isOptional() || !(pValue == null))
+ {
+ log.trace(String.format("Encoding property %s", p
+ .getName()));
+ p.getType().encode(enc, pValue);
+ }
+ }
+ }
+ log.debug(String.format("Done with %s:%s", pkg, cls));
+ } else
+ {
+ TypeBinding tb = bctx.getTypeBinding(value.getClass());
+ if (tb == null)
+ {
+ throw new BindingException(String.format(
+ "No class named %s defined for this context ", value
+ .getClass()));
+ } else
+ {
+ if (tb.isNative())
+ {
+ enc.writeUint8(tb.getCode());
+ }
+ tb.encode(enc, value);
+ }
+ }
+ }
+
+ public boolean isNative()
+ {
+ return false;
+ }
+
+ public boolean optionalDefault()
+ {
+ return true;
+ }
+
+ public String getRefClass()
+ {
+ return this.name;
+ }
+
+ public String getRefPackage()
+ {
+ return this.pkg;
+ }
+
+ public short getKind()
+ {
+ return kind;
+ }
+
+ public boolean isEvent()
+ {
+ return kind == 2;
+ }
+
+ public void setKind(short kind)
+ {
+ this.kind = kind;
+ }
+}