[1004] Wrap service loader calls in privileged actions if the security manager is enabled. Also ensure the LinkageError for the ClientBuilder is thrown if a security manager is enabled. (#1005)

Resolves #1004

Signed-off-by: James R. Perkins <jperkins@redhat.com>
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/client/ClientBuilder.java b/jaxrs-api/src/main/java/jakarta/ws/rs/client/ClientBuilder.java
index b07c2ba..7bf0237 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/client/ClientBuilder.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/client/ClientBuilder.java
@@ -19,7 +19,9 @@
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import java.net.URL;
+import java.security.AccessController;
 import java.security.KeyStore;
+import java.security.PrivilegedAction;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -59,16 +61,14 @@
         try {
             Object delegate = FactoryFinder.find(JAXRS_DEFAULT_CLIENT_BUILDER_PROPERTY, ClientBuilder.class);
             if (!(delegate instanceof ClientBuilder)) {
-                Class pClass = ClientBuilder.class;
-                String classnameAsResource = pClass.getName().replace('.', '/') + ".class";
-                ClassLoader loader = pClass.getClassLoader();
-                if (loader == null) {
-                    loader = ClassLoader.getSystemClassLoader();
+                final CreateErrorMessageAction action = new CreateErrorMessageAction(delegate);
+                final String errorMessage;
+                if (System.getSecurityManager() == null) {
+                    errorMessage = action.run();
+                } else {
+                    errorMessage = AccessController.doPrivileged(action);
                 }
-                URL targetTypeURL = loader.getResource(classnameAsResource);
-                throw new LinkageError("ClassCastException: attempting to cast"
-                        + delegate.getClass().getClassLoader().getResource(classnameAsResource)
-                        + " to " + targetTypeURL);
+                throw new LinkageError(errorMessage);
             }
             return (ClientBuilder) delegate;
         } catch (Exception ex) {
@@ -271,4 +271,26 @@
      * @return a new client instance.
      */
     public abstract Client build();
+
+    private static class CreateErrorMessageAction implements PrivilegedAction<String> {
+        private final Object delegate;
+
+        private CreateErrorMessageAction(final Object delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public String run() {
+            Class<?> pClass = ClientBuilder.class;
+            String classnameAsResource = pClass.getName().replace('.', '/') + ".class";
+            ClassLoader loader = pClass.getClassLoader();
+            if (loader == null) {
+                loader = ClassLoader.getSystemClassLoader();
+            }
+            URL targetTypeURL = loader.getResource(classnameAsResource);
+            return "ClassCastException: attempting to cast"
+                    + delegate.getClass().getClassLoader().getResource(classnameAsResource)
+                    + " to " + targetTypeURL;
+        }
+    }
 }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/client/FactoryFinder.java b/jaxrs-api/src/main/java/jakarta/ws/rs/client/FactoryFinder.java
index 9801cd8..74deb05 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/client/FactoryFinder.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/client/FactoryFinder.java
@@ -21,9 +21,7 @@
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.Iterator;
 import java.util.Properties;
-import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -45,6 +43,11 @@
     }
 
     static ClassLoader getContextClassLoader() {
+        // For performance reasons, check if a security manager is installed. If not there is no need to use a
+        // privileged action.
+        if (System.getSecurityManager() == null) {
+            return Thread.currentThread().getContextClassLoader();
+        }
         return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
             ClassLoader cl = null;
             try {
@@ -107,24 +110,16 @@
     static <T> Object find(final String factoryId, final Class<T> service) throws ClassNotFoundException {
         ClassLoader classLoader = getContextClassLoader();
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.getContextClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // First try the TCCL
+        Object result = findFirstService(factoryId, classLoader, service);
+        if (result != null) {
+            return result;
         }
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.class.getClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // Next try the class loader from the FactoryFinder
+        result = findFirstService(factoryId, getClassLoader(), service);
+        if (result != null) {
+            return result;
         }
 
         // try to read from $java.home/lib/jaxrs.properties
@@ -168,4 +163,29 @@
         throw new ClassNotFoundException(
                 "Provider for " + factoryId + " cannot be found", null);
     }
+
+    private static ClassLoader getClassLoader() {
+        if (System.getSecurityManager() == null) {
+            return FactoryFinder.class.getClassLoader();
+        }
+        return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) FactoryFinder.class::getClassLoader);
+    }
+
+    private static <T> T findFirstService(final String factoryId, final ClassLoader cl, final Class<T> service) {
+        final PrivilegedAction<T> action = () -> {
+            try {
+                final ServiceLoader<T> loader = ServiceLoader.load(service, cl);
+                if (loader.iterator().hasNext()) {
+                    return loader.iterator().next();
+                }
+            } catch (Exception e) {
+                LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", e);
+            }
+            return null;
+        };
+        if (System.getSecurityManager() == null) {
+            return action.run();
+        }
+        return AccessController.doPrivileged(action);
+    }
 }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/FactoryFinder.java b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/FactoryFinder.java
index d3a91c8..bbd5afd 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/ext/FactoryFinder.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/ext/FactoryFinder.java
@@ -21,9 +21,7 @@
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.Iterator;
 import java.util.Properties;
-import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -45,6 +43,11 @@
     }
 
     private static ClassLoader getContextClassLoader() {
+        // For performance reasons, check if a security manager is installed. If not there is no need to use a
+        // privileged action.
+        if (System.getSecurityManager() == null) {
+            return Thread.currentThread().getContextClassLoader();
+        }
         return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
             ClassLoader cl = null;
             try {
@@ -107,24 +110,16 @@
     static <T> Object find(final String factoryId, final Class<T> service) throws ClassNotFoundException {
         ClassLoader classLoader = getContextClassLoader();
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.getContextClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // First try the TCCL
+        Object result = findFirstService(factoryId, classLoader, service);
+        if (result != null) {
+            return result;
         }
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.class.getClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // Next try the class loader from the FactoryFinder
+        result = findFirstService(factoryId, getClassLoader(), service);
+        if (result != null) {
+            return result;
         }
 
         // try to read from $java.home/lib/jaxrs.properties
@@ -168,4 +163,29 @@
         throw new ClassNotFoundException(
                 "Provider for " + factoryId + " cannot be found", null);
     }
+
+    private static ClassLoader getClassLoader() {
+        if (System.getSecurityManager() == null) {
+            return FactoryFinder.class.getClassLoader();
+        }
+        return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) FactoryFinder.class::getClassLoader);
+    }
+
+    private static <T> T findFirstService(final String factoryId, final ClassLoader cl, final Class<T> service) {
+        final PrivilegedAction<T> action = () -> {
+            try {
+                final ServiceLoader<T> loader = ServiceLoader.load(service, cl);
+                if (loader.iterator().hasNext()) {
+                    return loader.iterator().next();
+                }
+            } catch (Exception e) {
+                LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", e);
+            }
+            return null;
+        };
+        if (System.getSecurityManager() == null) {
+            return action.run();
+        }
+        return AccessController.doPrivileged(action);
+    }
 }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/FactoryFinder.java b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/FactoryFinder.java
