diff options
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.java | 601 |
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; + } +} |