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,
93        boolean spaRedirect) {
94      this.classpath = classpath;
95      this.alias = alias;
96      this.welcomeFile = welcomeFile;
97      this.classloader = classloader;
98      this.spaRedirect = spaRedirect;
99    }
100 
101   /**
102    * Activates the static resource when it is instantiated using Declarative Services.
103    *
104    * @param componentProperties
105    *          the DS component context
106    */
107   @SuppressWarnings("unchecked")
108   public void activate(Map componentProperties) {
109     if (welcomeFile == null) {
110       welcomeFile = (String) componentProperties.get("welcome.file");
111     }
112     boolean welcomeFileSpecified = true;
113     if (welcomeFile == null) {
114       welcomeFileSpecified = false;
115       welcomeFile = "index.html";
116     }
117     if (alias == null) {
118       alias = (String) componentProperties.get("alias");
119     }
120     if (classpath == null) {
121       classpath = (String) componentProperties.get("classpath");
122     }
123     logger.info("registering classpath:{} at {} with welcome file {} {}", classpath, alias, welcomeFile,
124             welcomeFileSpecified ? "" : "(via default)");
125   }
126 
127   public String getDefaultUrl() {
128     return alias;
129   }
130 
131   @Override
132   public String toString() {
133     return "StaticResource [alias=" + alias + ", classpath=" + classpath + ", welcome file=" + welcomeFile + "]";
134   }
135 
136   @Override
137   public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
138     String pathInfo = req.getPathInfo();
139     String servletPath = req.getServletPath();
140     String path = pathInfo == null ? servletPath : servletPath + pathInfo;
141     logger.debug("handling path {}, pathInfo={}, servletPath={}", path, pathInfo, servletPath);
142 
143     // If the URL points to a "directory", redirect to the welcome file
144     if ("/".equals(path) || alias.equals(path) || (alias + "/").equals(path)) {
145       String redirectPath;
146       if ("/".equals(alias)) {
147         redirectPath = "/" + welcomeFile;
148       } else {
149         redirectPath = alias + "/" + welcomeFile;
150       }
151       String queryString = req.getQueryString();
152       redirectPath += queryString != null ? "?" + queryString : "";
153       logger.debug("redirecting {} to {}", path, redirectPath);
154       resp.sendRedirect(redirectPath);
155       return;
156     }
157 
158     // Find and deliver the resource
159     String classpathToResource;
160     if (pathInfo == null) {
161       if (!servletPath.equals(alias)) {
162         classpathToResource = classpath + servletPath;
163       } else {
164         classpathToResource = classpath + "/" + welcomeFile;
165       }
166     } else {
167       classpathToResource = classpath + pathInfo;
168     }
169 
170     // Make sure we are using an absolute path
171     if (!classpathToResource.startsWith("/")) {
172       classpathToResource = "/" + classpathToResource;
173     }
174 
175     // Try to load the resource from the classloader
176     URL url = classloader.getResource(classpathToResource);
177 
178     // Support SPA path locations
179     if (spaRedirect && url == null) {
180       String spaRedirect = classpath + "/" + welcomeFile;
181       logger.trace("using fallback {}", spaRedirect);
182       url = classloader.getResource(spaRedirect);
183     }
184 
185     if (url == null) {
186       resp.sendError(404);
187       return;
188     }
189 
190     logger.debug("opening url {} {}", classpathToResource, url);
191     try (InputStream in = url.openStream()) {
192       String md5 = DigestUtils.md5Hex(in);
193       if (md5.equals(req.getHeader("If-None-Match"))) {
194         resp.setStatus(304);
195         return;
196       }
197       resp.setHeader("ETag", md5);
198     }
199 
200     String contentType = MimeTypes.getMimeType(url.getPath());
201     if (!MimeTypes.DEFAULT_TYPE.equals(contentType)) {
202       resp.setHeader("Content-Type", contentType);
203     }
204 
205     try (InputStream in = url.openStream()) {
206       IOUtils.copy(in, resp.getOutputStream());
207     }
208   }
209 }