Browse Source

Replaced android-xmlrpc library for aXMLRPC to get a more reliable rTorrent connection (speed could be better, but I'm working on that).

pull/82/head
Eric Kok 11 years ago
parent
commit
ffdf8bfb12
  1. BIN
      core/libs/transdroid-connect.jar
  2. 55
      lib/src/de/timroes/axmlrpc/AuthenticationManager.java
  3. 86
      lib/src/de/timroes/axmlrpc/Call.java
  4. 95
      lib/src/de/timroes/axmlrpc/CookieManager.java
  5. 96
      lib/src/de/timroes/axmlrpc/ResponseParser.java
  6. 36
      lib/src/de/timroes/axmlrpc/XMLRPCCallback.java
  7. 812
      lib/src/de/timroes/axmlrpc/XMLRPCClient.java
  8. 26
      lib/src/de/timroes/axmlrpc/XMLRPCException.java
  9. 17
      lib/src/de/timroes/axmlrpc/XMLRPCRuntimeException.java
  10. 38
      lib/src/de/timroes/axmlrpc/XMLRPCServerException.java
  11. 15
      lib/src/de/timroes/axmlrpc/XMLRPCTimeoutException.java
  12. 124
      lib/src/de/timroes/axmlrpc/XMLUtil.java
  13. 78
      lib/src/de/timroes/axmlrpc/serializer/ArraySerializer.java
  14. 24
      lib/src/de/timroes/axmlrpc/serializer/Base64Serializer.java
  15. 24
      lib/src/de/timroes/axmlrpc/serializer/BooleanSerializer.java
  16. 32
      lib/src/de/timroes/axmlrpc/serializer/DateTimeSerializer.java
  17. 27
      lib/src/de/timroes/axmlrpc/serializer/DoubleSerializer.java
  18. 23
      lib/src/de/timroes/axmlrpc/serializer/IntSerializer.java
  19. 23
      lib/src/de/timroes/axmlrpc/serializer/LongSerializer.java
  20. 21
      lib/src/de/timroes/axmlrpc/serializer/NullSerializer.java
  21. 34
      lib/src/de/timroes/axmlrpc/serializer/Serializer.java
  22. 228
      lib/src/de/timroes/axmlrpc/serializer/SerializerHandler.java
  23. 38
      lib/src/de/timroes/axmlrpc/serializer/StringSerializer.java
  24. 112
      lib/src/de/timroes/axmlrpc/serializer/StructSerializer.java
  25. 31
      lib/src/de/timroes/axmlrpc/xmlcreator/SimpleXMLCreator.java
  26. 73
      lib/src/de/timroes/axmlrpc/xmlcreator/XmlElement.java
  27. 161
      lib/src/de/timroes/base64/Base64.java
  28. 30
      lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java
  29. 2
      lib/src/org/transdroid/daemon/util/FakeTrustManager.java
  30. 199
      lib/src/org/xmlrpc/android/Base64Coder.java
  31. 379
      lib/src/org/xmlrpc/android/XMLRPCClient.java
  32. 17
      lib/src/org/xmlrpc/android/XMLRPCException.java
  33. 24
      lib/src/org/xmlrpc/android/XMLRPCFault.java
  34. 205
      lib/src/org/xmlrpc/android/XMLRPCSerializer.java

BIN
core/libs/transdroid-connect.jar

Binary file not shown.

55
lib/src/de/timroes/axmlrpc/AuthenticationManager.java

@ -0,0 +1,55 @@
package de.timroes.axmlrpc;
import de.timroes.base64.Base64;
import java.net.HttpURLConnection;
/**
* The AuthenticationManager handle basic HTTP authentication.
*
* @author Tim Roes
*/
public class AuthenticationManager {
private String user;
private String pass;
/**
* Clear the username and password. No basic HTTP authentication will be used
* in the next calls.
*/
public void clearAuthData() {
this.user = null;
this.pass = null;
}
/**
* Set the username and password that should be used to perform basic
* http authentication.
*
* @param user Username
* @param pass Password
*/
public void setAuthData(String user, String pass) {
this.user = user;
this.pass = pass;
}
/**
* Set the authentication at the HttpURLConnection.
*
* @param http The HttpURLConnection to set authentication.
*/
public void setAuthentication(HttpURLConnection http) {
if(user == null || pass == null
|| user.length() <= 0 || pass.length() <= 0) {
return;
}
String base64login = Base64.encode(user + ":" + pass);
http.addRequestProperty("Authorization", "Basic " + base64login);
}
}

86
lib/src/de/timroes/axmlrpc/Call.java

@ -0,0 +1,86 @@
package de.timroes.axmlrpc;
import de.timroes.axmlrpc.serializer.SerializerHandler;
import de.timroes.axmlrpc.xmlcreator.SimpleXMLCreator;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
/**
* A Call object represents a call of a remote methode.
* It contains the name of the method to be called and the parameters to use
* in this remote procedure call. To send it over the network the method getXML
* returns an xml representation according to the XML-RPC specification as a String.
*
* @author Tim Roes
*/
public class Call {
private String method;
private Object[] params;
/**
* Create a new method call with the given name and no parameters.
* @param method The method to be called.
*/
public Call(String method) {
this(method, null);
}
/**
* Create a new method call with the given name and parameters.
* @param method The method to be called.
* @param params An array of parameters for the method.
*/
public Call(String method, Object[] params) {
this.method = method;
this.params = params;
}
/**
* Return an xml representation of the method call as specified in
* http://www.xmlrpc.com/spec. If flags have been set in the XMLRPCClient
* the returning xml does not comply strict to the standard.
*
* @return The string of the xml representing this call.
* @throws XMLRPCException Will be thrown whenever the xml representation cannot
* be build without errors.
* @see XMLRPCClient
*/
public String getXML() throws XMLRPCException {
SimpleXMLCreator creator = new SimpleXMLCreator();
XmlElement methodCall = new XmlElement(XMLRPCClient.METHOD_CALL);
creator.setRootElement(methodCall);
XmlElement methodName = new XmlElement(XMLRPCClient.METHOD_NAME);
methodName.setContent(method);
methodCall.addChildren(methodName);
if(params != null && params.length > 0) {
XmlElement params = new XmlElement(XMLRPCClient.PARAMS);
methodCall.addChildren(params);
for(Object o : this.params) {
params.addChildren(getXMLParam(o));
}
}
return creator.toString();
}
/**
* Generates the param xml tag for a specific parameter object.
*
* @param o The parameter object.
* @return The object serialized into an xml tag.
* @throws XMLRPCException Will be thrown if the serialization failed.
*/
private XmlElement getXMLParam(Object o) throws XMLRPCException {
XmlElement param = new XmlElement(XMLRPCClient.PARAM);
XmlElement value = new XmlElement(XMLRPCClient.VALUE);
param.addChildren(value);
value.addChildren(SerializerHandler.getDefault().serialize(o));
return param;
}
}

95
lib/src/de/timroes/axmlrpc/CookieManager.java

@ -0,0 +1,95 @@
package de.timroes.axmlrpc;
import java.net.HttpURLConnection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The CookieManager handles cookies for the http requests.
* If the FLAGS_ENABLE_COOKIES has been set, it will save cookies
* and send it with every request.
*
* @author Tim Roes
*/
class CookieManager {
private static final String SET_COOKIE = "Set-Cookie";
private static final String COOKIE = "Cookie";
private int flags;
private Map<String,String> cookies = new ConcurrentHashMap<String, String>();
/**
* Create a new CookieManager with the given flags.
*
* @param flags A combination of flags to be set.
*/
public CookieManager(int flags) {
this.flags = flags;
}
/**
* Delete all cookies.
*/
public void clearCookies() {
cookies.clear();
}
/**
* Returns a {@link Map} of all cookies.
*
* @return All cookies
*/
public Map<String,String> getCookies() {
return cookies;
}
/**
* Read the cookies from an http response. It will look at every Set-Cookie
* header and put the cookie to the map of cookies.
*
* @param http A http connection.
*/
public void readCookies(HttpURLConnection http) {
// Only save cookies if FLAGS_ENABLE_COOKIES has been set.
if((flags & XMLRPCClient.FLAGS_ENABLE_COOKIES) == 0)
return;
String cookie, key;
String[] split;
// Extract every Set-Cookie field and put the cookie to the cookies map.
for(int i = 0; i < http.getHeaderFields().size(); i++) {
key = http.getHeaderFieldKey(i);
if(key != null && SET_COOKIE.toLowerCase().equals(key.toLowerCase())) {
cookie = http.getHeaderField(i).split(";")[0];
split = cookie.split("=");
if(split.length >= 2)
cookies.put(split[0], split[1]);
}
}
}
/**
* Write the cookies to a http connection. It will set the Cookie field
* to all currently set cookies in the map.
*
* @param http A http connection.
*/
public void setCookies(HttpURLConnection http) {
// Only save cookies if FLAGS_ENABLE_COOKIES has been set.
if((flags & XMLRPCClient.FLAGS_ENABLE_COOKIES) == 0)
return;
String concat = "";
for(Map.Entry<String,String> cookie : cookies.entrySet()) {
concat += cookie.getKey() + "=" + cookie.getValue() + "; ";
}
http.setRequestProperty(COOKIE, concat);
}
}

96
lib/src/de/timroes/axmlrpc/ResponseParser.java

@ -0,0 +1,96 @@
package de.timroes.axmlrpc;
import de.timroes.axmlrpc.serializer.SerializerHandler;
import java.io.InputStream;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* The ResponseParser parses the response of an XMLRPC server to an object.
*
* @author Tim Roes
*/
class ResponseParser {
private static final String FAULT_CODE = "faultCode";
private static final String FAULT_STRING = "faultString";
/**
* The given InputStream must contain the xml response from an xmlrpc server.
* This method extract the content of it as an object.
*
* @param response The InputStream of the server response.
* @return The returned object.
* @throws XMLRPCException Will be thrown whenever something fails.
* @throws XMLRPCServerException Will be thrown, if the server returns an error.
*/
public Object parse(InputStream response) throws XMLRPCException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document dom = builder.parse(response);
Element e = dom.getDocumentElement();
// Check for root tag
if(!e.getNodeName().equals(XMLRPCClient.METHOD_RESPONSE)) {
throw new XMLRPCException("MethodResponse root tag is missing.");
}
e = XMLUtil.getOnlyChildElement(e.getChildNodes());
if(e.getNodeName().equals(XMLRPCClient.PARAMS)) {
e = XMLUtil.getOnlyChildElement(e.getChildNodes());
if(!e.getNodeName().equals(XMLRPCClient.PARAM)) {
throw new XMLRPCException("The params tag must contain a param tag.");
}
return getReturnValueFromElement(e);
} else if(e.getNodeName().equals(XMLRPCClient.FAULT)) {
@SuppressWarnings("unchecked")
Map<String,Object> o = (Map<String,Object>)getReturnValueFromElement(e);
throw new XMLRPCServerException((String)o.get(FAULT_STRING), (Integer)o.get(FAULT_CODE));
}
throw new XMLRPCException("The methodResponse tag must contain a fault or params tag.");
} catch (Exception ex) {
if(ex instanceof XMLRPCServerException)
throw (XMLRPCServerException)ex;
else
throw new XMLRPCException("Error getting result from server.", ex);
}
}
/**
* This method takes an element (must be a param or fault element) and
* returns the deserialized object of this param tag.
*
* @param element An param element.
* @return The deserialized object within the given param element.
* @throws XMLRPCException Will be thrown when the structure of the document
* doesn't match the XML-RPC specification.
*/
private Object getReturnValueFromElement(Element element) throws XMLRPCException {
element = XMLUtil.getOnlyChildElement(element.getChildNodes());
return SerializerHandler.getDefault().deserialize(element);
}
}

