package org.apache.ws.jaxme.js.apps;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.apache.ws.jaxme.js.DirectAccessible;
import org.apache.ws.jaxme.js.JavaComment;
import org.apache.ws.jaxme.js.JavaConstructor;
import org.apache.ws.jaxme.js.JavaField;
import org.apache.ws.jaxme.js.JavaInnerClass;
import org.apache.ws.jaxme.js.JavaMethod;
import org.apache.ws.jaxme.js.JavaQName;
import org.apache.ws.jaxme.js.JavaQNameImpl;
import org.apache.ws.jaxme.js.JavaSource;
import org.apache.ws.jaxme.js.JavaSourceFactory;
import org.apache.ws.jaxme.js.LocalJavaField;
import org.apache.ws.jaxme.js.Parameter;
import org.apache.ws.jaxme.js.pattern.MethodKey;
/** This class generates clients for Apache XML-RPC.
* The basic idea goes as follows:
* <ol>
* <li>Provide a class implementing the interface {@link XmlRpcCaller}.</li>
* <li>Provide a server side class being called via XML-RPC. The class
* must have a public default constructor, should be stateless, and
* all callable methods must be public instance methods.</li>
* <li>Run the generator, specifying a target package.</li>
* </ol>
* On the client, use the generated class, as if it were the server
* side class.
*/
public class XmlRpcClientGenerator {
private final JavaSourceFactory factory;
private final String targetPackage;
private final Map methods = new HashMap();
private boolean dispatcherImplementsXmlRpcHandler = true;
/** Returns whether the generated dispatcher implements
* XmlRpcHandler. The default value is true.
*/
public boolean isDispatcherImplementsXmlRpcHandler() {
return dispatcherImplementsXmlRpcHandler;
}
/** Sets whether the generated dispatcher implements
* XmlRpcHandler. The default value is true.
*/
public void setDispatcherImplementsXmlRpcHandler(
boolean pDispatcherImplementsXmlRpcHandler) {
dispatcherImplementsXmlRpcHandler = pDispatcherImplementsXmlRpcHandler;
}
/** Creates a new instance with the given factory and target package.
*/
public XmlRpcClientGenerator(JavaSourceFactory pFactory, String pTargetPackage) {
factory = pFactory;
targetPackage = pTargetPackage;
}
/** Returns the factory, that was submitted to the constructor.
*/
public JavaSourceFactory getFactory() { return factory; }
/** Returns the target package, that was submitted to the constructor.
*/
public String getTargetPackage() { return targetPackage; }
/** Generates a name for the method <code>pMethod</code> and
* adds it to the method map, the name being the key.
* @return The generated name.
*/
protected String addMethod(JavaMethod pMethod) {
String className = pMethod.getJavaSource().getQName().toString();
String methodName = pMethod.getName();
for (int i = 0; ; i++) {
String name = className + "-" + methodName;
if (i > 0) {
name += i;
}
if (!methods.containsKey(name)) {
methods.put(name, pMethod);
return name;
}
}
}
/** Converts the result value <code>pValue</code> into the
* requested type <code>pType</code>.
*/
protected Object getResultValue(JavaMethod pMethod, JavaQName pType, Object pValue) {
if (JavaQNameImpl.BOOLEAN.equals(pType)) {
return new Object[]{"((", Boolean.class, ") ", pValue, ").booleanValue()"};
} else if (JavaQNameImpl.BYTE.equals(pType)) {
return new Object[]{"((", Byte.class, ") ", pValue, ").byteValue()"};
} else if (JavaQNameImpl.SHORT.equals(pType)) {
return new Object[]{"((", Short.class, ") ", pValue, ").shortValue()"};
} else if (JavaQNameImpl.INT.equals(pType)) {
return new Object[]{"((", Integer.class, ") ", pValue, ").intValue()"};
} else if (JavaQNameImpl.LONG.equals(pType)) {
return new Object[]{"((", Long.class, ") ", pValue, ").longValue()"};
} else if (JavaQNameImpl.FLOAT.equals(pType)) {
return new Object[]{"((", Float.class, ") ", pValue, ").floatValue()"};
} else if (JavaQNameImpl.DOUBLE.equals(pType)) {
return new Object[]{"((", Double.class, ") ", pValue, ").doubleValue()"};
} else if (pType.isArray()) {
LocalJavaField resultV = pMethod.newJavaField(Vector.class);
resultV.addLine("(", Vector.class, ") ", pValue, ";");
LocalJavaField resultA = pMethod.newJavaField(pType);
pMethod.addIf(resultV , " == null");
pMethod.addLine(resultA, " = null;");
pMethod.addElse();
pMethod.addLine(resultA, " = new ", pType.getInstanceClass(), "[", resultV, ".size()];");
DirectAccessible i = pMethod.addForArray(resultA);
Object element = new Object[]{resultV, ".elementAt(", i, ")"};
pMethod.addLine(resultA, "[", i, "] = ", getResultValue(pMethod, pType.getInstanceClass(), element), ";");
pMethod.addEndFor();
pMethod.addEndIf();
return resultA;
} else if (JavaQNameImpl.getInstance(Object.class).equals(pType)) {
return pValue;
} else {
return new Object[]{"(", pType, ") ", pValue};
}
}
/** Converts the given input <code>pValue</code> with type
* <code>pType</code> into a valid XML-RPC type.
*/
protected Object getInputValue(JavaMethod pMethod, JavaQName pType, Object pValue) {
if (pType.equals(JavaQNameImpl.BOOLEAN)) {
return new Object[]{pValue, " ? ", Boolean.class, ".TRUE : ", Boolean.class, ".FALSE"};
} else if (pType.equals(JavaQNameImpl.BYTE)) {
return new Object[]{"new ", Byte.class, "(", pValue, ")"};
} else if (pType.equals(JavaQNameImpl.SHORT)) {
return new Object[]{"new ", Short.class, "(", pValue, ")"};
} else if (pType.equals(JavaQNameImpl.INT)) {
return new Object[]{"new ", Integer.class, "(", pValue, ")"};
} else if (pType.equals(JavaQNameImpl.LONG)) {
return new Object[]{"new ", Long.class, "(", pValue, ")"};
} else if (pType.equals(JavaQNameImpl.FLOAT)) {
return new Object[]{"new ", Float.class, "(", pValue, ")"};
} else if (pType.equals(JavaQNameImpl.DOUBLE)) {
return new Object[]{"new ", Double.class, "(", pValue, ")"};
} else if (pType.isArray()) {
if (!(pValue instanceof DirectAccessible)) {
LocalJavaField val = pMethod.newJavaField(pType);
val.addLine(pValue);
pValue = val;
}
LocalJavaField v = pMethod.newJavaField(Vector.class);
pMethod.addIf(pValue, " == null");
pMethod.addLine(v, " = null;");
pMethod.addElse();
pMethod.addLine(v, " = new ", Vector.class, "();");
DirectAccessible i = pMethod.addForArray(pValue);
Object element = new Object[]{pValue, "[", i, "]"};
pMethod.addLine(v, ".add(", getInputValue(pMethod, pType.getInstanceClass(), element), ");");
pMethod.addEndFor();
pMethod.addEndIf();
return v;
} else {
return pValue;
}
}
/** Generates a method, invoking method <code>pMethod</code> using
* the name <code>pName</code>.
* @throws NoSuchMethodException
* @throws SecurityException
*/
protected JavaMethod getMethod(JavaSource pJs, JavaField pCaller,
String pName, JavaMethod pMethod)
throws SecurityException, NoSuchMethodException {
Method m = XmlRpcCaller.class.getMethod("xmlRpcCall", new Class[]{String.class, Vector.class});
Class[] exceptions = m.getExceptionTypes();
List exceptionList = new ArrayList();
if (exceptions != null) {
for (int i = 0; i < exceptions.length; i++) {
JavaQName qName = JavaQNameImpl.getInstance(exceptions[i]);
if (!pMethod.isThrowing(qName)) {
exceptionList.add(qName);
}
}
}
JavaMethod jm = pJs.newJavaMethod(pMethod);
LocalJavaField v = jm.newJavaField(Vector.class);
v.addLine("new ", Vector.class, "()");
Parameter[] params = jm.getParams();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
jm.addLine(v, ".add(", getInputValue(jm, p.getType(), p), ");");
}
if (!exceptionList.isEmpty()) {
jm.addTry();
}
Object result = new Object[]{pCaller, ".xmlRpcCall(",
JavaSource.getQuoted(pName), ", ", v, ")"};
if (JavaQNameImpl.VOID.equals(jm.getType())) {
jm.addLine(result, ";");
} else {
jm.addLine("return ", getResultValue(jm, jm.getType(), result), ";");
}
if (!exceptionList.isEmpty()) {
for (int i = 0; i < exceptionList.size(); i++) {
JavaQName exClass = (JavaQName) exceptionList.get(i);
DirectAccessible e = jm.addCatch(exClass);
jm.addThrowNew(UndeclaredThrowableException.class, e);
}
jm.addEndTry();
}
return jm;
}
protected JavaField getXmlRpcCaller(JavaSource pJs) {
JavaField jf = pJs.newJavaField("caller", XmlRpcCaller.class, JavaSource.PRIVATE);
jf.setFinal(true);
return jf;
}
protected JavaConstructor getConstructor(JavaSource pJs, JavaField jf) {
JavaConstructor jcon = pJs.newJavaConstructor(JavaSource.PUBLIC);
Parameter param = jcon.addParam(XmlRpcCaller.class, "pCaller");
jcon.addLine(jf, " = ", param, ";");
return jcon;
}
/** Returns, whether a remote method call is generated for method
* <code>pMethod</code>. The default implementation returns true,
* if the method is public and not static.
*/
protected boolean isMethodGenerated(JavaMethod pMethod) {
return JavaSource.PUBLIC.equals(pMethod.getProtection())
&& !pMethod.isStatic();
}
/** Creates a new client class, which is invoking the given
* server side class <code>pSource</code>.
*/
public JavaSource addClass(JavaSource pSource, JavaSourceResolver pResolver)
throws SecurityException, NoSuchMethodException {
JavaSource js = getFactory().newJavaSource(JavaQNameImpl.getInstance(getTargetPackage(), pSource.getQName().getClassName()), JavaSource.PUBLIC);
JavaField jf = getXmlRpcCaller(js);
getConstructor(js, jf);
Map keys = new HashMap();
addMethods(js, pSource, keys, jf, pResolver);
return js;
}
protected void addMethods(JavaSource pResult, JavaSource pSource, Map pKeys,
JavaField pField, JavaSourceResolver pResolver)
throws SecurityException, NoSuchMethodException {
JavaMethod[] methods = pSource.getMethods();
for (int i = 0; i < methods.length; i++) {
JavaMethod m = methods[i];
if (isMethodGenerated(m)) {
MethodKey key = new MethodKey(m);
if (pKeys.containsKey(key)) {
continue;
}
String name = addMethod(m);
pKeys.put(key, getMethod(pResult, pField, name, m));
}
}
if (pResolver != null) {
JavaQName[] qNames = pSource.getExtends();
for (int i = 0; i < qNames.length; i++) {
JavaSource js = pResolver.getJavaSource(qNames[i]);
if (js != null) {
addMethods(pResult, js, pKeys, pField, pResolver);
}
}
}
}
/** Generates the abstract invoker class.
*/
public JavaSource getInvokerClass(JavaSource pSource) {
JavaInnerClass invoker = pSource.newJavaInnerClass("Invoker", JavaSource.PUBLIC);
JavaComment comment = invoker.newComment();
comment.addLine("The dispatcher is implemented with a {@link java.util.Map}.");
comment.addLine("The map keys are the method names, the values");
comment.addLine("are instances of <code>Invoker</code>.");
invoker.setType(JavaSource.INTERFACE);
JavaMethod jm = invoker.newJavaMethod("invoke", Object.class, JavaSource.PUBLIC);
comment = jm.newComment();
comment.addLine("This method creates a new instance of the class being");
comment.addLine("called, converts the parameter objects (if required)");
comment.addLine("and invokes the requested method. If required, the");
comment.addLine("result is converted also and returned.");
jm.addParam(Vector.class, "pParams");
jm.addThrows(Throwable.class);
return invoker;
}
/** Creates the field with the {@link Map} of invokers.
*/
protected JavaField getInvokerMap(JavaSource pSource) {
JavaField result = pSource.newJavaField("map", Map.class, JavaSource.PRIVATE);
result.addLine("new ", HashMap.class, "()");
return result;
}
/** Creates a new invoker class for the given method.
*/
protected JavaSource getInvoker(JavaSource pSource, JavaMethod pMethod, JavaQName pInvoker, int pNum) {
Parameter[] params = pMethod.getParams();
JavaInnerClass js = pSource.newJavaInnerClass("Invoker" + pNum, JavaSource.PUBLIC);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < params.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(params[i].getType());
}
JavaComment comment = js.newComment();
comment.addLine("Invoker for method " + pMethod.getName() + "(" + sb + ")");
comment.addLine("in class " + pMethod.getJavaSource().getQName() + ".");
js.setStatic(true);
js.addImplements(pInvoker);
JavaMethod jm = js.newJavaMethod("invoke", Object.class, JavaSource.PUBLIC);
Parameter param = jm.addParam(Vector.class, "params");
JavaQName[] classes = pMethod.getExceptions();
for (int i = 0; i < classes.length; i++) {
jm.addThrows(classes[i]);
}
List args = new ArrayList();
for (int i = 0; i < params.length; i++) {
if (i > 0) {
args.add(", ");
}
Parameter p = params[i];
args.add(getResultValue(jm, p.getType(), new Object[]{param, ".elementAt(" + i + ")"}));
}
Object o = new Object[]{"new ", pMethod.getJavaSource().getQName(), "().",
pMethod.getName(), "(", args, ")"};
if (JavaQNameImpl.VOID.equals(pMethod.getType())) {
jm.addLine(o, ";");
jm.addLine("return null;");
} else {
jm.addLine("return ", getInputValue(jm, pMethod.getType(), o), ";");
}
return js;
}
/** Creates the dispatchers constructor.
*/
public JavaConstructor getDispatcherConstructor(JavaSource pSource,
JavaField pMap,
JavaQName pInvoker) {
JavaConstructor con = pSource.newJavaConstructor(JavaSource.PUBLIC);
JavaComment comment = con.newComment();
comment.addLine("Creates a new dispatcher.");
int num = 0;
for (Iterator iter = methods.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
String name = (String) entry.getKey();
JavaMethod method = (JavaMethod) entry.getValue();
JavaSource innerClass = getInvoker(pSource, method, pInvoker, num++);
con.addLine(pMap, ".put(", JavaSource.getQuoted(name), ", new ", innerClass.getQName(), "());");
}
return con;
}
/** Creates the dispatchers <code>getInvoker</code> method.
*/
protected JavaMethod getGetInvokerMethod(JavaSource pSource, JavaQName pInvoker, JavaField pMap) {
JavaMethod jm = pSource.newJavaMethod("getInvoker", pInvoker, JavaSource.PROTECTED);
Parameter param = jm.addParam(String.class, "pName");
jm.addLine("return (", pInvoker, ") ", pMap, ".get(", param, ");");
return jm;
}
/** Creates the dispatchers <code>invoke</code> method.
*/
protected JavaMethod getDispatcherInvokeMethod(JavaSource pSource, JavaQName pInvoker) {
JavaMethod jm = pSource.newJavaMethod("execute", Object.class, JavaSource.PUBLIC);
JavaComment comment = jm.newComment();
comment.addLine("Called for invocation of method <code>pName</code> with");
comment.addLine("the parameters given by <code>pParams</code>.");
Parameter name = jm.addParam(String.class, "pName");
Parameter args = jm.addParam(Vector.class, "pParams");
jm.addThrows(Exception.class);
LocalJavaField invoker = jm.newJavaField(pInvoker);
invoker.addLine("getInvoker(", name, ")");
jm.addIf(invoker, " == null");
jm.addThrowNew(IllegalStateException.class,
JavaSource.getQuoted("Unknown method name: "),
" + ", name);
jm.addEndIf();
jm.addTry();
jm.addLine("return ", invoker, ".invoke(", args, ");");
DirectAccessible e = jm.addCatch(Exception.class);
jm.addLine("throw ", e, ";");
DirectAccessible t = jm.addCatch(Throwable.class);
jm.addThrowNew(UndeclaredThrowableException.class, t);
jm.addEndTry();
return jm;
}
/** Creates the dispatcher class. Make sure, that this method
* is invoked <em>after</em> {@link #addClass(JavaSource, JavaSourceResolver)}!
* @param pQName Fully qualified class name of the dispatcher class.
*/
public JavaSource getDispatcher(JavaQName pQName) {
JavaSource js = getFactory().newJavaSource(pQName, JavaSource.PUBLIC);
if (isDispatcherImplementsXmlRpcHandler()) {
js.addImport(JavaQNameImpl.getInstance("org.apache.xmlrpc.XmlRpcHandler", true));
}
JavaComment comment = js.newComment();
comment.addLine("The dispatcher is being used by the XmlRpcServer.");
comment.addLine("It delegates incoming XML-RPC calls to the classes");
comment.addLine("and methods, for which client classes have been");
comment.addLine("created by the " +
XmlRpcClientGenerator.class.getName() + ".");
JavaSource invoker = getInvokerClass(js);
JavaField map = getInvokerMap(js);
getDispatcherConstructor(js, map, invoker.getQName());
getGetInvokerMethod(js, invoker.getQName(), map);
getDispatcherInvokeMethod(js, invoker.getQName());
return js;
}
}