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