36
lib/src/de/timroes/axmlrpc/XMLRPCCallback.java

@ -0,0 +1,36 @@
package de.timroes.axmlrpc;
/**
* The XMLRPCCallback interface must be implemented by a listener for an
* asynchronous call to a server method.
* When the server responds, the corresponding method on the listener is called.
*
* @author Tim Roes
*/
public interface XMLRPCCallback {
/**
* This callback is called whenever the server successfully responds.
*
* @param id The id as returned by the XMLRPCClient.asyncCall(..) method for this request.
* @param result The Object returned from the server.
*/
public void onResponse(long id, Object result);
/**
* This callback is called whenever an error occurs during the method call.
*
* @param id The id as returned by the XMLRPCClient.asyncCall(..) method for this request.
* @param error The error occured.
*/
public void onError(long id, XMLRPCException error);
/**
* This callback is called whenever the server returns an error.
*
* @param id The id as returned by the XMLRPCClient.asyncCall(..) method for this request.
* @param error The error returned from the server.
*/
public void onServerError(long id, XMLRPCServerException error);
}

812
lib/src/de/timroes/axmlrpc/XMLRPCClient.java

@ -0,0 +1,812 @@
package de.timroes.axmlrpc;
import de.timroes.axmlrpc.serializer.SerializerHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.*;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.*;
/**
* An XMLRPCClient is a client used to make XML-RPC (Extensible Markup Language
* Remote Procedure Calls).
* The specification of XMLRPC can be found at http://www.xmlrpc.com/spec.
* You can use flags to extend the functionality of the client to some extras.
* Further information on the flags can be found in the documentation of these.
* For a documentation on how to use this class see also the README file delivered
* with the source of this library.
*
* @author Tim Roes
*/
public class XMLRPCClient {
private static final String DEFAULT_USER_AGENT = "aXMLRPC";
/**
* Constants from the http protocol.
*/
static final String USER_AGENT = "User-Agent";
static final String CONTENT_TYPE = "Content-Type";
static final String TYPE_XML = "text/xml; charset=utf-8";
static final String HOST = "Host";
static final String CONTENT_LENGTH = "Content-Length";
static final String HTTP_POST = "POST";
/**
* XML elements to be used.
*/
static final String METHOD_RESPONSE = "methodResponse";
static final String PARAMS = "params";
static final String PARAM = "param";
public static final String VALUE = "value";
static final String FAULT = "fault";
static final String METHOD_CALL = "methodCall";
static final String METHOD_NAME = "methodName";
static final String STRUCT_MEMBER = "member";
/**
* No flags should be set.
*/
public static final int FLAGS_NONE = 0x0;
/**
* The client should parse responses strict to specification.
* It will check if the given content-type is right.
* The method name in a call must only contain of A-Z, a-z, 0-9, _, ., :, /
* Normally this is not needed.
*/
public static final int FLAGS_STRICT = 0x01;
/**
* The client will be able to handle 8 byte integer values (longs).
* The xml type tag &lt;i8&gt; will be used. This is not in the specification
* but some libraries and servers support this behaviour.
* If this isn't enabled you cannot recieve 8 byte integers and if you try to
* send a long the value must be within the 4byte integer range.
*/
public static final int FLAGS_8BYTE_INT = 0x02;
/**
* With this flag, the client will be able to handle cookies, meaning saving cookies
* from the server and sending it with every other request again. This is needed
* for some XML-RPC interfaces that support login.
*/
public static final int FLAGS_ENABLE_COOKIES = 0x04;
/**
* The client will be able to send null values. A null value will be send
* as <nil/>. This extension is described under: http://ontosys.com/xml-rpc/extensions.php
*/
public static final int FLAGS_NIL = 0x08;
/**
* With this flag enabled, the XML-RPC client will ignore the HTTP status
* code of the response from the server. According to specification the
* status code must be 200. This flag is only needed for the use with
* not standard compliant servers.
*/
public static final int FLAGS_IGNORE_STATUSCODE = 0x10;
/**
* With this flag enabled, the client will forward the request, if
* the 301 or 302 HTTP status code has been received. If this flag has not
* been set, the client will throw an exception on these HTTP status codes.
*/
public static final int FLAGS_FORWARD = 0x20;
/**
* With this flag enabled, the client will ignore, if the URL doesn't match
* the SSL Certificate. This should be used with caution. Normally the URL
* should always match the URL in the SSL certificate, even with self signed
* certificates.
*/
public static final int FLAGS_SSL_IGNORE_INVALID_HOST = 0x40;
/**
* With this flag enabled, the client will ignore all unverified SSL/TLS
* certificates. This must be used, if you use self-signed certificates
* or certificated from unknown (or untrusted) authorities. If this flag is
* used, calls to {@link #installCustomTrustManager(javax.net.ssl.TrustManager)}
* won't have any effect.
*/
public static final int FLAGS_SSL_IGNORE_INVALID_CERT = 0x80;
/**
* With this flag enabled, a value with a missing type tag, will be parsed
* as a string element. This is just for incoming messages. Outgoing messages
* will still be generated according to specification.
*/
public static final int FLAGS_DEFAULT_TYPE_STRING = 0x100;
/**
* With this flag enabled, the {@link XMLRPCClient} ignores all namespaces
* used within the response from the server.
*/
public static final int FLAGS_IGNORE_NAMESPACES = 0x200;
/**
* With this flag enabled, the {@link XMLRPCClient} will use the system http
* proxy to connect to the XML-RPC server.
*/
public static final int FLAGS_USE_SYSTEM_PROXY = 0x400;
/**
* This prevents the decoding of incoming strings, meaning &amp; and &lt;
* won't be decoded to the & sign and the "less then" sign. See
* {@link #FLAGS_NO_STRING_ENCODE} for the counterpart.
*/
public static final int FLAGS_NO_STRING_DECODE = 0x800;
/**
* By default outgoing string values will be encoded according to specification.
* Meaning the & sign will be encoded to &amp; and the "less then" sign to &lt;.
* If you set this flag, the encoding won't be done for outgoing string values.
* See {@link #FLAGS_NO_STRING_ENCODE} for the counterpart.
*/
public static final int FLAGS_NO_STRING_ENCODE = 0x1000;
/**
* This flag disables all SSL warnings. It is an alternative to use
* FLAGS_SSL_IGNORE_INVALID_CERT | FLAGS_SSL_IGNORE_INVALID_HOST. There
* is no functional difference.
*/
public static final int FLAGS_SSL_IGNORE_ERRORS =
FLAGS_SSL_IGNORE_INVALID_CERT | FLAGS_SSL_IGNORE_INVALID_HOST;
/**
* This flag should be used if the server is an apache ws xmlrpc server.
* This will set some flags, so that the not standard conform behavior
* of the server will be ignored.
* This will enable the following flags: FLAGS_IGNORE_NAMESPACES, FLAGS_NIL,
* FLAGS_DEFAULT_TYPE_STRING
*/
public static final int FLAGS_APACHE_WS = FLAGS_IGNORE_NAMESPACES | FLAGS_NIL
| FLAGS_DEFAULT_TYPE_STRING;
private final int flags;
private URL url;
private Map<String,String> httpParameters = new ConcurrentHashMap<String, String>();
private Map<Long,Caller> backgroundCalls = new ConcurrentHashMap<Long, Caller>();
private ResponseParser responseParser;
private CookieManager cookieManager;
private AuthenticationManager authManager;
private TrustManager[] trustManagers;
private KeyManager[] keyManagers;
private Proxy proxy;
private int timeout;
/**
* Create a new XMLRPC client for the given URL.
*
* @param url The URL to send the requests to.
* @param userAgent A user agent string to use in the HTTP requests.
* @param flags A combination of flags to be set.
*/
public XMLRPCClient(URL url, String userAgent, int flags) {
SerializerHandler.initialize(flags);
this.url = url;
this.flags = flags;
// Create a parser for the http responses.
responseParser = new ResponseParser();
cookieManager = new CookieManager(flags);
authManager = new AuthenticationManager();
httpParameters.put(CONTENT_TYPE, TYPE_XML);
httpParameters.put(USER_AGENT, userAgent);
// If invalid ssl certs are ignored, instantiate an all trusting TrustManager
if(isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) {
trustManagers = new TrustManager[] {
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string)
throws CertificateException { }
public void checkServerTrusted(X509Certificate[] xcs, String string)
throws CertificateException { }
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
}
if(isFlagSet(FLAGS_USE_SYSTEM_PROXY)) {
// Read system proxy settings and generate a proxy from that
Properties prop = System.getProperties();
String proxyHost = prop.getProperty("http.proxyHost");
int proxyPort = Integer.parseInt(prop.getProperty("http.proxyPort", "0"));
if(proxyPort > 0 && proxyHost.length() > 0 && !proxyHost.equals("null")) {
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
}
}
}
/**
* Create a new XMLRPC client for the given URL.
* The default user agent string will be used.
*
* @param url The URL to send the requests to.
* @param flags A combination of flags to be set.
*/
public XMLRPCClient(URL url, int flags) {
this(url, DEFAULT_USER_AGENT, flags);
}
/**
* Create a new XMLRPC client for the given url.
* No flags will be set.
*
* @param url The url to send the requests to.
* @param userAgent A user agent string to use in the http request.
*/
public XMLRPCClient(URL url, String userAgent) {
this(url, userAgent, FLAGS_NONE);
}
/**
* Create a new XMLRPC client for the given url.
* No flags will be used.
* The default user agent string will be used.
*
* @param url The url to send the requests to.
*/
public XMLRPCClient(URL url) {
this(url, DEFAULT_USER_AGENT, FLAGS_NONE);
}
/**
* Returns the URL this XMLRPCClient is connected to. If that URL permanently forwards
* to another URL, this method will return the forwarded URL, as soon as
* the first call has been made.
*
* @return Returns the URL for this XMLRPCClient.
*/
public URL getURL() {
return url;
}
/**
* Sets the time in seconds after which a call should timeout.
* If {@code timeout} will be zero or less the connection will never timeout.
* In case the connection times out and {@link XMLRPCTimeoutException} will
* be thrown for calls made by {@link #call(java.lang.String, java.lang.Object[])}.
* For calls made by {@link #callAsync(de.timroes.axmlrpc.XMLRPCCallback, java.lang.String, java.lang.Object[])}
* the {@link XMLRPCCallback#onError(long, de.timroes.axmlrpc.XMLRPCException)} method
* of the callback will be called. By default connections won't timeout.
*
* @param timeout The timeout for connections in seconds.
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* Sets the user agent string.
* If this method is never called the default
* user agent 'aXMLRPC' will be used.
*
* @param userAgent The new user agent string.
*/
public void setUserAgentString(String userAgent) {
httpParameters.put(USER_AGENT, userAgent);
}
/**
* Sets a proxy to use for this client. If you want to use the system proxy,
* use {@link #FLAGS_adbUSE_SYSTEM_PROXY} instead. If combined with
* {@code FLAGS_USE_SYSTEM_PROXY}, this proxy will be used instead of the
* system proxy.
*
* @param proxy A proxy to use for the connection.
*/
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
/**
* Set a HTTP header field to a custom value.
* You cannot modify the Host or Content-Type field that way.
* If the field already exists, the old value is overwritten.
*
* @param headerName The name of the header field.
* @param headerValue The new value of the header field.
*/
public void setCustomHttpHeader(String headerName, String headerValue) {
if(CONTENT_TYPE.equals(headerName) || HOST.equals(headerName)
|| CONTENT_LENGTH.equals(headerName)) {
throw new XMLRPCRuntimeException("You cannot modify the Host, Content-Type or Content-Length header.");
}
httpParameters.put(headerName, headerValue);
}
/**
* Set the username and password that should be used to perform basic
* http authentication.
*
* @param user Username
* @param pass Password
*/
public void setLoginData(String user, String pass) {
authManager.setAuthData(user, pass);
}
/**
* Clear the username and password. No basic HTTP authentication will be used
* in the next calls.
*/
public void clearLoginData() {
authManager.clearAuthData();
}
/**
* Returns a {@link Map} of all cookies. It contains each cookie key as a map
* key and its value as a map value. Cookies will only be used if {@link #FLAGS_ENABLE_COOKIES}
* has been set for the client. This map will also be available (and empty)
* when this flag hasn't been said, but has no effect on the HTTP connection.
*
* @return A {@code Map} of all cookies.
*/
public Map<String,String> getCookies() {
return cookieManager.getCookies();
}
/**
* Delete all cookies currently used by the client.
* This method has only an effect, as long as the FLAGS_ENABLE_COOKIES has
* been set on this client.
*/
public void clearCookies() {
cookieManager.clearCookies();
}
/**
* Installs a custom {@link TrustManager} to handle SSL/TLS certificate verification.
* This will replace any previously installed {@code TrustManager}s.
* If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything.
*
* @param trustManager {@link TrustManager} to install.
*
* @see #installCustomTrustManagers(javax.net.ssl.TrustManager[])
*/
public void installCustomTrustManager(TrustManager trustManager) {
if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) {
trustManagers = new TrustManager[] { trustManager };
}
}
/**
* Installs custom {@link TrustManager TrustManagers} to handle SSL/TLS certificate
* verification. This will replace any previously installed {@code TrustManagers}s.
* If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything.
*
* @param trustManagers {@link TrustManager TrustManagers} to install.
*
* @see #installCustomTrustManager(javax.net.ssl.TrustManager)
*/
public void installCustomTrustManagers(TrustManager[] trustManagers) {
if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) {
this.trustManagers = trustManagers.clone();
}
}
/**
* Installs a custom {@link KeyManager} to handle SSL/TLS certificate verification.
* This will replace any previously installed {@code KeyManager}s.
* If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything.
*
* @param keyManager {@link KeyManager} to install.
*
* @see #installCustomKeyManagers(javax.net.ssl.KeyManager[])
*/
public void installCustomKeyManager(KeyManager keyManager) {
if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) {
keyManagers = new KeyManager[] { keyManager };
}
}
/**
* Installs custom {@link KeyManager KeyManagers} to handle SSL/TLS certificate
* verification. This will replace any previously installed {@code KeyManagers}s.
* If {@link #FLAGS_SSL_IGNORE_INVALID_CERT} is set, this won't do anything.
*
* @param keyManagers {@link KeyManager KeyManagers} to install.
*
* @see #installCustomKeyManager(javax.net.ssl.KeyManager)
*/
public void installCustomKeyManagers(KeyManager[] keyManagers) {
if(!isFlagSet(FLAGS_SSL_IGNORE_INVALID_CERT)) {
this.keyManagers = keyManagers.clone();
}
}
/**
* Call a remote procedure on the server. The method must be described by
* a method name. If the method requires parameters, this must be set.
* The type of the return object depends on the server. You should consult
* the server documentation and then cast the return value according to that.
* This method will block until the server returned a result (or an error occurred).
* Read the README file delivered with the source code of this library for more
* information.
*
* @param method A method name to call.
* @param params An array of parameters for the method.
* @return The result of the server.
* @throws XMLRPCException Will be thrown if an error occurred during the call.
*/
public Object call(String method, Object... params) throws XMLRPCException {
return new Caller().call(method, params);
}
/**
* Asynchronously call a remote procedure on the server. The method must be
* described by a method name. If the method requires parameters, this must
* be set. When the server returns a response the onResponse method is called
* on the listener. If the server returns an error the onServerError method
* is called on the listener. The onError method is called whenever something
* fails. This method returns immediately and returns an identifier for the
* request. All listener methods get this id as a parameter to distinguish between
* multiple requests.
*
* @param listener A listener, which will be notified about the server response or errors.
* @param methodName A method name to call on the server.
* @param params An array of parameters for the method.
* @return The id of the current request.
*/
public long callAsync(XMLRPCCallback listener, String methodName, Object... params) {
long id = System.currentTimeMillis();
new Caller(listener, id, methodName, params).start();
return id;
}
/**
* Cancel a specific asynchronous call.
*
* @param id The id of the call as returned by the callAsync method.
*/
public void cancel(long id) {
// Lookup the background call for the given id.
Caller cancel = backgroundCalls.get(id);
if(cancel == null) {
return;
}
// Cancel the thread
cancel.cancel();
try {
// Wait for the thread
cancel.join();
} catch (InterruptedException ex) {
// Ignore this
}
}
/**
* Create a call object from a given method string and parameters.
*
* @param method The method that should be called.
* @param params An array of parameters or null if no parameters needed.
* @return A call object.
*/
private Call createCall(String method, Object[] params) {
if(isFlagSet(FLAGS_STRICT) && !method.matches("^[A-Za-z0-9\\._:/]*$")) {
throw new XMLRPCRuntimeException("Method name must only contain A-Z a-z . : _ / ");
}
return new Call(method, params);
}
/**
* Checks whether a specific flag has been set.
*
* @param flag The flag to check for.
* @return Whether the flag has been set.
*/
private boolean isFlagSet(int flag) {
return (this.flags & flag) != 0;
}
/**
* The Caller class is used to make asynchronous calls to the server.
* For synchronous calls the Thread function of this class isn't used.
*/
private class Caller extends Thread {
private XMLRPCCallback listener;
private long threadId;
private String methodName;
private Object[] params;
private volatile boolean canceled;
private HttpURLConnection http;
/**
* Create a new Caller for asynchronous use.
*
* @param listener The listener to notice about the response or an error.
* @param threadId An id that will be send to the listener.
* @param methodName The method name to call.
* @param params The parameters of the call or null.
*/
public Caller(XMLRPCCallback listener, long threadId, String methodName, Object[] params) {
this.listener = listener;
this.threadId = threadId;
this.methodName = methodName;
this.params = params;
}
/**
* Create a new Caller for synchronous use.
* If the caller has been created with this constructor you cannot use the
* start method to start it as a thread. But you can call the call method
* on it for synchronous use.
*/
public Caller() { }
/**
* The run method is invoked when the thread gets started.
* This will only work, if the Caller has been created with parameters.
* It execute the call method and notify the listener about the result.
*/
@Override
public void run() {
if(listener == null)
return;
try {
backgroundCalls.put(threadId, this);
Object o = this.call(methodName, params);
listener.onResponse(threadId, o);
} catch(CancelException ex) {
// Don't notify the listener, if the call has been canceled.
} catch(XMLRPCServerException ex) {
listener.onServerError(threadId, ex);
} catch (XMLRPCException ex) {
listener.onError(threadId, ex);
} finally {
backgroundCalls.remove(threadId);
}
}
/**
* Cancel this call. This will abort the network communication.
*/
public void cancel() {
// Set the flag, that this thread has been canceled
canceled = true;
// Disconnect the connection to the server
http.disconnect();
}
/**
* Call a remote procedure on the server. The method must be described by
* a method name. If the method requires parameters, this must be set.
* The type of the return object depends on the server. You should consult
* the server documentation and then cast the return value according to that.
* This method will block until the server returned a result (or an error occurred).
* Read the README file delivered with the source code of this library for more
* information.
*
* @param method A method name to call.
* @param params An array of parameters for the method.
* @return The result of the server.
* @throws XMLRPCException Will be thrown if an error occurred during the call.
*/
public Object call(String methodName, Object[] params) throws XMLRPCException {
try {
Call c = createCall(methodName, params);
// If proxy is available, use it
URLConnection conn;
if(proxy != null)
conn = url.openConnection(proxy);
else
conn = url.openConnection();
http = verifyConnection(conn);
http.setInstanceFollowRedirects(false);
http.setRequestMethod(HTTP_POST);
http.setDoOutput(true);
http.setDoInput(true);
// Set timeout
if(timeout > 0) {
http.setConnectTimeout(timeout * 1000);
http.setReadTimeout(timeout * 1000);
}
// Set the request parameters
for(Map.Entry<String,String> param : httpParameters.entrySet()) {
http.setRequestProperty(param.getKey(), param.getValue());
}
authManager.setAuthentication(http);
cookieManager.setCookies(http);
OutputStreamWriter stream = new OutputStreamWriter(http.getOutputStream());
stream.write(c.getXML());
stream.flush();
stream.close();
// Try to get the status code from the connection
int statusCode;
try {
statusCode = http.getResponseCode();
} catch(IOException ex) {
// Due to a bug on android, the getResponseCode()-method will
// fail the first time, with a IOException, when 401 or 403 has been returned.
// The second time it should success. If it fail the second time again
// the normal exceptipon handling can take care of this, since
// it is a real error.
statusCode = http.getResponseCode();
}
InputStream istream;
// If status code was 401 or 403 throw exception or if appropriate
// flag is set, ignore error code.
if(statusCode == HttpURLConnection.HTTP_FORBIDDEN
|| statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
if(isFlagSet(FLAGS_IGNORE_STATUSCODE)) {
// getInputStream will fail if server returned above
// error code, use getErrorStream instead
istream = http.getErrorStream();
} else {
throw new XMLRPCException("Invalid status code '"
+ statusCode + "' returned from server.");
}
} else {
istream = http.getInputStream();
}
// If status code is 301 Moved Permanently or 302 Found ...
if(statusCode == HttpURLConnection.HTTP_MOVED_PERM
|| statusCode == HttpURLConnection.HTTP_MOVED_TEMP) {
// ... do either a foward
if(isFlagSet(FLAGS_FORWARD)) {
boolean temporaryForward = (statusCode == HttpURLConnection.HTTP_MOVED_TEMP);
// Get new location from header field.
String newLocation = http.getHeaderField("Location");
// Try getting header in lower case, if no header has been found
if(newLocation == null || newLocation.length() <= 0)
newLocation = http.getHeaderField("location");
// Set new location, disconnect current connection and request to new location.
URL oldURL = url;
url = new URL(newLocation);
http.disconnect();
Object forwardedResult = call(methodName, params);
// In case of temporary forward, restore original URL again for next call.
if(temporaryForward) {
url = oldURL;
}
return forwardedResult;
} else {
// ... or throw an exception
throw new XMLRPCException("The server responded with a http 301 or 302 status "
+ "code, but forwarding has not been enabled (FLAGS_FORWARD).");
}
}
if(!isFlagSet(FLAGS_IGNORE_STATUSCODE)
&& statusCode != HttpURLConnection.HTTP_OK) {
throw new XMLRPCException("The status code of the http response must be 200.");
}
// Check for strict parameters
if(isFlagSet(FLAGS_STRICT)) {
if(!http.getContentType().startsWith(TYPE_XML)) {
throw new XMLRPCException("The Content-Type of the response must be text/xml.");
}
}
cookieManager.readCookies(http);
return responseParser.parse(istream);
} catch(SocketTimeoutException ex) {
throw new XMLRPCTimeoutException("The XMLRPC call timed out.");
} catch (IOException ex) {
// If the thread has been canceled this exception will be thrown.
// So only throw an exception if the thread hasnt been canceled
// or if the thred has not been started in background.
if(!canceled || threadId <= 0) {
throw new XMLRPCException(ex);
} else {
throw new CancelException();
}
}
}
/**
* Verifies the given URLConnection to be a valid HTTP or HTTPS connection.
* If the SSL ignoring flags are set, the method will ignore SSL warnings.
*
* @param conn The URLConnection to validate.
* @return The verified HttpURLConnection.
* @throws XMLRPCException Will be thrown if an error occurred.
*/
private HttpURLConnection verifyConnection(URLConnection conn) throws XMLRPCException {
if(!(conn instanceof HttpURLConnection)) {
throw new IllegalArgumentException("The URL is not valid for a http connection.");
}
// Validate the connection if its an SSL connection
if(conn instanceof HttpsURLConnection) {
HttpsURLConnection h = (HttpsURLConnection)conn;
// Don't check, that URL matches the certificate.
if(isFlagSet(FLAGS_SSL_IGNORE_INVALID_HOST)) {
h.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String host, SSLSession ssl) {
return true;
}
});
}
// Associate the TrustManager with TLS and SSL connections, if present.
if(trustManagers != null) {
try {
String[] sslContexts = new String[]{ "TLS", "SSL" };
for(String ctx : sslContexts) {
SSLContext sc = SSLContext.getInstance(ctx);
sc.init(keyManagers, trustManagers, new SecureRandom());
h.setSSLSocketFactory(sc.getSocketFactory());
}
} catch(Exception ex) {
throw new XMLRPCException(ex);
}
}
return h;
}
return (HttpURLConnection)conn;
}
}
private class CancelException extends RuntimeException { }
}

