KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > kohsuke > stapler > Stapler


1 package org.kohsuke.stapler;
2
3 import org.kohsuke.stapler.jelly.JellyClassTearOff;
4
5 import javax.servlet.RequestDispatcher JavaDoc;
6 import javax.servlet.ServletConfig JavaDoc;
7 import javax.servlet.ServletContextEvent JavaDoc;
8 import javax.servlet.ServletContextListener JavaDoc;
9 import javax.servlet.ServletException JavaDoc;
10 import javax.servlet.http.HttpServlet JavaDoc;
11 import javax.servlet.http.HttpServletRequest JavaDoc;
12 import javax.servlet.http.HttpServletResponse JavaDoc;
13 import java.io.File JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.io.InputStream JavaDoc;
16 import java.lang.reflect.InvocationTargetException JavaDoc;
17 import java.net.MalformedURLException JavaDoc;
18 import java.net.URI JavaDoc;
19 import java.net.URISyntaxException JavaDoc;
20 import java.net.URL JavaDoc;
21 import java.net.URLConnection JavaDoc;
22 import java.text.ParseException JavaDoc;
23 import java.text.SimpleDateFormat JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.Date JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Locale JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.WeakHashMap JavaDoc;
32
33 /**
34  * Maps an HTTP request to a method call / JSP invocation against a model object
35  * by evaluating the request URL in a EL-ish way.
36  *
37  * <p>
38  * This servlet should be used as the default servlet.
39  *
40  * @author Kohsuke Kawaguchi
41  */

