001    package biz.hammurapi.remoting.http;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.ByteArrayOutputStream;
005    import java.io.IOException;
006    import java.io.ObjectInputStream;
007    import java.io.ObjectOutputStream;
008    import java.lang.reflect.InvocationHandler;
009    import java.lang.reflect.Method;
010    import java.lang.reflect.Proxy;
011    import java.util.zip.GZIPInputStream;
012    import java.util.zip.GZIPOutputStream;
013    
014    import org.apache.commons.httpclient.HttpClient;
015    import org.apache.commons.httpclient.HttpConnectionManager;
016    import org.apache.commons.httpclient.HttpStatus;
017    import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
018    import org.apache.commons.httpclient.methods.PostMethod;
019    
020    import biz.hammurapi.remoting.Invocation;
021    
022    public class HttpRemoteInvocationHandler implements InvocationHandler {
023            
024            private HttpReference reference;
025            private HttpConnectionManager connectionManager;
026            private String cookie;
027            private ClassLoader classLoader;
028    
029            public HttpRemoteInvocationHandler(HttpReference reference, 
030                            String cookie, 
031                            HttpConnectionManager connectionManager,
032                            ClassLoader classLoader) {
033                    this.reference = reference;
034                    this.connectionManager = connectionManager;
035                    this.cookie = cookie;
036                    this.classLoader = classLoader;
037            }
038            
039            /**
040             * Used for remote class and resource loading.
041             * @param name
042             * @return
043             */
044            public byte[] getResource(String resourceName) {
045                    try {
046                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
047                            ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(baos));
048                            oos.writeObject(resourceName);
049                            oos.close();
050                            HttpClient httpClient = new HttpClient(connectionManager);                                              
051                            PostMethod httpMethod = new PostMethod(reference.getRemoteUrl());
052                            try {
053                                    httpMethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray()));
054                                            if (cookie!=null) {
055                                                    httpMethod.setRequestHeader("Cookie", cookie);
056                                            }
057                                            int statusCode = httpClient.executeMethod(httpMethod);
058                                            if (statusCode == HttpStatus.SC_OK) {
059                                                    return httpMethod.getResponseBody();
060                                            } else if (statusCode == HttpStatus.SC_NOT_FOUND) {
061                                                    return null;
062                                            }
063                                            throw new HttpRemoteException("Remoting problem, HTTP error code: "+statusCode+", "+httpMethod.getResponseBodyAsString());
064                            } finally {
065                                    httpMethod.releaseConnection();
066                            }                                                                               
067                    } catch (HttpRemoteException e) {
068                            throw e;                                        
069                    } catch (Exception e) {
070                            throw new HttpRemoteException("Remoting problem: "+e, e);
071                    }
072            }
073    
074            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
075                    try {
076                            Invocation invocation = new Invocation(null, method, args, reference.getId());
077                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
078                            ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(baos)) {
079                                    
080                                    {
081                                            enableReplaceObject(true);
082                                    }
083                                    
084                                    protected Object replaceObject(Object obj) throws IOException {
085                                            if (Proxy.isProxyClass(obj.getClass())) {
086                                                    InvocationHandler ih = Proxy.getInvocationHandler(obj);
087                                                    if (ih instanceof HttpRemoteInvocationHandler) {
088                                                            return ((HttpRemoteInvocationHandler) ih).reference;
089                                                    }
090                                            }
091                                            return super.replaceObject(obj);
092                                    }
093                            };
094                            oos.writeObject(invocation);
095                            oos.close();
096                            
097                            HttpClient httpClient = new HttpClient(connectionManager);                                              
098                            PostMethod httpMethod = new PostMethod(reference.getRemoteUrl());
099                            try {
100                                    httpMethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray()));
101                                            if (cookie!=null) {
102                                                    httpMethod.setRequestHeader("Cookie", cookie);
103                                            }
104                                            int statusCode = httpClient.executeMethod(httpMethod);
105                                            if (statusCode == HttpStatus.SC_OK) {
106                                                    ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(new ByteArrayInputStream(httpMethod.getResponseBody()))) {
107                                                            
108                                                            {
109                                                                    enableResolveObject(true);
110                                                            }
111                                                            
112                                                            protected Object resolveObject(Object obj) throws IOException {
113                                                                    if (obj instanceof HttpReference) {
114                                                                            try {
115                                                                                    return createProxy((HttpReference) obj);
116                                                                            } catch (ClassNotFoundException e) {
117                                                                                    throw new IOException("Could not create proxy: "+e);
118                                                                            }
119                                                                    }
120                                                                    return super.resolveObject(obj);
121                                                            }
122                                                    };
123                                                    try {
124                                                            // First boolean is a discriminator - true - success, false - exception
125                                                            if (ois.readBoolean()) {
126                                                                    return ois.readObject();
127                                                            } else {
128                                                                    throw (Exception) ois.readObject();
129                                                            }
130                                                    } finally {
131                                                            ois.close();
132                                                    }
133                                            }
134                                            throw new HttpRemoteException("Remoting problem, HTTP error code: "+statusCode+", "+httpMethod.getResponseBodyAsString());
135                            } finally {
136                                    httpMethod.releaseConnection();
137                            }                                                       
138                    } catch (HttpRemoteException e) {
139                            throw e;                                        
140                    } catch (Exception e) {
141                            throw new HttpRemoteException("Remoting problem: "+e, e);
142                    }
143            }
144            
145            /**
146             * Creates proxy from reference
147             * @param reference
148             * @return
149             * @throws ClassNotFoundException 
150             */
151            protected Object createProxy(final HttpReference reference) throws ClassNotFoundException {
152                    final HttpRemoteInvocationHandler ih = new HttpRemoteInvocationHandler(
153                                    reference, 
154                                    cookie, 
155                                    connectionManager, 
156                                    classLoader);
157                    
158                    class RemoteClassLoader extends ClassLoader {
159    
160                            public RemoteClassLoader(ClassLoader parent) {
161                                    super(parent);
162                            }
163    
164                            // TODO load resources from remote location, cache, define classes, cache.
165                    }
166                    
167                    RemoteClassLoader remoteClassLoader = new RemoteClassLoader(classLoader==null ? getClass().getClassLoader() : classLoader);
168                    
169                    Class[] interfaces = new Class[reference.getInterfaces().length]; 
170                    for (int i=0; i<interfaces.length; ++i) {
171                            interfaces[i] = remoteClassLoader.loadClass(reference.getInterfaces()[i]);
172                    }
173                    
174                    return Proxy.newProxyInstance(remoteClassLoader, interfaces, ih);                               
175                    
176            }
177                    
178    }