26
lib/src/de/timroes/axmlrpc/XMLRPCException.java

@ -0,0 +1,26 @@
package de.timroes.axmlrpc;
/**
* The exception is thrown whenever the remote procedure call fails in some point.
*
* @author Tim Roes
*/
public class XMLRPCException extends Exception {
public XMLRPCException() {
super();
}
public XMLRPCException(Exception ex) {
super(ex);
}
public XMLRPCException(String ex) {
super(ex);
}
public XMLRPCException(String msg, Exception ex) {
super(msg, ex);
}
}

17
lib/src/de/timroes/axmlrpc/XMLRPCRuntimeException.java

@ -0,0 +1,17 @@
package de.timroes.axmlrpc;
/**
*
* @author Tim Roes
*/
public class XMLRPCRuntimeException extends RuntimeException {
public XMLRPCRuntimeException(String ex) {
super(ex);
}
public XMLRPCRuntimeException(Exception ex) {
super(ex);
}
}

38
lib/src/de/timroes/axmlrpc/XMLRPCServerException.java

@ -0,0 +1,38 @@
package de.timroes.axmlrpc;
/**
* This exception will be thrown if the server returns an error. It contains the
* message and the error number returned from the server.
*
* @author Tim Roes
*/
public class XMLRPCServerException extends XMLRPCException {
private int errornr;
public XMLRPCServerException(String ex, int errnr) {
super(ex);
this.errornr = errnr;
}
/**
* Returns the detail message string of this throwable.
* It will have the server error number at the end.
*
* @return The detail message string of this error.
*/
@Override
public String getMessage() {
return super.getMessage() + " [" + errornr + "]";
}
/**
* Return the error number.
*
* @return The error number.
*/
public int getErrorNr() {
return errornr;
}
}

