aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/CodeStore.java
blob: 8f5959993cfcadd4c54b3985529de1452638b809 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Base64;
import java.util.Map;

/**
 * A code cache for persistent caching of compiled scripts.
 */
final class CodeStore {

    private final File dir;
    private final int minSize;

    // Message digest to file name encoder
    private final static Base64.Encoder BASE64 = Base64.getUrlEncoder().withoutPadding();

    // Default minimum size for storing a compiled script class
    private final static int DEFAULT_MIN_SIZE = 1000;

    /**
     * Constructor
     * @param path directory to store code in
     * @throws IOException
     */
    public CodeStore(final String path) throws IOException {
        this(path, DEFAULT_MIN_SIZE);
    }

    /**
     * Constructor
     * @param path directory to store code in
     * @param minSize minimum file size for caching scripts
     * @throws IOException
     */
    public CodeStore(final String path, final int minSize) throws IOException {
        this.dir = checkDirectory(path);
        this.minSize = minSize;
    }

    private static File checkDirectory(final String path) throws IOException {
        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);
                    } else if (!dir.isDirectory()) {
                        throw new IOException("Not a directory: " + dir);
                    } else if (!dir.canRead() || !dir.canWrite()) {
                        throw new IOException("Directory not readable or writable: " + dir);
                    }
                    return dir;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
    }

    /**
     * Return a compiled script from the cache, or null if it isn't found.
     *
     * @param source the source
     * @return the compiled script or null
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public CompiledScript getScript(final Source source) throws IOException, ClassNotFoundException {
        if (source.getLength() < minSize) {
            return null;
        }

        final String digest = BASE64.encodeToString(source.getDigest());
        final File file = new File(dir, digest);

        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction<CompiledScript>() {
                @Override
                public CompiledScript run() throws IOException, ClassNotFoundException {
                    if (!file.exists()) {
                        return null;
                    }
                    try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                        CompiledScript compiledScript = (CompiledScript) in.readObject();
                        compiledScript.setSource(source);
                        return compiledScript;
                    }
                }
            });
        } catch (PrivilegedActionException e) {
            final Exception ex = e.getException();
            if (ex instanceof IOException) {
                throw  (IOException) ex;
            } else if (ex instanceof ClassNotFoundException) {
                throw (ClassNotFoundException) ex;
            }
            throw (new RuntimeException(ex));
        }
    }

    /**
     * Store a compiled script in the cache.
     *
     * @param source the source
     * @param mainClassName the main class name
     * @param classBytes a map of class bytes
     * @param constants the constants array
     * @throws IOException
     */
    public void putScript(final Source source, final String mainClassName, final Map<String, byte[]> classBytes, final Object[] constants)
            throws IOException {
        if (source.getLength() < minSize) {
            return;
        }
        for (final Object constant : constants) {
            // Make sure all constant data is serializable
            if (! (constant instanceof Serializable)) {
                return;
            }
        }

        final String digest = BASE64.encodeToString(source.getDigest());
        final File file = new File(dir, digest);
        final CompiledScript script = new CompiledScript(source, mainClassName, classBytes, constants);

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                @Override
                public Void run() throws IOException {
                    try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
                        out.writeObject(script);
                    }
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
             throw (IOException) e.getException();
        }
    }
}