View Javadoc

1   /*
2    * Copyright 2008 Simon Martinelli, Rebenweg 32, 3236 Gampelen, Switzerland
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.simject.remoting.client;
17  
18  import java.beans.XMLDecoder;
19  import java.beans.XMLEncoder;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.lang.reflect.InvocationHandler;
23  import java.lang.reflect.Method;
24  import java.net.HttpURLConnection;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import org.simject.util.Protocol;
31  import org.simject.util.SimConstants;
32  
33  /**
34   * Used to provide remote access over HTTP to a resource. HttpClientProxy uses
35   * XStream for XML serialization or normal Java serialization for binary
36   * protocol
37   * 
38   * @author Simon Martinelli
39   */
40  public final class HttpClientProxy implements InvocationHandler {
41  
42  	private final static Logger logger = Logger.getLogger(HttpClientProxy.class
43  			.getName());
44  
45  	/**
46  	 * Holds the requested URL
47  	 */
48  	final private URL url;
49  
50  	/**
51  	 * Holds the protocol
52  	 */
53  	final private Protocol protocol;
54  
55  	/**
56  	 * Creates a new instance of a proxy
57  	 * 
58  	 * @param loader
59  	 * @param interfaces
60  	 * @param url
61  	 * @return an instance of HttpClientProxy
62  	 * @throws MalformedURLException
63  	 */
64  	public static Object newInstance(final ClassLoader loader,
65  			final Class<?>[] interfaces, final String target)
66  			throws MalformedURLException {
67  
68  		// Extract the protocol
69  		final String protcolString = target.substring(0, 3);
70  		Protocol protocol = Protocol.Xml;
71  		if (protcolString.equals(Protocol.Binary.getString())) {
72  			protocol = Protocol.Binary;
73  		}
74  		// Extract the URL
75  		final String urlString = target.substring(4);
76  		final URL url = new URL(urlString);
77  
78  		logger.info("Creating proxy for URL <" + url + "> using <" + protocol
79  				+ "> protocol");
80  		// Create an instance of the proxy
81  		return java.lang.reflect.Proxy.newProxyInstance(loader, interfaces,
82  				new HttpClientProxy(url, protocol));
83  	}
84  
85  	/**
86  	 * Private Constructor
87  	 * 
88  	 * @param url
89  	 */
90  	private HttpClientProxy(final URL url, final Protocol protocol) {
91  		this.url = url;
92  		this.protocol = protocol;
93  	}
94  
95  	@Override
96  	public Object invoke(final Object proxy, final Method method,
97  			final Object[] args) throws Throwable {
98  		logger.info("Invoking method <" + method.getName()
99  				+ "> with arguments <" + args + ">");
100 		Object result;
101 		try {
102 			if (protocol == Protocol.Binary) {
103 				result = this.invokeUrlBinary2(method, args);
104 			} else {
105 				result = this.invokeUrlXml2(method, args);
106 			}
107 		} catch (Exception e) {
108 			logger.log(Level.SEVERE, e.getMessage(), e);
109 			throw e;
110 		}
111 		return result;
112 	}
113 
114 	/**
115 	 * Synchronous call over HTTP using binary protocol
116 	 * 
117 	 * 1. Serializes the arguments to Binary and make a remote call over HTTP
118 	 * with Commons HttpClient to the desired method. 2. Deserializes the binary
119 	 * response from the server.
120 	 * 
121 	 * @param method
122 	 * @param args
123 	 * @return
124 	 * @throws Throwable
125 	 */
126 	private Object invokeUrlBinary2(final Method method, final Object[] args)
127 			throws Throwable {
128 
129 		HttpURLConnection con = (HttpURLConnection) this.url.openConnection();
130 		con.setDoOutput(true);
131 
132 		// The method name and the parameter types are set to the header
133 		this.createHeader2(method, con);
134 
135 		final ObjectOutputStream oos = new ObjectOutputStream(con
136 				.getOutputStream());
137 		oos.writeObject(args);
138 		oos.close();
139 
140 		// Get response if the content is > 0
141 		Object result = null;
142 		if (con.getContentLength() > 0) {
143 			final ObjectInputStream ois = new ObjectInputStream(con
144 					.getInputStream());
145 			result = ois.readObject();
146 
147 			if (result instanceof Throwable) {
148 				throw ((Throwable) result);
149 			}
150 		}
151 
152 		con.disconnect();
153 
154 		return result;
155 	}
156 
157 	/**
158 	 * Synchronous call over HTTP using XML protocol
159 	 * 
160 	 * 1. Serializes the arguments to XML using XMLEncoder and make a remote
161 	 * call over HTTP with Commons HttpClient to the desired method. 2.
162 	 * Deserializes the XML response from the server using XMLDecoder.
163 	 * 
164 	 * @param method
165 	 * @param args
166 	 * @return
167 	 * @throws Throwable
168 	 */
169 	private Object invokeUrlXml2(final Method method, final Object[] args)
170 			throws Throwable {
171 
172 		final HttpURLConnection con = (HttpURLConnection) this.url
173 				.openConnection();
174 		con.setDoOutput(true);
175 
176 		// The method name and the parameter types are set to the header
177 		this.createHeader2(method, con);
178 
179 		final XMLEncoder encoder = new XMLEncoder(con.getOutputStream());
180 		encoder.writeObject(args);
181 
182 		// Get response if the content is > 0
183 		Object result = null;
184 		if (con.getContentLength() > 0) {
185 			final XMLDecoder decoder = new XMLDecoder(con.getInputStream());
186 			result = decoder.readObject();
187 			if (result instanceof Throwable) {
188 				throw ((Throwable) result);
189 			}
190 		}
191 
192 		con.disconnect();
193 
194 		return result;
195 	}
196 
197 	/**
198 	 * Create the necessairy Header entries
199 	 * 
200 	 * @param method
201 	 * @param post
202 	 */
203 	private void createHeader2(final Method method, final HttpURLConnection con) {
204 		con.setRequestProperty(SimConstants.PARAM_METHOD, method.getName());
205 
206 		// Get all parameter types and add them to a string delimited by ,
207 		final StringBuffer params = new StringBuffer();
208 		for (Class<?> param : method.getParameterTypes()) {
209 			final String paramString = param.getName()
210 					+ SimConstants.PARAM_TYPE_DELIM;
211 			params.append(paramString);
212 		}
213 		if (params.length() > 0) {
214 			final String parameters = params.toString();
215 			con.setRequestProperty(SimConstants.PARAM_TYPES, parameters);
216 		}
217 	}
218 }