15
lib/src/de/timroes/axmlrpc/XMLRPCTimeoutException.java

@ -0,0 +1,15 @@
package de.timroes.axmlrpc;
/**
* Will be thrown when a call to the server times out. The timeout can be
* set via {@link XMLRPCClient#setTimeout(int)}.
*
* @author Tim Roes <mail@timroes.de>
*/
public class XMLRPCTimeoutException extends XMLRPCException {
XMLRPCTimeoutException(String ex) {
super(ex);
}
}

124
lib/src/de/timroes/axmlrpc/XMLUtil.java

@ -0,0 +1,124 @@
package de.timroes.axmlrpc;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class provides some utility methods for the use with the Java DOM parser.
*
* @author Tim Roes
*/
public class XMLUtil {
/**
* Returns the only child element in a given NodeList.
* Will throw an error if there is more then one child element or any other
* child that is not an element or an empty text string (whitespace are normal).
*
* @param list A NodeList of children nodes.
* @return The only child element in the given node list.
* @throws XMLRPCException Will be thrown if there is more then one child element
* except empty text nodes.
*/
public static Element getOnlyChildElement(NodeList list) throws XMLRPCException {
Element e = null;
Node n;
for(int i = 0; i < list.getLength(); i++) {
n = list.item(i);
// Strip only whitespace text elements and comments
if((n.getNodeType() == Node.TEXT_NODE
&& n.getNodeValue().trim().length() <= 0)
|| n.getNodeType() == Node.COMMENT_NODE)
continue;
// Check if there is anything else than an element node.
if(n.getNodeType() != Node.ELEMENT_NODE) {
throw new XMLRPCException("Only element nodes allowed.");
}
// If there was already an element, throw exception.
if(e != null) {
throw new XMLRPCException("Element has more than one children.");
}
e = (Element)n;
}
return e;
}
/**
* Returns the text node from a given NodeList. If the list contains
* more then just text nodes, an exception will be thrown.
*
* @param list The given list of nodes.
* @return The text of the given node list.
* @throws XMLRPCException Will be thrown if there is more than just one
* text node within the list.
*/
public static String getOnlyTextContent(NodeList list) throws XMLRPCException {
StringBuilder builder = new StringBuilder();
Node n;
for(int i = 0; i < list.getLength(); i++) {
n = list.item(i);
// Skip comments inside text tag.
if(n.getNodeType() == Node.COMMENT_NODE) {
continue;
}
if(n.getNodeType() != Node.TEXT_NODE) {
throw new XMLRPCException("Element must contain only text elements.");
}
builder.append(n.getNodeValue());
}
return builder.toString();
}
/**
* Checks if the given {@link NodeList} contains a child element.
*
* @param list The {@link NodeList} to check.
* @return Whether the {@link NodeList} contains children.
*/
public static boolean hasChildElement(NodeList list) {
Node n;
for(int i = 0; i < list.getLength(); i++) {
n = list.item(i);
if(n.getNodeType() == Node.ELEMENT_NODE) {
return true;
}
}
return false;
}
/**
* Creates an xml tag with a given type and content.
*
* @param type The type of the xml tag. What will be filled in the <..>.
* @param content The content of the tag.
* @return The xml tag with its content as a string.
*/
public static XmlElement makeXmlTag(String type, String content) {
XmlElement xml = new XmlElement(type);
xml.setContent(content);
return xml;
}
}

78
lib/src/de/timroes/axmlrpc/serializer/ArraySerializer.java

@ -0,0 +1,78 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLRPCRuntimeException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
*
* @author Tim Roes
*/
public class ArraySerializer implements Serializer {
private static final String ARRAY_DATA = "data";
private static final String ARRAY_VALUE = "value";
public Object deserialize(Element content) throws XMLRPCException {
List<Object> list = new ArrayList<Object>();
Element data = XMLUtil.getOnlyChildElement(content.getChildNodes());
if(!ARRAY_DATA.equals(data.getNodeName())) {
throw new XMLRPCException("The array must contain one data tag.");
}
// Deserialize every array element
Node value;
for(int i = 0; i < data.getChildNodes().getLength(); i++) {
value = data.getChildNodes().item(i);
// Strip only whitespace text elements and comments
if(value == null || (value.getNodeType() == Node.TEXT_NODE
&& value.getNodeValue().trim().length() <= 0)
|| value.getNodeType() == Node.COMMENT_NODE)
continue;
if(value.getNodeType() != Node.ELEMENT_NODE) {
throw new XMLRPCException("Wrong element inside of array.");
}
list.add(SerializerHandler.getDefault().deserialize((Element)value));
}
return list.toArray();
}
public XmlElement serialize(Object object) {
Iterable<?> iter = (Iterable<?>)object;
XmlElement array = new XmlElement(SerializerHandler.TYPE_ARRAY);
XmlElement data = new XmlElement(ARRAY_DATA);
array.addChildren(data);
try {
XmlElement e;
for(Object obj : iter) {
e = new XmlElement(ARRAY_VALUE);
e.addChildren(SerializerHandler.getDefault().serialize(obj));
data.addChildren(e);
}
} catch(XMLRPCException ex) {
throw new XMLRPCRuntimeException(ex);
}
return array;
}
}

24
lib/src/de/timroes/axmlrpc/serializer/Base64Serializer.java

@ -0,0 +1,24 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import de.timroes.base64.Base64;
import org.w3c.dom.Element;
/**
*
* @author Tim Roes
*/
public class Base64Serializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return Base64.decode(XMLUtil.getOnlyTextContent(content.getChildNodes()));
}
public XmlElement serialize(Object object) {
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_BASE64,
Base64.encode((Byte[])object));
}
}

24
lib/src/de/timroes/axmlrpc/serializer/BooleanSerializer.java

@ -0,0 +1,24 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
*
* @author Tim Roes
*/
public class BooleanSerializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return (XMLUtil.getOnlyTextContent(content.getChildNodes()).equals("1"))
? Boolean.TRUE : Boolean.FALSE;
}
public XmlElement serialize(Object object) {
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_BOOLEAN,
((Boolean)object == true) ? "1" : "0");
}
}

32
lib/src/de/timroes/axmlrpc/serializer/DateTimeSerializer.java

@ -0,0 +1,32 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.w3c.dom.Element;
/**
*
* @author timroes
*/
public class DateTimeSerializer implements Serializer {
private static final String DATETIME_FORMAT = "yyyyMMdd'T'HH:mm:ss";
private static final SimpleDateFormat DATE_FORMATER = new SimpleDateFormat(DATETIME_FORMAT);
public Object deserialize(Element content) throws XMLRPCException {
try {
return DATE_FORMATER.parse(XMLUtil.getOnlyTextContent(content.getChildNodes()));
} catch (ParseException ex) {
throw new XMLRPCException("Unable to parse given date.", ex);
}
}
public XmlElement serialize(Object object) {
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_DATETIME,
DATE_FORMATER.format(object));
}
}

27
lib/src/de/timroes/axmlrpc/serializer/DoubleSerializer.java

@ -0,0 +1,27 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import java.math.BigDecimal;
import org.w3c.dom.Element;
/**
* This serializer is responsible for floating point numbers.
*
* @author Tim Roes
*/
public class DoubleSerializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return Double.valueOf(XMLUtil.getOnlyTextContent(content.getChildNodes()));
}
public XmlElement serialize(Object object) {
// Turn double value of object into a BigDecimal to get the
// right decimal point format.
BigDecimal bd = BigDecimal.valueOf(((Number)object).doubleValue());
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_DOUBLE, bd.toPlainString());
}
}

