Update project structure to org.onap.aaf
[aaf/authz.git] / authz-core / src / main / java / org / onap / aaf / cssa / rserv / CachingFileAccess.java
diff --git a/authz-core/src/main/java/org/onap/aaf/cssa/rserv/CachingFileAccess.java b/authz-core/src/main/java/org/onap/aaf/cssa/rserv/CachingFileAccess.java
new file mode 100644 (file)
index 0000000..019257a
--- /dev/null
@@ -0,0 +1,476 @@
+/*******************************************************************************\r
+ * ============LICENSE_START====================================================\r
+ * * org.onap.aaf\r
+ * * ===========================================================================\r
+ * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
+ * * ===========================================================================\r
+ * * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * * you may not use this file except in compliance with the License.\r
+ * * You may obtain a copy of the License at\r
+ * * \r
+ *  *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * * \r
+ *  * Unless required by applicable law or agreed to in writing, software\r
+ * * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * * See the License for the specific language governing permissions and\r
+ * * limitations under the License.\r
+ * * ============LICENSE_END====================================================\r
+ * *\r
+ * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
+ * *\r
+ ******************************************************************************/\r
+package org.onap.aaf.cssa.rserv;\r
+\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.FileReader;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.Writer;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.FileChannel;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Date;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.NavigableMap;\r
+import java.util.Set;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import java.util.TreeMap;\r
+import java.util.concurrent.ConcurrentSkipListMap;\r
+\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpServletResponse;\r
+\r
+import com.att.aft.dme2.internal.jetty.http.HttpStatus;\r
+import org.onap.aaf.inno.env.Env;\r
+import org.onap.aaf.inno.env.EnvJAXB;\r
+import org.onap.aaf.inno.env.LogTarget;\r
+import org.onap.aaf.inno.env.Store;\r
+import org.onap.aaf.inno.env.TimeTaken;\r
+import org.onap.aaf.inno.env.Trans;\r
+/*\r
+ * CachingFileAccess\r
+ * \r
+ *  \r
+ */\r
+public class CachingFileAccess<TRANS extends Trans> extends HttpCode<TRANS, Void> {\r
+       public static void setEnv(Store store, String[] args) {\r
+               for(int i=0;i<args.length-1;i+=2) { // cover two parms required for each \r
+                       if(CFA_WEB_DIR.equals(args[i])) {\r
+                               store.put(store.staticSlot(CFA_WEB_DIR), args[i+1]); \r
+                       } else if(CFA_CACHE_CHECK_INTERVAL.equals(args[i])) {\r
+                               store.put(store.staticSlot(CFA_CACHE_CHECK_INTERVAL), Long.parseLong(args[i+1]));\r
+                       } else if(CFA_MAX_SIZE.equals(args[i])) {\r
+                               store.put(store.staticSlot(CFA_MAX_SIZE), Integer.parseInt(args[i+1]));\r
+                       }\r
+               }\r
+       }\r
+       \r
+       private static String MAX_AGE = "max-age=3600"; // 1 hour Caching\r
+       private final Map<String,String> typeMap;\r
+       private final NavigableMap<String,Content> content;\r
+       private final Set<String> attachOnly;\r
+       private final static String WEB_DIR_DEFAULT = "theme";\r
+       public final static String CFA_WEB_DIR = "CFA_WebPath";\r
+       // when to re-validate from file\r
+       // Re validating means comparing the Timestamp on the disk, and seeing it has changed.  Cache is not marked\r
+       // dirty unless file has changed, but it still makes File IO, which for some kinds of cached data, i.e. \r
+       // deployed GUI elements is unnecessary, and wastes time.\r
+       // This parameter exists to cover the cases where data can be more volatile, so the user can choose how often the\r
+       // File IO will be accessed, based on probability of change.  "0", of course, means, check every time.\r
+       private final static String CFA_CACHE_CHECK_INTERVAL = "CFA_CheckIntervalMS";\r
+       private final static String CFA_MAX_SIZE = "CFA_MaxSize"; // Cache size limit\r
+       private final static String CFA_CLEAR_COMMAND = "CFA_ClearCommand";\r
+\r
+       // Note: can be null without a problem, but included\r
+       // to tie in with existing Logging.\r
+       public LogTarget logT = null;\r
+       public long checkInterval; // = 600000L; // only check if not hit in 10 mins by default\r
+       public int maxItemSize; // = 512000; // max file 500k\r
+       private Timer timer;\r
+       private String web_path;\r
+       // A command key is set in the Properties, preferably changed on deployment.\r
+       // it is compared at the beginning of the path, and if so, it is assumed to issue certain commands\r
+       // It's purpose is to protect, to some degree the command, even though it is HTTP, allowing \r
+       // local batch files to, for instance, clear caches on resetting of files.\r
+       private String clear_command;\r
+       \r
+       public CachingFileAccess(EnvJAXB env, String ... args) {\r
+               super(null,"Caching File Access");\r
+               setEnv(env,args);\r
+               content = new ConcurrentSkipListMap<String,Content>(); // multi-thread changes possible\r
+\r
+               attachOnly = new HashSet<String>();     // short, unchanged\r
+\r
+               typeMap = new TreeMap<String,String>(); // Structure unchanged after Construction\r
+               typeMap.put("ico","image/icon");\r
+               typeMap.put("html","text/html");\r
+               typeMap.put("css","text/css");\r
+               typeMap.put("js","text/javascript");\r
+               typeMap.put("txt","text/plain");\r
+               typeMap.put("xml","text/xml");\r
+               typeMap.put("xsd","text/xml");\r
+               attachOnly.add("xsd");\r
+               typeMap.put("crl", "application/x-pkcs7-crl");\r
+               typeMap.put("appcache","text/cache-manifest");\r
+\r
+               typeMap.put("json","text/json");\r
+               typeMap.put("ogg", "audio/ogg");\r
+               typeMap.put("jpg","image/jpeg");\r
+               typeMap.put("gif","image/gif");\r
+               typeMap.put("png","image/png");\r
+               typeMap.put("svg","image/svg+xml");\r
+               typeMap.put("jar","application/x-java-applet");\r
+               typeMap.put("jnlp", "application/x-java-jnlp-file");\r
+               typeMap.put("class", "application/java");\r
+               \r
+               timer = new Timer("Caching Cleanup",true);\r
+               timer.schedule(new Cleanup(content,500),60000,60000);\r
+               \r
+               // Property params\r
+               web_path = env.getProperty(CFA_WEB_DIR,WEB_DIR_DEFAULT);\r
+               Object obj;\r
+               obj = env.get(env.staticSlot(CFA_CACHE_CHECK_INTERVAL),600000L);  // Default is 10 mins\r
+               if(obj instanceof Long) {checkInterval=(Long)obj;\r
+               } else {checkInterval=Long.parseLong((String)obj);}\r
+               \r
+               obj = env.get(env.staticSlot(CFA_MAX_SIZE), 512000);    // Default is max file 500k\r
+               if(obj instanceof Integer) {maxItemSize=(Integer)obj;\r
+               } else {maxItemSize =Integer.parseInt((String)obj);}\r
+                       \r
+               clear_command = env.getProperty(CFA_CLEAR_COMMAND,null);\r
+       }\r
+\r
+       \r
+\r
+       @Override\r
+       public void handle(TRANS trans, HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
+               String key = pathParam(req, ":key");\r
+               if(key.equals(clear_command)) {\r
+                       String cmd = pathParam(req,":cmd");\r
+                       resp.setHeader("Content-type",typeMap.get("txt"));\r
+                       if("clear".equals(cmd)) {\r
+                               content.clear();\r
+                               resp.setStatus(HttpStatus.OK_200);\r
+                       } else {\r
+                               resp.setStatus(HttpStatus.BAD_REQUEST_400);\r
+                       }\r
+                       return;\r
+               }\r
+               Content c = load(logT , web_path,key, null, checkInterval);\r
+               if(c.attachmentOnly) {\r
+                       resp.setHeader("Content-disposition", "attachment");\r
+               }\r
+               c.write(resp.getOutputStream());\r
+               c.setHeader(resp);\r
+               trans.checkpoint(req.getPathInfo());\r
+       }\r
+\r
+\r
+       public String webPath() {\r
+               return web_path;\r
+       }\r
+       \r
+       /**\r
+        * Reset the Cleanup size and interval\r
+        * \r
+        * The size and interval when started are 500 items (memory size unknown) checked every minute in a background thread.\r
+        * \r
+        * @param size\r
+        * @param interval\r
+        */\r
+       public void cleanupParams(int size, long interval) {\r
+               timer.cancel();\r
+               timer.schedule(new Cleanup(content,size), interval, interval);\r
+       }\r
+       \r
+\r
+       \r
+       /**\r
+        * Load a file, first checking cache\r
+        * \r
+        * \r
+        * @param logTarget - logTarget can be null (won't log)\r
+        * @param dataRoot - data root storage directory\r
+        * @param key - relative File Path\r
+        * @param mediaType - what kind of file is it.  If null, will check via file extension\r
+        * @param timeCheck - "-1" will take system default - Otherwise, will compare "now" + timeCheck(Millis) before looking at File mod\r
+        * @return\r
+        * @throws IOException\r
+        */\r
+       public Content load(LogTarget logTarget, String dataRoot, String key, String mediaType, long _timeCheck) throws IOException {\r
+           long timeCheck = _timeCheck;\r
+               if(timeCheck<0) {\r
+                       timeCheck=checkInterval; // if time < 0, then use default\r
+               }\r
+               String fileName = dataRoot + '/' + key;\r
+               Content c = content.get(key);\r
+               long systime = System.currentTimeMillis(); \r
+               File f=null;\r
+               if(c!=null) {\r
+                       // Don't check every hit... only after certain time value\r
+                       if(c.date < systime + timeCheck) {\r
+                               f = new File(fileName);\r
+                               if(f.lastModified()>c.date) {\r
+                                       c=null;\r
+                               }\r
+                       }\r
+               }\r
+               if(c==null) {   \r
+                       if(logTarget!=null) {\r
+                               logTarget.log("File Read: ",key);\r
+                       }\r
+                       \r
+                       if(f==null){\r
+                               f = new File(fileName);\r
+                       }\r
+\r
+                       boolean cacheMe;\r
+                       if(f.exists()) {\r
+                               if(f.length() > maxItemSize) {\r
+                                       c = new DirectFileContent(f);\r
+                                       cacheMe = false;\r
+                               } else {\r
+                                       c = new CachedContent(f);\r
+                                       cacheMe = checkInterval>0;\r
+                               }\r
+                               \r
+                               if(mediaType==null) { // determine from file Ending\r
+                                       int idx = key.lastIndexOf('.');\r
+                                       String subkey = key.substring(++idx);\r
+                                       if((c.contentType = idx<0?null:typeMap.get(subkey))==null) {\r
+                                               // if nothing else, just set to default type...\r
+                                               c.contentType = "application/octet-stream";\r
+                                       }\r
+                                       c.attachmentOnly = attachOnly.contains(subkey);\r
+                               } else {\r
+                                       c.contentType=mediaType;\r
+                                       c.attachmentOnly = false;\r
+                               }\r
+                               \r
+                               c.date = f.lastModified();\r
+                               \r
+                               if(cacheMe) {\r
+                                       content.put(key, c);\r
+                               }\r
+                       } else {\r
+                               c=NULL;\r
+                       }\r
+               } else {\r
+                       if(logTarget!=null)logTarget.log("Cache Read: ",key);\r
+               }\r
+\r
+               // refresh hit time\r
+               c.access = systime;\r
+               return c;\r
+       }\r
+       \r
+       public Content loadOrDefault(Trans trans, String targetDir, String targetFileName, String sourcePath, String mediaType) throws IOException {\r
+               try {\r
+                       return load(trans.info(),targetDir,targetFileName,mediaType,0);\r
+               } catch(FileNotFoundException e) {\r
+                       String targetPath = targetDir + '/' + targetFileName;\r
+                       TimeTaken tt = trans.start("File doesn't exist; copy " + sourcePath + " to " + targetPath, Env.SUB);\r
+                       try {\r
+                               FileInputStream sourceFIS = new FileInputStream(sourcePath);\r
+                               FileChannel sourceFC = sourceFIS.getChannel();\r
+                               File targetFile = new File(targetPath);\r
+                               targetFile.getParentFile().mkdirs(); // ensure directory exists\r
+                               FileOutputStream targetFOS = new FileOutputStream(targetFile);\r
+                               try {\r
+                                       ByteBuffer bb = ByteBuffer.allocate((int)sourceFC.size());\r
+                                       sourceFC.read(bb);\r
+                                       bb.flip();  // ready for reading\r
+                                       targetFOS.getChannel().write(bb);\r
+                               } finally {\r
+                                       sourceFIS.close();\r
+                                       targetFOS.close();\r
+                               }\r
+                       } finally {\r
+                               tt.done();\r
+                       }\r
+                       return load(trans.info(),targetDir,targetFileName,mediaType,0);\r
+               }\r
+       }\r
+\r
+       public void invalidate(String key) {\r
+               content.remove(key);\r
+       }\r
+       \r
+       private static final Content NULL=new Content() {\r
+               \r
+               @Override\r
+               public void setHeader(HttpServletResponse resp) {\r
+                       resp.setStatus(HttpStatus.NOT_FOUND_404);\r
+                       resp.setHeader("Content-type","text/plain");\r
+               }\r
+\r
+               @Override\r
+               public void write(Writer writer) throws IOException {\r
+               }\r
+\r
+               @Override\r
+               public void write(OutputStream os) throws IOException {\r
+               }\r
+               \r
+       };\r
+\r
+       private static abstract class Content {\r
+               private long date;   // date of the actual artifact (i.e. File modified date)\r
+               private long access; // last accessed\r
+               \r
+               protected String  contentType;\r
+               protected boolean attachmentOnly;\r
+               \r
+               public void setHeader(HttpServletResponse resp) {\r
+                       resp.setStatus(HttpStatus.OK_200);\r
+                       resp.setHeader("Content-type",contentType);\r
+                       resp.setHeader("Cache-Control", MAX_AGE);\r
+               }\r
+               \r
+               public abstract void write(Writer writer) throws IOException;\r
+               public abstract void write(OutputStream os) throws IOException;\r
+\r
+       }\r
+\r
+       private static class DirectFileContent extends Content {\r
+               private File file; \r
+               public DirectFileContent(File f) {\r
+                       file = f;\r
+               }\r
+               \r
+               public String toString() {\r
+                       return file.getName();\r
+               }\r
+               \r
+               public void write(Writer writer) throws IOException {\r
+                       FileReader fr = new FileReader(file);\r
+                       char[] buff = new char[1024];\r
+                       try {\r
+                               int read;\r
+                               while((read = fr.read(buff,0,1024))>=0) {\r
+                                       writer.write(buff,0,read);\r
+                               }\r
+                       } finally {\r
+                               fr.close();\r
+                       }\r
+               }\r
+\r
+               public void write(OutputStream os) throws IOException {\r
+                       FileInputStream fis = new FileInputStream(file);\r
+                       byte[] buff = new byte[1024];\r
+                       try {\r
+                               int read;\r
+                               while((read = fis.read(buff,0,1024))>=0) {\r
+                                       os.write(buff,0,read);\r
+                               }\r
+                       } finally {\r
+                               fis.close();\r
+                       }\r
+               }\r
+\r
+       }\r
+       private static class CachedContent extends Content {\r
+               private byte[] data;\r
+               private int end;\r
+               private char[] cdata; \r
+               \r
+               public CachedContent(File f) throws IOException {\r
+                       // Read and Cache\r
+                       ByteBuffer bb = ByteBuffer.allocate((int)f.length());\r
+                       FileInputStream fis = new FileInputStream(f);\r
+                       try {\r
+                               fis.getChannel().read(bb);\r
+                       } finally {\r
+                               fis.close();\r
+                       }\r
+\r
+                       data = bb.array();\r
+                       end = bb.position();\r
+                       cdata=null;\r
+               }\r
+               \r
+               public String toString() {\r
+                       return data.toString();\r
+               }\r
+               \r
+               public void write(Writer writer) throws IOException {\r
+                       synchronized(this) {\r
+                               // do the String Transformation once, and only if actually used\r
+                               if(cdata==null) {\r
+                                       cdata = new char[end];\r
+                                       new String(data).getChars(0, end, cdata, 0);\r
+                               }\r
+                       }\r
+                       writer.write(cdata,0,end);\r
+               }\r
+               public void write(OutputStream os) throws IOException {\r
+                       os.write(data,0,end);\r
+               }\r
+\r
+       }\r
+\r
+       public void setEnv(LogTarget env) {\r
+               logT = env;\r
+       }\r
+\r
+       /**\r
+        * Cleanup thread to remove older items if max Cache is reached.\r
+        *\r
+        */\r
+       private static class Cleanup extends TimerTask {\r
+               private int maxSize;\r
+               private NavigableMap<String, Content> content;\r
+               \r
+               public Cleanup(NavigableMap<String, Content> content, int size) {\r
+                       maxSize = size;\r
+                       this.content = content;\r
+               }\r
+               \r
+               private class Comp implements Comparable<Comp> {\r
+                       public Map.Entry<String, Content> entry;\r
+                       \r
+                       public Comp(Map.Entry<String, Content> en) {\r
+                               entry = en;\r
+                       }\r
+                       \r
+                       @Override\r
+                       public int compareTo(Comp o) {\r
+                               return (int)(entry.getValue().access-o.entry.getValue().access);\r
+                       }\r
+                       \r
+               }\r
+               @SuppressWarnings("unchecked")\r
+               @Override\r
+               public void run() {\r
+                       int size = content.size();\r
+                       if(size>maxSize) {\r
+                               ArrayList<Comp> scont = new ArrayList<Comp>(size);\r
+                               Object[] entries = content.entrySet().toArray();\r
+                               for(int i=0;i<size;++i) {\r
+                                       scont.add(i, new Comp((Map.Entry<String,Content>)entries[i]));\r
+                               }\r
+                               Collections.sort(scont);\r
+                               int end = size - ((maxSize/4)*3); // reduce to 3/4 of max size\r
+                               System.out.println("------ Cleanup Cycle ------ " + new Date().toString() + " -------");\r
+                               for(int i=0;i<end;++i) {\r
+                                       Entry<String, Content> entry = scont.get(i).entry;\r
+                                       content.remove(entry.getKey());\r
+                                       System.out.println("removed Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString());\r
+                               }\r
+                               for(int i=end;i<size;++i) {\r
+                                       Entry<String, Content> entry = scont.get(i).entry;\r
+                                       System.out.println("remaining Cache Item " + entry.getKey() + "/" + new Date(entry.getValue().access).toString());\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r