Java und Apache XML-RPC - Default Handler?

Hi,

ich arbeite gerade an einer Java Software, die mit einem am Netzwerk angebundenen Gerät per XML-RPC kommunziert. Das Gerät macht Callbacks auf einen XML-RPC Server, den ich bereitstellen muss. Und da liegt das Problem: Diese Callbacks sehen etwa so aus:

Code:
mathias@mini:~$ nc -l 8000
POST /xmlrpc HTTP/1.1
User-Agent: XMLRPC++ 0.7
Host: 10.0.0.211:8000
Content-Type: text/xml
Content-length: 141

<?xml version="1.0"?>
<methodCall><methodName>listDevices</methodName>
<params><param><value>someID</value></param></params></methodCall>

Das Problem liegt darin, dass der Method Name keinen Handler spezifiziert. Apache XMP-RPC ist so gebaut, dass der Methoden Name eigentlich so aussehen sollte: Handler.name wobei ein Handler dann eine Klasse ist und der name der Name einer Methode.

Weiss jemand ob und wie ich mit solchen Calls überhaupt etwas anfangen kann bzw sie irgendwie behandeln kann?

Grüße
serow

EDIT: Um es mal auf ein Minibespiel herunterzubrechen habe ich hier einen EchoServer und EchoClient. Der EchoClient ruft auf dem Server die Methode "echo" auf, aber genauso wie mein Gerät hier ohne einen Handler angzugeben.

Code:
public class EchoServer {
	
    public static void main(String[] args) throws Exception {
    	
		  WebServer webServer = new WebServer(8080);
		 
		  EchoService echo = new EchoServiceImpl();
		  
		  PropertyHandlerMapping phm = new PropertyHandlerMapping();
		  phm.setRequestProcessorFactoryFactory(new EchoRequestProcessorFactoryFactory(echo));
		  phm.setVoidMethodEnabled(true);
		  phm.addHandler("EchoService", EchoService.class);
		  
		  XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
		  xmlRpcServer.setHandlerMapping(phm);
		
		  XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
		  serverConfig.setEnabledForExtensions(true);
		  serverConfig.setContentLengthOptional(false);
		 
		  webServer.start();

    }
}
    
public class EchoClient {
	
	public static void main(String[] args) throws Exception {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		config.setServerURL(new URL("http://127.0.0.1:8080/xmlrpc"));
		config.setEnabledForExtensions(true);
		config.setConnectionTimeout(60 * 1000);
		config.setReplyTimeout(60 * 1000);
		XmlRpcClient client = new XmlRpcClient();
		client.setConfig(config);
		
		Object[] params = new Object[] {"bla"};
		client.execute("echo", params);
	}
	
}

Laut dem Server Code würde jetzt erwartet werden, dass ich die Methode als "EchoServer.echo" anspreche und nicht nur mit "echo". Leider kann ich mir das nicht aussuchen ;)
 
Zuletzt bearbeitet:
Hi nochmal,

mir sind gerade Zweifel gekommen ob ich mit diesem doch sehr speziellen Anliegen hier überhaupt richtig bin. Deswegen habe ich den Beitrag noch in einem Java Forum gepostet:

Apache XMl-RPC - Default Handler

Grüße
serow
 
Hallo Serow,

ich hab mir mal die Sourcen von Apache XML-RPC angeschaut. Ich konnte keine Möglichkeit finden einen Default-Handler zu bestimmen. Ich habs aber dennoch geschafft daß ein Clientaufruf ohne Handler "angenommen" wird. Ich hab einfach die Klasse org/apache/xmlrpc/server/AbstractReflectiveHandlerMapping.java überschrieben und konnte somit bei der Methode getHandler einen Defaultwert hinterlegen. Wenn dir diese Variante nicht zu dirty ist kann ich die Sourcen hier posten.

Gruß
odigo
 
Ach was solls, ich poste es einfach. Du kannst es benutzen oder auch nicht.

Vorab nochmal zur Erklärung was ich gemacht habe.

