| 1 package org.kohsuke.stapler; 2 3 import org.kohsuke.stapler.jelly.JellyClassTearOff; 4 5 import javax.servlet.RequestDispatcher ; 6 import javax.servlet.ServletConfig ; 7 import javax.servlet.ServletContextEvent ; 8 import javax.servlet.ServletContextListener ; 9 import javax.servlet.ServletException ; 10 import javax.servlet.http.HttpServlet ; 11 import javax.servlet.http.HttpServletRequest ; 12 import javax.servlet.http.HttpServletResponse ; 13 import java.io.File ; 14 import java.io.IOException ; 15 import java.io.InputStream ; 16 import java.lang.reflect.InvocationTargetException ; 17 import java.net.MalformedURLException ; 18 import java.net.URI ; 19 import java.net.URISyntaxException ; 20 import java.net.URL ; 21 import java.net.URLConnection ; 22 import java.text.ParseException ; 23 import java.text.SimpleDateFormat ; 24 import java.util.ArrayList ; 25 import java.util.Collections ; 26 import java.util.Date ; 27 import java.util.HashMap ; 28 import java.util.List ; 29 import java.util.Locale ; 30 import java.util.Map ; 31 import java.util.WeakHashMap ; 32 33 42 public class Stapler extends HttpServlet { 43 44 47 private Object root; 48 49 52 private Map <Class ,Class []> wrappers; 53 54 57 private static final Map <Class ,List <Dispatcher>> dispatchers 58 = Collections.synchronizedMap(new WeakHashMap <Class , List <Dispatcher>>()); 59 60 public void init(ServletConfig servletConfig) throws ServletException { 61 super.init(servletConfig); 62 root = servletConfig.getServletContext().getAttribute("app"); 63 if(root==null) 64 throw new ServletException ("there's no \"app\" attribute in the application context."); 65 wrappers = (Map <Class ,Class []>) servletConfig.getServletContext().getAttribute("wrappers"); 66 if(wrappers==null) 67 wrappers = new HashMap <Class ,Class []>(); 68 } 69 70 protected void service(HttpServletRequest req, HttpServletResponse rsp) throws ServletException , IOException { 71 URL url = getServletContext().getResource(req.getServletPath()); 73 if(url!=null && serveStaticResource(req,rsp,url)) 74 return; 76 invoke( req, rsp, root, req.getServletPath() ); 78 } 79 80 86 boolean serveStaticResource(HttpServletRequest req, HttpServletResponse rsp, URL url) throws IOException { 87 File f = toFile(url); 90 if(f!=null && f.isDirectory()) 91 return false; 92 93 URLConnection con = url.openConnection(); 94 InputStream in; 95 try { 96 in = con.getInputStream(); 97 } catch (IOException e) { 98 return false; 100 } 101 102 con.connect(); 103 { long lastModified = con.getLastModified(); 105 if(lastModified!=0) { 106 String since = req.getHeader("If-Modified-Since"); 107 SimpleDateFormat format = HTTP_DATE_FORMAT.get(); 108 if(since!=null) { 109 try { 110 long ims = format.parse(since).getTime(); 111 if(lastModified<ims+1000) { 112 rsp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 114 return true; 115 } 116 } catch (ParseException e) { 117 } catch (NumberFormatException e) { 119 getServletContext().log("Error parsing ["+since+"]",e); 121 throw e; 122 } 123 } 124 rsp.setHeader("Last-Modified",format.format(new Date (lastModified))); 125 } 126 } 127 128 129 if(con.getContentLength()!=-1) 130 rsp.setContentLength(con.getContentLength()); 131 132 String 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 148 private File toFile(URL url) { 149 String urlstr = url.toExternalForm(); 150 if(!urlstr.startsWith("file:")) 151 return null; 152 try { 153 return new File (new URI (urlstr).getPath()); 154 } catch (URISyntaxException e) { 155 return null; 156 } 157 } 158 159 160 void invoke(HttpServletRequest req, HttpServletResponse rsp, Object root, String url) throws IOException , ServletException { 161 invoke( 162 new RequestImpl(this,req,new ArrayList <AncestorImpl>(),new TokenList(url)), 163 new ResponseImpl(this,rsp), 164 root ); 165 } 166 167 void invoke(RequestImpl req, ResponseImpl rsp, Object node ) throws IOException , ServletException { 168 while(node instanceof StaplerProxy) { 169 Object n = ((StaplerProxy)node).getTarget(); 170 if(n==node) 171 break; node = n; 173 } 174 175 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 RequestDispatcher 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 e) { 202 if(!jellyLinkageErrorReported) { 204 jellyLinkageErrorReported = true; 205 getServletContext().log("Jelly not present. Skipped",e); 206 } 207 } 208 209 URL indexHtml = getSideFileURL(node,"index.html"); 210 if(indexHtml!=null && serveStaticResource(req,rsp,indexHtml)) 211 return; } 213 214 try { 215 for( Dispatcher d : metaClass.dispatchers ) { 216 if(d.dispatch(req,rsp,node)) 217 return; 218 } 219 } catch (IllegalAccessException e) { 220 getServletContext().log("Error while serving "+req.getRequestURL(),e); 222 throw new ServletException (e); 223 } catch (InvocationTargetException e) { 224 getServletContext().log("Error while serving "+req.getRequestURL(),e); 225 throw new ServletException (e.getTargetException()); 226 } 227 228 rsp.sendError(HttpServletResponse.SC_NOT_FOUND); 230 } 231 232 void forward(RequestDispatcher dispatcher, StaplerRequest req, HttpServletResponse rsp) throws ServletException , IOException { 233 dispatcher.forward(req,new ResponseImpl(this,rsp)); 234 } 235 236 RequestDispatcher getResourceDispatcher(Object node, String fileName) throws MalformedURLException { 237 for( Class c = node.getClass(); c!=Object .class; c=c.getSuperclass() ) { 238 String name = "/WEB-INF/side-files/"+c.getName().replace('.','/').replace('$','/')+'/'+fileName; 239 if(getServletContext().getResource(name)!=null) { 240 RequestDispatcher 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 getSideFileURL(Object node,String fileName) throws MalformedURLException { 252 for( Class c = node.getClass(); c!=Object .class; c=c.getSuperclass() ) { 253 String name = "/WEB-INF/side-files/"+c.getName().replace('.','/')+'/'+fileName; 254 URL url = getServletContext().getResource(name); 255 if(url!=null) return url; 256 } 257 return null; 258 } 259 260 261 262 266 public static String getViewURL(Class clazz,String jspName) { 267 return "/WEB-INF/side-files/"+clazz.getName().replace('.','/')+'/'+jspName; 268 } 269 270 285 public static void setRoot( ServletContextEvent event, Object rootApp ) { 286 event.getServletContext().setAttribute("app",rootApp); 287 } 288 289 290 291 294 static final ThreadLocal <SimpleDateFormat > HTTP_DATE_FORMAT = 295 new ThreadLocal <SimpleDateFormat >() { 296 protected SimpleDateFormat initialValue() { 297 return new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); 298 } 299 }; 300 301 private static boolean jellyLinkageErrorReported; 302 } 303 | Popular Tags |