1   package org.mortbay.servlet;
2   
3   import java.io.IOException;
4   
5   import javax.servlet.RequestDispatcher;
6   import javax.servlet.ServletContext;
7   import javax.servlet.ServletException;
8   import javax.servlet.http.HttpServlet;
9   import javax.servlet.http.HttpServletRequest;
10  import javax.servlet.http.HttpServletResponse;
11  
12  /* ------------------------------------------------------------ */
13  /** Concatenation Servlet
14   * This servlet may be used to concatenate multiple resources into
15   * a single response.  It is intended to be used to load multiple
16   * javascript or css files, but may be used for any content of the 
17   * same mime type that can be meaningfully concatenated.
18   * <p>
19   * The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
20   * to combine the requested content, so dynamically generated content
21   * may be combined (Eg engine.js for DWR).
22   * <p>
23   * The servlet uses parameter names of the query string as resource names
24   * relative to the context root.  So these script tags:
25   * <pre>
26   *  &lt;script type="text/javascript" src="../js/behaviour.js"&gt;&lt;/script&gt;
27   *  &lt;script type="text/javascript" src="../js/ajax.js&/chat/chat.js"&gt;&lt;/script&gt;
28   *  &lt;script type="text/javascript" src="../chat/chat.js"&gt;&lt;/script&gt;
29   * </pre> can be replaced with the single tag (with the ConcatServlet mapped to /concat):
30   * <pre>
31   *  &lt;script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"&gt;&lt;/script&gt;
32   * </pre>
33   * The {@link ServletContext#getMimeType(String)} method is used to determine the 
34   * mime type of each resource.  If the types of all resources do not match, then a 415 
35   * UNSUPPORTED_MEDIA_TYPE error is returned.
36   * <p>
37   * If the init parameter "development" is set to "true" then the servlet will run in
38   * development mode and the content will be concatenated on every request. Otherwise
39   * the init time of the servlet is used as the lastModifiedTime of the combined content
40   * and If-Modified-Since requests are handled with 206 NOT Modified responses if 
41   * appropriate. This means that when not in development mode, the servlet must be 
42   * restarted before changed content will be served.
43   * 
44   * @author gregw
45   *
46   */
47  public class ConcatServlet extends HttpServlet
48  {
49      boolean _development;
50      long _lastModified;
51      ServletContext _context;
52  
53      /* ------------------------------------------------------------ */
54      public void init() throws ServletException
55      {
56          _lastModified=System.currentTimeMillis();
57          _context=getServletContext();   
58          _development="true".equals(getInitParameter("development"));
59      }
60  
61      /* ------------------------------------------------------------ */
62      /* 
63       * @return The start time of the servlet unless in development mode, in which case -1 is returned.
64       */
65      protected long getLastModified(HttpServletRequest req)
66      {
67          return _development?-1:_lastModified;
68      }
69      
70      /* ------------------------------------------------------------ */
71      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
72      {
73          String q=req.getQueryString();
74          if (q==null)
75          {
76              resp.sendError(HttpServletResponse.SC_NO_CONTENT);
77              return;
78          }
79          
80          String[] parts = q.split("\\&");
81          String type=null;
82          for (int i=0;i<parts.length;i++)
83          {
84              String t = _context.getMimeType(parts[i]);
85              if (t!=null)
86              {
87                  if (type==null)
88                      type=t;
89                  else if (!type.equals(t))
90                  {
91                      resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
92                      return;
93                  }
94              }   
95          }
96  
97          if (type!=null)
98              resp.setContentType(type);
99  
100         for (int i=0;i<parts.length;i++)
101         {
102             RequestDispatcher dispatcher=_context.getRequestDispatcher(parts[i]);
103             if (dispatcher!=null)
104                 dispatcher.include(req,resp);
105         }
106     }
107 }