/* Copyright (c) 2003 eInnovation Inc. All rights reserved This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ /* * (c) Copyright 2002 eInnovation Inc. * All Rights Reserved. */ package com.openedit.generators; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Date; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.openedit.Generator; import com.openedit.OpenEditException; import com.openedit.WebPageRequest; import com.openedit.error.ContentNotAvailableException; import com.openedit.page.Page; import com.openedit.page.manage.PageManager; import com.openedit.util.FileUtils; import com.openedit.util.SimpleDateFormatPerThread; /** * This generator uses file steam to process a request for a page * * @author Chris Burkey */ public class FileGenerator extends BaseGenerator implements Generator { private static Log log = LogFactory.getLog(FileGenerator.class); protected PageManager fieldPageManager; protected SimpleDateFormatPerThread fieldLastModFormat; public SimpleDateFormatPerThread getLastModFormat() { if( fieldLastModFormat == null) { //Tue, 05 Jan 2010 14:20:51 GMT -- just english fieldLastModFormat = new SimpleDateFormatPerThread("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); //log.info( fieldLastModFormat.format(new Date()) ); } return fieldLastModFormat; } public PageManager getPageManager() { return fieldPageManager; } public void setPageManager(PageManager inPageManager) { fieldPageManager = inPageManager; } public void generate( WebPageRequest inContext, Page inPage, Output inOut ) throws OpenEditException { Page contentpage = inPage; HttpServletResponse res = inContext.getResponse(); HttpServletRequest req = inContext.getRequest(); if( res != null && req != null) { checkCors( req, res); } long start = -1; InputStream in = null; try { in = contentpage.getInputStream(); if( in == null) { String vir = contentpage.get("virtual"); if ( !Boolean.parseBoolean(vir) ) { log.info("Missing: " +contentpage.getPath()); throw new ContentNotAvailableException("Missing: " +contentpage.getPath(),contentpage.getPath()); } else { log.debug("not found: " + contentpage); return; //do nothing } } //only bother if we are the content page and not in development if ( res != null && inContext.getContentPage() == contentpage ) { boolean cached = checkCache(inContext, contentpage, req, res); if( cached ) { return; } } //sometimes we can specify the length of the document //long length = -1; long end = -1; if( req != null && inContext.getContentPage() == contentpage ) { //might need to seek String range = req.getHeader("Range"); if( range != null && range.startsWith("bytes=")) { //will need to seek int cutoff = range.indexOf("-"); start = Long.parseLong( range.substring(6,cutoff)); cutoff++;// 0 based if( range.length() > cutoff) // a trailing - means everything left { end = Long.parseLong( range.substring(cutoff)); } //log.info("Requested " + range); res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } } long length = -1; if ( res != null && !contentpage.isHtml() && !contentpage.isJson() && inContext.getContentPage() == contentpage ) { //we can set the length unless there is a decorator on it somehow length = (long)contentpage.getContentItem().getLength(); if ( length != -1) { if( start > -1) { if( end == -1) { end = length - 1; //1024-1 1023 } res.setHeader("Accept-Ranges", "bytes"); res.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + length); //len is total long sent = end + 1 - start; // 0 1024 - 1024 length length = sent; } if(length > 0 && length < Integer.MAX_VALUE) { res.setContentLength((int)length); } else { log.info("Zero length file " + contentpage.getPath()); } //res.removeHeader("Content-Length"); //res.setHeader("Content-Length", String.valueOf(length)); } } if ( contentpage.isBinary() ) { in = streamBinary(inContext, contentpage, in, start, end, length, inOut); } else { InputStreamReader reader = null; if ( contentpage.getCharacterEncoding() != null ) { reader = new InputStreamReader( in, contentpage.getCharacterEncoding() ); } else { reader = new InputStreamReader( in ); } //If you get an error about content length then your character encoding is not correct. Use UTF-8 //maybe we need to write with the correct encoding then the files should match getOutputFiller().fill(reader, inOut.getWriter()); } } catch ( Exception eof ) { if( ignoreError(eof)) { //log.error(eof); ignored return; } if( in == null) { log.error("Could not load " + contentpage.getPath()); } throw new OpenEditException(eof); } finally { FileUtils.safeClose(in); } } protected InputStream streamBinary(WebPageRequest inReq, Page inPage, InputStream in, long start, long end, long length, Output inOut) throws IOException { OutputStream outs = inOut.getStream(); if( start > -1) { BufferedInputStream buffer = new BufferedInputStream(in); long did = buffer.skip(start); if( did != start) { throw new OpenEditException("Could not seek to start"); } in = buffer; } if( end != -1) { getOutputFiller().fill(in, outs, length); } else { getOutputFiller().fill(in, outs); } return in; } protected boolean checkCache(WebPageRequest inContext, Page contentpage, HttpServletRequest req, HttpServletResponse res) { long now = System.currentTimeMillis(); boolean cache = true; String nocache = inContext.findValue("cache"); if( nocache != null ) { cache = Boolean.parseBoolean(nocache); } else { //is this recenlty modified? //3333333recent99 + 24 hours (mil * sec * min * hours) will be more than now cache = contentpage.lastModified() + (1000 * 60 * 60 * 24 ) < now; } if( cache && req != null) { String since = req.getHeader("If-Modified-Since"); if( since != null && since.endsWith("GMT")) { //304 Not Modified try { Date old = getLastModFormat().parse(since); if( !contentpage.getLastModified().after(old)) { //log.info("if since" + since); res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return true; } } catch( Exception ex) { log.error(since); } } } res.setDateHeader("Last-Modified",contentpage.getLastModified().getTime()); if( cache ) { res.setDateHeader("Expires", now + (1000 * 60 * 60 * 24 )); //sec * min * hour * 48 Hours } else { res.setDateHeader("Expires", now - (1000 * 60 * 60 * 24)); //expired 24 hours ago } return false; } public boolean canGenerate(WebPageRequest inReq) { return true; } }