PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/host-common/src/main/java/com/atlassian/plugin/remotable/host/common/service/http/DefaultHostXmlRpcClient.java

https://bitbucket.org/rodogu/remotable-plugins
Java | 225 lines | 185 code | 22 blank | 18 comment | 8 complexity | d994b5fcaad1f09d27f6a7c875c323c7 MD5 | raw file
  1. package com.atlassian.plugin.remotable.host.common.service.http;
  2. import com.atlassian.httpclient.api.Response;
  3. import com.atlassian.httpclient.api.ResponsePromises;
  4. import com.atlassian.plugin.remotable.api.service.http.HostHttpClient;
  5. import com.atlassian.plugin.remotable.api.service.http.HostXmlRpcClient;
  6. import com.atlassian.plugin.remotable.api.service.http.XmlRpcException;
  7. import com.atlassian.plugin.remotable.api.service.http.XmlRpcFault;
  8. import com.atlassian.plugin.util.ChainingClassLoader;
  9. import com.atlassian.plugin.util.ContextClassLoaderSwitchingUtil;
  10. import com.atlassian.util.concurrent.Promise;
  11. import com.atlassian.xmlrpc.BindingException;
  12. import com.atlassian.xmlrpc.ServiceObject;
  13. import com.atlassian.xmlrpc.XmlRpcClientProvider;
  14. import com.google.common.annotations.VisibleForTesting;
  15. import com.google.common.base.Function;
  16. import redstone.xmlrpc.XmlRpcMessages;
  17. import redstone.xmlrpc.XmlRpcSerializer;
  18. import redstone.xmlrpc.XmlRpcStruct;
  19. import java.io.BufferedInputStream;
  20. import java.io.IOException;
  21. import java.io.StringWriter;
  22. import java.io.Writer;
  23. import java.lang.reflect.Proxy;
  24. import java.net.URI;
  25. import java.util.Vector;
  26. import java.util.concurrent.Callable;
  27. import static java.lang.System.*;
  28. /**
  29. * Helps make authenticated xmlrpc calls
  30. */
  31. public class DefaultHostXmlRpcClient implements HostXmlRpcClient
  32. {
  33. private final static URI serverXmlRpcPath = URI.create("/rpc/xmlrpc");
  34. private final XmlRpcSerializer serializer = new XmlRpcSerializer();
  35. private final HostHttpClient httpClient;
  36. public DefaultHostXmlRpcClient(HostHttpClient httpClient)
  37. {
  38. this.httpClient = httpClient;
  39. }
  40. @Override
  41. public <T> T bind(Class<T> bindClass)
  42. {
  43. if (!bindClass.isInterface())
  44. {
  45. throw new IllegalArgumentException("Class " + bindClass.getName() + "is not an interface");
  46. }
  47. ServiceObject serviceObject = bindClass.getAnnotation(ServiceObject.class);
  48. if (serviceObject == null)
  49. {
  50. throw new IllegalArgumentException("Could not find ServiceObject annotation on " + bindClass.getName());
  51. }
  52. PromiseAwareXmlRpcInvocationHandler handler = new PromiseAwareXmlRpcInvocationHandler(new XmlRpcClientProvider()
  53. {
  54. public Object execute(String serviceName, String methodName, Vector arguments) throws BindingException
  55. {
  56. Object[] argsWithToken = new Object[arguments.size() + 1];
  57. argsWithToken[0] = "";
  58. arraycopy(arguments.toArray(), 0, argsWithToken, 1, arguments.size());
  59. return invoke(serviceName + "." + methodName, Object.class, argsWithToken);
  60. }
  61. });
  62. return (T) Proxy.newProxyInstance(new ChainingClassLoader(getClass().getClassLoader(), bindClass.getClassLoader()), new Class[]{bindClass},
  63. handler);
  64. }
  65. @Override
  66. public <T> Promise<T> invoke(String method, Class<T> resultType, Object... arguments)
  67. {
  68. Writer writer = new StringWriter(2048);
  69. beginCall(writer, method);
  70. FaultHandlingXmlRpcParser parser = new FaultHandlingXmlRpcParser();
  71. for (Object argument : arguments)
  72. {
  73. try
  74. {
  75. writer.write("<param>");
  76. serializer.serialize(argument, writer);
  77. writer.write("</param>");
  78. }
  79. catch (IOException ioe)
  80. {
  81. throw new XmlRpcException(
  82. XmlRpcMessages.getString("XmlRpcClient.NetworkError"), ioe);
  83. }
  84. }
  85. return endCall(writer, parser, resultType);
  86. }
  87. /**
  88. * Initializes the XML buffer to be sent to the server with the XML-RPC content common to all
  89. * method calls, or serializes it directly over the writer if streaming is used. The parameters
  90. * to the call are added in the execute() method, and the closing tags are appended when the
  91. * call is finalized in endCall().
  92. *
  93. * @param methodName The name of the method to call.
  94. */
  95. private void beginCall(Writer writer, String methodName) throws XmlRpcException
  96. {
  97. try
  98. {
  99. ((StringWriter) writer).getBuffer().setLength(0);
  100. writer.write("<?xml version=\"1.0\" encoding=\"");
  101. writer.write(XmlRpcMessages.getString("XmlRpcClient.Encoding"));
  102. writer.write("\"?>");
  103. writer.write("<methodCall><methodName>");
  104. writer.write(methodName);
  105. writer.write("</methodName><params>");
  106. }
  107. catch (IOException ioe)
  108. {
  109. throw new XmlRpcException(
  110. XmlRpcMessages.getString("XmlRpcClient.NetworkError"), ioe);
  111. }
  112. }
  113. /**
  114. * Finalizes the XML buffer to be sent to the server, and creates a HTTP buffer for the call.
  115. * Both buffers are combined into an XML-RPC message that is sent over a socket to the server.
  116. *
  117. * @return The parsed return value of the call.
  118. * @throws XmlRpcException when some IO problem occur.
  119. */
  120. @VisibleForTesting
  121. <T> Promise<T> endCall(Writer writer, final FaultHandlingXmlRpcParser parser, final Class<T> castResultTo)
  122. {
  123. try
  124. {
  125. writer.write("</params>");
  126. writer.write("</methodCall>");
  127. return httpClient
  128. .newRequest(serverXmlRpcPath)
  129. .setContentType("text/xml")
  130. .setContentCharset(XmlRpcMessages.getString("XmlRpcClient.Encoding"))
  131. .setEntity(writer.toString())
  132. .post()
  133. .<T>transform()
  134. .ok(new Function<Response, T>()
  135. {
  136. @Override
  137. public T apply(final Response response)
  138. {
  139. return parseResponse(parser, response, castResultTo);
  140. }
  141. })
  142. .others(ResponsePromises.<T>newUnexpectedResponseFunction())
  143. .toPromise();
  144. }
  145. catch (IOException ioe)
  146. {
  147. throw new RuntimeException("Should never happen", ioe);
  148. }
  149. }
  150. private <T> T parseResponse(final FaultHandlingXmlRpcParser parser, final Response response, final Class<T> castResultTo)
  151. {
  152. try
  153. {
  154. return ContextClassLoaderSwitchingUtil.runInContext(getClass().getClassLoader(),
  155. new Callable<T>()
  156. {
  157. @Override
  158. public T call()
  159. {
  160. try
  161. {
  162. parser.parse(new BufferedInputStream(response.getEntityStream()));
  163. }
  164. catch (Exception e)
  165. {
  166. throw new XmlRpcException(
  167. XmlRpcMessages.getString("XmlRpcClient.ParseError"), e);
  168. }
  169. if (parser.isFaultResponse())
  170. {
  171. XmlRpcStruct fault = (XmlRpcStruct) parser.getParsedValue();
  172. throw new XmlRpcFault(fault.getInteger("faultCode"),
  173. fault.getString("faultString"));
  174. }
  175. else
  176. {
  177. final Object parsedValue = parser.getParsedValue();
  178. if (parsedValue != null && castResultTo.isAssignableFrom(
  179. parsedValue.getClass()))
  180. {
  181. return castResultTo.cast(parsedValue);
  182. }
  183. else
  184. {
  185. throw new XmlRpcException(
  186. "Unexpected return type: '" + parsedValue.getClass()
  187. .getName() + "', expected: '" + castResultTo
  188. .getName() + "'");
  189. }
  190. }
  191. }
  192. });
  193. }
  194. catch (RuntimeException e)
  195. {
  196. throw e;
  197. }
  198. catch (Exception e)
  199. {
  200. throw new RuntimeException("Should never happen", e);
  201. }
  202. }
  203. }