index fa614af..40440ae 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/sse/FactoryFinder.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/sse/FactoryFinder.java
@@ -21,9 +21,7 @@
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.Iterator;
 import java.util.Properties;
-import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -45,6 +43,11 @@
     }
 
     private static ClassLoader getContextClassLoader() {
+        // For performance reasons, check if a security manager is installed. If not there is no need to use a
+        // privileged action.
+        if (System.getSecurityManager() == null) {
+            return Thread.currentThread().getContextClassLoader();
+        }
         return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
             ClassLoader cl = null;
             try {
@@ -107,24 +110,16 @@
     static <T> Object find(final String factoryId, final Class<T> service) throws ClassNotFoundException {
         ClassLoader classLoader = getContextClassLoader();
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.getContextClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // First try the TCCL
+        Object result = findFirstService(factoryId, classLoader, service);
+        if (result != null) {
+            return result;
         }
 
-        try {
-            Iterator<T> iterator = ServiceLoader.load(service, FactoryFinder.class.getClassLoader()).iterator();
-
-            if (iterator.hasNext()) {
-                return iterator.next();
-            }
-        } catch (Exception | ServiceConfigurationError ex) {
-            LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", ex);
+        // Next try the class loader from the FactoryFinder
+        result = findFirstService(factoryId, getClassLoader(), service);
+        if (result != null) {
+            return result;
         }
 
         // try to read from $java.home/lib/jaxrs.properties
@@ -168,4 +163,29 @@
         throw new ClassNotFoundException(
                 "Provider for " + factoryId + " cannot be found", null);
     }
+
+    private static ClassLoader getClassLoader() {
+        if (System.getSecurityManager() == null) {
+            return FactoryFinder.class.getClassLoader();
+        }
+        return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) FactoryFinder.class::getClassLoader);
+    }
+
+    private static <T> T findFirstService(final String factoryId, final ClassLoader cl, final Class<T> service) {
+        final PrivilegedAction<T> action = () -> {
+            try {
+                final ServiceLoader<T> loader = ServiceLoader.load(service, cl);
+                if (loader.iterator().hasNext()) {
+                    return loader.iterator().next();
+                }
+            } catch (Exception e) {
+                LOGGER.log(Level.FINER, "Failed to load service " + factoryId + ".", e);
+            }
+            return null;
+        };
+        if (System.getSecurityManager() == null) {
+            return action.run();
+        }
+        return AccessController.doPrivileged(action);
+    }
 }