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