File: JreCompat.java

package info (click to toggle)
tomcat9 9.0.111-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 47,852 kB
  • sloc: java: 381,732; xml: 70,399; jsp: 4,682; sh: 1,336; perl: 324; makefile: 18; ansic: 14
file content (658 lines) | stat: -rw-r--r-- 23,704 bytes parent folder | download
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.tomcat.util.compat;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.PrivilegedExceptionAction;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import java.util.jar.JarFile;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.security.auth.Subject;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;

/**
 * This is the base implementation class for JRE compatibility and provides an implementation based on Java 8.
 * Subclasses may extend this class and provide alternative implementations for later JRE versions
 */
public class JreCompat {

    private static final Log log = LogFactory.getLog(JreCompat.class);
    private static final StringManager sm = StringManager.getManager(JreCompat.class);

    private static final int RUNTIME_MAJOR_VERSION = 8;

    private static final JreCompat instance;
    private static final boolean graalAvailable;
    private static final boolean jre9Available;
    private static final boolean jre11Available;
    private static final boolean jre12Available;
    private static final boolean jre16Available;
    private static final boolean jre19Available;
    private static final boolean jre20Available;
    private static final boolean jre21Available;
    private static final boolean jre22Available;

    protected static final String USE_CANON_CACHES_CMD_ARG = "-Dsun.io.useCanonCaches=";
    protected static volatile Boolean canonCachesDisabled;
    protected static final Object canonCachesDisabledLock = new Object();
    protected static volatile Optional<Field> useCanonCachesField;
    protected static final Object useCanonCachesFieldLock = new Object();
    protected static final Method setApplicationProtocolsMethod;
    protected static final Method getApplicationProtocolMethod;

    static {
        boolean result = false;
        try {
            Class<?> nativeImageClazz = Class.forName("org.graalvm.nativeimage.ImageInfo");
            result = Boolean.TRUE.equals(nativeImageClazz.getMethod("inImageCode").invoke(null));
        } catch (ClassNotFoundException e) {
            // Must be Graal
        } catch (ReflectiveOperationException | IllegalArgumentException e) {
            // Should never happen
        }
        graalAvailable = result || System.getProperty("org.graalvm.nativeimage.imagecode") != null;

        // This is Tomcat 9 with a minimum Java version of Java 8.
        // Look for the highest supported JVM first
        if (Jre22Compat.isSupported()) {
            instance = new Jre22Compat();
            jre22Available = true;
            jre21Available = true;
            jre20Available = true;
            jre19Available = true;
            jre16Available = true;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre21Compat.isSupported()) {
            instance = new Jre21Compat();
            jre22Available = false;
            jre21Available = true;
            jre20Available = true;
            jre19Available = true;
            jre16Available = true;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre20Compat.isSupported()) {
            instance = new Jre20Compat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = true;
            jre19Available = true;
            jre16Available = true;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre19Compat.isSupported()) {
            instance = new Jre19Compat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = false;
            jre19Available = true;
            jre16Available = true;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre16Compat.isSupported()) {
            instance = new Jre16Compat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = false;
            jre19Available = false;
            jre16Available = true;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre12Compat.isSupported()) {
            instance = new Jre12Compat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = false;
            jre19Available = false;
            jre16Available = false;
            jre12Available = true;
            jre9Available = true;
        } else if (Jre9Compat.isSupported()) {
            instance = new Jre9Compat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = false;
            jre19Available = false;
            jre16Available = false;
            jre12Available = false;
            jre9Available = true;
        } else {
            instance = new JreCompat();
            jre22Available = false;
            jre21Available = false;
            jre20Available = false;
            jre19Available = false;
            jre16Available = false;
            jre12Available = false;
            jre9Available = false;
        }
        jre11Available = instance.jarFileRuntimeMajorVersion() >= 11;

        Method m1 = null;
        Method m2 = null;
        try {
            m1 = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
            m2 = SSLEngine.class.getMethod("getApplicationProtocol");
        } catch (ReflectiveOperationException | IllegalArgumentException e) {
            // Only the newest Java 8 have the ALPN API, so ignore
        }
        setApplicationProtocolsMethod = m1;
        getApplicationProtocolMethod = m2;
    }


    public static JreCompat getInstance() {
        return instance;
    }


    public static boolean isGraalAvailable() {
        return graalAvailable;
    }


    public static boolean isAlpnSupported() {
        return setApplicationProtocolsMethod != null && getApplicationProtocolMethod != null;
    }


    public static boolean isJre9Available() {
        return jre9Available;
    }


