StaticResources.java
/***************************************************************************
Copyright 2008 Emily Estes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************/
package net.metanotion.simpletemplate;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.metanotion.io.File;
import net.metanotion.io.FileSystem;
import net.metanotion.util.Pair;
import net.metanotion.web.HttpStatus;
import net.metanotion.web.HttpValues;
import net.metanotion.web.RequestObject;
import net.metanotion.web.concrete.SimpleHttpValues;
import net.metanotion.scripting.ObjectServer;
/** This resource factory produces resources that simply evaluate to the files in the underlying file system.
Essentially this is just a static file server. It attempts to generate cache control headers from the metadata in the
underlying file system(not all of them are implemented yet though).
*/
public final class StaticResources implements ResourceFactory {
private static final Logger logger = LoggerFactory.getLogger(StaticResources.class);
private static final List<Map.Entry<String,Object>> emptyList = Collections.emptyList();
private static final int OK = HttpStatus.OK.codeNumber();
private static final int NOT_MODIFIED = HttpStatus.NOT_MODIFIED.codeNumber();
private static final int NOT_FOUND = HttpStatus.NOT_FOUND.codeNumber();
private static final class StaticResource implements Resource {
private final File<File> f;
private final Long maxExpire;
private final boolean doETag;
protected StaticResource(File<File> f, Long maxExpire, boolean doETag) {
this.f = f;
this.maxExpire = maxExpire;
this.doETag = doETag;
}
@Override public HttpValues skin(ObjectServer so, RequestObject ro) {
final List<Map.Entry<String,Object>> headers = new ArrayList<Map.Entry<String,Object>>(1);
int sc = OK;
/*
Calculate Last-Modified date
Check If-Modified-Since header from ro
Calculate ETag
Check ETag header from ro
*/
final Date imsDate = ro.getDateHeader("If-Modified-Since");
Date fDate = null;
try {
fDate = (Date) f.getAttribute("LastModifiedDate");
} catch (Exception e) { }
String eTag = null;
if(doETag) {
// TO DO
}
if((imsDate != null) && (fDate != null) && (fDate.getTime() <= imsDate.getTime())) { sc = NOT_MODIFIED; }
InputStream out = null;
try {
out = f.openInput();
} catch (Exception e) { sc = NOT_FOUND; }
if(sc != NOT_FOUND) {
headers.add(new Pair<String,Object>("Content-Type", f.getAttribute("MIMEType")));
try {
headers.add(new Pair<String,Object>("Content-Length", Long.valueOf(f.length())));
} catch (UnsupportedOperationException uoe) {
/* Not every file system can give us the file length, so we won't send the header if the request
fails. */
}
if(maxExpire != null) {
// TO DO
//headers.add(new Pair<String,Object>("Expires", ));
}
/* TO DO if(doETag) { headers.add(new Pair<String,Object>("ETag", eTag)); } */
// for ETAG's http://stackoverflow.com/questions/132052/servlet-for-serving-static-content/29991447#29991447 consider this technique
if(fDate != null) { headers.add(new Pair<String,Object>("Last-Modified", fDate)); }
}
return new SimpleHttpValues(headers, emptyList, out, sc);
}
}
private final FileSystem<? extends File> fs;
private final Long maxExpire;
private final ResourceFactory parent;
private final boolean doETag;
/** Create a resource factory using the provided file system for scripts with all the default options.
@param fs The file system where files will be loaded from.
*/
public StaticResources(FileSystem<? extends File> fs) { this(fs, false, null, NullTemplateHelper.INSTANCE); }
/** Create an instance.
@param fs The file system where files will be loaded from.
@param doETag If true, create an ETag value and send an ETag header with the resource.
*/
public StaticResources(FileSystem<? extends File> fs, boolean doETag) {
this(fs, doETag, null, NullTemplateHelper.INSTANCE);
}
/** Create an instance.
@param fs The file system where files will be loaded from.
@param parent When a resource cannot be loaded from the file system, delegate interpretation to this resource
factory.
*/
public StaticResources(FileSystem<? extends File> fs, ResourceFactory parent) { this(fs, false, null, parent); }
/** Create an instance.
@param fs The file system where files will be loaded from.
@param doETag If true, create an ETag value and send an ETag header with the resource.
@param parent When a resource cannot be loaded from the file system, delegate interpretation to this resource
factory.
*/
public StaticResources(FileSystem<? extends File> fs, boolean doETag, ResourceFactory parent) {
this(fs, doETag, null, parent);
}
/** Create an instance.
@param fs The file system where files will be loaded from.
@param maxExpire If not-null set the max expiration date header to this value.
*/
public StaticResources(FileSystem<? extends File> fs, Long maxExpire) {
this(fs, false, maxExpire, NullTemplateHelper.INSTANCE);
}
/** Create an instance.
@param fs The file system where files will be loaded from.
@param doETag If true, create an ETag value and send an ETag header with the resource.
@param maxExpire If not-null set the max expiration date header to this value.
*/
public StaticResources(FileSystem<? extends File> fs, boolean doETag, Long maxExpire) {
this(fs, doETag, maxExpire, NullTemplateHelper.INSTANCE);
}
/** Create an instance.
@param fs The file system where files will be loaded from.
@param maxExpire If not-null set the max expiration date header to this value.
@param parent When a resource cannot be loaded from the file system, delegate interpretation to this resource
factory.
*/
public StaticResources(FileSystem<? extends File> fs, Long maxExpire, ResourceFactory parent) {
this(fs, false, maxExpire, parent);
}
/** Create an instance.
@param fs The file system where files will be loaded from.
@param doETag If true, create an ETag value and send an ETag header with the resource.
@param maxExpire If not-null set the max expiration date header to this value.
@param parent When a resource cannot be loaded from the file system, delegate interpretation to this resource
factory.
*/
public StaticResources(FileSystem<? extends File> fs, boolean doETag, Long maxExpire, ResourceFactory parent) {
this.fs = fs;
this.doETag = doETag;
this.maxExpire = maxExpire;
this.parent = parent;
}
@Override public Resource get(String urn) {
logger.debug("Static Resource Request " + urn);
final File<File> f = fs.get(urn);
try {
if(f.exists()) {
return new StaticResource(f, maxExpire, doETag);
}
} catch (Exception e) { }
return parent.get(urn);
}
}