src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/CodeStore.java

Print this page

        

@@ -32,85 +32,148 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.ServiceLoader;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
 import jdk.nashorn.internal.runtime.logging.Logger;
+import jdk.nashorn.internal.runtime.options.Options;
 
 /**
  * A code cache for persistent caching of compiled scripts.
  */
 @Logger(name="codestore")
-final class CodeStore implements Loggable {
-
-    private final File dir;
-    private final int minSize;
-    private final DebugLogger log;
-
-    // Default minimum size for storing a compiled script class
-    private final static int DEFAULT_MIN_SIZE = 1000;
+public abstract class CodeStore implements Loggable {
 
     /**
-     * Constructor
-     * @throws IOException
+     * Permission needed to provide a CodeStore instance via ServiceLoader.
      */
-    public CodeStore(final Context context, final String path) throws IOException {
-        this(context, path, DEFAULT_MIN_SIZE);
-    }
+    public final static String NASHORN_PROVIDE_CODE_STORE = "nashorn.provideCodeStore";
+
+    private DebugLogger log;
 
     /**
      * Constructor
-     * @param path directory to store code in
-     * @param minSize minimum file size for caching scripts
-     * @throws IOException
      */
-    public CodeStore(final Context context, final String path, final int minSize) throws IOException {
-        this.dir = checkDirectory(path);
-        this.minSize = minSize;
-        this.log = initLogger(context);
+    protected CodeStore() {
     }
 
     @Override
     public DebugLogger initLogger(final Context context) {
-         return context.getLogger(getClass());
+        log = context.getLogger(getClass());
+        return log;
     }
 
     @Override
     public DebugLogger getLogger() {
         return log;
     }
 
-    private static File checkDirectory(final String path) throws IOException {
+    /**
+     * Returns a new code store instance.
+     *
+     * @param context the current context
+     * @return The instance
+     * @throws IOException If an error occurs
+     */
+    public static CodeStore newCodeStore(final Context context) throws IOException {
+        final Class<CodeStore> baseClass = CodeStore.class;
         try {
-            return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
-                @Override
-                public File run() throws IOException {
-                    final File dir = new File(path).getAbsoluteFile();
-                    if (!dir.exists() && !dir.mkdirs()) {
-                        throw new IOException("Could not create directory: " + dir.getPath());
-                    } else if (!dir.isDirectory()) {
-                        throw new IOException("Not a directory: " + dir.getPath());
-                    } else if (!dir.canRead() || !dir.canWrite()) {
-                        throw new IOException("Directory not readable or writable: " + dir.getPath());
+            // security check first
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(new RuntimePermission(NASHORN_PROVIDE_CODE_STORE));
+            }
+            final ServiceLoader<CodeStore> services = ServiceLoader.load(baseClass);
+            final Iterator<CodeStore> iterator = services.iterator();
+            if (iterator.hasNext()) {
+                final CodeStore store = iterator.next();
+                store.initLogger(context).info("using code store provider ", store.getClass().getCanonicalName());
+                return store;
+            }
+        } catch (final AccessControlException e) {
+            context.getLogger(CodeStore.class).warning("failed to load code store provider ", e);
+        }
+        final CodeStore store = new DirectoryCodeStore();
+        store.initLogger(context);
+        return store;
                     }
-                    return dir;
+
+
+    /**
+     * Store a compiled script in the cache.
+     *
+     * @param functionKey   the function key
+     * @param source        the source
+     * @param mainClassName the main class name
+     * @param classBytes    a map of class bytes
+     * @param initializers  the function initializers
+     * @param constants     the constants array
+     * @param compilationId the compilation id
+     */
+    public StoredScript store(final String functionKey,
+                              final Source source,
+                              final String mainClassName,
+                              final Map<String, byte[]> classBytes,
+                              final Map<Integer, FunctionInitializer> initializers,
+                              final Object[] constants,
+                              final int compilationId) {
+        return store(functionKey, source, storedScriptFor(source, mainClassName, classBytes, initializers, constants, compilationId));
                 }
-            });
-        } catch (final PrivilegedActionException e) {
-            throw (IOException) e.getException();
+
+    /**
+     * Stores a compiled script.
+     *
+     * @param functionKey the function key
+     * @param source the source
+     * @param script The compiled script
+     * @return The compiled script or {@code null} if not stored
+     */
+    public abstract StoredScript store(final String functionKey,
+                                       final Source source,
+                                       final StoredScript script);
+
+    /**
+     * Return a compiled script from the cache, or null if it isn't found.
+     *
+     * @param source      the source
+     * @param functionKey the function key
+     * @return the stored script or null
+     */
+    public abstract StoredScript load(final Source source, final String functionKey);
+
+    /**
+     * Returns a new StoredScript instance.
+     *
+     * @param mainClassName the main class name
+     * @param classBytes a map of class bytes
+     * @param initializers function initializers
+     * @param constants the constants array
+     * @param compilationId the compilation id
+     * @return The compiled script
+     */
+    public StoredScript storedScriptFor(final Source source, final String mainClassName,
+                                        final Map<String, byte[]> classBytes,
+                                        final Map<Integer, FunctionInitializer> initializers,
+                                        final Object[] constants, final int compilationId) {
+        for (final Object constant : constants) {
+            // Make sure all constant data is serializable
+            if (!(constant instanceof Serializable)) {
+                getLogger().warning("cannot store ", source, " non serializable constant ", constant);
+                return null;
         }
     }
-
-    private File getCacheFile(final Source source, final String functionKey) {
-        return new File(dir, source.getDigest() + '-' + functionKey);
+        return new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
     }
 
     /**
      * Generate a string representing the function with {@code functionId} and {@code paramTypes}.
      * @param functionId function id

@@ -127,17 +190,72 @@
         }
         return b.toString();
     }
 
     /**
-     * Return a compiled script from the cache, or null if it isn't found.
+     * A store using a file system directory.
+     */
+    public static class DirectoryCodeStore extends CodeStore {
+
+        // Default minimum size for storing a compiled script class
+        private final static int DEFAULT_MIN_SIZE = 1000;
+
+        private final File dir;
+        private final boolean readOnly;
+        private final int minSize;
+
+        /**
+         * Constructor
      *
-     * @param source the source
-     * @param functionKey the function key
-     * @return the stored script or null
+         * @throws IOException
+         */
+        public DirectoryCodeStore() throws IOException {
+            this(Options.getStringProperty("nashorn.persistent.code.cache", "nashorn_code_cache"), false, DEFAULT_MIN_SIZE);
+        }
+
+        /**
+         * Constructor
+         *
+         * @param path    directory to store code in
+         * @param minSize minimum file size for caching scripts
+         * @throws IOException
      */
-    public StoredScript loadScript(final Source source, final String functionKey) {
+        public DirectoryCodeStore(final String path, final boolean readOnly, final int minSize) throws IOException {
+            this.dir = checkDirectory(path, readOnly);
+            this.readOnly = readOnly;
+            this.minSize = minSize;
+        }
+
+        private static File checkDirectory(final String path, final boolean readOnly) throws IOException {
+            try {
+                return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
+                    @Override
+                    public File run() throws IOException {
+                        final File dir = new File(path).getAbsoluteFile();
+                        if (readOnly) {
+                            if (!dir.exists() || !dir.isDirectory()) {
+                                throw new IOException("Not a directory: " + dir.getPath());
+                            } else if (!dir.canRead()) {
+                                throw new IOException("Directory not readable: " + dir.getPath());
+                            }
+                        } else if (!dir.exists() && !dir.mkdirs()) {
+                            throw new IOException("Could not create directory: " + dir.getPath());
+                        } else if (!dir.isDirectory()) {
+                            throw new IOException("Not a directory: " + dir.getPath());
+                        } else if (!dir.canRead() || !dir.canWrite()) {
+                            throw new IOException("Directory not readable or writable: " + dir.getPath());
+                        }
+                        return dir;
+                    }
+                });
+            } catch (final PrivilegedActionException e) {
+                throw (IOException) e.getException();
+            }
+        }
+
+        @Override
+        public StoredScript load(final Source source, final String functionKey) {
         if (source.getLength() < minSize) {
             return null;
         }
 
         final File file = getCacheFile(source, functionKey);

@@ -160,47 +278,45 @@
             getLogger().warning("failed to load ", source, "-", functionKey, ": ", e.getException());
             return null;
         }
     }
 
-    /**
-     * Store a compiled script in the cache.
-     *
-     * @param functionKey the function key
-     * @param source the source
-     * @param mainClassName the main class name
-     * @param classBytes a map of class bytes
-     * @param constants the constants array
-     */
-    public void storeScript(final String functionKey, final Source source, final String mainClassName, final Map<String, byte[]> classBytes,
-                          final Map<Integer, FunctionInitializer> initializers, final Object[] constants, final int compilationId) {
-        if (source.getLength() < minSize) {
-            return;
-        }
-        for (final Object constant : constants) {
-            // Make sure all constant data is serializable
-            if (! (constant instanceof Serializable)) {
-                getLogger().warning("cannot store ", source, " non serializable constant ", constant);
-                return;
-            }
+        @Override
+        public StoredScript store(final String functionKey, final Source source, final StoredScript script) {
+            if (readOnly || script == null || belowThreshold(source)) {
+                return null;
         }
 
         final File file = getCacheFile(source, functionKey);
-        final StoredScript script = new StoredScript(compilationId, mainClassName, classBytes, initializers, constants);
 
         try {
-            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                return AccessController.doPrivileged(new PrivilegedExceptionAction<StoredScript>() {
                 @Override
-                public Void run() throws IOException {
+                    public StoredScript run() throws IOException {
                     try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
                         out.writeObject(script);
                     }
                     getLogger().info("stored ", source, "-", functionKey);
-                    return null;
+                        return script;
                 }
             });
         } catch (final PrivilegedActionException e) {
             getLogger().warning("failed to store ", script, "-", functionKey, ": ", e.getException());
+                return null;
+            }
+        }
+
+
+        private File getCacheFile(final Source source, final String functionKey) {
+            return new File(dir, source.getDigest() + '-' + functionKey);
+        }
+
+        private boolean belowThreshold(final Source source) {
+            if (source.getLength() < minSize) {
+                getLogger().info("below size threshold ", source);
+                return true;
+            }
+            return false;
         }
     }
 }