KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > soa > rest > RestProtocolServlet


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Emil Ong
28  */

29
30 package com.caucho.soa.rest;
31
32 import com.caucho.jaxb.JAXBUtil;
33 import com.caucho.server.util.CauchoSystem;
34 import com.caucho.soa.servlet.ProtocolServlet;
35 import com.caucho.vfs.Vfs;
36 import com.caucho.vfs.WriteStream;
37 import com.caucho.xml.stream.XMLStreamWriterImpl;
38
39 import javax.jws.WebMethod;
40 import javax.jws.WebParam;
41 import javax.jws.WebService;
42 import javax.servlet.GenericServlet JavaDoc;
43 import javax.servlet.ServletException JavaDoc;
44 import javax.servlet.ServletRequest JavaDoc;
45 import javax.servlet.ServletResponse JavaDoc;
46 import javax.servlet.http.HttpServletRequest JavaDoc;
47 import javax.servlet.http.HttpServletResponse JavaDoc;
48 import javax.xml.bind.JAXBContext;
49 import javax.xml.bind.Marshaller;
50 import javax.xml.bind.Unmarshaller;
51 import java.io.IOException JavaDoc;
52 import java.io.InputStream JavaDoc;
53 import java.io.OutputStream JavaDoc;
54 import java.lang.annotation.Annotation JavaDoc;
55 import java.lang.reflect.Method JavaDoc;
56 import java.lang.reflect.Modifier JavaDoc;
57 import java.util.ArrayList JavaDoc;
58 import java.util.HashMap JavaDoc;
59 import java.util.Map JavaDoc;
60 import java.util.logging.Logger JavaDoc;
61
62 /**
63  * A binding for REST services.
64  */

