| /* |
| |
| Copyright 2004, 2005 (C) John Wilson. All Rights Reserved. |
| |
| Redistribution and use of this software and associated documentation |
| ("Software"), with or without modification, are permitted provided |
| that the following conditions are met: |
| |
| 1. Redistributions of source code must retain copyright |
| statements and notices. Redistributions must also contain a |
| copy of this document. |
| |
| 2. Redistributions in binary form must reproduce the |
| above copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| 3. The name "groovy" must not be used to endorse or promote |
| products derived from this Software without prior written |
| permission of The Codehaus. For written permission, |
| please contact info@codehaus.org. |
| |
| 4. Products derived from this Software may not be called "groovy" |
| nor may "groovy" appear in their names without prior written |
| permission of The Codehaus. "groovy" is a registered |
| trademark of The Codehaus. |
| |
| 5. Due credit should be given to The Codehaus - |
| http://groovy.codehaus.org/ |
| |
| THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS |
| ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT |
| NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
| THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| */ |
| |
| package groovy.net.xmlrpc; |
| |
| import groovy.lang.Closure; |
| import groovy.lang.GString; |
| import groovy.lang.GroovyClassLoader; |
| import groovy.lang.GroovyObject; |
| import groovy.lang.GroovyObjectSupport; |
| import groovy.lang.GroovyRuntimeException; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import uk.co.wilson.net.MinMLSocketServer; |
| import uk.co.wilson.net.xmlrpc.XMLRPCFailException; |
| |
| /** |
| * @author John Wilson |
| * |
| */ |
| |
| public class RPCServer extends GroovyObjectSupport { |
| protected MinMLSocketServer server = null; |
| protected final Map registeredMethods = Collections.synchronizedMap(new HashMap()); |
| protected Closure defaultMethod = null; |
| protected Closure preCallMethod = null; |
| protected Closure postCallMethod = null; |
| protected Closure faultMethod = null; |
| |
| public Object getProperty(final String property) { |
| return new GroovyObjectSupport() { |
| /** |
| * |
| * Allow server.a.b.c = {....} |
| * This deefines a remote method with the name "a.b.c" |
| * This technique is shamelessly stolen from the Python XML-RPC implementation |
| * Thanks and credit to Fredrik Lundh |
| * |
| */ |
| |
| private final StringBuffer propertyPrefix = new StringBuffer(property + "."); |
| |
| public Object getProperty(final String property) { |
| this.propertyPrefix.append(property).append('.'); |
| |
| return this; |
| } |
| |
| public void setProperty(final String name, final Object args) { |
| RPCServer.this.setProperty(this.propertyPrefix + name, name, args); |
| } |
| }; |
| } |
| |
| public void setProperty(final String methodName, final Object method) { |
| setProperty(methodName, methodName, method); |
| } |
| |
| private void setProperty(final String methodName, final String javaMethodName, final Object method) { |
| Closure closure = null; |
| |
| if (method instanceof Closure) { |
| // |
| // This malarky with the CloneNotSupportedException is to keep the broken sun |
| // java compiler from barfing |
| // |
| try { |
| if (false) throw new CloneNotSupportedException(); |
| closure = (Closure)(((Closure)method).clone()); |
| closure.setDelegate(this); |
| } |
| catch (final CloneNotSupportedException e) { |
| // never thrown |
| } |
| } else if (method instanceof Class) { |
| // |
| // calling a static method on a class |
| // |
| |
| final int numberofParameters = getNumberOfParameters(Modifier.PUBLIC | Modifier.STATIC, ((Class)method).getMethods(), javaMethodName); |
| |
| if (numberofParameters != -1) { |
| closure = makeMethodProxy(javaMethodName, numberofParameters, ((Class)method).getName()); |
| } else { |
| throw new GroovyRuntimeException("No static method " |
| + javaMethodName |
| + " on class " |
| + ((Class)method).getName()); |
| } |
| } else { |
| // |
| // calling a method on an instance of a class |
| // |
| |
| final int numberofParameters = getNumberOfParameters(Modifier.PUBLIC, method.getClass().getMethods(), javaMethodName); |
| |
| if (numberofParameters != -1) { |
| closure = makeMethodProxy(javaMethodName, numberofParameters, "delegate"); |
| closure.setDelegate(method); |
| } else { |
| throw new GroovyRuntimeException("No method " |
| + javaMethodName |
| + " on class " |
| + method.getClass().getName()); |
| } |
| } |
| |
| this.registeredMethods.put(methodName, closure); |
| } |
| |
| private int getNumberOfParameters(final int type, final Method methods[], final String property) { |
| boolean foundMatch = false; |
| int numberofParameters = -1; |
| |
| for (int i = 0; i != methods.length; i++) { |
| if ((methods[i].getModifiers() & type) == type) { |
| if (methods[i].getName().equals(property)) { |
| if (foundMatch) { |
| if (numberofParameters != methods[i].getParameterTypes().length) { |
| throw new GroovyRuntimeException("More than one methods " |
| + property |
| + " on class " |
| + methods[i].getDeclaringClass().getName() |
| + " with different numbers of parameters"); |
| } |
| } else { |
| foundMatch = true; |
| numberofParameters = methods[i].getParameterTypes().length; |
| } |
| } |
| } |
| } |
| |
| return numberofParameters; |
| } |
| |
| private Closure makeMethodProxy(final String methodName, final int numberOfParameters, final String qualifier) { |
| final String paramIn, paramOut; |
| |
| if (numberOfParameters == 0) { |
| paramIn = paramOut = ""; |
| } else { |
| final StringBuffer params = new StringBuffer(); |
| |
| for (int i = 0; i != numberOfParameters; i++) { |
| params.append(", p" + i); |
| } |
| |
| paramOut = params.delete(0, 2).toString(); |
| paramIn = paramOut + " -> "; |
| } |
| |
| final String generatedCode = "class X { public def closure = {" + paramIn + " " + qualifier + "." + methodName + "(" + paramOut + ") }}"; |
| // System.out.println(generatedCode); |
| |
| try { |
| final InputStream in = new ByteArrayInputStream(generatedCode.getBytes()); |
| final GroovyObject groovyObject = (GroovyObject)new GroovyClassLoader().parseClass(in, methodName).newInstance(); |
| |
| return (Closure)(groovyObject.getProperty("closure")); |
| } catch (Exception e) { |
| throw new GroovyRuntimeException("Can't generate proxy for XML-RPC method " + methodName, e); |
| } |
| } |
| |
| /** |
| * Starts the server shutdown process |
| * This will return before the server has shut down completely |
| * Full shutdown may take some time |
| * |
| * @throws IOException |
| */ |
| public void stopServer() throws IOException { |
| this.server.shutDown(); |
| } |
| |
| /** |
| * |
| * Convenience method to be called by closures executing remote calls |
| * Called when the closure wants to return a fault |
| * The method always throws an exception |
| * |
| * @param msg Fault message to be returned to the caller |
| * @param code Fault code to be returned to the caller |
| */ |
| public void returnFault(String msg, int code) throws XMLRPCFailException { |
| throw new XMLRPCFailException(msg, code); |
| } |
| |
| /** |
| * |
| * Convenience method to be called by closures executing remote calls |
| * Called when the closure wants to return a fault |
| * The method always throws an exception |
| * |
| * @param msg Fault message to be returned to the caller |
| * @param code Fault code to be returned to the caller |
| */ |
| public void returnFault(GString msg, int code) throws XMLRPCFailException { |
| returnFault(msg.toString(), code); // sometimes Groovy doesn't do the cconversion to String |
| } |
| |
| /** |
| * Supply a closure to be called if there is no closure supplied to handle the call |
| * Typically this logs the bad call and returns a fault by calling returnFault |
| * |
| * The closure is called with two parameters - a String containing the method |
| * name and an array of Object containing the parameters |
| * |
| * @param defaultMethod The closure to be called - if this is null the setting is not changed |
| */ |
| public void setupDefaultMethod(final Closure defaultMethod) { |
| if (defaultMethod != null) this.defaultMethod = defaultMethod; |
| } |
| |
| /** |
| * Supply a closure to be called before the closure which handles the remote call |
| * (or the default closure if there is no handler) is called. |
| * |
| * The closure is called with two parameters - a String containing the method |
| * name and an array of Object containing the parameters |
| * |
| * @param preCallMethod The closure to be called - if this is null the setting is not changed |
| */ |
| public void setupPreCallMethod(final Closure preCallMethod) { |
| if (preCallMethod != null) this.preCallMethod = preCallMethod; |
| } |
| |
| /** |
| * Supply a closure to be called after the closure which handles the remote call |
| * (or the default closure if there is no handler) is called. |
| * |
| * The closure is called with two parameters - a String containing the method |
| * name and an Object containing the result |
| * |
| * @param postCallMethod The closure to be called - if this is null the setting is not changed |
| */ |
| public void setupPostCallMethod(final Closure postCallMethod) { |
| if (postCallMethod != null) this.postCallMethod = postCallMethod; |
| } |
| |
| /** |
| * Supply a closure to be called if the process of executing the remote call throws an exception. |
| * |
| * The closure is called with two parameters - a String containing the fault string |
| * name and an Integer containing the fault value. |
| * The name of the method being called is not passed as the fault could have been |
| * generated before the method name was known. |
| * |
| * @param faultMethod The closure to be called - if this is null the setting is not changed |
| */ |
| public void setupFaultMethod(final Closure faultMethod) { |
| if (faultMethod != null) this.faultMethod = faultMethod; |
| } |
| |
| } |