Die unveränderte Methode getHandler
Code:
public XmlRpcHandler getHandler(String pHandlerName)
            throws XmlRpcNoSuchHandlerException, XmlRpcException {
        XmlRpcHandler result = (XmlRpcHandler) handlerMap.get(pHandlerName);
        if (result == null) {
            throw new XmlRpcNoSuchHandlerException("No such handler: " + pHandlerName);
        }
        return result;
    }
Hier meine Veränderung (kannst du natürlich schöner machen)
Code:
public XmlRpcHandler getHandler(String pHandlerName)
            throws XmlRpcNoSuchHandlerException, XmlRpcException {
        XmlRpcHandler result = (XmlRpcHandler) handlerMap.get("Calculator." + pHandlerName);
        if (result == null) {
            throw new XmlRpcNoSuchHandlerException("No such handler: " + pHandlerName);
        }
        return result;
    }
Und hier die ganze Klasse (package org.apache.xmlrpc.server)
Code:
/*
 * 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.xmlrpc.server;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcHandler;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.common.TypeConverterFactory;
import org.apache.xmlrpc.common.TypeConverterFactoryImpl;
import org.apache.xmlrpc.metadata.ReflectiveXmlRpcMetaDataHandler;
import org.apache.xmlrpc.metadata.Util;
import org.apache.xmlrpc.metadata.XmlRpcListableHandlerMapping;
import org.apache.xmlrpc.metadata.XmlRpcMetaDataHandler;
import org.apache.xmlrpc.server.RequestProcessorFactoryFactory.RequestProcessorFactory;


/** Abstract base class of handler mappings, which are
 * using reflection.
 */