23
lib/src/de/timroes/axmlrpc/serializer/IntSerializer.java

@ -0,0 +1,23 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
*
* @author timroes
*/
public class IntSerializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return Integer.parseInt(XMLUtil.getOnlyTextContent(content.getChildNodes()));
}
public XmlElement serialize(Object object) {
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_INT,
object.toString());
}
}

23
lib/src/de/timroes/axmlrpc/serializer/LongSerializer.java

@ -0,0 +1,23 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
*
* @author Tim Roes
*/
class LongSerializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return Long.parseLong(XMLUtil.getOnlyTextContent(content.getChildNodes()));
}
public XmlElement serialize(Object object) {
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_LONG,
((Long)object).toString());
}
}

21
lib/src/de/timroes/axmlrpc/serializer/NullSerializer.java

@ -0,0 +1,21 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
*
* @author Tim Roes
*/
public class NullSerializer implements Serializer {
public Object deserialize(Element content) throws XMLRPCException {
return null;
}
public XmlElement serialize(Object object) {
return new XmlElement(SerializerHandler.TYPE_NULL);
}
}

34
lib/src/de/timroes/axmlrpc/serializer/Serializer.java

@ -0,0 +1,34 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
* A Serializer is responsible to serialize a specific type of data to
* an xml tag and deserialize the content of this xml tag back to an object.
*
* @author Tim Roes
*/
public interface Serializer {
/**
* This method takes an xml type element and deserialize it to an object.
*
* @param content Must be an xml element of a specific type.
* @return The deserialized content.
* @throws XMLRPCException Will be thrown whenervt the deserialization fails.
*/
public Object deserialize(Element content) throws XMLRPCException;
/**
* This method takes an object and returns a representation as a string
* containing the right xml type tag. The returning string must be useable
* within a value tag.
*
* @param object The object that should be serialized.
* @return An XmlElement representation of the object.
*/
public XmlElement serialize(Object object);
}

228
lib/src/de/timroes/axmlrpc/serializer/SerializerHandler.java

@ -0,0 +1,228 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCClient;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLRPCRuntimeException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import org.w3c.dom.Element;
/**
* The serializer handler serializes and deserialized objects.
* It takes an object, determine its type and let the responsible handler serialize it.
* For deserialization it looks at the xml tag around the element.
* The class is designed as a kind of singleton, so it can be accessed from anywhere in
* the library.
*
* @author Tim Roes
*/
public class SerializerHandler {
public static final String TYPE_STRING = "string";
public static final String TYPE_BOOLEAN = "boolean";
public static final String TYPE_INT = "int";
public static final String TYPE_INT2 = "i4";
public static final String TYPE_LONG = "i8";
public static final String TYPE_DOUBLE = "double";
public static final String TYPE_DATETIME = "dateTime.iso8601";
public static final String TYPE_STRUCT = "struct";
public static final String TYPE_ARRAY = "array";
public static final String TYPE_BASE64 = "base64";
public static final String TYPE_NULL = "nil";
private static SerializerHandler instance;
/**
* Initialize the serialization handler. This method must be called before
* the get method returns any object.
*
* @param flags The flags that has been set in the XMLRPCClient.
* @see XMLRPCClient
*/
public static void initialize(int flags) {
instance = new SerializerHandler(flags);
}
/**
* Return the instance of the SerializerHandler.
* It must have been initialized with initialize() before.
*
* @return The instance of the SerializerHandler.
*/
public static SerializerHandler getDefault() {
if(instance == null) {
throw new XMLRPCRuntimeException("The SerializerHandler has not been initialized.");
}
return instance;
}
private StringSerializer string;
private BooleanSerializer bool = new BooleanSerializer();
private IntSerializer integer = new IntSerializer();
private LongSerializer long8 = new LongSerializer();
private StructSerializer struct = new StructSerializer();
private DoubleSerializer floating = new DoubleSerializer();
private DateTimeSerializer datetime = new DateTimeSerializer();
private ArraySerializer array = new ArraySerializer();
private Base64Serializer base64 = new Base64Serializer();
private NullSerializer nil = new NullSerializer();
private int flags;
/**
* Generates the SerializerHandler.
* This method can only called from within the class (the initialize method).
*
* @param flags The flags to use.
*/
private SerializerHandler(int flags) {
this.flags = flags;
string = new StringSerializer(
(flags & XMLRPCClient.FLAGS_NO_STRING_ENCODE) == 0,
(flags & XMLRPCClient.FLAGS_NO_STRING_DECODE) == 0
);
}
/**
* Deserializes an incoming xml element to an java object.
* The xml element must be the value element around the type element.
* The type of the returning object depends on the type tag.
*
* @param element An type element from within a value tag.
* @return The deserialized object.
* @throws XMLRPCException Will be thrown whenever an error occurs.
*/
public Object deserialize(Element element) throws XMLRPCException {
if(!XMLRPCClient.VALUE.equals(element.getNodeName())) {
throw new XMLRPCException("Value tag is missing around value.");
}
if(!XMLUtil.hasChildElement(element.getChildNodes())) {
// Value element doesn't contain a child element
if((flags & XMLRPCClient.FLAGS_DEFAULT_TYPE_STRING) != 0) {
return string.deserialize(element);
} else {
throw new XMLRPCException("Missing type element inside of value element.");
}
}
// Grep type element from inside value element
element = XMLUtil.getOnlyChildElement(element.getChildNodes());
Serializer s = null;
String type;
// If FLAGS_IGNORE_NAMESPACE has been set, only use local name.
if((flags & XMLRPCClient.FLAGS_IGNORE_NAMESPACES) != 0) {
type = element.getLocalName() == null ? element.getNodeName() : element.getLocalName();
} else {
type = element.getNodeName();
}
if((flags & XMLRPCClient.FLAGS_NIL) != 0 && TYPE_NULL.equals(type)) {
s = nil;
} else if(TYPE_STRING.equals(type)) {
s = string;
} else if(TYPE_BOOLEAN.equals(type)) {
s = bool;
} else if(TYPE_DOUBLE.equals(type)) {
s = floating;
} else if (TYPE_INT.equals(type) || TYPE_INT2.equals(type)) {
s = integer;
} else if(TYPE_DATETIME.equals(type)) {
s = datetime;
} else if (TYPE_LONG.equals(type)) {
if((flags & XMLRPCClient.FLAGS_8BYTE_INT) != 0) {
s = long8;
} else {
throw new XMLRPCException("8 byte integer is not in the specification. "
+ "You must use FLAGS_8BYTE_INT to enable the i8 tag.");
}
} else if(TYPE_STRUCT.equals(type)) {
s = struct;
} else if(TYPE_ARRAY.equals(type)) {
s = array;
} else if(TYPE_BASE64.equals(type)) {
s = base64;
} else {
throw new XMLRPCException("No deserializer found for type '" + type + "'.");
}
return s.deserialize(element);
}
/**
* Serialize an object to its representation as an xml element.
* The xml element will be the type element for the use within a value tag.
*
* @param object The object that should be serialized.
* @return The xml representation of this object.
* @throws XMLRPCException Will be thrown, if an error occurs (e.g. the object
* cannot be serialized to an xml element.
*/
public XmlElement serialize(Object object) throws XMLRPCException {
Serializer s = null;
if((flags & XMLRPCClient.FLAGS_NIL) != 0 && object == null) {
s = nil;
} else if(object instanceof String) {
s = string;
} else if(object instanceof Boolean) {
s = bool;
} else if(object instanceof Double || object instanceof Float
|| object instanceof BigDecimal) {
s = floating;
} else if (object instanceof Integer || object instanceof Short
|| object instanceof Byte) {
s = integer;
} else if(object instanceof Long) {
// Check whether the 8 byte integer flag was set.
if((flags & XMLRPCClient.FLAGS_8BYTE_INT) != 0) {
s = long8;
} else {
// Allow long values as long as their fit within the 4 byte integer range.
long l = (Long)object;
if(l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) {
throw new XMLRPCException("FLAGS_8BYTE_INT must be set, if values "
+ "outside the 4 byte integer range should be transfered.");
} else {
s = integer;
}
}
} else if(object instanceof Date) {
s = datetime;
} else if(object instanceof Calendar) {
object = ((Calendar)object).getTime();
s = datetime;
} else if (object instanceof Map) {
s = struct;
} else if(object instanceof byte[]) {
byte[] old = (byte[])object;
Byte[] boxed = new Byte[old.length];
for(int i = 0; i < boxed.length; i++) {
boxed[i] = new Byte(old[i]);
}
object = boxed;
s = base64;
} else if(object instanceof Byte[]) {
s = base64;
} else if(object instanceof Iterable<?>) {
s = array;
} else {
throw new XMLRPCException("No serializer found for type '"
+ object.getClass().getName() + "'.");
}
return s.serialize(object);
}
}

38
lib/src/de/timroes/axmlrpc/serializer/StringSerializer.java

@ -0,0 +1,38 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import org.w3c.dom.Element;
/**
*
* @author Tim Roes
*/
public class StringSerializer implements Serializer {
private boolean decodeStrings;
private boolean encodeStrings;
public StringSerializer(boolean encodeStrings, boolean decodeStrings) {
this.decodeStrings = decodeStrings;
this.encodeStrings = encodeStrings;
}
public Object deserialize(Element content) throws XMLRPCException {
String text = XMLUtil.getOnlyTextContent(content.getChildNodes());
if(decodeStrings) {
text = text.replaceAll("&lt;", "<").replaceAll("&amp;", "&");
}
return text;
}
public XmlElement serialize(Object object) {
String content = object.toString();
if(encodeStrings) {
content = content.replaceAll("&", "&amp;").replaceAll("<", "&lt;");
}
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_STRING, content);
}
}

112
lib/src/de/timroes/axmlrpc/serializer/StructSerializer.java

@ -0,0 +1,112 @@
package de.timroes.axmlrpc.serializer;
import de.timroes.axmlrpc.XMLRPCException;
import de.timroes.axmlrpc.XMLRPCRuntimeException;
import de.timroes.axmlrpc.XMLUtil;
import de.timroes.axmlrpc.xmlcreator.XmlElement;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
*
* @author Tim Roes
*/
public class StructSerializer implements Serializer {
private static final String STRUCT_MEMBER = "member";
private static final String STRUCT_NAME = "name";
private static final String STRUCT_VALUE = "value";
public Object deserialize(Element content) throws XMLRPCException {
Map<String, Object> map = new HashMap<String, Object>();
Node n, m;
String s;
Object o;
for(int i = 0; i < content.getChildNodes().getLength(); i++) {
n = content.getChildNodes().item(i);
// Strip only whitespace text elements and comments
if((n.getNodeType() == Node.TEXT_NODE
&& n.getNodeValue().trim().length() <= 0)
|| n.getNodeType() == Node.COMMENT_NODE)
continue;
if(n.getNodeType() != Node.ELEMENT_NODE
|| !STRUCT_MEMBER.equals(n.getNodeName())) {
throw new XMLRPCException("Only struct members allowed within a struct.");
}
// Grep name and value from member
s = null; o = null;
for(int j = 0; j < n.getChildNodes().getLength(); j++) {
m = n.getChildNodes().item(j);
// Strip only whitespace text elements and comments
if((m.getNodeType() == Node.TEXT_NODE
&& m.getNodeValue().trim().length() <= 0)
|| m.getNodeType() == Node.COMMENT_NODE)
continue;
if(STRUCT_NAME.equals(m.getNodeName())) {
if(s != null) {
throw new XMLRPCException("Name of a struct member cannot be set twice.");
} else {
s = XMLUtil.getOnlyTextContent(m.getChildNodes());
}
} else if(m.getNodeType() == Node.ELEMENT_NODE && STRUCT_VALUE.equals(m.getNodeName())) {
if(o != null) {
throw new XMLRPCException("Value of a struct member cannot be set twice.");
} else {
o = SerializerHandler.getDefault().deserialize((Element)m);
}
} else {
throw new XMLRPCException("A struct member must only contain one name and one value.");
}
}
map.put(s, o);
}
return map;
}
public XmlElement serialize(Object object) {
XmlElement struct = new XmlElement(SerializerHandler.TYPE_STRUCT);
try {
XmlElement entry, name, value;
// We can safely cast here, this Serializer should only be called when
// the parameter is a map.
@SuppressWarnings("unchecked")
Map<String,Object> map = (Map<String,Object>)object;
for(Map.Entry<String,Object> member : map.entrySet()) {
entry = new XmlElement(STRUCT_MEMBER);
name = new XmlElement(STRUCT_NAME);
value = new XmlElement(STRUCT_VALUE);
name.setContent(member.getKey());
value.addChildren(SerializerHandler.getDefault().serialize(member.getValue()));
entry.addChildren(name);
entry.addChildren(value);
struct.addChildren(entry);
}
} catch(XMLRPCException ex) {
throw new XMLRPCRuntimeException(ex);
}
return struct;
}
}

