1 /*
2  * Copyright 2002-2009 the original author or authors.
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 
17 package org.springframework.remoting.httpinvoker;
18 
19 import java.io.ByteArrayInputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.OutputStream;
26 import java.io.Serializable;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.Arrays;
29 import java.util.zip.GZIPInputStream;
30 import java.util.zip.GZIPOutputStream;
31 import javax.servlet.ServletException;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34 
35 import junit.framework.TestCase;
36 import org.aopalliance.intercept.MethodInvocation;
37 
38 import org.springframework.beans.ITestBean;
39 import org.springframework.beans.TestBean;
40 import org.springframework.beans.factory.BeanClassLoaderAware;
41 import org.springframework.mock.web.MockHttpServletRequest;
42 import org.springframework.mock.web.MockHttpServletResponse;
43 import org.springframework.remoting.RemoteAccessException;
44 import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
45 import org.springframework.remoting.support.RemoteInvocation;
46 import org.springframework.remoting.support.RemoteInvocationFactory;
47 import org.springframework.remoting.support.RemoteInvocationResult;
48 
49 /**
50  * @author Juergen Hoeller
51  * @since 09.08.2004
52  */
53 public class HttpInvokerTests extends TestCase {
54 
testHttpInvokerProxyFactoryBeanAndServiceExporter()55 	public void testHttpInvokerProxyFactoryBeanAndServiceExporter() throws Throwable {
56 		doTestHttpInvokerProxyFactoryBeanAndServiceExporter(false);
57 	}
58 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithExplicitClassLoader()59 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithExplicitClassLoader() throws Throwable {
60 		doTestHttpInvokerProxyFactoryBeanAndServiceExporter(true);
61 	}
62 
doTestHttpInvokerProxyFactoryBeanAndServiceExporter(boolean explicitClassLoader)63 	private void doTestHttpInvokerProxyFactoryBeanAndServiceExporter(boolean explicitClassLoader) throws Throwable {
64 		TestBean target = new TestBean("myname", 99);
65 
66 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
67 		exporter.setServiceInterface(ITestBean.class);
68 		exporter.setService(target);
69 		exporter.afterPropertiesSet();
70 
71 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
72 		pfb.setServiceInterface(ITestBean.class);
73 		pfb.setServiceUrl("http://myurl");
74 
75 		pfb.setHttpInvokerRequestExecutor(new AbstractHttpInvokerRequestExecutor() {
76 			protected RemoteInvocationResult doExecuteRequest(
77 					HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception {
78 				assertEquals("http://myurl", config.getServiceUrl());
79 				MockHttpServletRequest request = new MockHttpServletRequest();
80 				MockHttpServletResponse response = new MockHttpServletResponse();
81 				request.setContent(baos.toByteArray());
82 				exporter.handleRequest(request, response);
83 				return readRemoteInvocationResult(
84 						new ByteArrayInputStream(response.getContentAsByteArray()), config.getCodebaseUrl());
85 			}
86 		});
87 		if (explicitClassLoader) {
88 			((BeanClassLoaderAware) pfb.getHttpInvokerRequestExecutor()).setBeanClassLoader(getClass().getClassLoader());
89 		}
90 
91 		pfb.afterPropertiesSet();
92 		ITestBean proxy = (ITestBean) pfb.getObject();
93 		assertEquals("myname", proxy.getName());
94 		assertEquals(99, proxy.getAge());
95 		proxy.setAge(50);
96 		assertEquals(50, proxy.getAge());
97 		proxy.setStringArray(new String[] {"str1", "str2"});
98 		assertTrue(Arrays.equals(new String[] {"str1", "str2"}, proxy.getStringArray()));
99 		proxy.setSomeIntegerArray(new Integer[] {1, 2, 3});
100 		assertTrue(Arrays.equals(new Integer[] {1, 2, 3}, proxy.getSomeIntegerArray()));
101 		proxy.setNestedIntegerArray(new Integer[][] {{1, 2, 3}, {4, 5, 6}});
102 		Integer[][] integerArray = proxy.getNestedIntegerArray();
103 		assertTrue(Arrays.equals(new Integer[] {1, 2, 3}, integerArray[0]));
104 		assertTrue(Arrays.equals(new Integer[] {4, 5, 6}, integerArray[1]));
105 		proxy.setSomeIntArray(new int[] {1, 2, 3});
106 		assertTrue(Arrays.equals(new int[] {1, 2, 3}, proxy.getSomeIntArray()));
107 		proxy.setNestedIntArray(new int[][] {{1, 2, 3}, {4, 5, 6}});
108 		int[][] intArray = proxy.getNestedIntArray();
109 		assertTrue(Arrays.equals(new int[] {1, 2, 3}, intArray[0]));
110 		assertTrue(Arrays.equals(new int[] {4, 5, 6}, intArray[1]));
111 
112 		try {
113 			proxy.exceptional(new IllegalStateException());
114 			fail("Should have thrown IllegalStateException");
115 		}
116 		catch (IllegalStateException ex) {
117 			// expected
118 		}
119 		try {
120 			proxy.exceptional(new IllegalAccessException());
121 			fail("Should have thrown IllegalAccessException");
122 		}
123 		catch (IllegalAccessException ex) {
124 			// expected
125 		}
126 	}
127 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithIOException()128 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithIOException() throws Exception {
129 		TestBean target = new TestBean("myname", 99);
130 
131 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
132 		exporter.setServiceInterface(ITestBean.class);
133 		exporter.setService(target);
134 		exporter.afterPropertiesSet();
135 
136 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
137 		pfb.setServiceInterface(ITestBean.class);
138 		pfb.setServiceUrl("http://myurl");
139 
140 		pfb.setHttpInvokerRequestExecutor(new HttpInvokerRequestExecutor() {
141 			public RemoteInvocationResult executeRequest(
142 					HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws IOException {
143 				throw new IOException("argh");
144 			}
145 		});
146 
147 		pfb.afterPropertiesSet();
148 		ITestBean proxy = (ITestBean) pfb.getObject();
149 		try {
150 			proxy.setAge(50);
151 			fail("Should have thrown RemoteAccessException");
152 		}
153 		catch (RemoteAccessException ex) {
154 			// expected
155 			assertTrue(ex.getCause() instanceof IOException);
156 		}
157 	}
158 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithGzipCompression()159 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithGzipCompression() throws Throwable {
160 		TestBean target = new TestBean("myname", 99);
161 
162 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter() {
163 			protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException {
164 				if ("gzip".equals(request.getHeader("Compression"))) {
165 					return new GZIPInputStream(is);
166 				}
167 				else {
168 					return is;
169 				}
170 			}
171 			protected OutputStream decorateOutputStream(
172 					HttpServletRequest request, HttpServletResponse response, OutputStream os) throws IOException {
173 				if ("gzip".equals(request.getHeader("Compression"))) {
174 					return new GZIPOutputStream(os);
175 				}
176 				else {
177 					return os;
178 				}
179 			}
180 		};
181 		exporter.setServiceInterface(ITestBean.class);
182 		exporter.setService(target);
183 		exporter.afterPropertiesSet();
184 
185 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
186 		pfb.setServiceInterface(ITestBean.class);
187 		pfb.setServiceUrl("http://myurl");
188 
189 		pfb.setHttpInvokerRequestExecutor(new AbstractHttpInvokerRequestExecutor() {
190 			protected RemoteInvocationResult doExecuteRequest(
191 					HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)
192 					throws IOException, ClassNotFoundException {
193 				assertEquals("http://myurl", config.getServiceUrl());
194 				MockHttpServletRequest request = new MockHttpServletRequest();
195 				request.addHeader("Compression", "gzip");
196 				MockHttpServletResponse response = new MockHttpServletResponse();
197 				request.setContent(baos.toByteArray());
198 				try {
199 					exporter.handleRequest(request, response);
200 				}
201 				catch (ServletException ex) {
202 					throw new IOException(ex.toString());
203 				}
204 				return readRemoteInvocationResult(
205 						new ByteArrayInputStream(response.getContentAsByteArray()), config.getCodebaseUrl());
206 			}
207 			protected OutputStream decorateOutputStream(OutputStream os) throws IOException {
208 				return new GZIPOutputStream(os);
209 			}
210 			protected InputStream decorateInputStream(InputStream is) throws IOException {
211 				return new GZIPInputStream(is);
212 			}
213 		});
214 
215 		pfb.afterPropertiesSet();
216 		ITestBean proxy = (ITestBean) pfb.getObject();
217 		assertEquals("myname", proxy.getName());
218 		assertEquals(99, proxy.getAge());
219 		proxy.setAge(50);
220 		assertEquals(50, proxy.getAge());
221 
222 		try {
223 			proxy.exceptional(new IllegalStateException());
224 			fail("Should have thrown IllegalStateException");
225 		}
226 		catch (IllegalStateException ex) {
227 			// expected
228 		}
229 		try {
230 			proxy.exceptional(new IllegalAccessException());
231 			fail("Should have thrown IllegalAccessException");
232 		}
233 		catch (IllegalAccessException ex) {
234 			// expected
235 		}
236 	}
237 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithWrappedInvocations()238 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithWrappedInvocations() throws Throwable {
239 		TestBean target = new TestBean("myname", 99);
240 
241 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter() {
242 			protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)
243 					throws IOException, ClassNotFoundException {
244 				Object obj = ois.readObject();
245 				if (!(obj instanceof TestRemoteInvocationWrapper)) {
246 					throw new IOException("Deserialized object needs to be assignable to type [" +
247 							TestRemoteInvocationWrapper.class.getName() + "]: " + obj);
248 				}
249 				return ((TestRemoteInvocationWrapper) obj).remoteInvocation;
250 			}
251 			protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)
252 					throws IOException {
253 				oos.writeObject(new TestRemoteInvocationResultWrapper(result));
254 			}
255 		};
256 		exporter.setServiceInterface(ITestBean.class);
257 		exporter.setService(target);
258 		exporter.afterPropertiesSet();
259 
260 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
261 		pfb.setServiceInterface(ITestBean.class);
262 		pfb.setServiceUrl("http://myurl");
263 
264 		pfb.setHttpInvokerRequestExecutor(new AbstractHttpInvokerRequestExecutor() {
265 			protected RemoteInvocationResult doExecuteRequest(
266 					HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception {
267 				assertEquals("http://myurl", config.getServiceUrl());
268 				MockHttpServletRequest request = new MockHttpServletRequest();
269 				MockHttpServletResponse response = new MockHttpServletResponse();
270 				request.setContent(baos.toByteArray());
271 				exporter.handleRequest(request, response);
272 				return readRemoteInvocationResult(
273 						new ByteArrayInputStream(response.getContentAsByteArray()), config.getCodebaseUrl());
274 			}
275 			protected void doWriteRemoteInvocation(RemoteInvocation invocation, ObjectOutputStream oos) throws IOException {
276 				oos.writeObject(new TestRemoteInvocationWrapper(invocation));
277 			}
278 			protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)
279 					throws IOException, ClassNotFoundException {
280 				Object obj = ois.readObject();
281 				if (!(obj instanceof TestRemoteInvocationResultWrapper)) {
282 					throw new IOException("Deserialized object needs to be assignable to type ["
283 							+ TestRemoteInvocationResultWrapper.class.getName() + "]: " + obj);
284 				}
285 				return ((TestRemoteInvocationResultWrapper) obj).remoteInvocationResult;
286 			}
287 		});
288 
289 		pfb.afterPropertiesSet();
290 		ITestBean proxy = (ITestBean) pfb.getObject();
291 		assertEquals("myname", proxy.getName());
292 		assertEquals(99, proxy.getAge());
293 		proxy.setAge(50);
294 		assertEquals(50, proxy.getAge());
295 
296 		try {
297 			proxy.exceptional(new IllegalStateException());
298 			fail("Should have thrown IllegalStateException");
299 		}
300 		catch (IllegalStateException ex) {
301 			// expected
302 		}
303 		try {
304 			proxy.exceptional(new IllegalAccessException());
305 			fail("Should have thrown IllegalAccessException");
306 		}
307 		catch (IllegalAccessException ex) {
308 			// expected
309 		}
310 	}
311 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithInvocationAttributes()312 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithInvocationAttributes() throws Exception {
313 		TestBean target = new TestBean("myname", 99);
314 
315 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
316 		exporter.setServiceInterface(ITestBean.class);
317 		exporter.setService(target);
318 		exporter.setRemoteInvocationExecutor(new DefaultRemoteInvocationExecutor() {
319 			public Object invoke(RemoteInvocation invocation, Object targetObject)
320 					throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
321 				assertNotNull(invocation.getAttributes());
322 				assertEquals(1, invocation.getAttributes().size());
323 				assertEquals("myValue", invocation.getAttributes().get("myKey"));
324 				assertEquals("myValue", invocation.getAttribute("myKey"));
325 				return super.invoke(invocation, targetObject);
326 			}
327 		});
328 		exporter.afterPropertiesSet();
329 
330 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
331 		pfb.setServiceInterface(ITestBean.class);
332 		pfb.setServiceUrl("http://myurl");
333 		pfb.setRemoteInvocationFactory(new RemoteInvocationFactory() {
334 			public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
335 				RemoteInvocation invocation = new RemoteInvocation(methodInvocation);
336 				invocation.addAttribute("myKey", "myValue");
337 				try {
338 					invocation.addAttribute("myKey", "myValue");
339 					fail("Should have thrown IllegalStateException");
340 				}
341 				catch (IllegalStateException ex) {
342 					// expected: already defined
343 				}
344 				assertNotNull(invocation.getAttributes());
345 				assertEquals(1, invocation.getAttributes().size());
346 				assertEquals("myValue", invocation.getAttributes().get("myKey"));
347 				assertEquals("myValue", invocation.getAttribute("myKey"));
348 				return invocation;
349 			}
350 		});
351 
352 		pfb.setHttpInvokerRequestExecutor(new AbstractHttpInvokerRequestExecutor() {
353 			protected RemoteInvocationResult doExecuteRequest(
354 					HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception {
355 				assertEquals("http://myurl", config.getServiceUrl());
356 				MockHttpServletRequest request = new MockHttpServletRequest();
357 				MockHttpServletResponse response = new MockHttpServletResponse();
358 				request.setContent(baos.toByteArray());
359 				exporter.handleRequest(request, response);
360 				return readRemoteInvocationResult(
361 						new ByteArrayInputStream(response.getContentAsByteArray()), config.getCodebaseUrl());
362 			}
363 		});
364 
365 		pfb.afterPropertiesSet();
366 		ITestBean proxy = (ITestBean) pfb.getObject();
367 		assertEquals("myname", proxy.getName());
368 		assertEquals(99, proxy.getAge());
369 	}
370 
testHttpInvokerProxyFactoryBeanAndServiceExporterWithCustomInvocationObject()371 	public void testHttpInvokerProxyFactoryBeanAndServiceExporterWithCustomInvocationObject() throws Exception {
372 		TestBean target = new TestBean("myname", 99);
373 
374 		final HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
375 		exporter.setServiceInterface(ITestBean.class);
376 		exporter.setService(target);
377 		exporter.setRemoteInvocationExecutor(new DefaultRemoteInvocationExecutor() {
378 			public Object invoke(RemoteInvocation invocation, Object targetObject)
379 					throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
380 				assertTrue(invocation instanceof TestRemoteInvocation);
381 				assertNull(invocation.getAttributes());
382 				assertNull(invocation.getAttribute("myKey"));
383 				return super.invoke(invocation, targetObject);
384 			}
385 		});
386 		exporter.afterPropertiesSet();
387 
388 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
389 		pfb.setServiceInterface(ITestBean.class);
390 		pfb.setServiceUrl("http://myurl");
391 		pfb.setRemoteInvocationFactory(new RemoteInvocationFactory() {
392 			public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
393 				RemoteInvocation invocation = new TestRemoteInvocation(methodInvocation);
394 				assertNull(invocation.getAttributes());
395 				assertNull(invocation.getAttribute("myKey"));
396 				return invocation;
397 			}
398 		});
399 
400 		pfb.setHttpInvokerRequestExecutor(new AbstractHttpInvokerRequestExecutor() {
401 			protected RemoteInvocationResult doExecuteRequest(
402 					HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception {
403 				assertEquals("http://myurl", config.getServiceUrl());
404 				MockHttpServletRequest request = new MockHttpServletRequest();
405 				MockHttpServletResponse response = new MockHttpServletResponse();
406 				request.setContent(baos.toByteArray());
407 				exporter.handleRequest(request, response);
408 				return readRemoteInvocationResult(
409 						new ByteArrayInputStream(response.getContentAsByteArray()), config.getCodebaseUrl());
410 			}
411 		});
412 
413 		pfb.afterPropertiesSet();
414 		ITestBean proxy = (ITestBean) pfb.getObject();
415 		assertEquals("myname", proxy.getName());
416 		assertEquals(99, proxy.getAge());
417 	}
418 
testHttpInvokerWithSpecialLocalMethods()419 	public void testHttpInvokerWithSpecialLocalMethods() throws Exception {
420 		String serviceUrl = "http://myurl";
421 		HttpInvokerProxyFactoryBean pfb = new HttpInvokerProxyFactoryBean();
422 		pfb.setServiceInterface(ITestBean.class);
423 		pfb.setServiceUrl(serviceUrl);
424 
425 		pfb.setHttpInvokerRequestExecutor(new HttpInvokerRequestExecutor() {
426 			public RemoteInvocationResult executeRequest(
427 					HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws IOException {
428 				throw new IOException("argh");
429 			}
430 		});
431 
432 		pfb.afterPropertiesSet();
433 		ITestBean proxy = (ITestBean) pfb.getObject();
434 
435 		// shouldn't go through to remote service
436 		assertTrue(proxy.toString().indexOf("HTTP invoker") != -1);
437 		assertTrue(proxy.toString().indexOf(serviceUrl) != -1);
438 		assertEquals(proxy.hashCode(), proxy.hashCode());
439 		assertTrue(proxy.equals(proxy));
440 
441 		// should go through
442 		try {
443 			proxy.setAge(50);
444 			fail("Should have thrown RemoteAccessException");
445 		}
446 		catch (RemoteAccessException ex) {
447 			// expected
448 			assertTrue(ex.getCause() instanceof IOException);
449 		}
450 	}
451 
452 
453 	private static class TestRemoteInvocation extends RemoteInvocation {
454 
TestRemoteInvocation(MethodInvocation methodInvocation)455 		public TestRemoteInvocation(MethodInvocation methodInvocation) {
456 			super(methodInvocation);
457 		}
458 	}
459 
460 
461 	private static class TestRemoteInvocationWrapper implements Serializable {
462 
463 		private final RemoteInvocation remoteInvocation;
464 
TestRemoteInvocationWrapper(RemoteInvocation remoteInvocation)465 		public TestRemoteInvocationWrapper(RemoteInvocation remoteInvocation) {
466 			this.remoteInvocation = remoteInvocation;
467 		}
468 	}
469 
470 
471 	private static class TestRemoteInvocationResultWrapper implements Serializable {
472 
473 		private final RemoteInvocationResult remoteInvocationResult;
474 
TestRemoteInvocationResultWrapper(RemoteInvocationResult remoteInvocationResult)475 		public TestRemoteInvocationResultWrapper(RemoteInvocationResult remoteInvocationResult) {
476 			this.remoteInvocationResult = remoteInvocationResult;
477 		}
478 	}
479 
480 }
481