* 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.
import org.onap.aaf.misc.env.LogTarget;
import org.onap.aaf.misc.env.Store;
import org.onap.aaf.misc.env.Trans;
+import org.owasp.encoder.Encode;
/*
* CachingFileAccess
- *
+ *
* Author: Jonathan Gathman, Gathsys 2010
- *
+ *
*/
public class CachingFileAccess<TRANS extends Trans> extends HttpCode<TRANS, Void> {
public static void setEnv(Store store, String[] args) {
- for (int i=0;i<args.length-1;i+=2) { // cover two parms required for each
+ for (int i=0;i<args.length-1;i+=2) { // cover two parms required for each
if (CFA_WEB_PATH.equals(args[i])) {
- store.put(store.staticSlot(CFA_WEB_PATH), args[i+1]);
+ store.put(store.staticSlot(CFA_WEB_PATH), args[i+1]);
} else if (CFA_CACHE_CHECK_INTERVAL.equals(args[i])) {
store.put(store.staticSlot(CFA_CACHE_CHECK_INTERVAL), Long.parseLong(args[i+1]));
} else if (CFA_MAX_SIZE.equals(args[i])) {
}
}
}
-
- private static String MAX_AGE = "max-age=3600"; // 1 hour Caching
+
+ private static String maxAge = "max-age=3600"; // 1 hour Caching
private final Map<String,String> typeMap;
private final NavigableMap<String,Content> content;
private final Set<String> attachOnly;
- public final static String CFA_WEB_PATH = "aaf_cfa_web_path";
+ public static final String CFA_WEB_PATH = "aaf_cfa_web_path";
// when to re-validate from file
// Re validating means comparing the Timestamp on the disk, and seeing it has changed. Cache is not marked
- // dirty unless file has changed, but it still makes File IO, which for some kinds of cached data, i.e.
+ // dirty unless file has changed, but it still makes File IO, which for some kinds of cached data, i.e.
// deployed GUI elements is unnecessary, and wastes time.
// This parameter exists to cover the cases where data can be more volatile, so the user can choose how often the
// File IO will be accessed, based on probability of change. "0", of course, means, check every time.
- private final static String CFA_CACHE_CHECK_INTERVAL = "aaf_cfa_cache_check_interval";
- private final static String CFA_MAX_SIZE = "aaf_cfa_max_size"; // Cache size limit
- private final static String CFA_CLEAR_COMMAND = "aaf_cfa_clear_command";
+ private static final String CFA_CACHE_CHECK_INTERVAL = "aaf_cfa_cache_check_interval";
+ private static final String CFA_MAX_SIZE = "aaf_cfa_max_size"; // Cache size limit
+ private static final String CFA_CLEAR_COMMAND = "aaf_cfa_clear_command";
// Note: can be null without a problem, but included
// to tie in with existing Logging.
public long checkInterval; // = 600000L; // only check if not hit in 10 mins by default
public int maxItemSize; // = 512000; // max file 500k
private Timer timer;
- private String web_path;
+ private String webPath;
// A command key is set in the Properties, preferably changed on deployment.
// it is compared at the beginning of the path, and if so, it is assumed to issue certain commands
- // It's purpose is to protect, to some degree the command, even though it is HTTP, allowing
+ // It's purpose is to protect, to some degree the command, even though it is HTTP, allowing
// local batch files to, for instance, clear caches on resetting of files.
- private String clear_command;
-
+ private String clearCommand;
+
+ public static final String TEXT_PLAIN = "text/plain";
+
public CachingFileAccess(EnvJAXB env, String ... args) throws IOException {
super(null,"Caching File Access");
setEnv(env,args);
typeMap.put("html","text/html");
typeMap.put("css","text/css");
typeMap.put("js","text/javascript");
- typeMap.put("txt","text/plain");
+ typeMap.put("txt", TEXT_PLAIN);
typeMap.put("xml","text/xml");
typeMap.put("xsd","text/xml");
attachOnly.add("xsd");
typeMap.put("jar","application/x-java-applet");
typeMap.put("jnlp", "application/x-java-jnlp-file");
typeMap.put("class", "application/java");
- typeMap.put("props", "text/plain");
+ typeMap.put("props", TEXT_PLAIN);
typeMap.put("jks", "application/octet-stream");
-
+
+ // Fonts
+ typeMap.put("ttf","font/ttf");
+ typeMap.put("woff","font/woff");
+ typeMap.put("woff2","font/woff2");
+
+
timer = new Timer("Caching Cleanup",true);
timer.schedule(new Cleanup(content,500),60000,60000);
-
+
// Property params
- web_path = env.get(env.staticSlot(CFA_WEB_PATH));
- env.init().log("CachingFileAccess path: " + new File(web_path).getCanonicalPath());
+ webPath = env.get(env.staticSlot(CFA_WEB_PATH));
+ env.init().log("CachingFileAccess path: " + new File(webPath).getCanonicalPath());
Object obj;
obj = env.get(env.staticSlot(CFA_CACHE_CHECK_INTERVAL),600000L); // Default is 10 mins
- if (obj instanceof Long) {checkInterval=(Long)obj;
- } else {checkInterval=Long.parseLong((String)obj);}
-
+ if (obj instanceof Long) {
+ checkInterval=(Long)obj;
+ } else {
+ checkInterval=Long.parseLong((String)obj);
+ }
+
obj = env.get(env.staticSlot(CFA_MAX_SIZE), 512000); // Default is max file 500k
- if (obj instanceof Integer) {maxItemSize=(Integer)obj;
- } else {maxItemSize =Integer.parseInt((String)obj);}
-
- clear_command = env.getProperty(CFA_CLEAR_COMMAND,null);
+ if (obj instanceof Integer) {
+ maxItemSize=(Integer)obj;
+ } else {
+ maxItemSize =Integer.parseInt((String)obj);
+ }
+
+ clearCommand = env.getProperty(CFA_CLEAR_COMMAND,null);
}
-
+
@Override
public void handle(TRANS trans, HttpServletRequest req, HttpServletResponse resp) throws IOException {
String key = pathParam(req, ":key");
- String cmd = pathParam(req,":cmd");
- if (key.equals(clear_command)) {
+ int slash = key.indexOf('/');
+ if(key.length()>2 && slash>=0 && key.substring(0,slash).equals(clearCommand)) {
resp.setHeader("Content-Type",typeMap.get("txt"));
- if ("clear".equals(cmd)) {
+ if ("clear".equals(key.substring(slash+1))) {
content.clear();
resp.setStatus(200/*HttpStatus.OK_200*/);
} else {
}
return;
}
- Content c = load(logT , web_path,cmd!=null && cmd.length()>0?key+'/'+cmd:key, null, checkInterval);
+ Content c = load(logT , webPath,key, null, checkInterval);
if (c.attachmentOnly) {
resp.setHeader("Content-disposition", "attachment");
}
public String webPath() {
- return web_path;
+ return webPath;
}
-
+
/**
* Reset the Cleanup size and interval
- *
+ *
* The size and interval when started are 500 items (memory size unknown) checked every minute in a background thread.
- *
+ *
* @param size
* @param interval
*/
timer = new Timer();
timer.schedule(new Cleanup(content,size), interval, interval);
}
-
-
+
+
/**
* Load a file, first checking cache
- *
- *
+ *
+ *
* @param logTarget - logTarget can be null (won't log)
* @param dataRoot - data root storage directory
* @param key - relative File Path
isRoot = false;
}
Content c = content.get(key);
- long systime = System.currentTimeMillis();
+ long systime = System.currentTimeMillis();
File f=null;
if (c!=null) {
// Don't check every hit... only after certain time value
}
}
}
- if (c==null) {
+ if (c==null) {
if (logTarget!=null) {
logTarget.log("File Read: ",key);
}
-
+
if (f==null){
f = new File(fileName);
}
c = new CachedContent(f);
cacheMe = checkInterval>0;
}
-
+
if (mediaType==null) { // determine from file Ending
int idx = key.lastIndexOf('.');
String subkey = key.substring(++idx);
c.contentType=mediaType;
c.attachmentOnly = false;
}
-
+
c.date = f.lastModified();
-
+
if (cacheMe) {
content.put(key, c);
}
c.access = systime;
return c;
}
-
+
public void invalidate(String key) {
content.remove(key);
}
-
+
private static final Content NULL=new Content() {
-
+
@Override
public void setHeader(HttpServletResponse resp) {
resp.setStatus(404/*NOT_FOUND_404*/);
- resp.setHeader("Content-type","text/plain");
+ resp.setHeader("Content-type",TEXT_PLAIN);
}
@Override
@Override
public void write(OutputStream os) throws IOException {
}
-
+
};
- private static abstract class Content {
+ private abstract static class Content {
private long date; // date of the actual artifact (i.e. File modified date)
private long access; // last accessed
-
+
protected String contentType;
protected boolean attachmentOnly;
-
+
public void setHeader(HttpServletResponse resp) {
resp.setStatus(200/*OK_200*/);
resp.setHeader("Content-Type",contentType);
- resp.setHeader("Cache-Control", MAX_AGE);
+ resp.setHeader("Cache-Control", maxAge);
}
-
+
public abstract void write(Writer writer) throws IOException;
public abstract void write(OutputStream os) throws IOException;
}
private static class DirectFileContent extends Content {
- private File file;
+ private File file;
public DirectFileContent(File f) {
file = f;
}
-
+
public String toString() {
return file.getName();
}
-
+
public void write(Writer writer) throws IOException {
FileReader fr = new FileReader(file);
char[] buff = new char[1024];
public DirectoryContent(File directory, boolean isRoot) {
notRoot = !isRoot;
-
+
files = directory.listFiles();
Arrays.sort(files,new Comparator<File>() {
@Override
// if numbers, are the numbers in the same start position
int i1 = m1.start();
int i2 = m2.start();
-
+
// If same start position and the text is the same, then reverse sort
if (i1==i2 && f1.getName().startsWith(f2.getName().substring(0,i1))) {
// reverse sort files that start similarly, but have numbers in them
}
return f1.compareTo(f2);
}
-
+
});
name = directory.getName();
attachmentOnly = false;
contentType = "text/html";
}
-
-
+
+
@Override
public void write(Writer w) throws IOException {
w.append(H1);
w.append(name);
w.append('/');
}
- w.append(f.getName());
+ w.append(Encode.forJava(f.getName()));
w.append("\">");
- w.append(f.getName());
+ w.append(Encode.forJava(f.getName()));
w.append("</a></li>\n");
}
w.append(F);
w.flush();
}
-
+
@Override
public void write(OutputStream os) throws IOException {
write(new OutputStreamWriter(os));
}
-
+
}
private static class CachedContent extends Content {
private byte[] data;
private int end;
- private char[] cdata;
-
+ private char[] cdata;
+
public CachedContent(File f) throws IOException {
// Read and Cache
ByteBuffer bb = ByteBuffer.allocate((int)f.length());
end = bb.position();
cdata=null;
}
-
+
public String toString() {
return Arrays.toString(data);
}
-
+
public void write(Writer writer) throws IOException {
synchronized(this) {
// do the String Transformation once, and only if actually used
private static class Cleanup extends TimerTask {
private int maxSize;
private NavigableMap<String, Content> content;
-
+
public Cleanup(NavigableMap<String, Content> content, int size) {
maxSize = size;
this.content = content;
}
-
+
private class Comp implements Comparable<Comp> {
public Map.Entry<String, Content> entry;
-
+
public Comp(Map.Entry<String, Content> en) {
entry = en;
}
-
+
@Override
public int compareTo(Comp o) {
return (int)(entry.getValue().access-o.entry.getValue().access);
}
-
+
}
@SuppressWarnings("unchecked")
@Override
}
Collections.sort(scont);
int end = size - ((maxSize/4)*3); // reduce to 3/4 of max size
- //System.out.println("------ Cleanup Cycle ------ " + new Date().toString() + " -------");
for (int i=0;i<end;++i) {
Entry<String, Content> entry = scont.get(i).entry;
content.remove(entry.getKey());
- //System.out.println("removed Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString());
}
-// for (int i=end;i<size;++i) {
-// Entry<String, Content> entry = scont.get(i).entry;
-// //System.out.println("remaining Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString());
-// }
}
}
}