    public static boolean isJre11Available() {
        return jre11Available;
    }


    public static boolean isJre12Available() {
        return jre12Available;
    }


    public static boolean isJre16Available() {
        return jre16Available;
    }


    public static boolean isJre19Available() {
        return jre19Available;
    }


    public static boolean isJre20Available() {
        return jre20Available;
    }


    public static boolean isJre21Available() {
        return jre21Available;
    }


    public static boolean isJre22Available() {
        return jre22Available;
    }


    // Java 8 implementation of Java 9 methods

    /**
     * Test if the provided exception is an instance of java.lang.reflect.InaccessibleObjectException.
     *
     * @param t The exception to test
     *
     * @return {@code true} if the exception is an instance of InaccessibleObjectException, otherwise {@code false}
     */
    public boolean isInstanceOfInaccessibleObjectException(Throwable t) {
        // Exception does not exist prior to Java 9
        return false;
    }


    /**
     * Set the application protocols the server will accept for ALPN
     *
     * @param sslParameters The SSL parameters for a connection
     * @param protocols     The application protocols to be allowed for that connection
     */
    public void setApplicationProtocols(SSLParameters sslParameters, String[] protocols) {
        if (setApplicationProtocolsMethod != null) {
            try {
                setApplicationProtocolsMethod.invoke(sslParameters, (Object) protocols);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                throw new UnsupportedOperationException(e);
            }
        } else {
            throw new UnsupportedOperationException(sm.getString("jreCompat.noApplicationProtocols"));
        }
    }


    /**
     * Get the application protocol that has been negotiated for connection associated with the given SSLEngine.
     *
     * @param sslEngine The SSLEngine for which to obtain the negotiated protocol
     *
     * @return The name of the negotiated protocol
     */
    public String getApplicationProtocol(SSLEngine sslEngine) {
        if (getApplicationProtocolMethod != null) {
            try {
                return (String) getApplicationProtocolMethod.invoke(sslEngine);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                throw new UnsupportedOperationException(e);
            }
        } else {
            throw new UnsupportedOperationException(sm.getString("jreCompat.noApplicationProtocol"));
        }
    }


    /**
     * Disables caching for JAR URL connections. For Java 8 and earlier, this also disables caching for ALL URL
     * connections.
     *
     * @throws IOException If a dummy JAR URLConnection can not be created
     */
    public void disableCachingForJarUrlConnections() throws IOException {
        // Doesn't matter that this JAR doesn't exist - just as
        // long as the URL is well-formed
        URL url = new URL("jar:file://dummy.jar!/");
        URLConnection uConn = url.openConnection();
        uConn.setDefaultUseCaches(false);
    }


    /**
     * Obtains the URLs for all the JARs on the module path when the JVM starts and adds them to the provided Deque.
     *
     * @param classPathUrlsToProcess The Deque to which the modules should be added
     */
    public void addBootModulePath(Deque<URL> classPathUrlsToProcess) {
        // NO-OP for Java 8. There is no module path.
    }


    /**
     * Creates a new JarFile instance. When running on Java 9 and later, the JarFile will be multi-release JAR aware.
     * While this isn't strictly required to be in this package, it is provided as a convenience method.
     *
     * @param s The JAR file to open
     *
     * @return A JarFile instance based on the provided path
     *
     * @throws IOException If an I/O error occurs creating the JarFile instance
     */
    public final JarFile jarFileNewInstance(String s) throws IOException {
        return jarFileNewInstance(new File(s));
    }


    /**
     * Creates a new JarFile instance. When running on Java 9 and later, the JarFile will be multi-release JAR aware.
     *
     * @param f The JAR file to open
     *
     * @return A JarFile instance based on the provided file
     *
     * @throws IOException If an I/O error occurs creating the JarFile instance
     */
    public JarFile jarFileNewInstance(File f) throws IOException {
        return new JarFile(f);
    }


    /**
     * Is this JarFile a multi-release JAR file.
     *
     * @param jarFile The JarFile to test
     *
     * @return {@code true} If it is a multi-release JAR file and is configured to behave as such.
     */
    public boolean jarFileIsMultiRelease(JarFile jarFile) {
        // Java 8 doesn't support multi-release so default to false
        return false;
    }


    public int jarFileRuntimeMajorVersion() {
        return RUNTIME_MAJOR_VERSION;
    }


    /**
     * Is the accessibleObject accessible (as a result of appropriate module exports) on the provided instance?
     *
     * @param base             The specific instance to be tested.
     * @param accessibleObject The method/field/constructor to be tested.
     *
     * @return {code true} if the AccessibleObject can be accessed otherwise {code false}
     */
    public boolean canAccess(Object base, AccessibleObject accessibleObject) {
        // Java 8 doesn't support modules so default to true
        return true;
    }


