1 /*
   2  * Copyright (c) 2010, 2016, 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.OptimisticTypesPersistence;
  42 import jdk.nashorn.internal.codegen.types.Type;
  43 import jdk.nashorn.internal.runtime.logging.DebugLogger;
  44 import jdk.nashorn.internal.runtime.logging.Loggable;
  45 import jdk.nashorn.internal.runtime.logging.Logger;
  46 import jdk.nashorn.internal.runtime.options.Options;
  47 
  48 /**
  49  * A code cache for persistent caching of compiled scripts.
  50  */
  51 @Logger(name="codestore")
  52 public abstract class CodeStore implements Loggable {
  53 
  54     private DebugLogger log;
  55 
  56     /**
  57      * Constructor
  58      */
  59     protected CodeStore() {
  60     }
  61 
  62     @Override
  63     public DebugLogger initLogger(final Context context) {
  64         log = context.getLogger(getClass());
  65         return log;
  66     }
  67 
  68     @Override
  69     public DebugLogger getLogger() {
  70         return log;
  71     }
  72 
  73     /**
  74      * Returns a new code store instance.
  75      *
  76      * @param context the current context
  77      * @return The instance, or null if code store could not be created
  78      */
  79     public static CodeStore newCodeStore(final Context context) {
  80         try {
  81             final CodeStore store = new DirectoryCodeStore(context);
  82             store.initLogger(context);
  83             return store;
  84         } catch (final IOException e) {
  85             context.getLogger(CodeStore.class).warning("failed to create cache directory ", e);
  86             return null;
  87         }
  88     }
  89 
  90 
  91     /**
  92      * Store a compiled script in the cache.
  93      *
  94      * @param functionKey   the function key
  95      * @param source        the source
  96      * @param mainClassName the main class name
  97      * @param classBytes    a map of class bytes
  98      * @param initializers  the function initializers
  99      * @param constants     the constants array
 100      * @param compilationId the compilation id
 101      *
 102      * @return stored script
 103      */
 104     public StoredScript store(final String functionKey,
 105                               final Source source,
 106                               final String mainClassName,
 107                               final Map<String, byte[]> classBytes,
 108                               final Map<Integer, FunctionInitializer> initializers,
 109                               final Object[] constants,
 110                               final int compilationId) {
 111         return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId));
 112     }
 113 
 114     /**
 115      * Stores a compiled script.
 116      *
 117      * @param functionKey the function key
 118      * @param source the source
 119      * @param script The compiled script
 120      * @return The compiled script or {@code null} if not stored
 121      */
 122     public abstract StoredScript store(final String functionKey,
 123                                        final Source source,
 124                                        final StoredScript script);
 125 
 126     /**
 127      * Return a compiled script from the cache, or null if it isn't found.
 128      *
 129      * @param source      the source
 130      * @param functionKey the function key
 131      * @return the stored script or null
 132      */
 133     public abstract StoredScript load(final Source source, final String functionKey);
 134 
 135     /**
 136      * Returns a new StoredScript instance.
 137      *
 138      * @param source the source
 139      * @param mainClassName the main class name
 140      * @param classBytes a map of class bytes
 141      * @param initializers function initializers
 142      * @param constants the constants array
 143      * @param compilationId the compilation id
 144      *
 145      * @return The compiled script
 146      */
 147     public StoredScript storedScriptFor(final Source source, final String mainClassName,
 148                                         final Map<String, byte[]> classBytes,
 149                                         final Map<Integer, FunctionInitializer> initializers,
 150                                         final Object[] constants, final int compilationId) {
 151         for (final Object constant : constants) {
 152             // Make sure all constant data is serializable
 153             if (!(constant instanceof Serializable)) {
 154                 getLogger().warning("cannot store ", source, " non serializable constant ", constant);
 155                 return null;
 156             }
 157         }
 158         return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
 159     }
 160 
 161     /**
 162      * Generate a string representing the function with {@code functionId} and {@code paramTypes}.
 163      * @param functionId function id
 164      * @param paramTypes parameter types
 165      * @return a string representing the function
 166      */
 167     public static String getCacheKey(final Object functionId, final Type[] paramTypes) {
 168         final StringBuilder b = new StringBuilder().append(functionId);
 169         if(paramTypes != null && paramTypes.length > 0) {
 170             b.append('-');
 171             for(final Type t: paramTypes) {
 172                 b.append(Type.getShortSignatureDescriptor(t));
 173             }
 174         }
 175         return b.toString();
 176     }
 177 
 178     /**
 179      * A store using a file system directory.
 180      */
 181     public static class DirectoryCodeStore extends CodeStore {
 182 
 183         // Default minimum size for storing a compiled script class
 184         private final static int DEFAULT_MIN_SIZE = 1000;
 185 
 186         private final File dir;
 187         private final boolean readOnly;
 188         private final int minSize;
 189 
 190         /**
 191          * Constructor
 192          *
 193          * @param context the current context
 194          * @throws IOException if there are read/write problems with the cache and cache directory
 195          */
 196         public DirectoryCodeStore(final Context context) throws IOException {
 197             this(context, Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE);
 198         }
 199 
 200         /**
 201          * Constructor
 202          *
 203          * @param context the current context
 204          * @param path    directory to store code in
 205          * @param readOnly is this a read only code store
 206          * @param minSize minimum file size for caching scripts
 207          * @throws IOException if there are read/write problems with the cache and cache directory
 208          */
 209         public DirectoryCodeStore(final Context context, final String path, final boolean readOnly, final int minSize) throws IOException {
 210             this.dir = checkDirectory(path, context.getEnv(), readOnly);
 211             this.readOnly = readOnly;
 212             this.minSize = minSize;
 213         }
 214 
 215         private static File checkDirectory(final String path, final ScriptEnvironment env, final boolean readOnly) throws IOException {
 216             try {
 217                 return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
 218                     @Override
 219                     public File run() throws IOException {
 220                         final File dir = new File(path, getVersionDir(env)).getAbsoluteFile();
 221                         if (readOnly) {
 222                             if (!dir.exists() || !dir.isDirectory()) {
 223                                 throw new IOException("Not a directory: " + dir.getPath());
 224                             } else if (!dir.canRead()) {
 225                                 throw new IOException("Directory not readable: " + dir.getPath());
 226                             }
 227                         } else if (!dir.exists() && !dir.mkdirs()) {
 228                             throw new IOException("Could not create directory: " + dir.getPath());
 229                         } else if (!dir.isDirectory()) {
 230                             throw new IOException("Not a directory: " + dir.getPath());
 231                         } else if (!dir.canRead() || !dir.canWrite()) {
 232                             throw new IOException("Directory not readable or writable: " + dir.getPath());
 233                         }
 234                         return dir;
 235                     }
 236                 });
 237             } catch (final PrivilegedActionException e) {
 238                 throw (IOException) e.getException();
 239             }
 240         }
 241 
 242         private static String getVersionDir(final ScriptEnvironment env) throws IOException {
 243             try {
 244                 final String versionDir = OptimisticTypesPersistence.getVersionDirName();
 245                 return env._optimistic_types ? versionDir + "_opt" : versionDir;
 246             } catch (final Exception e) {
 247                 throw new IOException(e);
 248             }
 249         }
 250 
 251         @Override
 252         public StoredScript load(final Source source, final String functionKey) {
 253             if (belowThreshold(source)) {
 254                 return null;
 255             }
 256 
 257             final File file = getCacheFile(source, functionKey);
 258 
 259             try {
 260                 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
 261                     @Override
 262                     public StoredScript run() throws IOException, ClassNotFoundException {
 263                         if (!file.exists()) {
 264                             return null;
 265                         }
 266                         try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
 267                             final StoredScript storedScript = (StoredScript) in.readObject();
 268                             getLogger().info("loaded ", source, "-", functionKey);
 269                             return storedScript;
 270                         }
 271                     }
 272                 });
 273             } catch (final PrivilegedActionException e) {
 274                 getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
 275                 return null;
 276             }
 277         }
 278 
 279         @Override
 280         public StoredScript store(final String functionKey, final Source source, final StoredScript script) {
 281             if (readOnly || script == null || belowThreshold(source)) {
 282                 return null;
 283             }
 284 
 285             final File file = getCacheFile(source, functionKey);
 286 
 287             try {
 288                 return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
 289                     @Override
 290                     public StoredScript run() throws IOException {
 291                         try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
 292                             out.writeObject(script);
 293                         }
 294                         getLogger().info("stored ", source, "-", functionKey);
 295                         return script;
 296                     }
 297                 });
 298             } catch (final PrivilegedActionException e) {
 299                 getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
 300                 return null;
 301             }
 302         }
 303 
 304 
 305         private File getCacheFile(final Source source, final String functionKey) {
 306             return new File(dir, source.getDigest() + '-' + functionKey);
 307         }
 308 
 309         private boolean belowThreshold(final Source source) {
 310             if (source.getLength() < minSize) {
 311                 getLogger().info("below size threshold ", source);
 312                 return true;
 313             }
 314             return false;
 315         }
 316     }
 317 }
 318