View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.rest;
23  
24  import org.opencastproject.util.MimeTypes;
25  
26  import org.apache.commons.codec.digest.DigestUtils;
27  import org.apache.commons.io.IOUtils;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.net.URL;
34  import java.util.Map;
35  
36  import javax.servlet.ServletException;
37  import javax.servlet.http.HttpServlet;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  /**
42   * A static resource for registration with the http service.
43   */
44  public class StaticResource extends HttpServlet {
45    /** The java.io.serialization uid */
46    private static final long serialVersionUID = 1L;
47  
48    /** The logger */
49    private static final Logger logger = LoggerFactory.getLogger(StaticResource.class);
50  
51    /** The classpath to search for the static resources */
52    protected String classpath = null;
53  
54    /** The base URL for these static resources */
55    protected String alias = null;
56  
57    /** The welcome file to redirect to, if only the alias is specified in the request */
58    protected String welcomeFile = null;
59  
60    /** The enable spa redirect flag */
61    protected boolean spaRedirect = false;
62  
63    /** The classloader to use to search for the static resources. */
64    protected ClassLoader classloader = null;
65  
66    /**
67     * Constructs a static resources.
68     *
69     * @param classpath
70     *          the classpath to the static resources
71     * @param alias
72     *          the URL alias
73     * @param welcomeFile
74     *          the default welcome file
75     */
76    public StaticResource(ClassLoader classloader, String classpath, String alias, String welcomeFile) {
77      this(classloader, classpath, alias, welcomeFile, false);
78    }
79  
80    /**
81     * Constructs a static resources.
82     *
83     * @param classpath
84     *          the classpath to the static resources
85     * @param alias
86     *          the URL alias
87     * @param welcomeFile
88     *          the default welcome file
89     * @param spaRedirect
90     *          enable spa redirects
91     */
92    public StaticResource(ClassLoader classloader, String classpath, String alias, String welcomeFile, boolean spaRedirect) {
93      this.classpath = classpath;
94      this.alias = alias;
95      this.welcomeFile = welcomeFile;
96      this.classloader = classloader;
97      this.spaRedirect = spaRedirect;
98    }
99  
100   /**
101    * Activates the static resource when it is instantiated using Declarative Services.
102    *
103    * @param componentProperties
104    *          the DS component context
105    */
106   @SuppressWarnings("unchecked")
107   public void activate(Map componentProperties) {
108     if (welcomeFile == null)
109       welcomeFile = (String) componentProperties.get("welcome.file");
110     boolean welcomeFileSpecified = true;
111     if (welcomeFile == null) {
112       welcomeFileSpecified = false;
113       welcomeFile = "index.html";
114     }
115     if (alias == null)
116       alias = (String) componentProperties.get("alias");
117     if (classpath == null)
118       classpath = (String) componentProperties.get("classpath");
119     logger.info("registering classpath:{} at {} with welcome file {} {}", classpath, alias, welcomeFile,
120             welcomeFileSpecified ? "" : "(via default)");
121   }
122 
123   public String getDefaultUrl() {
124     return alias;
125   }
126 
127   @Override
128   public String toString() {
129     return "StaticResource [alias=" + alias + ", classpath=" + classpath + ", welcome file=" + welcomeFile + "]";
130   }
131 
132   @Override
133   public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
134     String pathInfo = req.getPathInfo();
135     String servletPath = req.getServletPath();
136     String path = pathInfo == null ? servletPath : servletPath + pathInfo;
137     logger.debug("handling path {}, pathInfo={}, servletPath={}", path, pathInfo, servletPath);
138 
139     // If the URL points to a "directory", redirect to the welcome file
140     if ("/".equals(path) || alias.equals(path) || (alias + "/").equals(path)) {
141       String redirectPath;
142       if ("/".equals(alias)) {
143         redirectPath = "/" + welcomeFile;
144       } else {
145         redirectPath = alias + "/" + welcomeFile;
146       }
147       String queryString = req.getQueryString();
148       redirectPath += queryString != null ? "?" + queryString : "";
149       logger.debug("redirecting {} to {}", path, redirectPath);
150       resp.sendRedirect(redirectPath);
151       return;
152     }
153 
154     // Find and deliver the resource
155     String classpathToResource;
156     if (pathInfo == null) {
157       if (!servletPath.equals(alias)) {
158         classpathToResource = classpath + servletPath;
159       } else {
160         classpathToResource = classpath + "/" + welcomeFile;
161       }
162     } else {
163       classpathToResource = classpath + pathInfo;
164     }
165 
166     // Make sure we are using an absolute path
167     if (!classpathToResource.startsWith("/"))
168       classpathToResource = "/" + classpathToResource;
169 
170     // Try to load the resource from the classloader
171     URL url = classloader.getResource(classpathToResource);
172 
173     // Support SPA path locations
174     if (spaRedirect && url == null) {
175       String spaRedirect = classpath + "/" + welcomeFile;
176       logger.trace("using fallback {}", spaRedirect);
177       url = classloader.getResource(spaRedirect);
178     }
179 
180     if (url == null) {
181       resp.sendError(404);
182       return;
183     }
184 
185     logger.debug("opening url {} {}", classpathToResource, url);
186     try (InputStream in = url.openStream()) {
187       String md5 = DigestUtils.md5Hex(in);
188       if (md5.equals(req.getHeader("If-None-Match"))) {
189         resp.setStatus(304);
190         return;
191       }
192       resp.setHeader("ETag", md5);
193     }
194 
195     String contentType = MimeTypes.getMimeType(url.getPath());
196     if (!MimeTypes.DEFAULT_TYPE.equals(contentType)) {
197       resp.setHeader("Content-Type", contentType);
198     }
199 
200     try (InputStream in = url.openStream()) {
201       IOUtils.copy(in, resp.getOutputStream());
202     }
203   }
204 }