31
lib/src/de/timroes/axmlrpc/xmlcreator/SimpleXMLCreator.java

@ -0,0 +1,31 @@
package de.timroes.axmlrpc.xmlcreator;
/**
* This is a very simple xml creator. It allows creating an xml document
* containing multiple xml tags. No attributes are supported.
*
* @author Tim Roes
*/
public class SimpleXMLCreator {
private XmlElement root;
/**
* Set the root element of the xml tree.
*
* @param element The element to use as root element in this tree.
*/
public void setRootElement(XmlElement element) {
this.root = element;
}
/**
* Return the string representation of the xml tree.
* @return String representation of the xml tree.
*/
@Override
public String toString() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + root.toString();
}
}

73
lib/src/de/timroes/axmlrpc/xmlcreator/XmlElement.java

@ -0,0 +1,73 @@
package de.timroes.axmlrpc.xmlcreator;
import java.util.ArrayList;
import java.util.List;
/**
* An xml element within an xml tree.
* In this case an xml element can have a text content OR a multiple amount
* of children. The xml element itself has a name.
*
* @author Tim Roes
*/
public class XmlElement {
private List<XmlElement> children = new ArrayList<XmlElement>();
private String name;
private String content;
/**
* Create a new xml element with the given name.
*
* @param name The name of the xml element.
*/
public XmlElement(String name) {
this.name = name;
}
/**
* Add a child to this xml element.
*
* @param element The child to add.
*/
public void addChildren(XmlElement element) {
children.add(element);
}
/**
* Set the content of this xml tag. If the content is set the children
* won't be used in a string representation.
*
* @param content Content of the xml element.
*/
public void setContent(String content) {
this.content = content;
}
/**
* Return a string representation of this xml element.
*
* @return String representation of xml element.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if(content != null && content.length() > 0) {
builder.append("\n<").append(name).append(">")
.append(content)
.append("</").append(name).append(">\n");
return builder.toString();
} else if(children.size() > 0) {
builder.append("\n<").append(name).append(">");
for(XmlElement x : children) {
builder.append(x.toString());
}
builder.append("</").append(name).append(">\n");
return builder.toString();
} else {
builder.append("\n<").append(name).append("/>\n");
return builder.toString();
}
}
}

161
lib/src/de/timroes/base64/Base64.java

@ -0,0 +1,161 @@
package de.timroes.base64;
import java.util.HashMap;
/**
* A Base64 en/decoder. You can use it to encode and decode strings and byte arrays.
*
* @author Tim Roes
*/
public class Base64 {
private static final char[] code = ("=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/").toCharArray();
private static final HashMap<Character,Byte> map = new HashMap<Character, Byte>();
static {
for(int i = 0; i < code.length; i++) {
map.put(code[i], (byte)i);
}
}
/**
* Decode a base64 encoded string to a byte array.
*
* @param in A string representing a base64 encoding.
* @return The decoded byte array.
*/
public static byte[] decode(String in) {
in = in.replaceAll("\\r|\\n","");
if(in.length() % 4 != 0) {
throw new IllegalArgumentException("The length of the input string must be a multiple of four.");
}
if(!in.matches("^[A-Za-z0-9+/]*[=]{0,3}$")) {
throw new IllegalArgumentException("The argument contains illegal characters.");
}
byte[] out = new byte[(in.length()*3)/4];
char[] input = in.toCharArray();
int outi = 0;
int b1, b2, b3, b4;
for(int i = 0; i < input.length; i+=4) {
b1 = (map.get(input[i]) - 1);
b2 = (map.get(input[i+1]) - 1);
b3 = (map.get(input[i+2]) - 1);
b4 = (map.get(input[i+3]) - 1);
out[outi++] = (byte)(b1 << 2 | b2 >>> 4);
out[outi++] = (byte)((b2 & 0x0F) << 4 | b3 >>> 2);
out[outi++] = (byte)((b3 & 0x03) << 6 | (b4 & 0x3F));
}
if(in.endsWith("=")) {
byte[] trimmed = new byte[out.length - (in.length() - in.indexOf("="))];
System.arraycopy(out, 0, trimmed, 0, trimmed.length);
return trimmed;
}
return out;
}
/**
* Decode a base64 encoded string to a string.
*
* @param in The string representation of the base64 encoding.
* @return The decoded string in the default charset of the JVM.
*/
public static String decodeAsString(String in) {
return new String(decode(in));
}
/**
* Encode a String and return the encoded string.
*
* @param in A string to encode.
* @return The encoded byte array.
*/
public static String encode(String in) {
return encode(in.getBytes());
}
/**
* Encode a Byte array and return the encoded string.
*
* @param in A string to encode.
* @return The encoded byte array.
*/
public static String encode(Byte[] in) {
byte[] tmp = new byte[in.length];
for(int i = 0; i < tmp.length; i++) {
tmp[i] = in[i];
}
return encode(tmp);
}
/**
* Encode a byte array and return the encoded string.
*
* @param in A string to encode.
* @return The encoded byte array.
*/
public static String encode(byte[] in) {
StringBuilder builder = new StringBuilder(4 * ((in.length+2)/3));
byte[] encoded = encodeAsBytes(in);
for(int i = 0; i < encoded.length; i++) {
builder.append(code[encoded[i]+1]);
if(i % 72 == 71)
builder.append("\n");
}
return builder.toString();
}
/**
* Encode a String and return the encoded byte array. Bytes that has been
* appended to pad the string to a multiple of four are set to -1 in the array.
*
* @param in A string to encode.
* @return The encoded byte array.
*/
public static byte[] encodeAsBytes(String in) {
return encodeAsBytes(in.getBytes());
}
/**
* Encode a byte array and return the encoded byte array. Bytes that has been
* appended to pad the string to a multiple of four are set to -1 in the array.
*
* @param inArray A string to encode.
* @return The encoded byte array.
*/
public static byte[] encodeAsBytes(byte[] inArray) {
// Output string must be 4 * floor((n+2)/3) large
byte[] out = new byte[4 * ((inArray.length+2)/3)];
// Create padded input array with the next largest length that is a multiple of 3
byte[] in = new byte[(inArray.length+2)/3*3];
// Copy content form unpadded to padded array
System.arraycopy(inArray, 0, in, 0, inArray.length);
int outi = 0;
for(int i = 0; i < in.length; i+=3) {
out[outi++] = (byte)((in[i] & 0xFF) >>> 2);
out[outi++] = (byte)(((in[i] & 0x03) << 4) | ((in[i+1] & 0xFF) >>> 4));
out[outi++] = (byte)(((in[i+1] & 0x0F) << 2) | ((in[i+2] & 0xFF) >>> 6));
out[outi++] = (byte)(in[i+2] & 0x3F);
}
for(int i = in.length - inArray.length; i > 0; i--) {
out[out.length - i] = -1;
}
return out;
}
}

30
lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java

@ -23,7 +23,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -35,6 +37,7 @@ import java.util.Map.Entry;
import org.base64.android.Base64; import org.base64.android.Base64;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Label; import org.transdroid.daemon.Label;
@ -43,7 +46,6 @@ import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
@ -62,13 +64,14 @@ import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetLabelTask; import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.DLog; import org.transdroid.daemon.util.DLog;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.FakeTrustManager;
import org.xmlrpc.android.XMLRPCClient;
import org.xmlrpc.android.XMLRPCException; import de.timroes.axmlrpc.XMLRPCClient;
import de.timroes.axmlrpc.XMLRPCException;
/** /**
* An adapter that allows for easy access to rTorrent torrent data. Communication * An adapter that allows for easy access to rTorrent torrent data. Communication
* is handled via the XML-RPC protocol. * is handled via the XML-RPC protocol as implemented by the aXMLRPC library.
* *
* @author erickok * @author erickok
* *
@ -234,7 +237,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
} }
} }
private Object makeRtorrentCall(String serverMethod, Object[] arguments) throws DaemonException { private Object makeRtorrentCall(String serverMethod, Object[] arguments) throws DaemonException, MalformedURLException {
// Initialise the HTTP client // Initialise the HTTP client
if (rpcclient == null) { if (rpcclient == null) {
@ -258,10 +261,19 @@ public class RtorrentAdapter implements IDaemonAdapter {
/** /**
* Instantiates a XML-RPC client with proper credentials. * Instantiates a XML-RPC client with proper credentials.
* @throws DaemonException On conflicting settings (i.e. user authentication but no password or username provided) * @throws DaemonException On conflicting settings (i.e. user authentication but no password or username provided)
* @throws MalformedURLException Thrown when the URL could not be properly constructed
*/ */
private void initialise() throws DaemonException { private void initialise() throws DaemonException, MalformedURLException {
this.rpcclient = new XMLRPCClient(HttpHelper.createStandardHttpClient(settings, true), buildWebUIUrl().trim()); //this.rpcclient = new XMLRPCClient(HttpHelper.createStandardHttpClient(settings, true), buildWebUIUrl().trim());
int flags = XMLRPCClient.FLAGS_8BYTE_INT | XMLRPCClient.FLAGS_ENABLE_COOKIES;
if (settings.getSsl() && settings.getSslTrustAll())
flags = XMLRPCClient.FLAGS_8BYTE_INT | XMLRPCClient.FLAGS_ENABLE_COOKIES | XMLRPCClient.FLAGS_SSL_IGNORE_INVALID_CERT;
this.rpcclient = new XMLRPCClient(new URL(buildWebUIUrl().trim()), flags);
if (settings.getSsl() && settings.getSslTrustKey() != null && !settings.getSslTrustKey().isEmpty())
this.rpcclient.installCustomTrustManager(new FakeTrustManager(settings.getSslTrustKey()));
this.rpcclient.setTimeout(settings.getTimeoutInMilliseconds() / 1000);
this.rpcclient.setLoginData(settings.getUsername(), settings.getPassword());
} }

2
lib/src/org/transdroid/daemon/util/FakeTrustManager.java

@ -13,7 +13,7 @@ public class FakeTrustManager implements X509TrustManager {
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {}; private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};
private static final String LOG_NAME = "TrustManager"; private static final String LOG_NAME = "TrustManager";
FakeTrustManager(String certKey){ public FakeTrustManager(String certKey){
super(); super();
this.certKey = certKey; this.certKey = certKey;
} }

199
lib/src/org/xmlrpc/android/Base64Coder.java

@ -1,199 +0,0 @@
package org.xmlrpc.android;
/**
* A Base64 Encoder/Decoder.
*
* <p>
* This class is used to encode and decode data in Base64 format as described in
* RFC 1521.
*
* <p>
* This is "Open Source" software and released under the <a
* href="http://www.gnu.org/licenses/lgpl.html">GNU/LGPL</a> license.<br>
* It is provided "as is" without warranty of any kind.<br>
* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland.<br>
* Home page: <a href="http://www.source-code.biz">www.source-code.biz</a><br>
*
* <p>
* Version history:<br>
* 2003-07-22 Christian d'Heureuse (chdh): Module created.<br>
* 2005-08-11 chdh: Lincense changed from GPL to LGPL.<br>
* 2006-11-21 chdh:<br>
* &nbsp; Method encode(String) renamed to encodeString(String).<br>
* &nbsp; Method decode(String) renamed to decodeString(String).<br>
* &nbsp; New method encode(byte[],int) added.<br>
* &nbsp; New method decode(String) added.<br>
*/
class Base64Coder {
// Mapping table from 6-bit nibbles to Base64 characters.
private static char[] map1 = new char[64];
static {
int i = 0;
for (char c = 'A'; c <= 'Z'; c++) {
map1[i++] = c;
}
for (char c = 'a'; c <= 'z'; c++) {
map1[i++] = c;
}
for (char c = '0'; c <= '9'; c++) {
map1[i++] = c;
}
map1[i++] = '+';
map1[i++] = '/';
}
// Mapping table from Base64 characters to 6-bit nibbles.
private static byte[] map2 = new byte[128];
static {
for (int i = 0; i < map2.length; i++) {
map2[i] = -1;
}
for (int i = 0; i < 64; i++) {
map2[map1[i]] = (byte) i;
}
}
/**
* Encodes a string into Base64 format. No blanks or line breaks are
* inserted.
*
* @param s
* a String to be encoded.
* @return A String with the Base64 encoded data.
*/
static String encodeString(String s) {
return new String(encode(s.getBytes()));
}
/**
* Encodes a byte array into Base64 format. No blanks or line breaks are
* inserted.
*
* @param in
* an array containing the data bytes to be encoded.
* @return A character array with the Base64 encoded data.
*/
static char[] encode(byte[] in) {
return encode(in, in.length);
}
/**
* Encodes a byte array into Base64 format. No blanks or line breaks are
* inserted.
*
* @param in
* an array containing the data bytes to be encoded.
* @param iLen
* number of bytes to process in <code>in</code>.
* @return A character array with the Base64 encoded data.
*/
static char[] encode(byte[] in, int iLen) {
int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
int oLen = ((iLen + 2) / 3) * 4; // output length including padding
char[] out = new char[oLen];
int ip = 0;
int op = 0;
while (ip < iLen) {
int i0 = in[ip++] & 0xff;
int i1 = ip < iLen ? in[ip++] & 0xff : 0;
int i2 = ip < iLen ? in[ip++] & 0xff : 0;
int o0 = i0 >>> 2;
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
int o3 = i2 & 0x3F;
out[op++] = map1[o0];
out[op++] = map1[o1];
out[op] = op < oDataLen ? map1[o2] : '=';
op++;
out[op] = op < oDataLen ? map1[o3] : '=';
op++;
}
return out;
}
/**
* Decodes a string from Base64 format.
*
* @param s
* a Base64 String to be decoded.
* @return A String containing the decoded data.
* @throws IllegalArgumentException
* if the input is not valid Base64 encoded data.
*/
static String decodeString(String s) {
return new String(decode(s));
}
/**
* Decodes a byte array from Base64 format.
*
* @param s
* a Base64 String to be decoded.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException
* if the input is not valid Base64 encoded data.
*/
static byte[] decode(String s) {
return decode(s.toCharArray());
}
/**
* Decodes a byte array from Base64 format. No blanks or line breaks are
* allowed within the Base64 encoded data.
*
* @param in
* a character array containing the Base64 encoded data.
* @return An array containing the decoded data bytes.
* @throws IllegalArgumentException
* if the input is not valid Base64 encoded data.
*/
static byte[] decode(char[] in) {
int iLen = in.length;
if (iLen % 4 != 0) {
throw new IllegalArgumentException(
"Length of Base64 encoded input string is not a multiple of 4.");
}
while (iLen > 0 && in[iLen - 1] == '=') {
iLen--;
}
int oLen = (iLen * 3) / 4;
byte[] out = new byte[oLen];
int ip = 0;
int op = 0;
while (ip < iLen) {
int i0 = in[ip++];
int i1 = in[ip++];
int i2 = ip < iLen ? in[ip++] : 'A';
int i3 = ip < iLen ? in[ip++] : 'A';
if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) {
throw new IllegalArgumentException(
"Illegal character in Base64 encoded data.");
}
int b0 = map2[i0];
int b1 = map2[i1];
int b2 = map2[i2];
int b3 = map2[i3];
if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) {
throw new IllegalArgumentException(
"Illegal character in Base64 encoded data.");
}
int o0 = (b0 << 2) | (b1 >>> 4);
int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
int o2 = ((b2 & 3) << 6) | b3;
out[op++] = (byte) o0;
if (op < oLen) {
out[op++] = (byte) o1;
}
if (op < oLen) {
out[op++] = (byte) o2;
}
}
return out;
}
// Dummy constructor.
private Base64Coder() {
}
}

