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