Eric Kok
11 years ago
34 changed files with 2351 additions and 834 deletions
Binary file not shown.
@ -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); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
|
||||||
|
} |
@ -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 <i8> 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 & and < |
||||||
|
* 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 & and the "less then" sign to <. |
||||||
|
* 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 { } |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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("<", "<").replaceAll("&", "&"); |
||||||
|
} |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
public XmlElement serialize(Object object) { |
||||||
|
String content = object.toString(); |
||||||
|
if(encodeStrings) { |
||||||
|
content = content.replaceAll("&", "&").replaceAll("<", "<"); |
||||||
|
} |
||||||
|
return XMLUtil.makeXmlTag(SerializerHandler.TYPE_STRING, content); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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> |
|
||||||
* Method encode(String) renamed to encodeString(String).<br> |
|
||||||
* Method decode(String) renamed to decodeString(String).<br> |
|
||||||
* New method encode(byte[],int) added.<br> |
|
||||||
* 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() { |
|
||||||
} |
|
||||||
} |
|
@ -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<Object><br />Object[]</td><td>Object[]</td> |
|
||||||
* </tr> |
|
||||||
* <tr> |
|
||||||
* <td>struct</td><td>java.util.Map<String, Object></td><td>java.util.Map<String, Object></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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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…
Reference in new issue