View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.http;
14  
15  import org.abstracthorizon.danube.connection.Connection;
16  import org.abstracthorizon.danube.connection.ConnectionHandler;
17  import org.abstracthorizon.danube.http.util.ErrorConnectionHandler;
18  import org.abstracthorizon.danube.http.util.MultiStringMap;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.PrintWriter;
24  import java.lang.reflect.Method;
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  /**
29   * <p>
30   * This is base http connection handler that splits different HTTP methods
31   * (GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS and possibly others) to
32   * methods with names starting with &quot;method&quotl and ending with HTTP method name.
33   * For instanceof <code>methodGET</code>, <code>methodPOST</code>, etc.
34   * </p>
35   * <p>
36   * This handler also defines default HEAD and TRACE method implementations
37   * (<code>methodHEAD</code> and <code>methodTRACE</code>). These can be easily
38   * disabled with {@link #setNoDefaultHead(boolean)} and {@link #setNoDefaultTrace(boolean)} properties.
39   * That does not affect user defined HEAD and TRACE implementations.
40   * </p>
41   * <p>
42   * HTTP method OPTIONS is handled by {@link #methodOPTIONS(HTTPConnection)} method.
43   * It returns all those that are defined in the class.
44   * </p>
45   * @author Daniel Sendula
46   */
47  public class BaseReflectionHTTPConnectionHandler implements ConnectionHandler {
48  
49      protected Map<String, Method> cachedMethods;
50  
51      /** Flag that shows should default HEAD handling method be included or not ({@link methodHead(HttpConnection)} */
52      protected boolean noDefaultHead = false;
53  
54      /** Flag that shows should default TRACE handling method be included or not ({@link methodTrace(HttpConnection)} */
55      protected boolean noDefaultTrace = false;
56  
57      /** Error response */
58      protected ConnectionHandler errorResponse = new ErrorConnectionHandler();
59  
60      /**
61       * Constructor
62       */
63      public BaseReflectionHTTPConnectionHandler() {
64      }
65  
66      /**
67       * Returns error response connection handler
68       * @return error response connection handler
69       */
70      public ConnectionHandler getErrorResponse() {
71          return errorResponse;
72      }
73  
74      /**
75       * Sets error response connection handler
76       * @param errorResponse error response handler
77       */
78      public void setErrorResponse(ConnectionHandler errorResponse) {
79          this.errorResponse = errorResponse;
80      }
81  
82      /**
83       * Returns if default HEAD handling method is to be included or not
84       * @return <code>true</code> if default HEAD handling method is to be included
85       */
86      public boolean getNoDefaultHead() {
87          return noDefaultHead;
88      }
89  
90      /**
91       * Sets if default HEAD handling method is to be included or not
92       * @param noDefaultHead should default HEAD handling method is to be included or not
93       */
94      public void setNoDefaultHead(boolean noDefaultHead) {
95          this.noDefaultHead = noDefaultHead;
96          updateDefaultHeadMethod();
97      }
98  
99      /**
100      * Updates the state of default HEAD method implementation.
101      */
102     protected void updateDefaultHeadMethod() {
103         try {
104             Method method = BaseReflectionHTTPConnectionHandler.class.getMethod("methodHEAD", new Class[]{HTTPConnection.class});
105             if (noDefaultHead) {
106                 if (method == cachedMethods.get("HEAD")) {
107                     cachedMethods.remove("HEAD");
108                 }
109             } else {
110                 cachedMethods.put("HEAD", method);
111             }
112         } catch (Exception ignore) {
113         }
114     }
115 
116     /**
117      * Returns if default TRACE handling method is to be included or not
118      * @return <code>true</code> if default TRACE handling method is to be included
119      */
120     public boolean getNoDefaultTrace() {
121         return noDefaultTrace;
122     }
123 
124     /**
125      * Sets if default TRACE handling method is to be included or not
126      * @param noDefaultTrace should default TRACE handling method is to be included or not
127      */
128     public void setNoDefaultTrace(boolean noDefaultTrace) {
129         this.noDefaultTrace = noDefaultTrace;
130         updateDefaultTraceMethod();
131     }
132 
133     /**
134      * Updates the state of default TRACE method implementation.
135      */
136     protected void updateDefaultTraceMethod() {
137         try {
138             Method method = BaseReflectionHTTPConnectionHandler.class.getMethod("methodTRACE", new Class[]{HTTPConnection.class});
139             if (noDefaultTrace) {
140                 if (method == cachedMethods.get("TRACE")) {
141                     cachedMethods.remove("TRACE");
142                 }
143             } else {
144                 cachedMethods.put("TRACE", method);
145             }
146         } catch (Exception ignore) {
147         }
148     }
149 
150     /**
151      * Caches HTTP to java methods. Methods that start with &quot;method&quot;, has
152      * {@link HTTPConnection} as a single parameter are cached.
153      */
154     protected void cacheMethods() {
155         cachedMethods = new HashMap<String, Method>();
156         Method[] methods = getClass().getMethods();
157         for (Method method : methods) {
158             String methodName = method.getName();
159             if ((methodName.length() > 6)
160                     && methodName.startsWith("method")
161                     && (method.getParameterTypes().length == 1)
162                     && (HTTPConnection.class.isAssignableFrom(method.getParameterTypes()[0]))) {
163 
164                 cachedMethods.put(methodName.substring(6), method);
165             }
166         }
167         updateDefaultHeadMethod();
168         updateDefaultTraceMethod();
169     }
170 
171     /**
172      * Handles connection
173      * @param connection connection
174      */
175     public void handleConnection(Connection connection) {
176         if (cachedMethods == null) {
177             cacheMethods();
178         }
179         HTTPConnection httpConnection = (HTTPConnection)connection.adapt(HTTPConnection.class);
180         invokeMethod(httpConnection, httpConnection.getRequestMethod());
181     }
182 
183     /**
184      * Invokes object's method
185      * @param httpConnection http connection
186      * @param methodName method name
187      */
188     protected void invokeMethod(HTTPConnection httpConnection, String methodName) {
189         Method method = cachedMethods.get(methodName);
190         if (method != null) {
191             try {
192                 method.invoke(this, new Object[]{httpConnection});
193             } catch (Exception e) {
194                 throw new RuntimeException(e);
195             }
196         } else {
197             httpConnection.setResponseStatus(Status.METHOD_NOT_ALLOWED);
198             errorResponse.handleConnection(httpConnection);
199         }
200     }
201 
202     /**
203      * Handles OPTIONS HTTP method
204      * @param httpConnection HTTP connection
205      */
206     public void methodOPTIONS(HTTPConnection httpConnection) {
207         StringBuffer allowedMethods = new StringBuffer();
208         boolean first = true;
209         for (String method : cachedMethods.keySet()) {
210             if (first) {
211                 first = false;
212             } else {
213                 allowedMethods.append(", ");
214             }
215             allowedMethods.append(method);
216         }
217         httpConnection.getResponseHeaders().putOnly("Allow", allowedMethods.toString());
218         httpConnection.getResponseHeaders().removeAll("Content-Type");
219     }
220 
221     /**
222      * Handles OPTIONS HTTP method
223      * @param httpConnection HTTP connection
224      */
225     public void methodTRACE(HTTPConnection httpConnection) {
226         MultiStringMap requestHeaders = httpConnection.getRequestHeaders();
227         MultiStringMap responseHeaders = httpConnection.getResponseHeaders();
228         String contentLength = requestHeaders.getOnly("Content-Length");
229         if (contentLength == null) {
230             httpConnection.setResponseStatus(Status.LENGTH_REQUIRED);
231 
232 //            String message = "Content length required for resource " + httpConnection.getComponentResourcePath();
233 
234             errorResponse.handleConnection(httpConnection);
235         } else {
236             long len = Long.parseLong(contentLength);
237             responseHeaders.putOnly("Content-Length", contentLength);
238 
239             try {
240                 InputStream inputStream = (InputStream)httpConnection.adapt(InputStream.class);
241                 OutputStream outputStream = (OutputStream)httpConnection.adapt(OutputStream.class);
242                 int bufSize = 10240;
243                 if (len < bufSize) {
244                     bufSize = (int)len;
245                 }
246 
247                 byte[] buf = new byte[bufSize];
248 
249                 int r = bufSize;
250                 if (len < r) {
251                     r = (int)len;
252                 }
253 
254                 r = inputStream.read(buf, 0, r);
255                 while ((r > 0) && (len > 0)) {
256                     outputStream.write(buf, 0, r);
257                     len = len - r;
258                     r = bufSize;
259                     if (len < r) {
260                         r = (int)len;
261                     }
262                     r = inputStream.read(buf, 0, r);
263                 }
264                 outputStream.flush();
265             } catch (IOException ignore) {
266             }
267         }
268 
269     }
270 
271     /**
272      * Handles OPTIONS HTTP method
273      * @param httpConnection HTTP connection
274      */
275     public void methodHEAD(HTTPConnection httpConnection) {
276         invokeMethod(httpConnection, "GET");
277     }
278 
279     public void returnError(HTTPConnection httpConnection, Status status) {
280         httpConnection.setResponseStatus(status);
281         errorResponse.handleConnection(httpConnection);
282     }
283 
284     // TODO - do we want Status instead of code/msg?
285     public void returnSimpleContent(HTTPConnection httpConnection, Status status, String contentType, String content) {
286         httpConnection.setResponseStatus(status);
287         if (contentType != null) {
288             httpConnection.getResponseHeaders().putOnly("Content-Type", contentType);
289         }
290         if (content != null) {
291             PrintWriter out = (PrintWriter)httpConnection.adapt(PrintWriter.class);
292             out.print(content);
293         }
294     }
295 }