42 public class Stapler extends HttpServlet JavaDoc {
43
44     /**
45      * Root of the model object.
46      */

47     private /*final*/ Object JavaDoc root;
48
49     /**
50      * Duck-type wrappers for the given class.
51      */

52     private Map JavaDoc<Class JavaDoc,Class JavaDoc[]> wrappers;
53
54     /**
55      * All {@link Dispatcher}s.
56      */

57     private static final Map JavaDoc<Class JavaDoc,List JavaDoc<Dispatcher>> dispatchers
58         = Collections.synchronizedMap(new WeakHashMap JavaDoc<Class JavaDoc, List JavaDoc<Dispatcher>>());
59
60     public void init(ServletConfig JavaDoc servletConfig) throws ServletException JavaDoc {
61         super.init(servletConfig);
62         root = servletConfig.getServletContext().getAttribute("app");
63         if(root==null)
64             throw new ServletException JavaDoc("there's no \"app\" attribute in the application context.");
65         wrappers = (Map JavaDoc<Class JavaDoc,Class JavaDoc[]>) servletConfig.getServletContext().getAttribute("wrappers");
66         if(wrappers==null)
67             wrappers = new HashMap JavaDoc<Class JavaDoc,Class JavaDoc[]>();
68     }
69
70     protected void service(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc rsp) throws ServletException JavaDoc, IOException JavaDoc {
71         // TODO: test
72
URL JavaDoc url = getServletContext().getResource(req.getServletPath());
73         if(url!=null && serveStaticResource(req,rsp,url))
74             return; // done
75

76         // consider reusing this ArrayList.
77
invoke( req, rsp, root, req.getServletPath() );
78     }
79
80     /**
81      * Serves the specified URL as a static resource.
82      *
83      * @return false
84      * if the resource doesn't exist.
85      */

86     boolean serveStaticResource(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc rsp, URL JavaDoc url) throws IOException JavaDoc {
87         // jetty reports directories as URLs, which isn't what this is intended for,
88
// so check and reject.
89
File JavaDoc f = toFile(url);
90         if(f!=null && f.isDirectory())
91             return false;
92
93         URLConnection JavaDoc con = url.openConnection();
94         InputStream JavaDoc in;
95         try {
96             in = con.getInputStream();
97         } catch (IOException JavaDoc e) {
98             // Tomcat only reports a missing resource error here
99
return false;
100         }
101
102         con.connect();
103         {// send out Last-Modified, or check If-Modified-Since
104
long lastModified = con.getLastModified();
105             if(lastModified!=0) {
106                 String JavaDoc since = req.getHeader("If-Modified-Since");
107                 SimpleDateFormat JavaDoc format = HTTP_DATE_FORMAT.get();
108                 if(since!=null) {
109                     try {
110                         long ims = format.parse(since).getTime();
111                         if(lastModified<ims+1000) {
112                             // +1000 because date header is second-precision and Java has milli-second precision
113
rsp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
114                             return true;
115                         }
116                     } catch (ParseException JavaDoc e) {
117                         // just ignore and serve the content
118
} catch (NumberFormatException JavaDoc e) {
119                         // trying to locate a bug with Jetty
120
getServletContext().log("Error parsing ["+since+"]",e);
121                         throw e;
122                     }
123                 }
124                 rsp.setHeader("Last-Modified",format.format(new Date JavaDoc(lastModified)));
125             }
126         }
127
128
129         if(con.getContentLength()!=-1)
130             rsp.setContentLength(con.getContentLength());
131
132         String JavaDoc fileName = url.toString();
133         int idx = fileName.lastIndexOf('/');
134         fileName = fileName.substring(idx+1);
135         rsp.setContentType(getServletContext().getMimeType(fileName));
136
137         byte[] buf = new byte[1024];
138         int len;
139         while((len=in.read(buf))>0)
140             rsp.getOutputStream().write(buf,0,len);
141         in.close();
142         return true;
143     }
144
145     /**
146      * If the URL is "file://", return its file representation.
147      */

148     private File JavaDoc toFile(URL JavaDoc url) {
149         String JavaDoc urlstr = url.toExternalForm();
150         if(!urlstr.startsWith("file:"))
151             return null;
152         try {
153             return new File JavaDoc(new URI JavaDoc(urlstr).getPath());
154         } catch (URISyntaxException JavaDoc e) {
155             return null;
156         }
157     }
158
159
160     void invoke(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc rsp, Object JavaDoc root, String JavaDoc url) throws IOException JavaDoc, ServletException JavaDoc {
161         invoke(
162             new RequestImpl(this,req,new ArrayList JavaDoc<AncestorImpl>(),new TokenList(url)),
163             new ResponseImpl(this,rsp),
164             root );
165     }
166
167     void invoke(RequestImpl req, ResponseImpl rsp, Object JavaDoc node ) throws IOException JavaDoc, ServletException JavaDoc {
168         while(node instanceof StaplerProxy) {
169             Object JavaDoc n = ((StaplerProxy)node).getTarget();
170             if(n==node)
171                 break; // if the proxy returns itself, assume that it doesn't want to proxy
172
node = n;
173         }
174
175         // adds this node to ancestor list
176
AncestorImpl a = new AncestorImpl(req.ancestors);
177         a.set(node,req);
178
179         if(node==null) {
180             rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
181             return;
182         }
183
184         MetaClass metaClass = MetaClass.get(node.getClass());
185
186         if(!req.tokens.hasMore()) {
187             if(!req.getServletPath().endsWith("/")) {
188                 rsp.sendRedirect2(req.getContextPath()+req.getServletPath()+'/');
189                 return;
190             }
191             // TODO: find the list of welcome pages for this class by reading web.xml
192
RequestDispatcher JavaDoc indexJsp = getResourceDispatcher(node,"index.jsp");
193             if(indexJsp!=null) {
194                 forward(indexJsp,req,rsp);
195                 return;
196             }
197
198             try {
199                 if(metaClass.loadTearOff(JellyClassTearOff.class).serveIndexJelly(req,rsp,node))
200                     return;
201             } catch (LinkageError JavaDoc e) {
202                 // jelly is not present.
203
if(!jellyLinkageErrorReported) {
204                     jellyLinkageErrorReported = true;
205                     getServletContext().log("Jelly not present. Skipped",e);
206                 }
207             }
208
209             URL JavaDoc indexHtml = getSideFileURL(node,"index.html");
210             if(indexHtml!=null && serveStaticResource(req,rsp,indexHtml))
211                 return; // done
212
}
213
214         try {
215             for( Dispatcher d : metaClass.dispatchers ) {
216                 if(d.dispatch(req,rsp,node))
217                     return;
218             }
219         } catch (IllegalAccessException JavaDoc e) {
220             // this should never really happen
221
getServletContext().log("Error while serving "+req.getRequestURL(),e);
222             throw new ServletException JavaDoc(e);
223         } catch (InvocationTargetException JavaDoc e) {
224             getServletContext().log("Error while serving "+req.getRequestURL(),e);
225             throw new ServletException JavaDoc(e.getTargetException());
226         }
227
228         // we really run out of options.
229
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
230     }
231
232     void forward(RequestDispatcher JavaDoc dispatcher, StaplerRequest req, HttpServletResponse JavaDoc rsp) throws ServletException JavaDoc, IOException JavaDoc {
233         dispatcher.forward(req,new ResponseImpl(this,rsp));
234     }
235
236     RequestDispatcher JavaDoc getResourceDispatcher(Object JavaDoc node, String JavaDoc fileName) throws MalformedURLException JavaDoc {
237         for( Class JavaDoc c = node.getClass(); c!=Object JavaDoc.class; c=c.getSuperclass() ) {
238             String JavaDoc name = "/WEB-INF/side-files/"+c.getName().replace('.','/').replace('$','/')+'/'+fileName;
239             if(getServletContext().getResource(name)!=null) {
240                 // Tomcat returns a RequestDispatcher even if the JSP file doesn't exist.
241
// so check if the resource exists first.
242
RequestDispatcher JavaDoc disp = getServletContext().getRequestDispatcher(name);
243                 if(disp!=null) {
244                     return new RequestDispatcherWrapper(disp,node);
245                 }
246             }
247         }
248         return null;
249     }
250
251     private URL JavaDoc getSideFileURL(Object JavaDoc node,String JavaDoc fileName) throws MalformedURLException JavaDoc {
252         for( Class JavaDoc c = node.getClass(); c!=Object JavaDoc.class; c=c.getSuperclass() ) {
253             String JavaDoc name = "/WEB-INF/side-files/"+c.getName().replace('.','/')+'/'+fileName;
254             URL JavaDoc url = getServletContext().getResource(name);
255             if(url!=null) return url;
256         }
257         return null;
258     }
259
260
261
262     /**
263      * Gets the URL (e.g., "/WEB-INF/side-files/fully/qualified/class/name/jspName")
264      * from a class and the JSP name.
265      */

266     public static String JavaDoc getViewURL(Class JavaDoc clazz,String JavaDoc jspName) {
267         return "/WEB-INF/side-files/"+clazz.getName().replace('.','/')+'/'+jspName;
268     }
269
270     /**
271      * Sets the specified object as the root of the web application.
272      *
273      * <p>
274      * This method should be invoked from your implementation of
275      * {@link ServletContextListener#contextInitialized(ServletContextEvent)}.
276      *
277      * <p>
278      * This is just a convenience method to invoke
279      * <code>servletContext.setAttribute("app",rootApp)</code>.
280      *
281      * <p>
282      * The root object is bound to the URL '/' and used to resolve
283      * all the requests to this web application.
284      */

285     public static void setRoot( ServletContextEvent JavaDoc event, Object JavaDoc rootApp ) {
286         event.getServletContext().setAttribute("app",rootApp);
287     }
288
289
290
291     /**
292      * HTTP date format. Notice that {@link SimpleDateFormat} is thread unsafe.
293      */

294     static final ThreadLocal JavaDoc<SimpleDateFormat JavaDoc> HTTP_DATE_FORMAT =
295         new ThreadLocal JavaDoc<SimpleDateFormat JavaDoc>() {
296             protected SimpleDateFormat JavaDoc initialValue() {
297                 return new SimpleDateFormat JavaDoc("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
298             }
299         };
300
301     private static boolean jellyLinkageErrorReported;
302 }
303
Popular Tags