blob: 469939a52fab622a7951fdd20781dcfdad49b200 [file] [log] [blame]
/*
*
* 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;
}
}