379
lib/src/org/xmlrpc/android/XMLRPCClient.java

@ -1,379 +0,0 @@
package org.xmlrpc.android;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URI;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.transdroid.daemon.DaemonException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import android.util.Xml;
/**
* XMLRPCClient allows to call remote XMLRPC method.
*
* <p>
* The following table shows how XML-RPC types are mapped to java call parameters/response values.
* </p>
*
* <p>
* <table border="2" align="center" cellpadding="5">
* <thead><tr><th>XML-RPC Type</th><th>Call Parameters</th><th>Call Response</th></tr></thead>
*
* <tbody>
* <td>int, i4</td><td>byte<br />Byte<br />short<br />Short<br />int<br />Integer</td><td>int<br />Integer</td>
* </tr>
* <tr>
* <td>i8</td><td>long<br />Long</td><td>long<br />Long</td>
* </tr>
* <tr>
* <td>double</td><td>float<br />Float<br />double<br />Double</td><td>double<br />Double</td>
* </tr>
* <tr>
* <td>string</td><td>String</td><td>String</td>
* </tr>
* <tr>
* <td>boolean</td><td>boolean<br />Boolean</td><td>boolean<br />Boolean</td>
* </tr>
* <tr>
* <td>dateTime.iso8601</td><td>java.util.Date<br />java.util.Calendar</td><td>java.util.Date</td>
* </tr>
* <tr>
* <td>base64</td><td>byte[]</td><td>byte[]</td>
* </tr>
* <tr>
* <td>array</td><td>java.util.List&lt;Object&gt;<br />Object[]</td><td>Object[]</td>
* </tr>
* <tr>
* <td>struct</td><td>java.util.Map&lt;String, Object&gt;</td><td>java.util.Map&lt;String, Object&gt;</td>
* </tr>
* </tbody>
* </table>
* </p>
*/
public class XMLRPCClient {
private static final String TAG_METHOD_CALL = "methodCall";
private static final String TAG_METHOD_NAME = "methodName";
private static final String TAG_METHOD_RESPONSE = "methodResponse";
private static final String TAG_PARAMS = "params";
private static final String TAG_PARAM = "param";
private static final String TAG_FAULT = "fault";
private static final String TAG_FAULT_CODE = "faultCode";
private static final String TAG_FAULT_STRING = "faultString";
private HttpClient client;
private HttpPost postMethod;
private XmlSerializer serializer;
private HttpParams httpParams;
/**
* XMLRPCClient constructor. Creates new instance based on server URI
* @param XMLRPC server URI
*/
public XMLRPCClient(HttpClient client, URI uri) {
postMethod = new HttpPost(uri);
postMethod.addHeader("Content-Type", "text/xml");
// WARNING
// I had to disable "Expect: 100-Continue" header since I had
// two second delay between sending http POST request and POST body
httpParams = postMethod.getParams();
HttpProtocolParams.setUseExpectContinue(httpParams, false);
this.client = client;
serializer = Xml.newSerializer();
}
/**
* Convenience constructor. Creates new instance based on server String address
* @param XMLRPC server address
*/
public XMLRPCClient(HttpClient client, String url) {
this(client, URI.create(url));
}
/**
* Call method with optional parameters. This is general method.
* If you want to call your method with 0-8 parameters, you can use more
* convenience call methods
*
* @param method name of method to call
* @param params parameters to pass to method (may be null if method has no parameters)
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object[] params) throws XMLRPCException {
return callXMLRPC(method, params);
}
/**
* Convenience method call with no parameters
*
* @param method name of method to call
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method) throws XMLRPCException {
return callXMLRPC(method, null);
}
/**
* Convenience method call with one parameter
*
* @param method name of method to call
* @param p0 method's parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0) throws XMLRPCException {
Object[] params = {
p0,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with two parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1) throws XMLRPCException {
Object[] params = {
p0, p1,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with three parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2) throws XMLRPCException {
Object[] params = {
p0, p1, p2,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with four parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @param p3 method's 4th parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2, Object p3) throws XMLRPCException {
Object[] params = {
p0, p1, p2, p3,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with five parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @param p3 method's 4th parameter
* @param p4 method's 5th parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4) throws XMLRPCException {
Object[] params = {
p0, p1, p2, p3, p4,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with six parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @param p3 method's 4th parameter
* @param p4 method's 5th parameter
* @param p5 method's 6th parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) throws XMLRPCException {
Object[] params = {
p0, p1, p2, p3, p4, p5,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with seven parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @param p3 method's 4th parameter
* @param p4 method's 5th parameter
* @param p5 method's 6th parameter
* @param p6 method's 7th parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) throws XMLRPCException {
Object[] params = {
p0, p1, p2, p3, p4, p5, p6,
};
return callXMLRPC(method, params);
}
/**
* Convenience method call with eight parameters
*
* @param method name of method to call
* @param p0 method's 1st parameter
* @param p1 method's 2nd parameter
* @param p2 method's 3rd parameter
* @param p3 method's 4th parameter
* @param p4 method's 5th parameter
* @param p5 method's 6th parameter
* @param p6 method's 7th parameter
* @param p7 method's 8th parameter
* @return deserialized method return value
* @throws XMLRPCException
*/
public Object call(String method, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) throws XMLRPCException {
Object[] params = {
p0, p1, p2, p3, p4, p5, p6, p7,
};
return callXMLRPC(method, params);
}
/**
* Call method with optional parameters
*
* @param method name of method to call
* @param params parameters to pass to method (may be null if method has no parameters)
* @return deserialized method return value
* @throws XMLRPCException
*/
@SuppressWarnings("unchecked")
protected Object callXMLRPC(String method, Object[] params) throws XMLRPCException {
try {
// prepare POST body
StringWriter bodyWriter = new StringWriter();
serializer.setOutput(bodyWriter);
serializer.startDocument(null, null);
serializer.startTag(null, TAG_METHOD_CALL);
// set method name
serializer.startTag(null, TAG_METHOD_NAME).text(method).endTag(null, TAG_METHOD_NAME);
if (params != null && params.length != 0) {
// set method params
serializer.startTag(null, TAG_PARAMS);
for (int i=0; i<params.length; i++) {
serializer.startTag(null, TAG_PARAM).startTag(null, XMLRPCSerializer.TAG_VALUE);
XMLRPCSerializer.serialize(serializer, params[i]);
serializer.endTag(null, XMLRPCSerializer.TAG_VALUE).endTag(null, TAG_PARAM);
}
serializer.endTag(null, TAG_PARAMS);
}
serializer.endTag(null, TAG_METHOD_CALL);
serializer.endDocument();
// set POST body
HttpEntity entity = new StringEntity(bodyWriter.toString());
postMethod.setEntity(entity);
// execute HTTP POST request
HttpResponse response = client.execute(postMethod);
// check status code
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
if (statusCode == HttpStatus.SC_UNAUTHORIZED)
throw new DaemonException(DaemonException.ExceptionType.AuthenticationFailure, "HTTP status code: "
+ statusCode + " != " + HttpStatus.SC_OK);
throw new XMLRPCException("HTTP status code: " + statusCode + " != " + HttpStatus.SC_OK);
}
// parse response stuff
//
// setup pull parser
XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
entity = response.getEntity();
//String temp = HttpHelper.ConvertStreamToString(entity.getContent());
Reader reader = new InputStreamReader(entity.getContent());
pullParser.setInput(reader);
// lets start pulling...
pullParser.nextTag();
pullParser.require(XmlPullParser.START_TAG, null, TAG_METHOD_RESPONSE);
pullParser.nextTag(); // either TAG_PARAMS (<params>) or TAG_FAULT (<fault>)
String tag = pullParser.getName();
if (tag.equals(TAG_PARAMS)) {
// normal response
pullParser.nextTag(); // TAG_PARAM (<param>)
pullParser.require(XmlPullParser.START_TAG, null, TAG_PARAM);
pullParser.nextTag(); // TAG_VALUE (<value>)
// no parser.require() here since its called in XMLRPCSerializer.deserialize() below
// deserialize result
Object obj = XMLRPCSerializer.deserialize(pullParser);
entity.consumeContent();
return obj;
} else
if (tag.equals(TAG_FAULT)) {
// fault response
pullParser.nextTag(); // TAG_VALUE (<value>)
// no parser.require() here since its called in XMLRPCSerializer.deserialize() below
// deserialize fault result
Map<String, Object> map = (Map<String, Object>) XMLRPCSerializer.deserialize(pullParser);
String faultString = (String) map.get(TAG_FAULT_STRING);
int faultCode = (Integer) map.get(TAG_FAULT_CODE);
entity.consumeContent();
throw new XMLRPCFault(faultString, faultCode);
} else {
entity.consumeContent();
throw new XMLRPCException("Bad tag <" + tag + "> in XMLRPC response - neither <params> nor <fault>");
}
} catch (XMLRPCException e) {
// catch & propagate XMLRPCException/XMLRPCFault
throw e;
} catch (Exception e) {
// wrap any other Exception(s) around XMLRPCException
throw new XMLRPCException(e);
}
}
}

