PageRenderTime 61ms CodeModel.GetById 2ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/gwtrpccommlayer/src/main/java/com/googlecode/gwtrpccommlayer/client/impl/GwtRpcClientSideProxy.java

https://code.google.com/p/gwtrpccommlayer/
Java | 629 lines | 381 code | 93 blank | 155 comment | 53 complexity | 4f8fb3b536537e544ce115a913a3eacc MD5 | raw file
  1/*
  2 * Copyright 2010 Jeff McHugh (Segue Development LLC)
  3 * 
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5 * in compliance with the License. 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 distributed under the License
 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 11 * or implied. See the License for the specific language governing permissions and limitations under
 12 * the License.
 13 */
 14package com.googlecode.gwtrpccommlayer.client.impl;
 15
 16
 17import com.google.gwt.user.client.rpc.AsyncCallback;
 18import com.googlecode.gwtrpccommlayer.shared.GwtRpcCommLayerPojoConstants;
 19import com.googlecode.gwtrpccommlayer.shared.GwtRpcCommLayerPojoRequest;
 20import com.googlecode.gwtrpccommlayer.shared.GwtRpcCommLayerPojoResponse;
 21import org.apache.http.Header;
 22import org.apache.http.HttpEntity;
 23import org.apache.http.HttpResponse;
 24import org.apache.http.client.methods.HttpPost;
 25import org.apache.http.cookie.Cookie;
 26import org.apache.http.entity.InputStreamEntity;
 27import org.apache.http.impl.client.DefaultHttpClient;
 28
 29import java.io.*;
 30import java.lang.reflect.Constructor;
 31import java.lang.reflect.Method;
 32import java.net.URL;
 33import java.util.HashMap;
 34import java.util.List;
 35
 36/**
 37 * This class handles all the HTTP(S) client interaction with the GwtRpcCommLayer Servlet
 38 * 
 39 * @author jeff mchugh
 40 *
 41 */
 42public class GwtRpcClientSideProxy implements IGwtRpcClientSideProxy
 43{	
 44	
 45	static int DEFAULT_STANDARD_PORT 	= 80;
 46	static int DEFAULT_SECURE_PORT 		= 443;
 47	
 48	private HashMap<String,Cookie> cookies = null;
 49	
 50	private String scheme;
 51	private String host;
 52	private Integer port;
 53	private String path;
 54	private String queryString;
 55	private boolean showResponseHeaders = false;
 56	
 57	public GwtRpcClientSideProxy()
 58	{
 59		cookies = new HashMap<String, Cookie>();
 60	}
 61	
 62	public GwtRpcClientSideProxy(URL url)
 63	{
 64		this();
 65		setUrl(url);
 66	}
 67	
 68	boolean doesListIncludeGwtAsynchCallback(Class[] interfaces)
 69	{
 70		Class target = AsyncCallback.class;
 71		for (Class intf : interfaces) 
 72		{
 73			if ( intf.getName().equals(target.getName()))
 74			{
 75				return true;
 76			}
 77		}
 78		return false;
 79	}
 80	
 81	Object invoke_asynchronousMode(Object proxy, Method method, Object[] args) throws Throwable
 82	{
 83		if ( args.length == 0 )
 84		{
 85			throw new RuntimeException("A minimum of (1) object is required for asynchronous mode");
 86		}
 87		
 88		AsyncCallback theCallback = (AsyncCallback) args[args.length-1];
 89		
 90		/*
 91		 * Wrap the Method (and Method arguments)
 92		 */
 93		GwtRpcCommLayerPojoRequest pojoRequest = new GwtRpcCommLayerPojoRequest();
 94		
 95		//pojoRequest.setMethodName(method.getName());
 96        pojoRequest.setMethod(method);
 97		if ( args != null )
 98		{
 99			
100			for (Object object : args)
101			{
102				if ( object != theCallback )//the last object is always going to be the callback implementation
103				{
104					pojoRequest.getMethodParameters().add( (Serializable) object);
105				}
106			}
107		}
108		
109		/*
110		 * This block now makes it possible to execute in a "asynchronous" mode
111		 */
112		
113		GwtRpcCommLayerPojoResponse pojoResp = null;
114		try
115		{
116			pojoResp = executeRequest(pojoRequest);
117			if ( pojoResp != null )
118			{
119				Object result = pojoResp.getResponseInstance();
120				AsynchCallBackRunnable runnable = new AsynchCallBackRunnable(theCallback, result);
121				Thread thread = new Thread(runnable);
122				thread.start();
123				return null;
124			}
125			else
126			{
127				throw new RuntimeException("No valid GwtRpcCommLayerPojoResponse returned. Check for http errors in log.");	
128			}
129		}
130		catch(Throwable caught)
131		{
132			AsynchCallBackRunnable runnable = new AsynchCallBackRunnable(theCallback, caught);
133			Thread thread = new Thread(runnable);
134			thread.start();
135			return null;
136		}
137	}
138	
139	Object invoke_synchronousMode(Object proxy, Method method, Object[] args) throws Throwable
140	{
141		/*
142		 * Wrap the Method (and Method arguments)
143		 */
144		GwtRpcCommLayerPojoRequest pojoRequest = new GwtRpcCommLayerPojoRequest();
145		
146		//pojoRequest.setMethodName(method.getName());
147        pojoRequest.setMethod(method);
148		if ( args != null )
149		{
150			for (Object object : args)
151			{
152				pojoRequest.getMethodParameters().add( (Serializable) object);
153
154			}
155		}
156		
157		
158		/*
159		 * This is the original block of code that executed for the "synchronous" mode
160		 */
161		GwtRpcCommLayerPojoResponse pojoResp = executeRequest(pojoRequest);
162		if ( pojoResp != null )
163		{
164			return pojoResp.getResponseInstance();
165		}
166		else
167		{
168			throw new RuntimeException("No valid GwtRpcCommLayerPojoResponse returned. Check for http errors in log.");
169		}
170		
171		
172	}
173	
174	@Override
175	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
176	{
177		int mode = 0;//default to synchronous mode
178		
179		if ( args != null && args.length > 0 )
180		{
181			Object last = args[args.length-1];
182			if ( doesListIncludeGwtAsynchCallback(last.getClass().getInterfaces()))
183			{
184				mode = 1;
185			}
186		}
187		
188		if ( mode == 0 )
189		{
190			return invoke_synchronousMode(proxy, method, args);
191		}
192		else
193		{
194			return invoke_asynchronousMode(proxy, method, args);
195		}
196	}
197	
198	/**
199	 * The URL that this client shall interact with
200	 */
201	public void setUrl(URL url)
202	{
203		setScheme(url.getProtocol());
204		setHost(url.getHost());
205		setPort(url.getPort());
206		setPath(url.getPath());	
207		setQueryString(url.getQuery());
208	}
209
210	/**
211	 * Set this to true for debugging
212	 */
213	public void setShowResponseHeaders(boolean b)
214	{
215		this.showResponseHeaders = b;
216	}
217	
218	/**
219	 * Main method that executes to make call to remote server
220	 * this method uses an HTTP POST method and can keep state if needed (using cookies)
221	 * @throws IOException
222	 */
223	protected synchronized GwtRpcCommLayerPojoResponse executeRequest(GwtRpcCommLayerPojoRequest pojoRequest) throws IOException
224	{
225		/*
226		 * Create HTTP CLIENT
227		 */
228		DefaultHttpClient httpclient = new DefaultHttpClient();
229		
230		/*
231		 * Add Cookies to the request
232		 * this enables a state-full set of transactions
233		 * to take place. While this is NOT required for the
234		 * GwtRpcCommLayer framework, the actually developer might have this
235		 * requirement embedded into his/her servlet(s) and thus
236		 * this makes to possible to communicate just like
237		 * a browser would
238		 * 
239		 */
240		if ( getCookies().size() > 0 )
241		{
242			for (Cookie cookie : getCookies().values()) 
243			{
244				httpclient.getCookieStore().addCookie(cookie);
245			}
246		}
247		
248		
249		/*
250		 * SERIALZED THE POJO-REQUEST OBJECT INTO BYTES
251		 */
252		byte[] pojoByteArray = serializeIntoBytes(pojoRequest);
253		long length = pojoByteArray.length;
254        ByteArrayInputStream in = new ByteArrayInputStream(pojoByteArray);
255        InputStreamEntity reqEntity = new InputStreamEntity(in, length);
256        reqEntity.setContentType("binary/octet-stream");
257        reqEntity.setChunked(false);
258
259        
260		/*
261		 * CONSTRUCT THE URL
262		 */
263		String url = createFullyQualifiedURL();
264        
265		/*
266		 * Create POST instance
267		 */
268		HttpPost httppost = new HttpPost(url);
269		httppost.setEntity(reqEntity);
270		
271		/*
272		 * Add the correct user-agent
273		 */
274        httppost.addHeader(GwtRpcCommLayerPojoConstants.GWT_RPC_COMM_LAYER_CLIENT_KEY, GwtRpcCommLayerPojoConstants.GWT_RPC_COMM_LAYER_POJO_CLIENT);
275        
276		/*
277		 * Notify any last minute listener
278		 */
279        onBeforeRequest(httppost);
280        HttpResponse response = httpclient.execute(httppost);
281        
282        /*
283         * Provide a call back for timing, error handling, etc
284         */
285        onAfterRequest(httppost, response);
286        
287        /*
288         * Check for server error
289         */
290        if ( response.getStatusLine().getStatusCode() >= 400 && response.getStatusLine().getStatusCode() <= 599 )
291        {
292        	onResponseError(response.getStatusLine().getStatusCode(), response);
293        	return null;
294        }
295        else if (response.getFirstHeader("Content-Type") != null && !response.getFirstHeader("Content-Type").getValue().equals("binary/octet-stream"))
296        {
297        	onResponseError("Content-Type must be 'binary/octet-stream'");
298        	return null;        	
299        }
300        else
301        {
302        	/*
303        	 * Provide a call-back for examining the response headers
304        	 */
305        	if ( isShowResponseHeaders() && response.getAllHeaders() != null )
306        	{
307        		dumpResponseHeaders(response.getAllHeaders());
308        	}
309        	
310        	/*
311        	 * Provide a call-back for examining the response cookies
312        	 */
313        	List<Cookie> cookies = httpclient.getCookieStore().getCookies();
314        	if ( cookies != null )
315        	{
316        		recordCookies(cookies);
317        	}
318        	
319        	
320        	HttpEntity resEntity = response.getEntity();
321        	byte[] respData = deserializeIntoBytes(resEntity);
322        	try
323        	{
324        		GwtRpcCommLayerPojoResponse pojoResp = createInstanceFromBytes(respData);
325        		return pojoResp;
326        	}
327        	catch(ClassNotFoundException e)
328        	{
329        		throw new IOException(e);
330        	}
331        	
332        }
333        
334	}
335	
336	/*
337	 * ---------------- METHODS THAT CAN BE EXTENDED BY SUB-CLASSES -------------
338	 */
339	
340	protected String createFullyQualifiedURL()
341	{
342		/*
343		 * Configure the URL and PATH
344		 */
345		StringBuffer buff = new StringBuffer();
346		buff.append(getScheme());
347		buff.append("://");
348		buff.append(getHost());
349		if ( getPort() != null && getPort() != DEFAULT_STANDARD_PORT && getPort() != DEFAULT_SECURE_PORT )
350		{
351			buff.append(":"+getPort());
352		}
353		buff.append(getPath());
354		if ( getQueryString() != null )
355		{
356			buff.append("?");
357			buff.append(getQueryString());
358		}
359		return buff.toString();
360	}
361	
362	protected byte[] serializeIntoBytes(GwtRpcCommLayerPojoRequest pojoRequest) throws IOException
363	{
364        ByteArrayOutputStream bos = new ByteArrayOutputStream();
365        ObjectOutputStream objOut = new ObjectOutputStream(bos);
366        objOut.writeObject(pojoRequest);
367        objOut.flush();
368        objOut.close();
369        
370        byte[] all = bos.toByteArray();
371        return all;
372	}
373	
374	protected byte[] deserializeIntoBytes(HttpEntity respEntity ) throws IOException
375	{
376    	byte[] b = new byte[512];
377    	ByteArrayOutputStream buff = new ByteArrayOutputStream();
378    	InputStream respIn = respEntity.getContent();
379    	while(true)
380    	{
381    		int vv = respIn.read(b);
382    		if ( vv == -1 )
383    		{
384    			break;
385    		}
386    		buff.write(b, 0, vv);
387    	} 
388    	return buff.toByteArray();
389	}
390	
391	protected GwtRpcCommLayerPojoResponse createInstanceFromBytes(byte[] data) throws IOException, ClassNotFoundException
392	{
393    	ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(data));
394    	Object instance = objIn.readObject();
395    	GwtRpcCommLayerPojoResponse pojoResp = (GwtRpcCommLayerPojoResponse) instance;
396    	
397    	return pojoResp;
398	}
399	
400	protected void recordCookies(List<Cookie> cookies)
401	{
402		 for (Cookie cookie : cookies) 
403         {
404         	getCookies().put(cookie.getName(), cookie);
405 		}
406	}
407	
408	protected void dumpResponseHeaders(Header header[])
409	{
410    	for (Header header2 : header)
411    	{
412    		System.out.println("" + header2.getName() + ":" + header2.getValue() );
413		} 
414	}
415	
416	/**
417	 * Provides a call-back for future functionality. Will get called before the actual HTTP Request is executed
418	 * @param httppost
419	 */
420	protected void onBeforeRequest(HttpPost httppost)
421	{
422	}
423	
424	/**
425	 * Provides a call-back for future functionality. Will get called after the response is returned
426	 * @param httppost
427	 * @param response
428	 */
429	protected void onAfterRequest(HttpPost httppost, HttpResponse response)
430	{
431	}
432	
433	/**
434	 * Called in the event of an HTTP Response Error (400 through 599)
435	 * @param errorCode
436	 * @param response
437	 */
438	protected void onResponseError(int errorCode, HttpResponse response)
439	{	
440		System.out.println("HTTP_ERROR code=" + errorCode + " " + response.getStatusLine().getReasonPhrase() );
441	}
442	
443	/**
444	 * Called in the event of an general error outside of the http protocol
445	 * @param error
446	 */
447	protected void onResponseError(String error)
448	{	
449		System.out.println("Response Error=" + error);
450	}
451	
452	
453
454
455
456
457	
458	
459	
460	
461	
462	/*
463	 * ---------------------------- GETTER/SETTER METHODS ---------------------------
464	 */
465	
466	/**
467	 * @param cookies the cookies to set
468	 */
469	public void setCookies(HashMap<String,Cookie> cookies)
470	{
471		this.cookies = cookies;
472	}
473
474	/**
475	 * @return the cookies
476	 */
477	public HashMap<String,Cookie> getCookies()
478	{
479		return cookies;
480	}
481
482	/**
483	 * @param scheme the scheme to set
484	 */
485	public void setScheme(String scheme)
486	{
487		this.scheme = scheme;
488	}
489
490	/**
491	 * @return the scheme
492	 */
493	public String getScheme()
494	{
495		return scheme;
496	}
497
498	/**
499	 * @param host the host to set
500	 */
501	public void setHost(String host)
502	{
503		this.host = host;
504	}
505
506	/**
507	 * @return the host
508	 */
509	public String getHost()
510	{
511		return host;
512	}
513
514	/**
515	 * @param port the port to set
516	 */
517	public void setPort(Integer port)
518	{
519        if (port.equals(-1))
520            port = 80;
521		this.port = port;
522	}
523
524	/**
525	 * @return the port
526	 */
527	public Integer getPort()
528	{
529		return port;
530	}
531
532	/**
533	 * @param path the path to set
534	 */
535	public void setPath(String path)
536	{
537		this.path = path;
538	}
539
540	/**
541	 * @return the path
542	 */
543	public String getPath()
544	{
545		return path;
546	}
547
548	/**
549	 * @param queryString the queryString to set
550	 */
551	public void setQueryString(String queryString)
552	{
553		this.queryString = queryString;
554	}
555
556	/**
557	 * @return the queryString
558	 */
559	public String getQueryString()
560	{
561		return queryString;
562	}
563
564	/**
565	 * @return the showResponseHeaders
566	 */
567	boolean isShowResponseHeaders()
568	{
569		return showResponseHeaders;
570	}
571
572
573	/*
574	 * This class is for use with the asynchronous mode 
575	 */
576	static class AsynchCallBackRunnable<T> implements Runnable
577	{
578
579		AsyncCallback<T> callback;
580		Throwable caughtThrowable;
581		T result;
582		
583		public AsynchCallBackRunnable(AsyncCallback<T> callback, T result)
584		{
585			this.callback = callback;
586			this.result = result;
587		}
588		
589		public AsynchCallBackRunnable(AsyncCallback<T> callback, Throwable caughtThrowable)
590		{
591			this.callback = callback;
592			this.caughtThrowable = caughtThrowable;
593		}
594		
595		public void run() 
596		{
597			try
598			{
599                if ( this.caughtThrowable != null )
600            {
601                this.callback.onFailure(this.caughtThrowable);
602            }
603				else if ( this.result != null )
604				{
605					this.callback.onSuccess(this.result);
606				}
607				else
608				{
609                    //we hope the client wanted a Void.  may still be a bug.
610                    Constructor<Void> cv = Void.class.getDeclaredConstructor();
611                    cv.setAccessible(true);
612                    Void v = cv.newInstance();
613                    //nasty!
614                    this.callback.onSuccess((T) v);
615				}
616			}
617			catch(Throwable caught)
618			{
619				System.err.println("Failure in Asynch dispatch thread. " + caught.toString() );
620				caught.printStackTrace();
621			}
622		}
623		
624	}
625
626
627
628
629}