    /**
     * Is the given class in an exported package?
     *
     * @param type The class to test
     *
     * @return Always {@code true} for Java 8. {@code true} if the enclosing package is exported for Java 9+
     */
    public boolean isExported(Class<?> type) {
        return true;
    }


    /**
     * What is the module of the given class?
     *
     * @param type The class to test
     *
     * @return Always {@code true} for Java 8. {@code true} if the enclosing package is exported for Java 9+
     */
    public String getModuleName(Class<?> type) {
        return "NO_MODULE_JAVA_8";
    }


    // Java 8 implementations of Java 16 methods

    /**
     * Return Unix domain socket address for given path.
     *
     * @param path The path
     *
     * @return the socket address
     */
    public SocketAddress getUnixDomainSocketAddress(String path) {
        return null;
    }


    /**
     * Create server socket channel using the Unix domain socket ProtocolFamily.
     *
     * @return the server socket channel
     */
    public ServerSocketChannel openUnixDomainServerSocketChannel() {
        throw new UnsupportedOperationException(sm.getString("jreCompat.noUnixDomainSocket"));
    }


    /**
     * Create socket channel using the Unix domain socket ProtocolFamily.
     *
     * @return the socket channel
     */
    public SocketChannel openUnixDomainSocketChannel() {
        throw new UnsupportedOperationException(sm.getString("jreCompat.noUnixDomainSocket"));
    }


    // Java 8 implementations of Java 19 methods

    /**
     * Obtains the executor, if any, used to create the provided thread.
     *
     * @param thread The thread to examine
     *
     * @return The executor, if any, that created the provided thread
     *
     * @throws NoSuchFieldException     If a field used via reflection to obtain the executor cannot be found
     * @throws SecurityException        If a security exception occurs while trying to identify the executor
     * @throws IllegalArgumentException If the instance object does not match the class of the field when obtaining a
     *                                      field value via reflection
     * @throws IllegalAccessException   If a field is not accessible due to access restrictions
     */
    public Object getExecutor(Thread thread)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {

        Object result = null;

        // Runnable wrapped by Thread
        // "target" in Sun/Oracle JDK
        // "runnable" in IBM JDK
        // "action" in Apache Harmony
        Object target = null;
        for (String fieldName : new String[] { "target", "runnable", "action" }) {
            try {
                Field targetField = thread.getClass().getDeclaredField(fieldName);
                targetField.setAccessible(true);
                target = targetField.get(thread);
                break;
            } catch (NoSuchFieldException nfe) {
                continue;
            }
        }

        // "java.util.concurrent" code is in public domain,
        // so all implementations are similar including our
        // internal fork.
        if (target != null && target.getClass().getCanonicalName() != null && (target.getClass().getCanonicalName()
                .equals("org.apache.tomcat.util.threads.ThreadPoolExecutor.Worker") ||
                target.getClass().getCanonicalName().equals("java.util.concurrent.ThreadPoolExecutor.Worker"))) {
            Field executorField = target.getClass().getDeclaredField("this$0");
            executorField.setAccessible(true);
            result = executorField.get(target);
        }

        return result;
    }


    // Java 8 implementations of Java 21 methods

    /**
     * Create a thread builder for virtual threads using the given name to name the threads.
     *
     * @param name The base name for the threads
     *
     * @return The thread buidler for virtual threads
     */
    public Object createVirtualThreadBuilder(String name) {
        throw new UnsupportedOperationException(sm.getString("jreCompat.noVirtualThreads"));
    }


    /**
     * Create a thread with the given thread builder and use it to execute the given runnable.
     *
     * @param threadBuilder The thread builder to use to create a thread
     * @param command       The command to run
     */
    public void threadBuilderStart(Object threadBuilder, Runnable command) {
        throw new UnsupportedOperationException(sm.getString("jreCompat.noVirtualThreads"));
    }


    /*
     * This is a slightly different usage of JreCompat.
     *
     * Subject.doAs() was deprecated in Java 18 and replaced with Subject.callAs(). As of Java 23, calling
     * Subject.doAs() will trigger an UnsupportedOperationException unless the java.security.manager system property is
     * set. To avoid Tomcat installations using Spnego authentication having to set this value, JreCompat is used to
     * call Subject.callAs() instead.
     *
     * Because Java versions 18 to 22 inclusive support both the old and the new method, the switch over can occur at
     * any Java version from 18 to 22 inclusive. Java 21 onwards was selected as it as an LTS version and that removes
     * the need to add a Jre18Compat class.
     *
     * So, the slightly longer description for this method is:
     *
     * Java 8 implementation of a method replaced between Java 18 and 22 with the replacement method being used by
     * Tomcat when running on Java 21 onwards.
     */

