JreCompat.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.tomcat.util.compat;

  18. import java.lang.reflect.Field;
  19. import java.security.PrivilegedExceptionAction;
  20. import java.util.concurrent.Callable;
  21. import java.util.concurrent.CompletionException;

  22. import javax.security.auth.Subject;

  23. import org.apache.tomcat.util.res.StringManager;

  24. /**
  25.  * This is the base implementation class for JRE compatibility and provides an implementation based on Java 17.
  26.  * Sub-classes may extend this class and provide alternative implementations for later JRE versions
  27.  */
  28. public class JreCompat {

  29.     private static final JreCompat instance;
  30.     private static final boolean graalAvailable;
  31.     private static final boolean jre19Available;
  32.     private static final boolean jre21Available;
  33.     private static final boolean jre22Available;
  34.     private static final StringManager sm = StringManager.getManager(JreCompat.class);

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

  46.         // This is Tomcat 11.0.x with a minimum Java version of Java 17.
  47.         // Look for the highest supported JVM first
  48.         if (Jre22Compat.isSupported()) {
  49.             instance = new Jre22Compat();
  50.             jre22Available = true;
  51.             jre21Available = true;
  52.             jre19Available = true;
  53.         } else if (Jre21Compat.isSupported()) {
  54.             instance = new Jre21Compat();
  55.             jre22Available = false;
  56.             jre21Available = true;
  57.             jre19Available = true;
  58.         } else if (Jre19Compat.isSupported()) {
  59.             instance = new Jre19Compat();
  60.             jre22Available = false;
  61.             jre21Available = false;
  62.             jre19Available = true;
  63.         } else {
  64.             instance = new JreCompat();
  65.             jre22Available = false;
  66.             jre21Available = false;
  67.             jre19Available = false;
  68.         }
  69.     }


  70.     public static JreCompat getInstance() {
  71.         return instance;
  72.     }


  73.     public static boolean isGraalAvailable() {
  74.         return graalAvailable;
  75.     }


  76.     public static boolean isJre19Available() {
  77.         return jre19Available;
  78.     }


  79.     public static boolean isJre21Available() {
  80.         return jre21Available;
  81.     }


  82.     public static boolean isJre22Available() {
  83.         return jre22Available;
  84.     }


  85.     // Java 17 implementations of Java 19 methods

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

  101.         Object result = null;

  102.         // Runnable wrapped by Thread
  103.         // "target" in Sun/Oracle JDK
  104.         // "runnable" in IBM JDK
  105.         // "action" in Apache Harmony
  106.         Object target = null;
  107.         for (String fieldName : new String[] { "target", "runnable", "action" }) {
  108.             try {
  109.                 Field targetField = thread.getClass().getDeclaredField(fieldName);
  110.                 targetField.setAccessible(true);
  111.                 target = targetField.get(thread);
  112.                 break;
  113.             } catch (NoSuchFieldException nfe) {
  114.                 continue;
  115.             }
  116.         }

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

  127.         return result;
  128.     }


  129.     // Java 17 implementations of Java 21 methods

  130.     /**
  131.      * Create a thread builder for virtual threads using the given name to name the threads.
  132.      *
  133.      * @param name The base name for the threads
  134.      *
  135.      * @return The thread buidler for virtual threads
  136.      */
  137.     public Object createVirtualThreadBuilder(String name) {
  138.         throw new UnsupportedOperationException(sm.getString("jreCompat.noVirtualThreads"));
  139.     }


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


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

  166.     @SuppressWarnings("removal")
  167.     public <T> T callAs(Subject subject, Callable<T> action) throws CompletionException {
  168.         try {
  169.             return Subject.doAs(subject, new PrivilegedExceptionAction<T>() {

  170.                 @Override
  171.                 public T run() throws Exception {
  172.                     return action.call();
  173.                 }
  174.             });
  175.         } catch (Exception e) {
  176.             throw new CompletionException(e);
  177.         }
  178.     }
  179. }