1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import java.io.BufferedInputStream;
  29 import java.io.BufferedOutputStream;
  30 import java.io.File;
  31 import java.io.FileInputStream;
  32 import java.io.FileOutputStream;
  33 import java.io.IOException;
  34 import java.io.ObjectInputStream;
  35 import java.io.ObjectOutputStream;
  36 import java.io.Serializable;
  37 import java.security.AccessController;
  38 import java.security.PrivilegedActionException;
  39 import java.security.PrivilegedExceptionAction;
  40 import java.util.Map;
  41 import jdk.nashorn.internal.codegen.types.Type;
  42 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  43 import jdk.nashorn.internal.runtime.logging.Loggable;
  44 import jdk.nashorn.internal.runtime.logging.Logger;
  45 
  46 /**
  47  * A code cache for persistent caching of compiled scripts.
  48  */
  49 @Logger(name="codestore")
  50 final class CodeStore implements Loggable {
  51 
  52     private final File dir;
  53     private final int minSize;
  54     private final DebugLogger log;
  55 
  56     // Default minimum size for storing a compiled script class
  57     private final static int DEFAULT_MIN_SIZE = 1000;
  58 
  59     /**
  60      * Constructor
  61      * @throws IOException
  62      */
  63     public CodeStore(final Context context, final String path) throws IOException {
  64         this(context, path, DEFAULT_MIN_SIZE);
  65     }
  66 
  67     /**
  68      * Constructor
  69      * @param path directory to store code in
  70      * @param minSize minimum file size for caching scripts
  71      * @throws IOException
  72      */
  73     public CodeStore(final Context context, final String path, final int minSize) throws IOException {
  74         this.dir = checkDirectory(path);
  75         this.minSize = minSize;
  76         this.log = initLogger(context);
  77     }
  78 
  79     @Override
  80     public DebugLogger initLogger(final Context context) {
  81          return context.getLogger(getClass());
  82     }
  83 
  84     @Override
  85     public DebugLogger getLogger() {
  86         return log;
  87     }
  88 
  89     private static File checkDirectory(final String path) throws IOException {
  90         try {
  91             return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
  92                 @Override
  93                 public File run() throws IOException {
  94                     final File dir = new File(path).getAbsoluteFile();
  95                     if (!dir.exists() && !dir.mkdirs()) {
  96                         throw new IOException("Could not create directory: " + dir.getPath());
  97                     } else if (!dir.isDirectory()) {
  98                         throw new IOException("Not a directory: " + dir.getPath());
  99                     } else if (!dir.canRead() || !dir.canWrite()) {
 100                         throw new IOException("Directory not readable or writable: " + dir.getPath());
 101                     }
 102                     return dir;
 103                 }
 104             });
 105         } catch (final PrivilegedActionException e) {
 106             throw (IOException) e.getException();
 107         }
 108     }
 109 
 110     private File getCacheFile(final Source source, final String functionKey) {
 111         return new File(dir, source.getDigest() + '-' + functionKey);
 112     }
 113 
 114     /**
 115      * Generate a string representing the function with {@code functionId} and {@code paramTypes}.
 116      * @param functionId function id
 117      * @param paramTypes parameter types
 118      * @return a string representing the function
 119      */
 120     public static String getCacheKey(final int functionId, final Type[] paramTypes) {
 121         final StringBuilder b = new StringBuilder().append(functionId);
 122         if(paramTypes != null && paramTypes.length > 0) {
 123             b.append('-');
 124             for(final Type t: paramTypes) {
 125                 b.append(Type.getShortSignatureDescriptor(t));
 126             }
 127         }
 128         return b.toString();
 129     }
 130 
 131     /**
 132      * Return a compiled script from the cache, or null if it isn't found.
 133      *
 134      * @param source the source
 135      * @param functionKey the function key
 136      * @return the stored script or null
 137      */
 138     public StoredScript loadScript(final Source source, final String functionKey) {
 139         if (source.getLength() < minSize) {
 140             return null;
 141         }
 142 
 143         final File file = getCacheFile(source, functionKey);
 144 
 145         try {
 146             return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
 147                 @Override
 148                 public StoredScript run() throws IOException, ClassNotFoundException {
 149                     if (!file.exists()) {
 150                         return null;
 151                     }
 152                     try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
 153                         final StoredScript storedScript = (StoredScript) in.readObject();
 154                         getLogger().info("loaded ", source, "-", functionKey);
 155                         return storedScript;
 156                     }
 157                 }
 158             });
 159         } catch (final PrivilegedActionException e) {
 160             getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
 161             return null;
 162         }
 163     }
 164 
 165     /**
 166      * Store a compiled script in the cache.
 167      *
 168      * @param functionKey the function key
 169      * @param source the source
 170      * @param mainClassName the main class name
 171      * @param classBytes a map of class bytes
 172      * @param constants the constants array
 173      */
 174     public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes,
 175                           final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) {
 176         if (source.getLength() < minSize) {
 177             return;
 178         }
 179         for (final Object constant : constants) {
 180             // Make sure all constant data is serializable
 181             if (! (constant instanceof Serializable)) {
 182                 getLogger().warning("cannot store ", source, " non serializable constant ", constant);
 183                 return;
 184             }
 185         }
 186 
 187         final File file = getCacheFile(source, functionKey);
 188         final StoredScript script = new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
 189 
 190         try {
 191             AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
 192                 @Override
 193                 public Void run() throws IOException {
 194                     try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
 195                         out.writeObject(script);
 196                     }
 197                     getLogger().info("stored ", source, "-", functionKey);
 198                     return null;
 199                 }
 200             });
 201         } catch (final PrivilegedActionException e) {
 202             getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
 203         }
 204     }
 205 }
 206