001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/portal/tags/sakai_2-4-1/portal-impl/impl/src/java/org/sakaiproject/portal/charon/handlers/StaticHandler.java $
003: * $Id: StaticHandler.java 29143 2007-04-19 01:10:38Z ajpoland@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006, 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.portal.charon.handlers;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.util.Properties;
027:
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033:
034: /**
035: * Handler to process static content with an internal, in memory cache.
036: * Care should be taken not to put large volumes of static content within the
037: * portal space that is handled by this Handler as it will lead to increased
038: * memory usage.
039: *
040: * @author ieb
041: * @since Sakai 2.4
042: * @version $Rev: 29143 $
043: *
044: */
045: public abstract class StaticHandler extends BasePortalHandler {
046:
047: private Properties contentTypes = null;
048:
049: private static final ThreadLocal<StaticCache[]> staticCacheHolder = new ThreadLocal<StaticCache[]>();
050:
051: private static final Log log = LogFactory
052: .getLog(StaticHandler.class);
053:
054: public StaticHandler() {
055: contentTypes = new Properties();
056: try {
057: contentTypes
058: .load(this
059: .getClass()
060: .getResourceAsStream(
061: "/org/sakaiproject/portal/charon/staticcontenttypes.config"));
062: } catch (IOException e) {
063: throw new RuntimeException(
064: "Failed to load Static Content Types (staticcontenttypes.config) ",
065: e);
066: }
067:
068: }
069:
070: /**
071: * serve a registered static file
072: *
073: * @param req
074: * @param res
075: * @param parts
076: * @throws IOException
077: */
078: public void doStatic(HttpServletRequest req,
079: HttpServletResponse res, String[] parts) throws IOException {
080: try {
081: StaticCache[] staticCache = staticCacheHolder.get();
082: if (staticCache == null) {
083: staticCache = new StaticCache[100];
084: staticCacheHolder.set(staticCache);
085: }
086: String path = req.getPathInfo();
087: if (path.indexOf("..") >= 0) {
088: res.sendError(404);
089: return;
090: }
091: String realPath = servletContext.getRealPath(path);
092: File f = new File(realPath);
093: if (f.length() < 100 * 1024) {
094: for (int i = 0; i < staticCache.length; i++) {
095: StaticCache sc = staticCache[i];
096: if (sc != null && path.equals(sc.path)) {
097: if (f.lastModified() > sc.lastModified) {
098: sc.buffer = loadFileBuffer(f);
099: sc.path = path;
100: sc.lastModified = f.lastModified();
101: sc.contenttype = getContentType(f);
102: sc.added = System.currentTimeMillis();
103: }
104: // send the output
105: sendContent(res, sc);
106: return;
107: }
108: }
109: // not found in cache, find the oldest or null and evict
110: // this is thread Safe, since the cache is per thread.
111: StaticCache sc = null;
112: for (int i = 1; i < staticCache.length; i++) {
113: StaticCache current = staticCache[i];
114: if (sc == null) {
115: sc = current;
116: }
117: if (current == null) {
118: sc = new StaticCache();
119: staticCache[i] = sc;
120: break;
121: }
122: if (sc.added < current.added) {
123: sc = current;
124: }
125: }
126: sc.buffer = loadFileBuffer(f);
127: sc.path = path;
128: sc.lastModified = f.lastModified();
129: sc.contenttype = getContentType(f);
130: sc.added = System.currentTimeMillis();
131: sendContent(res, sc);
132: return;
133:
134: } else {
135: res.setContentType(getContentType(f));
136: res.addDateHeader("Last-Modified", f.lastModified());
137: res.setContentLength((int) f.length());
138: sendContent(res, f);
139: return;
140:
141: }
142:
143: } catch (IOException ex) {
144: log.error("Failed to send portal content ", ex);
145: res.sendError(404, ex.getMessage());
146: }
147:
148: }
149:
150: /**
151: * a simple static cache holder
152: *
153: * @author ieb
154: */
155: protected class StaticCache {
156:
157: public Object path;
158:
159: public long added;
160:
161: public byte[] buffer;
162:
163: public long lastModified;
164:
165: public String contenttype;
166:
167: }
168:
169: /**
170: * send the static content from the file
171: *
172: * @param res
173: * @param f
174: * @throws IOException
175: */
176: private void sendContent(HttpServletResponse res, File f)
177: throws IOException {
178: FileInputStream fin = null;
179: try {
180: fin = new FileInputStream(f);
181: res.setContentType(getContentType(f));
182: res.addDateHeader("Last-Modified", f.lastModified());
183: res.setContentLength((int) f.length());
184: byte[] buffer = new byte[4096];
185: int pos = 0;
186: int bsize = buffer.length;
187: int nr = fin.read(buffer, 0, bsize);
188: OutputStream out = res.getOutputStream();
189: while (nr > 0) {
190: out.write(buffer, 0, nr);
191: }
192:
193: } finally {
194: try {
195: fin.close();
196: } catch (Exception ex) {
197: }
198: }
199:
200: }
201:
202: /**
203: * load a file into a byte[]
204: *
205: * @param f
206: * @return
207: * @throws IOException
208: */
209: private byte[] loadFileBuffer(File f) throws IOException {
210: FileInputStream fin = null;
211: try {
212: fin = new FileInputStream(f);
213: byte[] buffer = new byte[(int) f.length()];
214: int pos = 0;
215: int remaining = buffer.length;
216: int nr = fin.read(buffer, pos, remaining);
217: while (nr > 0) {
218: pos = pos + nr;
219: remaining = remaining - nr;
220: nr = fin.read(buffer, pos, remaining);
221: }
222: return buffer;
223: } finally {
224: try {
225: fin.close();
226: } catch (Exception ex) {
227: }
228: }
229: }
230:
231: /**
232: * get the content type of the file
233: *
234: * @param f
235: * @return
236: */
237: private String getContentType(File f) {
238: String name = f.getName();
239: int dot = name.lastIndexOf(".");
240: String contentType = "application/octet-stream";
241: if (dot >= 0) {
242: String ext = name.substring(dot);
243: contentType = contentTypes.getProperty(ext);
244: }
245: return contentType;
246: }
247:
248: /**
249: * send the content from the static cache.
250: *
251: * @param res
252: * @param sc
253: * @throws IOException
254: */
255: private void sendContent(HttpServletResponse res, StaticCache sc)
256: throws IOException {
257: res.setContentType(sc.contenttype);
258: res.addDateHeader("Last-Modified", sc.lastModified);
259: res.setContentLength(sc.buffer.length);
260: res.getOutputStream().write(sc.buffer);
261:
262: }
263:
264: }
|