17
lib/src/org/xmlrpc/android/XMLRPCException.java

@ -1,17 +0,0 @@
package org.xmlrpc.android;
public class XMLRPCException extends Exception {
/**
*
*/
private static final long serialVersionUID = 7499675036625522379L;
public XMLRPCException(Exception e) {
super(e);
}
public XMLRPCException(String string) {
super(string);
}
}

24
lib/src/org/xmlrpc/android/XMLRPCFault.java

@ -1,24 +0,0 @@
package org.xmlrpc.android;
public class XMLRPCFault extends XMLRPCException {
/**
*
*/
private static final long serialVersionUID = 5676562456612956519L;
private String faultString;
private int faultCode;
public XMLRPCFault(String faultString, int faultCode) {
super("XMLRPC Fault: " + faultString + " [code " + faultCode + "]");
this.faultString = faultString;
this.faultCode = faultCode;
}
public String getFaultString() {
return faultString;
}
public int getFaultCode() {
return faultCode;
}
}

205
lib/src/org/xmlrpc/android/XMLRPCSerializer.java

@ -1,205 +0,0 @@
package org.xmlrpc.android;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
class XMLRPCSerializer {
static final String TAG_NAME = "name";
static final String TAG_MEMBER = "member";
static final String TAG_VALUE = "value";
static final String TAG_DATA = "data";
static final String TYPE_INT = "int";
static final String TYPE_I4 = "i4";
static final String TYPE_I8 = "i8";
static final String TYPE_DOUBLE = "double";
static final String TYPE_BOOLEAN = "boolean";
static final String TYPE_STRING = "string";
static final String TYPE_DATE_TIME_ISO8601 = "dateTime.iso8601";
static final String TYPE_BASE64 = "base64";
static final String TYPE_ARRAY = "array";
static final String TYPE_STRUCT = "struct";
static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
@SuppressWarnings("unchecked")
static void serialize(XmlSerializer serializer, Object object ) throws IOException {
// check for scalar types:
if (object instanceof Integer || object instanceof Short || object instanceof Byte) {
serializer.startTag(null, TYPE_I4).text(object.toString()).endTag(null, TYPE_I4);
} else
if (object instanceof Long) {
serializer.startTag(null, TYPE_I8).text(object.toString()).endTag(null, TYPE_I8);
} else
if (object instanceof Double || object instanceof Float) {
serializer.startTag(null, TYPE_DOUBLE).text(object.toString()).endTag(null, TYPE_DOUBLE);
} else
if (object instanceof Boolean) {
Boolean bool = (Boolean) object;
String boolStr = bool.booleanValue() ? "1" : "0";
serializer.startTag(null, TYPE_BOOLEAN).text(boolStr).endTag(null, TYPE_BOOLEAN);
} else
if (object instanceof String) {
serializer.startTag(null, TYPE_STRING).text(object.toString()).endTag(null, TYPE_STRING);
} else
if (object instanceof Date || object instanceof Calendar) {
String dateStr = dateFormat.format(object);
serializer.startTag(null, TYPE_DATE_TIME_ISO8601).text(dateStr).endTag(null, TYPE_DATE_TIME_ISO8601);
} else
if (object instanceof byte[] ){
String value = new String(Base64Coder.encode((byte[])object));
serializer.startTag(null, TYPE_BASE64).text(value).endTag(null, TYPE_BASE64);
} else
if (object instanceof List) {
serializer.startTag(null, TYPE_ARRAY).startTag(null, TAG_DATA);
List<Object> list = (List<Object>) object;
Iterator<Object> iter = list.iterator();
while (iter.hasNext()) {
Object o = iter.next();
serializer.startTag(null, TAG_VALUE);
serialize(serializer, o);
serializer.endTag(null, TAG_VALUE);
}
serializer.endTag(null, TAG_DATA).endTag(null, TYPE_ARRAY);
} else
if (object instanceof Object[]) {
serializer.startTag(null, TYPE_ARRAY).startTag(null, TAG_DATA);
Object[] objects = (Object[]) object;
for (int i=0; i<objects.length; i++) {
Object o = objects[i];
serializer.startTag(null, TAG_VALUE);
serialize(serializer, o);
serializer.endTag(null, TAG_VALUE);
}
serializer.endTag(null, TAG_DATA).endTag(null, TYPE_ARRAY);
} else
if (object instanceof Map) {
serializer.startTag(null, TYPE_STRUCT);
Map<String, Object> map = (Map<String, Object>) object;
Iterator<Entry<String, Object>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, Object> entry = iter.next();
String key = entry.getKey();
Object value = entry.getValue();
serializer.startTag(null, TAG_MEMBER);
serializer.startTag(null, TAG_NAME).text(key).endTag(null, TAG_NAME);
serializer.startTag(null, TAG_VALUE);
serialize(serializer, value);
serializer.endTag(null, TAG_VALUE);
serializer.endTag(null, TAG_MEMBER);
}
serializer.endTag(null, TYPE_STRUCT);
} else {
throw new IOException("Cannot serialize " + object);
}
}
static Object deserialize(XmlPullParser parser) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, null, TAG_VALUE);
parser.nextTag();
String typeNodeName = parser.getName();
Object obj;
if (typeNodeName.equals(TYPE_INT) || typeNodeName.equals(TYPE_I4)) {
String value = parser.nextText();
obj = Integer.parseInt(value);
} else
if (typeNodeName.equals(TYPE_I8)) {
String value = parser.nextText();
obj = Long.parseLong(value);
} else
if (typeNodeName.equals(TYPE_DOUBLE)) {
String value = parser.nextText();
obj = Double.parseDouble(value);
} else
if (typeNodeName.equals(TYPE_BOOLEAN)) {
String value = parser.nextText();
obj = value.equals("1") ? Boolean.TRUE : Boolean.FALSE;
} else
if (typeNodeName.equals(TYPE_STRING)) {
obj = parser.nextText();
} else
if (typeNodeName.equals(TYPE_DATE_TIME_ISO8601)) {
String value = parser.nextText();
try {
obj = dateFormat.parseObject(value);
} catch (ParseException e) {
throw new IOException("Cannot deserialize dateTime " + value);
}
} else
if (typeNodeName.equals(TYPE_BASE64)) {
String value = parser.nextText();
BufferedReader reader = new BufferedReader(new StringReader(value));
String line;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
obj = Base64Coder.decode(sb.toString());
} else
if (typeNodeName.equals(TYPE_ARRAY)) {
parser.nextTag(); // TAG_DATA (<data>)
parser.require(XmlPullParser.START_TAG, null, TAG_DATA);
parser.nextTag();
List<Object> list = new ArrayList<Object>();
while (parser.getName().equals(TAG_VALUE)) {
list.add(deserialize(parser));
parser.nextTag();
}
parser.require(XmlPullParser.END_TAG, null, TAG_DATA);
parser.nextTag(); // TAG_ARRAY (</array>)
parser.require(XmlPullParser.END_TAG, null, TYPE_ARRAY);
obj = list.toArray();
} else
if (typeNodeName.equals(TYPE_STRUCT)) {
parser.nextTag();
Map<String, Object> map = new HashMap<String, Object>();
while (parser.getName().equals(TAG_MEMBER)) {
String memberName = null;
Object memberValue = null;
while (true) {
parser.nextTag();
String name = parser.getName();
if (name.equals(TAG_NAME)) {
memberName = parser.nextText();
} else
if (name.equals(TAG_VALUE)) {
memberValue = deserialize(parser);
} else {
break;
}
}
if (memberName != null && memberValue != null) {
map.put(memberName, memberValue);
}
parser.require(XmlPullParser.END_TAG, null, TAG_MEMBER);
parser.nextTag();
}
parser.require(XmlPullParser.END_TAG, null, TYPE_STRUCT);
obj = map;
} else {
throw new IOException("Cannot deserialize " + parser.getName());
}
parser.nextTag(); // TAG_VALUE (</value>)
parser.require(XmlPullParser.END_TAG, null, TAG_VALUE);
return obj;
}
}
Loading…
Cancel
Save