public abstract class AbstractReflectiveHandlerMapping
        implements XmlRpcListableHandlerMapping {
    /** An object implementing this interface may be used
     * to validate user names and passwords.
     */
    public interface AuthenticationHandler {
        /** Returns, whether the user is authenticated and
         * authorized to perform the request.
         */
        boolean isAuthorized(XmlRpcRequest pRequest)
            throws XmlRpcException;
    }

    private TypeConverterFactory typeConverterFactory = new TypeConverterFactoryImpl();
    protected Map handlerMap = new HashMap();
    private AuthenticationHandler authenticationHandler;
    private RequestProcessorFactoryFactory requestProcessorFactoryFactory = new RequestProcessorFactoryFactory.RequestSpecificProcessorFactoryFactory();
    private boolean voidMethodEnabled;

    /**
     * Sets the mappings {@link TypeConverterFactory}.
     */
    public void setTypeConverterFactory(TypeConverterFactory pFactory) {
        typeConverterFactory = pFactory;
    }

    /**
     * Returns the mappings {@link TypeConverterFactory}.
     */
    public TypeConverterFactory getTypeConverterFactory() {
        return typeConverterFactory;
    }

    /** Sets the mappings {@link RequestProcessorFactoryFactory}. Note, that this doesn't
     * affect already registered handlers.
     */
    public void setRequestProcessorFactoryFactory(RequestProcessorFactoryFactory pFactory) {
        requestProcessorFactoryFactory = pFactory;
    }

    /** Returns the mappings {@link RequestProcessorFactoryFactory}.
     */
    public RequestProcessorFactoryFactory getRequestProcessorFactoryFactory() {
        return requestProcessorFactoryFactory;
    }

    /** Returns the authentication handler, if any, or null.
     */
    public AuthenticationHandler getAuthenticationHandler() {
        return authenticationHandler;
    }

    /** Sets the authentication handler, if any, or null.
     */
    public void setAuthenticationHandler(AuthenticationHandler pAuthenticationHandler) {
        authenticationHandler = pAuthenticationHandler;
    }

    protected boolean isHandlerMethod(Method pMethod) {
        if (!Modifier.isPublic(pMethod.getModifiers())) {
            return false;  // Ignore methods, which aren't public
        }
        if (Modifier.isStatic(pMethod.getModifiers())) {
            return false;  // Ignore methods, which are static
        }
        if (!isVoidMethodEnabled()  &&  pMethod.getReturnType() == void.class) {
            return false;  // Ignore void methods.
        }
        if (pMethod.getDeclaringClass() == Object.class) {
            return false;  // Ignore methods from Object.class
        }
        return true;
    }

    /** Searches for methods in the given class. For any valid
     * method, it creates an instance of {@link XmlRpcHandler}.
     * Valid methods are defined as follows:
     * <ul>
     *   <li>They must be public.</li>
     *   <li>They must not be static.</li>
     *   <li>The return type must not be void.</li>
     *   <li>The declaring class must not be
     *     {@link java.lang.Object}.</li>
     *   <li>If multiple methods with the same name exist,
     *     which meet the above conditins, then an attempt is
     *     made to identify a method with a matching signature.
     *     If such a method is found, then this method is
     *     invoked. If multiple such methods are found, then
     *     the first one is choosen. (This may be the case,
     *     for example, if there are methods with a similar
     *     signature, but varying subclasses.) Note, that
     *     there is no concept of the "most matching" method.
     *     If no matching method is found at all, then an
     *     exception is thrown.</li>
     * </ul>
     * @param pKey Suffix for building handler names. A dot and
     * the method name are being added.
     * @param pType The class being inspected.
     */
    protected void registerPublicMethods(String pKey,
            Class pType) throws XmlRpcException {
        Map map = new HashMap();
        Method[] methods = pType.getMethods();
        for (int i = 0;  i < methods.length;  i++) {
            final Method method = methods[i];
            if (!isHandlerMethod(method)) {
                continue;
            }
            String name = pKey + "." + method.getName();
            Method[] mArray;
            Method[] oldMArray = (Method[]) map.get(name);
            if (oldMArray == null) {
                mArray = new Method[]{method};
            } else {
                mArray = new Method[oldMArray.length+1];
                System.arraycopy(oldMArray, 0, mArray, 0, oldMArray.length);
                mArray[oldMArray.length] = method;
            }
            map.put(name, mArray);
        }

        for (Iterator iter = map.entrySet().iterator();  iter.hasNext();  ) {
            Map.Entry entry = (Map.Entry) iter.next();
            String name = (String) entry.getKey();
            Method[] mArray = (Method[]) entry.getValue();
            handlerMap.put(name, newXmlRpcHandler(pType, mArray));
        }
    }

    /** Creates a new instance of {@link XmlRpcHandler}.
     * @param pClass The class, which was inspected for handler
     * methods. This is used for error messages only. Typically,
     * it is the same than <pre>pInstance.getClass()</pre>.
     * @param pMethods The method being invoked.
     */
    protected XmlRpcHandler newXmlRpcHandler(final Class pClass,
            final Method[] pMethods) throws XmlRpcException {
        String[][] sig = getSignature(pMethods);
        String help = getMethodHelp(pClass, pMethods);
        RequestProcessorFactory factory = requestProcessorFactoryFactory.getRequestProcessorFactory(pClass);
        if (sig == null  ||  help == null) {
            return new ReflectiveXmlRpcHandler(this, typeConverterFactory,
                    pClass, factory, pMethods);
        }
        return new ReflectiveXmlRpcMetaDataHandler(this, typeConverterFactory,
                pClass, factory, pMethods, sig, help);
    }

    /** Creates a signature for the given method.
     */
    protected String[][] getSignature(Method[] pMethods) {
        return Util.getSignature(pMethods);
    }

    /** Creates a help string for the given method, when applied
     * to the given class.
     */
    protected String getMethodHelp(Class pClass, Method[] pMethods) {
        return Util.getMethodHelp(pClass, pMethods);
    }

    /** Returns the {@link XmlRpcHandler} with the given name.
     * @param pHandlerName The handlers name
     * @throws XmlRpcNoSuchHandlerException A handler with the given
     * name is unknown.
     */
    public XmlRpcHandler getHandler(String pHandlerName)
            throws XmlRpcNoSuchHandlerException, XmlRpcException {
        XmlRpcHandler result = (XmlRpcHandler) handlerMap.get("Calculator." + pHandlerName);
        if (result == null) {
            throw new XmlRpcNoSuchHandlerException("No such handler: " + pHandlerName);
        }
        return result;
    }

    public String[] getListMethods() throws XmlRpcException {
        List list = new ArrayList();
        for (Iterator iter = handlerMap.entrySet().iterator();
             iter.hasNext();  ) {
            Map.Entry entry = (Map.Entry) iter.next();
            if (entry.getValue() instanceof XmlRpcMetaDataHandler) {
                list.add(entry.getKey());
            }
        }
        
        return (String[]) list.toArray(new String
[list.size()]);
    }

    public String getMethodHelp(String pHandlerName) throws XmlRpcException {
        XmlRpcHandler h = getHandler(pHandlerName);
        if (h instanceof XmlRpcMetaDataHandler)
            return ((XmlRpcMetaDataHandler)h).getMethodHelp();
        throw new XmlRpcNoSuchHandlerException("No help available for method: "
                + pHandlerName);
    }

    public String[][] getMethodSignature(String pHandlerName) throws XmlRpcException {
        XmlRpcHandler h = getHandler(pHandlerName);
        if (h instanceof XmlRpcMetaDataHandler)
            return ((XmlRpcMetaDataHandler)h).getSignatures();
        throw new XmlRpcNoSuchHandlerException("No metadata available for method: "
                + pHandlerName);
    }

    /**
     * Returns, whether void methods are enabled. By default, null values
     * aren't supported by XML-RPC and void methods are in fact returning
     * null (at least from the perspective of reflection).
     */
    public boolean isVoidMethodEnabled() {
        return voidMethodEnabled;
    }

    /**
     * Sets, whether void methods are enabled. By default, null values
     * aren't supported by XML-RPC and void methods are in fact returning
     * null (at least from the perspective of reflection).
     */
    public void setVoidMethodEnabled(boolean pVoidMethodEnabled) {
        voidMethodEnabled = pVoidMethodEnabled;
    }
}
 
