1 | // Copyright 2004-2007 Jean-Francois Poilpret |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package net.sourceforge.hiveremoting.caucho; |
16 | |
17 | import java.io.BufferedInputStream; |
18 | import java.io.ByteArrayOutputStream; |
19 | import java.io.IOException; |
20 | import java.io.InputStream; |
21 | import java.io.OutputStream; |
22 | import java.lang.reflect.InvocationHandler; |
23 | import java.lang.reflect.Method; |
24 | import java.lang.reflect.Proxy; |
25 | import java.util.HashMap; |
26 | import java.util.Map; |
27 | import java.util.zip.GZIPInputStream; |
28 | import java.util.zip.GZIPOutputStream; |
29 | |
30 | import org.apache.commons.httpclient.Credentials; |
31 | import org.apache.commons.httpclient.Header; |
32 | import org.apache.commons.httpclient.HttpClient; |
33 | import org.apache.commons.httpclient.HttpConnectionManager; |
34 | import org.apache.commons.httpclient.HttpStatus; |
35 | import org.apache.commons.httpclient.UsernamePasswordCredentials; |
36 | import org.apache.commons.httpclient.auth.AuthScope; |
37 | import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; |
38 | import org.apache.commons.httpclient.methods.PostMethod; |
39 | import org.apache.commons.logging.Log; |
40 | |
41 | import com.caucho.hessian.io.SerializerFactory; |
42 | |
43 | /** |
44 | * Abstract dynamic proxy for Caucho protocols on client side (used for Hessian |
45 | * and Burlap). |
46 | * <p> |
47 | * This proxy is based on commons-httpclient for better connection management. |
48 | * In particular, it handles cookies, https. |
49 | * |
50 | * @author Jean-Francois Poilpret |
51 | */ |
52 | public abstract class AbstractCauchoProxy implements InvocationHandler |
53 | { |
54 | protected AbstractCauchoProxy( CauchoProxyContribution contrib, |
55 | SerializerFactory factory, |
56 | HttpConnectionManager cnxManager, |
57 | Log logger) |
58 | { |
59 | _logger = logger; |
60 | _factory = factory; |
61 | _overloadEnabled = contrib.getOverloadEnabled(); |
62 | _url = contrib.getUrl(); |
63 | _gzipThreshold = contrib.getGzipThreshold(); |
64 | _gzipInBufferSize = contrib.getGzipInBufferSize(); |
65 | _handler = contrib.getContextHandler(); |
66 | _callContextHandlerOnError = contrib.getCallContextHandlerOnError(); |
67 | _httpClient = new HttpClient(cnxManager); |
68 | _httpClient.getParams().setAuthenticationPreemptive(true); |
69 | if (contrib.getUser() != null) |
70 | { |
71 | Credentials credentials = new UsernamePasswordCredentials( |
72 | contrib.getUser(), contrib.getPassword()); |
73 | _httpClient.getState().setCredentials(AuthScope.ANY, credentials); |
74 | } |
75 | } |
76 | |
77 | // CSOFF: CyclomaticComplexityCheck |
78 | private Object invokeSpecialMethod(Object proxy, |
79 | String method, |
80 | Class[] params, |
81 | Object[] args) |
82 | { |
83 | // Special cases first (same behavior as default Caucho proxies) |
84 | if ( method.equals("equals") |
85 | && params.length == 1 |
86 | && params[0].equals(Object.class)) |
87 | { |
88 | Object value = args[0]; |
89 | if (value == null || !Proxy.isProxyClass(value.getClass())) |
90 | { |
91 | return Boolean.FALSE; |
92 | } |
93 | |
94 | AbstractCauchoProxy handler = |
95 | (AbstractCauchoProxy) Proxy.getInvocationHandler(value); |
96 | return Boolean.valueOf(_url.equals(handler._url)); |
97 | } |
98 | else if (method.equals("hashCode") && params.length == 0) |
99 | { |
100 | return new Integer(_url.hashCode()); |
101 | } |
102 | else if ( method.equals("getHessianType") |
103 | || method.equals("getBurlapType")) |
104 | { |
105 | return proxy.getClass().getInterfaces()[0].getName(); |
106 | } |
107 | else if ( method.equals("getHessianURL") |
108 | || method.equals("getBurlapURL")) |
109 | { |
110 | return _url.toString(); |
111 | } |
112 | else if (method.equals("toString") && params.length == 0) |
113 | { |
114 | return "[CauchoProxy " + _url + "]"; |
115 | } |
116 | else |
117 | { |
118 | // This is not a special method, return null as an indicator |
119 | return null; |
120 | } |
121 | } |
122 | // CSON: CyclomaticComplexityCheck |
123 | |
124 | private void initRemoteContext(PostMethod post) |
125 | { |
126 | if (_handler != null) |
127 | { |
128 | Map<String, String> context = new HashMap<String, String>(); |
129 | _handler.initContext(context); |
130 | for (Map.Entry<String, String> entry: context.entrySet()) |
131 | { |
132 | post.setRequestHeader(entry.getKey(), entry.getValue()); |
133 | } |
134 | } |
135 | } |
136 | |
137 | private void retrieveRemoteContext(int code, PostMethod post) |
138 | { |
139 | if ( _handler != null |
140 | && (_callContextHandlerOnError || code == HttpStatus.SC_OK)) |
141 | { |
142 | Map<String, String> context = new HashMap<String, String>(); |
143 | Header[] headers = post.getResponseHeaders(); |
144 | for (int i = 0; i < headers.length; i++) |
145 | { |
146 | context.put(headers[i].getName(), headers[i].getValue()); |
147 | } |
148 | _handler.extractContext(context); |
149 | } |
150 | } |
151 | |
152 | private String getActualMethodName(String method, Object[] args) |
153 | { |
154 | String methodName = method; |
155 | if (_overloadEnabled) |
156 | { |
157 | if (args != null) |
158 | { |
159 | methodName = methodName + "__" + args.length; |
160 | } |
161 | else |
162 | { |
163 | methodName = methodName + "__0"; |
164 | } |
165 | } |
166 | return methodName; |
167 | } |
168 | |
169 | private void prepareRequest(ByteArrayOutputStream stream, PostMethod post) |
170 | throws IOException |
171 | { |
172 | if (_gzipThreshold >= 0 && stream.size() > _gzipThreshold) |
173 | { |
174 | ByteArrayOutputStream output = new ByteArrayOutputStream(); |
175 | GZIPOutputStream gzipOutput = new GZIPOutputStream(output); |
176 | stream.writeTo(gzipOutput); |
177 | gzipOutput.finish(); |
178 | gzipOutput.close(); |
179 | post.setRequestHeader("Content-Encoding", "gzip"); |
180 | post.setRequestEntity(new ByteArrayRequestEntity(output.toByteArray(), "text/xml")); |
181 | } |
182 | else |
183 | { |
184 | post.setRequestEntity(new ByteArrayRequestEntity(stream.toByteArray(), "text/xml")); |
185 | } |
186 | } |
187 | |
188 | private InputStream getResponseStream(PostMethod post) |
189 | throws IOException |
190 | { |
191 | InputStream is = post.getResponseBodyAsStream(); |
192 | Header encodingHeader = post.getResponseHeader("Content-Encoding"); |
193 | if (encodingHeader != null) |
194 | { |
195 | String encoding = encodingHeader.getValue(); |
196 | // GZIP management for response |
197 | if ("gzip".equals(encoding)) |
198 | { |
199 | is = new BufferedInputStream(new GZIPInputStream(is), _gzipInBufferSize); |
200 | } |
201 | } |
202 | return is; |
203 | } |
204 | |
205 | //CSOFF: IllegalThrowsCheck |
206 | public Object invoke(Object proxy, Method method, Object[] args) |
207 | throws Throwable |
208 | { |
209 | String methodName = method.getName(); |
210 | Class[] params = method.getParameterTypes(); |
211 | |
212 | // Special cases first (same behavior as default Caucho proxies) |
213 | Object special = invokeSpecialMethod(proxy, methodName, params, args); |
214 | if (special != null) |
215 | { |
216 | return special; |
217 | } |
218 | |
219 | // Actual remoting takes place here |
220 | InputStream is = null; |
221 | PostMethod post = new PostMethod(_url); |
222 | initRemoteContext(post); |
223 | |
224 | long time = 0; |
225 | try |
226 | { |
227 | // Calculate actual method name |
228 | methodName = getActualMethodName(methodName, args); |
229 | |
230 | // GZIP management for request |
231 | if (_gzipThreshold >= 0) |
232 | { |
233 | post.setRequestHeader("Accept-Encoding", "gzip, identity"); |
234 | } |
235 | // Serialize whole method call through HessianOutput/BurlapOutput |
236 | // into memory buffer |
237 | ByteArrayOutputStream os = new ByteArrayOutputStream(); |
238 | callMethod(os, methodName, args); |
239 | // Gzip message if required |
240 | prepareRequest(os, post); |
241 | os = null; |
242 | |
243 | // Execute method |
244 | int code = _httpClient.executeMethod(post); |
245 | // Get Body and unserialize it through HessianInput |
246 | is = getResponseStream(post); |
247 | // Get context |
248 | retrieveRemoteContext(code, post); |
249 | if (code == HttpStatus.SC_OK) |
250 | { |
251 | time = System.currentTimeMillis(); |
252 | return getReturnValue(is, method.getReturnType()); |
253 | } |
254 | else |
255 | { |
256 | _logger.info( "invoke " + method.getName() + "(), status line: " + |
257 | post.getStatusLine()); |
258 | throw createException(post.getStatusLine().toString()); |
259 | } |
260 | } |
261 | catch (IOException e) |
262 | { |
263 | _logger.error("invoke " + method.getName(), e); |
264 | throw createException(e); |
265 | } |
266 | finally |
267 | { |
268 | if (_logger.isDebugEnabled()) |
269 | { |
270 | time = System.currentTimeMillis() - time; |
271 | _logger.debug( "invoke " + method.getName() + |
272 | "(), unzip time = " + time + " ms"); |
273 | } |
274 | close(is); |
275 | post.releaseConnection(); |
276 | } |
277 | } |
278 | //CSON: IllegalThrowsCheck |
279 | |
280 | protected void close(InputStream is) |
281 | { |
282 | //#### Should we also try to read it to its end? |
283 | try |
284 | { |
285 | if (is != null) |
286 | { |
287 | is.close(); |
288 | } |
289 | } |
290 | catch (IOException e) |
291 | { |
292 | // What more can we do here? |
293 | _logger.error("close", e); |
294 | } |
295 | } |
296 | |
297 | //CSOFF: IllegalThrowsCheck |
298 | abstract protected void callMethod(OutputStream os, String method, Object[] args) |
299 | throws Throwable; |
300 | abstract protected Object getReturnValue(InputStream is, Class type) |
301 | throws Throwable; |
302 | //CSON: IllegalThrowsCheck |
303 | |
304 | abstract protected RuntimeException createException(String message); |
305 | abstract protected RuntimeException createException(Throwable cause); |
306 | |
307 | protected final Log _logger; |
308 | protected final HttpClient _httpClient; |
309 | protected final String _url; |
310 | protected final boolean _overloadEnabled; |
311 | protected final SerializerFactory _factory; |
312 | protected final int _gzipThreshold; |
313 | protected final int _gzipInBufferSize; |
314 | protected final RemoteContextHandler _handler; |
315 | protected final boolean _callContextHandlerOnError; |
316 | } |