    public <T> T callAs(Subject subject, Callable<T> action) throws CompletionException {
        try {
            return Subject.doAs(subject, new PrivilegedExceptionAction<T>() {

                @Override
                public T run() throws Exception {
                    return action.call();
                }
            });
        } catch (Exception e) {
            throw new CompletionException(e);
        }
    }


    /*
     * The behaviour of the canonical file name cache varies by Java version.
     *
     * The cache was removed in Java 21 so these methods and the associated code can be removed once the minimum Java
     * version is 21.
     *
     * For 12 <= Java <= 20, the cache was present but disabled by default.
     *
     * For Java < 12, the cache was enabled by default. Tomcat assumes the cache is enabled unless proven otherwise.
     *
     * Tomcat 10.1 has a minimum Java version of 11.
     *
     * The static field in java.io.FileSystem will be set before any application code gets a chance to run. Therefore,
     * the value of that field can be determined by looking at the command line arguments. This enables us to determine
     * the status without having using reflection.
     *
     * This is Java 11.
     */
    public boolean isCanonCachesDisabled() {
        if (canonCachesDisabled != null) {
            return canonCachesDisabled.booleanValue();
        }
        synchronized (canonCachesDisabledLock) {
            if (canonCachesDisabled != null) {
                return canonCachesDisabled.booleanValue();
            }

            boolean cacheEnabled = true;
            List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
            for (String arg : args) {
                // To consider the cache disabled
                // - there must be at least one command line argument that disables it
                // - there must be no command line arguments that enable it
                if (arg.startsWith(USE_CANON_CACHES_CMD_ARG)) {
                    String valueAsString = arg.substring(USE_CANON_CACHES_CMD_ARG.length());
                    boolean valueAsBoolean = Boolean.valueOf(valueAsString).booleanValue();
                    if (valueAsBoolean) {
                        canonCachesDisabled = Boolean.FALSE;
                        return false;
                    } else {
                        cacheEnabled = false;
                    }
                }
            }
            if (cacheEnabled) {
                canonCachesDisabled = Boolean.FALSE;
            } else {
                canonCachesDisabled = Boolean.TRUE;
            }
            return canonCachesDisabled.booleanValue();
        }
    }


    /**
     * Disable the global canonical file cache.
     *
     * @return {@code true} if the global canonical file cache was already disabled prior to this call or was disabled
     *             as a result of this call, otherwise {@code false}
     */
    public boolean disableCanonCaches() {
        ensureUseCanonCachesFieldIsPopulated();
        if (!useCanonCachesField.isPresent()) {
            log.warn(sm.getString("jreCompat.useCanonCaches.none"));
            return false;
        }
        try {
            useCanonCachesField.get().set(null, Boolean.FALSE);
        } catch (ReflectiveOperationException | IllegalArgumentException e) {
            log.warn(sm.getString("jreCompat.useCanonCaches.failed"), e);
            return false;
        }
        synchronized (canonCachesDisabledLock) {
            canonCachesDisabled = Boolean.TRUE;
        }
        return true;
    }


    protected void ensureUseCanonCachesFieldIsPopulated() {
        if (useCanonCachesField != null) {
            return;
        }
        synchronized (useCanonCachesFieldLock) {
            if (useCanonCachesField != null) {
                return;
            }

            Field f = null;
            try {
                Class<?> clazz = Class.forName("java.io.FileSystem");
                f = clazz.getDeclaredField("useCanonCaches");
                // Need this because the 'useCanonCaches' field is private final
                f.setAccessible(true);
            } catch (ReflectiveOperationException | IllegalArgumentException e) {
                // Make sure field is not set.
                f = null;
                log.warn(sm.getString("jreCompat.useCanonCaches.init"), e);
            }

            if (f == null) {
                useCanonCachesField = Optional.empty();
            } else {
                useCanonCachesField = Optional.of(f);
            }
        }
    }

    /**
     * TLS groups configuration from JSSE API in Java 20.
     * @param sslParameters the parameters object
     * @param names the names of the groups to enable
     */
    public void setNamedGroupsMethod(Object sslParameters, String[] names) {
        throw new UnsupportedOperationException(sm.getString("jreCompat.noNamedGroups"));
    }

}