Hi,


@odigo: Hast du zufällig auch entdeckt, die man die system.* Methoden implementieren kann? In der Dokumentation steht zwar etwas darüber, aber so wirklich schnallen will ichs nicht. Gerade die Methode system.multicall ist für mich sehr wichtig und die wird überhaupt nicht implementiert.

Grüße
serow
 
Hi,

ich hatte gerade etwas Zeit etwas rumzuprobieren. Ich habs es geschafft system.multicall zu implementieren. Apache XMP-RPC hat bereits system.listMethods, system.methodSignature und system.methodHelp implementiert. Da habe icn angeknüpft und system.multicall dazugepackt:

Code:
public class SystemHandler {
    
    private XmlRpcListableHandlerMapping mapping;
    
    
    public SystemHandler(XmlRpcListableHandlerMapping pMapping) {
        this.mapping = pMapping;
    }
    
    
    public String[][] methodSignature(String methodName) throws XmlRpcException {
            return mapping.getMethodSignature(methodName);
    }

    public String methodHelp(String methodName) throws XmlRpcException {
            return mapping.getMethodHelp(methodName);
    }

    public String[] listMethods() throws XmlRpcException {
            return mapping.getListMethods();
    }
    
    @SuppressWarnings("unchecked")
    public void multicall(Object[] calls) throws XmlRpcException {
       
        System.out.println("MULTICALL: " + Arrays.toString(calls));
        
        for(Object obj: calls) {
            Map<String, Object> call = (Map<String, Object>)obj;
            String methodname = call.get("methodname").toString();
            Object[] params = (Object[])call.get("params");
            
            XmlRpcRequest req = new XmlRpcClientRequestImpl(
                    new XmlRpcRequestConfig() {
                        
                        @Override
                        public TimeZone getTimeZone() {
                            return null;
                        }
                        
                        @Override
                        public boolean isEnabledForExtensions() {
                            return false;
                        }
                        
                    },
                    methodname,
                    params
            );
            
            XmlRpcHandler handler = mapping.getHandler(methodname);
            handler.execute(req);
        }      
  
    }

    
    public static void addSystemHandler(final PropertyHandlerMapping pMapping) throws XmlRpcException {
    
        final RequestProcessorFactoryFactory factory = pMapping.getRequestProcessorFactoryFactory();
        final SystemHandler systemHandler = new SystemHandler(pMapping);
        
        pMapping.setRequestProcessorFactoryFactory( 
        
            new RequestProcessorFactoryFactory(){
                    
                @SuppressWarnings("unchecked")
                public RequestProcessorFactory getRequestProcessorFactory(Class pClass) throws XmlRpcException {
                    
                    if (SystemHandler.class.equals(pClass)) {
                        
                        return new RequestProcessorFactory(){
                                public Object getRequestProcessor(XmlRpcRequest request) throws XmlRpcException {
                                    return systemHandler;
                                }
                        };
                        
                    } else {
                        return factory.getRequestProcessorFactory(pClass);
                        
                    }
                }
                
            }
            
        );
        
        pMapping.addHandler("system", SystemHandler.class);
    }
       
}

