View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.servlet;
21  
22  import org.apache.myfaces.tobago.application.ProjectStage;
23  import org.apache.myfaces.tobago.config.TobagoConfig;
24  import org.apache.myfaces.tobago.context.Theme;
25  import org.apache.myfaces.tobago.internal.util.IoUtils;
26  import org.apache.myfaces.tobago.internal.util.MimeTypeUtils;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.servlet.ServletConfig;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServlet;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.util.HashSet;
39  import java.util.List;
40  import java.util.Set;
41  
42  /**
43   * <p><pre>
44   * &lt;servlet&gt;
45   *   &lt;servlet-name&gt;ResourceServlet&lt;/servlet-name&gt;
46   *   &lt;servlet-class&gt;org.apache.myfaces.tobago.servlet.ResourceServlet&lt;/servlet-class&gt;
47   *   &lt;init-param&gt;
48   *     &lt;description&gt;The value for the expires header in seconds.
49   *       The default for ProjectStage.Production is 86400 sec (24 h) otherwise no expires header.&lt;/description&gt;
50   *     &lt;param-name&gt;expires&lt;/param-name&gt;
51   *     &lt;param-value&gt;14400&lt;/param-value&gt;
52   *   &lt;/init-param&gt;
53   *   &lt;init-param&gt;
54   *     &lt;description&gt;The value for the copy buffer size.
55   *            Default is 4096.&lt;/description&gt;
56   *     &lt;param-name&gt;bufferSize&lt;/param-name&gt;
57   *     &lt;param-value&gt;4096&lt;/param-value&gt;
58   *   &lt;/init-param&gt;
59   * &lt;/servlet&gt;
60   * &lt;servlet-mapping&gt;
61   *   &lt;servlet-name&gt;ResourceServlet&lt;/servlet-name&gt;
62   *   &lt;url-pattern&gt;/org/apache/myfaces/tobago/renderkit/*&lt;/url-pattern&gt;
63   * &lt;/servlet-mapping&gt;
64   * </pre><p>
65   *
66   * @since 1.0.7
67   */
68  public class ResourceServlet extends HttpServlet {
69  
70    private static final long serialVersionUID = -4491419290205206466L;
71  
72    private static final Logger LOG = LoggerFactory.getLogger(ResourceServlet.class);
73  
74    private Long expires;
75    private int bufferSize;
76    private Set<String> resourceDirs = new HashSet<String>();
77  
78    @Override
79    public void init(ServletConfig servletConfig) throws ServletException {
80      super.init(servletConfig);
81      TobagoConfig tobagoConfig = TobagoConfig.getInstance(servletConfig.getServletContext());
82      if (tobagoConfig != null && tobagoConfig.getProjectStage() == ProjectStage.Production) {
83         expires = 24 * 60 * 60 * 1000L;
84      }
85      Theme defaultTheme = tobagoConfig.getDefaultTheme();
86      addResourceDir(defaultTheme.getFallbackList());
87      addResourceDir(tobagoConfig.getSupportedThemes());
88  
89      String expiresString = servletConfig.getInitParameter("expires");
90      if (expiresString != null) {
91        try {
92          expires = new Long(expiresString) * 1000;
93        } catch (NumberFormatException e) {
94          LOG.error("Caught: " + e.getMessage(), e);
95        }
96      }
97      String bufferSizeString = servletConfig.getInitParameter("bufferSize");
98      bufferSize = 1024 * 4;
99      if (bufferSizeString != null) {
100       try {
101         bufferSize = Integer.parseInt(bufferSizeString);
102       } catch (NumberFormatException e) {
103         LOG.error("Caught: " + e.getMessage(), e);
104       }
105     }
106   }
107 
108   private void addResourceDir(List<Theme> themes) {
109     for (Theme theme : themes) {
110         addResourceDir(theme);
111     }
112   }
113 
114   private void addResourceDir(Theme theme) {
115     String dir = theme.getResourcePath();
116     if (dir.startsWith("/")) {
117       resourceDirs.add(dir.substring(1));
118     } else {
119       resourceDirs.add(dir);
120     }
121   }
122 
123     @Override
124   protected void doGet(
125       HttpServletRequest request, HttpServletResponse response)
126       throws ServletException, IOException {
127 
128     String requestURI = request.getRequestURI();
129     String resource = requestURI.substring(request.getContextPath().length() + 1);
130     for (String resourceDir : resourceDirs) {
131       if (resource.startsWith(resourceDir)) {
132         if (Character.isDigit(resource.charAt(resourceDir.length()+1))) {
133           resource = resourceDir + resource.substring(resource.indexOf('/', resourceDir.length() + 1));
134         }
135         break;
136       }
137     }
138     if (expires != null) {
139       response.setDateHeader("Last-Modified", 0);
140       response.setHeader("Cache-Control", "Public, max-age=" + expires);
141       response.setDateHeader("Expires", System.currentTimeMillis() + expires);
142     }
143     String contentType = MimeTypeUtils.getMimeTypeForFile(requestURI);
144     if (contentType != null) {
145       response.setContentType(contentType);
146     } else {
147       String message = "Unsupported mime type of resource='" + resource + "'";
148       LOG.warn(message + " (because of security reasons)");
149       response.sendError(HttpServletResponse.SC_FORBIDDEN, message);
150       return;
151     }
152 
153     InputStream inputStream = null;
154     try {
155       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
156 
157       // meta inf (like in servlet 3.0)
158       inputStream = classLoader.getResourceAsStream("META-INF/resources/" + resource);
159 
160       // "normal" classpath
161       if (inputStream == null) {
162         inputStream = classLoader.getResourceAsStream(resource);
163       }
164 
165       if (inputStream != null) {
166         copy(inputStream, response.getOutputStream());
167       } else {
168         String message = "Resource '" + resource + "' not found!";
169         LOG.warn(message);
170         response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
171       }
172     } finally {
173       IoUtils.closeQuietly(inputStream);
174     }
175   }
176 
177   @Override
178   protected long getLastModified(HttpServletRequest request) {
179     if (expires != null) {
180       return 0;
181     } else {
182       return super.getLastModified(request);
183     }
184   }
185 
186   private void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
187     byte[] buffer = new byte[bufferSize];
188     int count;
189     while (-1 != (count = inputStream.read(buffer))) {
190       outputStream.write(buffer, 0, count);
191     }
192   }
193 }