65 public class RestProtocolServlet extends GenericServlet JavaDoc
66   implements ProtocolServlet
67 {
68   private static final Logger JavaDoc log =
69     Logger.getLogger(RestProtocolServlet.class.getName());
70
71   public static final String JavaDoc DELETE = "DELETE";
72   public static final String JavaDoc GET = "GET";
73   public static final String JavaDoc HEAD = "HEAD";
74   public static final String JavaDoc POST = "POST";
75   public static final String JavaDoc PUT = "PUT";
76
77   private Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Method>> _methods
78     = new HashMap JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Method>>();
79   
80   private HashMap JavaDoc<String JavaDoc,Method> _defaultMethods
81     = new HashMap JavaDoc<String JavaDoc,Method>();
82
83   private String JavaDoc _jaxbPackages;
84   private ArrayList JavaDoc<Class JavaDoc> _jaxbClasses = new ArrayList JavaDoc<Class JavaDoc>();
85
86   private JAXBContext _context = null;
87
88   private Object JavaDoc _service;
89
90   public RestProtocolServlet()
91   {
92   }
93   
94   public void setService(Object JavaDoc service)
95   {
96     _service = service;
97   }
98
99   public void addJaxbPackage(String JavaDoc packageName)
100   {
101     if (_jaxbPackages != null)
102       _jaxbPackages = _jaxbPackages + ";" + packageName;
103     else
104       _jaxbPackages = packageName;
105   }
106
107   public void addJaxbClass(Class JavaDoc cl)
108   {
109     _jaxbClasses.add(cl);
110   }
111
112   public void init()
113     throws ServletException JavaDoc
114   {
115     try {
116       Class JavaDoc cl = _service.getClass();
117
118       if (cl.isAnnotationPresent(WebService.class)) {
119         WebService webService
120           = (WebService) cl.getAnnotation(WebService.class);
121
122         String JavaDoc endpoint = webService.endpointInterface();
123
124         if (endpoint != null && ! "".equals(endpoint))
125           cl = CauchoSystem.loadClass(webService.endpointInterface());
126       }
127
128       _methods.put(DELETE, new HashMap JavaDoc<String JavaDoc,Method>());
129       _methods.put(GET, new HashMap JavaDoc<String JavaDoc,Method>());
130       _methods.put(HEAD, new HashMap JavaDoc<String JavaDoc,Method>());
131       _methods.put(POST, new HashMap JavaDoc<String JavaDoc,Method>());
132       _methods.put(PUT, new HashMap JavaDoc<String JavaDoc,Method>());
133
134       ArrayList JavaDoc<Class JavaDoc> jaxbClasses = _jaxbClasses;
135
136       for (Method method : cl.getMethods()) {
137         if (method.getDeclaringClass().equals(Object JavaDoc.class))
138           continue;
139
140         int modifiers = method.getModifiers();
141
142         // Allow abstract for interfaces
143
if (Modifier.isStatic(modifiers)
144             || Modifier.isFinal(modifiers)
145             || ! Modifier.isPublic(modifiers))
146           continue;
147
148         String JavaDoc methodName = method.getName();
149
150         if (method.isAnnotationPresent(WebMethod.class)) {
151           WebMethod webMethod =
152             (WebMethod) method.getAnnotation(WebMethod.class);
153
154           if (! "".equals(webMethod.operationName()))
155             methodName = webMethod.operationName();
156         }
157
158         if (method.isAnnotationPresent(RestMethod.class)) {
159           RestMethod restMethod =
160             (RestMethod) method.getAnnotation(RestMethod.class);
161
162           if (! "".equals(restMethod.operationName()))
163             methodName = restMethod.operationName();
164         }
165
166         boolean hasHTTPMethod = false;
167
168         if (method.isAnnotationPresent(Delete.class)) {
169           if (_methods.get(DELETE).containsKey(methodName)) {
170             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
171                 method.getName());
172           }
173
174           _methods.get(DELETE).put(methodName, method);
175
176           hasHTTPMethod = true;
177         }
178
179         if (method.isAnnotationPresent(Get.class)) {
180           if (_methods.get(GET).containsKey(methodName)) {
181             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
182                 method.getName());
183           }
184
185           _methods.get(GET).put(methodName, method);
186
187           hasHTTPMethod = true;
188         }
189
190         if (method.isAnnotationPresent(Post.class)) {
191           if (_methods.get(POST).containsKey(methodName)) {
192             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
193                 method.getName());
194           }
195
196           _methods.get(POST).put(methodName, method);
197
198           hasHTTPMethod = true;
199         }
200
201         if (method.isAnnotationPresent(Put.class)) {
202           if (_methods.get(PUT).containsKey(methodName)) {
203             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
204                 method.getName());
205           }
206
207           _methods.get(PUT).put(methodName, method);
208
209           hasHTTPMethod = true;
210         }
211
212         if (method.isAnnotationPresent(Head.class)) {
213           if (_methods.get(HEAD).containsKey(methodName)) {
214             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
215                 method.getName());
216           }
217
218           _methods.get(HEAD).put(methodName, method);
219
220           hasHTTPMethod = true;
221         }
222
223         if (! hasHTTPMethod) {
224           if (_defaultMethods.containsKey(methodName)) {
225             throw new UnsupportedOperationException JavaDoc("Overloaded method: " +
226                 method.getName());
227           }
228
229           _defaultMethods.put(methodName, method);
230         }
231
232         if (_context == null)
233           JAXBUtil.introspectMethod(method, jaxbClasses);
234       }
235
236       if (_context != null) {
237       }
238       else if (_jaxbPackages != null) {
239         _context = JAXBContext.newInstance(_jaxbPackages);
240       }
241       else {
242         Class JavaDoc[] classes = jaxbClasses.toArray(new Class JavaDoc[jaxbClasses.size()]);
243         _context = JAXBContext.newInstance(classes);
244       }
245     } catch (RuntimeException JavaDoc e) {
246       throw e;
247     } catch (Exception JavaDoc e) {
248       throw new ServletException JavaDoc(e);
249     }
250   }
251
252   public void service(ServletRequest JavaDoc request, ServletResponse JavaDoc response)
253     throws ServletException JavaDoc, IOException JavaDoc
254   {
255     HttpServletRequest JavaDoc req = (HttpServletRequest JavaDoc) request;
256     HttpServletResponse JavaDoc res = (HttpServletResponse JavaDoc) response;
257     
258     Map JavaDoc<String JavaDoc,String JavaDoc> queryArguments = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
259
260     if (req.getQueryString() != null)
261       queryToMap(req.getQueryString(), queryArguments);
262
263     String JavaDoc[] pathArguments = null;
264
265     if (req.getPathInfo() != null) {
266       String JavaDoc pathInfo = req.getPathInfo();
267
268       // remove the initial and final slashes
269
int startPos = 0;
270       int endPos = pathInfo.length();
271
272       if (pathInfo.length() > 0 && pathInfo.charAt(0) == '/')
273     startPos = 1;
274
275       if (pathInfo.length() > startPos &&
276       pathInfo.charAt(pathInfo.length() - 1) == '/')
277     endPos = pathInfo.length() - 1;
278
279       pathInfo = pathInfo.substring(startPos, endPos);
280
281       pathArguments = pathInfo.split("/");
282
283       if (pathArguments.length == 1 && pathArguments[0].length() == 0)
284         pathArguments = new String JavaDoc[0];
285     }
286     else
287       pathArguments = new String JavaDoc[0];
288
289     try {
290       invoke(_service, req.getMethod(), pathArguments, queryArguments,
291              req, req.getInputStream(), res.getOutputStream());
292     }
293     catch (NoSuchMethodException JavaDoc e) {
294       res.sendError(HttpServletResponse.SC_BAD_REQUEST);
295     }
296     catch (Throwable JavaDoc e) {
297       throw new ServletException JavaDoc(e);
298     }
299   }
300
301   private static void queryToMap(String JavaDoc query,
302                                  Map JavaDoc<String JavaDoc,String JavaDoc> queryArguments)
303   {
304     String JavaDoc[] entries = query.split("&");
305
306     for (String JavaDoc entry : entries) {
307       if (entry.indexOf("=") < 0)
308     continue;
309
310       String JavaDoc[] nameValue = entry.split("=", 2);
311
312       queryArguments.put(nameValue[0], nameValue[1]);
313     }
314   }
315
316   private void invoke(Object JavaDoc object,
317                       String JavaDoc httpMethod,
318                       String JavaDoc[] pathArguments,
319                       Map JavaDoc<String JavaDoc,String JavaDoc> queryArguments,
320                       HttpServletRequest JavaDoc req,
321                       InputStream JavaDoc postData,
322                       OutputStream JavaDoc out)
323     throws Throwable JavaDoc
324   {
325     int pathIndex = 0;
326     boolean pathMethod = false;
327
328     // Two special approaches: path and query
329
//
330
// Path takes the first part of the path as the method name
331
//
332
// Query checks for /?method=myMethod in the query part
333
//
334
// Query overrides path since it's more explicit
335

336     String JavaDoc methodName = queryArguments.get("method");
337
338     if ((methodName == null) && (pathArguments.length > 0)) {
339       methodName = pathArguments[0];
340
341       if (methodName != null)
342         pathMethod = true;
343     }
344
345     // First, look by http method and method name
346
// This may hit the default method since methodName can be null
347
Method method = _methods.get(httpMethod).get(methodName);
348
349     // next, check for a default method, ignoring http method
350
if (method == null)
351       method = _defaultMethods.get(methodName);
352
353     // finally, check for a completely default method
354
if (method == null) {
355       method = _defaultMethods.get(null);
356
357       pathMethod = false;
358     }
359
360     if (method == null)
361       throw new NoSuchMethodException JavaDoc(methodName);
362
363     if (pathMethod)
364       pathIndex = 1;
365
366     // Construct the arguments for the invocation
367
ArrayList JavaDoc arguments = new ArrayList JavaDoc();
368
369     Class JavaDoc[] parameterTypes = method.getParameterTypes();
370     Annotation JavaDoc[][] annotations = method.getParameterAnnotations();
371
372     for (int i = 0; i < parameterTypes.length; i++) {
373       RestParam.Source source = RestParam.Source.QUERY;
374       String JavaDoc key = "arg" + i;
375
376       for (int j = 0; j < annotations[i].length; j++) {
377         if (annotations[i][j].annotationType().equals(RestParam.class)) {
378           RestParam restParam = (RestParam) annotations[i][j];
379           source = restParam.source();
380         }
381         else if (annotations[i][j].annotationType().equals(WebParam.class)) {
382           WebParam webParam = (WebParam) annotations[i][j];
383
384           if (! "".equals(webParam.name()))
385             key = webParam.name();
386         }
387       }
388
389       switch (source) {
390         case PATH:
391           {
392             String JavaDoc arg = null;
393
394             if (pathIndex < pathArguments.length)
395               arg = pathArguments[pathIndex++];
396
397             arguments.add(stringToType(parameterTypes[i], arg));
398             // XXX var args
399
}
400           break;
401         case QUERY:
402           arguments.add(stringToType(parameterTypes[i],
403                                      queryArguments.get(key)));
404           break;
405         case POST:
406           {
407             Unmarshaller unmarshaller = _context.createUnmarshaller();
408             arguments.add(unmarshaller.unmarshal(postData));
409           }
410           break;
411         case HEADER:
412           arguments.add(stringToType(parameterTypes[i], req.getHeader(key)));
413           break;
414       }
415     }
416
417     Object JavaDoc result = method.invoke(object, arguments.toArray());
418
419     Marshaller marshaller = _context.createMarshaller();
420     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
421
422     WriteStream ws = Vfs.openWrite(out);
423
424     try {
425       XMLStreamWriterImpl writer = new XMLStreamWriterImpl(ws);
426       marshaller.marshal(result, writer);
427     }
428     finally {
429       ws.close();
430     }
431   }
432
433   private static Object JavaDoc stringToType(Class JavaDoc type, String JavaDoc arg)
434     throws Throwable JavaDoc
435   {
436     if (arg == null) {
437       return null;
438     }
439     else if (type.equals(boolean.class)) {
440       return new Boolean JavaDoc(arg);
441     }
442     else if (type.equals(Boolean JavaDoc.class)) {
443       return new Boolean JavaDoc(arg);
444     }
445     else if (type.equals(byte.class)) {
446       return new Byte JavaDoc(arg);
447     }
448     else if (type.equals(Byte JavaDoc.class)) {
449       return new Byte JavaDoc(arg);
450     }
451     else if (type.equals(char.class)) {
452       if (arg.length() != 1) {
453         throw new IllegalArgumentException JavaDoc("Cannot convert String to type " +
454                                            type.getName());
455       }
456
457       return new Character JavaDoc(arg.charAt(0));
458     }
459     else if (type.equals(Character JavaDoc.class)) {
460       if (arg.length() != 1) {
461         throw new IllegalArgumentException JavaDoc("Cannot convert String to type " +
462                                            type.getName());
463       }
464
465       return new Character JavaDoc(arg.charAt(0));
466     }
467     else if (type.equals(double.class)) {
468       return new Double JavaDoc(arg);
469     }
470     else if (type.equals(Double JavaDoc.class)) {
471       return new Double JavaDoc(arg);
472     }
473     else if (type.equals(float.class)) {
474       return new Float JavaDoc(arg);
475     }
476     else if (type.equals(Float JavaDoc.class)) {
477       return new Float JavaDoc(arg);
478     }
479     else if (type.equals(int.class)) {
480       return new Integer JavaDoc(arg);
481     }
482     else if (type.equals(Integer JavaDoc.class)) {
483       return new Integer JavaDoc(arg);
484     }
485     else if (type.equals(long.class)) {
486       return new Long JavaDoc(arg);
487     }
488     else if (type.equals(Long JavaDoc.class)) {
489       return new Long JavaDoc(arg);
490     }
491     else if (type.equals(short.class)) {
492       return new Short JavaDoc(arg);
493     }
494     else if (type.equals(Short JavaDoc.class)) {
495       return new Short JavaDoc(arg);
496     }
497     else if (type.equals(String JavaDoc.class)) {
498       return arg;
499     }
500     else
501       throw new IllegalArgumentException JavaDoc("Cannot convert String to type " +
502                                          type.getName());
503   }
504 }
505
Popular Tags