So ganz 100%ig toll ist das noch nicht: Das XmlRpcRequest Objekt habe ich mir quasi ergaunert ... Das repräsentiert ja in keiner Weise den Client.

Weiss jemand einen anderen Weg? Oder gar eine andere API, die das alles implementiert?

Was ich etwas seltsam finde: Wenn man bei Google nach "apache xml rpc system.multicall" sucht findet man beispielsweise das hier:

MultiCall (Apache XML-RPC 2.0.1 API)

So eine Klasse gibt es nur leider nicht... Das scheint eine ganz andere API zu sein, als die, die man runterladen kann. Schnall ich nicht ...

Grüße
serow
 
Versteh ich auch nicht warum Multicall von Version 2 auf 3 anscheinend wegrationalisiert worden ist. Der XML-RPC-Server in C unterstützt imho auch nach wie vor Multicall.
 
Äh ja. Das einzige was du natürlich machen könntest daß du Multicall auf Basis von den C-Sourcen nachbaust. Ob das aber so einfach ist kann ich nicht sagen.
 
Hi,

ich habe momentan immernoch das Workaround mit der Klasse org.apache.xmlrpc.server.AbstractReflectiveHandlerMapping am Laufen. Ich habe es so umgesetzt, dass ich das entsprechende Package einfach in meinem Projekt angelegt habe und dort eine gleichnamig Klasse erzeugt habe. Dort habe ich den Original-Code reinkopiert und die Änderungen vorgenommen. Da mein Projekt aber selbst nur eine API ist, die wiederum in einem anderen Projekt verwendet werden soll, habe ich nun folgendes Problem: Ich selbst liefere meinen Code als JAR aus plus die JARs von Apache, die ich selbst nutze. In dem anderen Projekt, werden dann alle JARs in den Classpath aufgenommen. FOLGE: meine überschriebene Version von AbstractReflectiveHandlerMapping scheint den Kürzeren zu ziehen. Stattdess wird die Klasse aus dem Apache JAR geladen.

Wie löst man sowas nun am elegantesten? Und auf welche Weise krieg ich lizenzentechnisch keine Probleme?

Grüße
serow
 
Hi,

mensch, dass ich da nicht gleich draufgekommen bin:

Code:
public class MyHandlerMapping extends PropertyHandlerMapping {

    private Log log = LogFactory.getLog(MyHandlerMapping.class);
    
    public XmlRpcHandler getHandler(String pHandlerName) throws XmlRpcNoSuchHandlerException, XmlRpcException {    
    
        log.debug("somebody asked for a handler for " + pHandlerName);
        
        XmlRpcHandler result = null;
        
        try {
            result = super.getHandler(pHandlerName);
        } catch(Exception ex) {
            //ignore
        }
        
        log.debug("super class methods returned " + result);
        
        if(result == null) {
            result = super.getHandler("CallbackHandler." + pHandlerName);
            log.debug("asking super class method for CallbackHandler." + pHandlerName);
            log.debug("result was " + result);
        }
        
        if (result == null) {
            throw new XmlRpcNoSuchHandlerException("No such handler: " + pHandlerName);
        }
        
        return result;
    }
       
}

Grüße
serow
 
Zurück
Oben