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"));
}
}
|