Allow to disable certain default providers (#4342)

* Allow to disable certain default providers

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
index 8001bd5..b6adbcc 100644
--- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
+++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
@@ -59,6 +59,16 @@
     }
 
     /**
+     * Property which allows (if true) default System properties configuration provider.
+     *
+     * If an external properties provider is used, the system properties are not used.
+     *
+     * Shall be set to turn on the ability to propagate system properties to Jersey configuration.
+     * @since 2.29
+     */
+    public static final String ALLOW_SYSTEM_PROPERTIES_PROVIDER = "jersey.config.allowSystemPropertiesProvider";
+
+    /**
      * If {@code true} then disable feature auto discovery globally on client/server.
      * <p>
      * By default auto discovery is automatically enabled. The value of this property may be overridden by the client/server
@@ -217,15 +227,21 @@
     public static final String OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER = "jersey.config.server.contentLength.buffer";
 
     /**
-     * Property which allows (if true) default System properties configuration provider.
+     * Disable some of the default providers from being loaded. The following providers extend application footprint
+     * by XML dependencies, which is too heavy for native image, or by AWT which may possibly be not available by JDK 11 desktop:
+     * <ul>
+     *     <li>java.awt.image.RenderedImage</li>
+     *     <li>javax.xml.transform.Source</li>
+     *     <li>javax.xml.transform.dom.DOMSource</li>
+     *     <li>javax.xml.transform.sax.SAXSource</li>
+     *     <li>javax.xml.transform.stream.StreamSource</li>
+     * </ul>
+     * The following are the options to disable the provides: {@code DOMSOURCE, RENDEREDIMAGE, SAXSOURCE, SOURCE, STREAMSOURCE},
+     * or to disable all: {@code ALL}. Multiple options can be disabled by adding multiple comma separated values.
      *
-     * Effective if there are no any external properties providers
-     *
-     * Shall be set (if used) in system properties.
-     * @since 2.29
+     * @since 2.30
      */
-
-    public static final String ALLOW_SYSTEM_PROPERTIES_PROVIDER = "jersey.config.allowSystemPropertiesProvider";
+    public static final String PROVIDER_DEFAULT_DISABLE = "jersey.config.disableDefaultProvider";
 
     /**
      * Prevent instantiation.
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java
index 95b000d..d551327 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java
@@ -16,7 +16,10 @@
 
 package org.glassfish.jersey.message.internal;
 
+import java.security.AccessController;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -26,8 +29,11 @@
 
 import javax.inject.Singleton;
 
+import org.glassfish.jersey.CommonProperties;
 import org.glassfish.jersey.internal.ServiceFinderBinder;
 import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.Tokenizer;
 import org.glassfish.jersey.spi.HeaderDelegateProvider;
 
 /**
@@ -77,20 +83,28 @@
             bindSingletonWorker(InputStreamProvider.class);
             bindSingletonWorker(BasicTypesMessageProvider.class);
             bindSingletonWorker(ReaderProvider.class);
-            bindSingletonWorker(RenderedImageProvider.class);
+            // bindSingletonWorker(RenderedImageProvider.class); - enabledProvidersBinder
             bindSingletonWorker(StringMessageProvider.class);
 
-            // Message body readers
-            bind(SourceProvider.StreamSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
-            bind(SourceProvider.SaxSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
-            bind(SourceProvider.DomSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            // Message body readers -- enabledProvidersBinder
+            // bind(SourceProvider.StreamSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            // bind(SourceProvider.SaxSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            // bind(SourceProvider.DomSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
             /*
              * TODO: com.sun.jersey.core.impl.provider.entity.EntityHolderReader
              */
 
             // Message body writers
             bind(StreamingOutputProvider.class).to(MessageBodyWriter.class).in(Singleton.class);
-            bind(SourceProvider.SourceWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+            // bind(SourceProvider.SourceWriter.class).to(MessageBodyWriter.class).in(Singleton.class); - enabledProvidersBinder
+
+            final EnabledProvidersBinder enabledProvidersBinder = new EnabledProvidersBinder();
+            if (applicationProperties != null && applicationProperties.get(CommonProperties.PROVIDER_DEFAULT_DISABLE) != null) {
+                enabledProvidersBinder.markDisabled(
+                        String.valueOf(applicationProperties.get(CommonProperties.PROVIDER_DEFAULT_DISABLE))
+                );
+            }
+            enabledProvidersBinder.bindToBinder(this);
 
             // Header Delegate Providers registered in META-INF.services
             install(new ServiceFinderBinder<>(HeaderDelegateProvider.class, applicationProperties, runtimeType));
@@ -137,4 +151,115 @@
             return providers;
         }
     }
+
+    private static final class EnabledProvidersBinder {
+        private enum Provider {
+            DOMSOURCE ("javax.xml.transform.dom.DOMSource"),
+            RENDEREDIMAGE ("java.awt.image.RenderedImage"),
+            SAXSOURCE ("javax.xml.transform.sax.SAXSource"),
+            SOURCE ("javax.xml.transform.Source"),
+            STREAMSOURCE ("javax.xml.transform.stream.StreamSource");
+            Provider(String className) {
+                this.className = className;
+            }
+            private String className;
+        }
+
+        private static final String ALL = "ALL";
+        private HashSet<Provider> enabledProviders = new HashSet<>();
+
+        private EnabledProvidersBinder() {
+            for (Provider provider : Provider.values()) {
+                enabledProviders.add(provider);
+            }
+        }
+
+        private void markDisabled(String properties) {
+            String[] tokens = Tokenizer.tokenize(properties);
+            for (int tokenIndex = 0; tokenIndex != tokens.length; tokenIndex++) {
+                String token = tokens[tokenIndex].toUpperCase(Locale.ROOT);
+                if (ALL.equals(token)) {
+                    enabledProviders.clear();
+                    return;
+                }
+                for (Iterator<Provider> iterator = enabledProviders.iterator(); iterator.hasNext();) {
+                    Provider provider = iterator.next();
+                    if (provider.name().equals(token)) {
+                        iterator.remove();
+                    }
+                }
+            }
+        }
+
+        private void bindToBinder(AbstractBinder binder) {
+            ProviderBinder providerBinder = null;
+            for (Provider provider : enabledProviders) {
+                if (isClass(provider.className)) {
+                    switch (provider) {
+                        case DOMSOURCE:
+                            providerBinder = new DomSourceBinder();
+                            break;
+                        case RENDEREDIMAGE:
+                            providerBinder = new RenderedImageBinder();
+                            break;
+                        case SAXSOURCE:
+                            providerBinder = new SaxSourceBinder();
+                            break;
+                        case SOURCE:
+                            providerBinder = new SourceBinder();
+                            break;
+                        case STREAMSOURCE:
+                            providerBinder = new StreamSourceBinder();
+                            break;
+                    }
+                    providerBinder.bind(binder, provider);
+                }
+            }
+        }
+
+        private static boolean isClass(String className) {
+            return null != AccessController.doPrivileged(ReflectionHelper.classForNamePA(className));
+        }
+
+
+        private interface ProviderBinder {
+            void bind(AbstractBinder binder, Provider provider);
+        }
+
+        private class DomSourceBinder implements ProviderBinder {
+            @Override
+            public void bind(AbstractBinder binder, Provider provider) {
+                binder.bind(SourceProvider.DomSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            }
+        }
+
+        private class RenderedImageBinder implements ProviderBinder {
+            @Override
+            public void bind(AbstractBinder binder, Provider provider) {
+                binder.bind(RenderedImageProvider.class)
+                        .to(MessageBodyReader.class).to(MessageBodyWriter.class).in(Singleton.class);
+            }
+        }
+
+        private class SaxSourceBinder implements ProviderBinder {
+            @Override
+            public void bind(AbstractBinder binder, Provider provider) {
+                binder.bind(SourceProvider.SaxSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            }
+        }
+
+        private class SourceBinder implements ProviderBinder {
+            @Override
+            public void bind(AbstractBinder binder, Provider provider) {
+                binder.bind(SourceProvider.SourceWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+            }
+        }
+
+        private class StreamSourceBinder implements ProviderBinder {
+            @Override
+            public void bind(AbstractBinder binder, Provider provider) {
+                binder.bind(SourceProvider.StreamSourceReader.class).to(MessageBodyReader.class).in(Singleton.class);
+            }
+        }
+    }
 }
diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/config/DisabledProvidersTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/config/DisabledProvidersTest.java
new file mode 100644
index 0000000..cccd36c
--- /dev/null
+++ b/core-common/src/test/java/org/glassfish/jersey/internal/config/DisabledProvidersTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.internal.config;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.internal.inject.ClassBinding;
+import org.glassfish.jersey.message.internal.MessagingBinders;
+import org.glassfish.jersey.message.internal.RenderedImageProvider;
+import org.glassfish.jersey.message.internal.SourceProvider;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.ws.rs.RuntimeType;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+public class DisabledProvidersTest {
+    private static class DisabledProvidersChecker extends MessagingBinders.MessageBodyProviders {
+        private HashSet<Class<?>> bindSet = new HashSet<>();
+        public DisabledProvidersChecker(Map<String, Object> applicationProperties, RuntimeType runtimeType) {
+            super(applicationProperties, runtimeType);
+        }
+
+        @Override
+        public <T> ClassBinding<T> bind(Class<T> serviceType) {
+            bindSet.add(serviceType);
+            return super.bind(serviceType);
+        }
+
+        @Override
+        public void configure() {
+            super.configure();
+        }
+    }
+
+    @Test
+    public void testNoRenderedImageProviderNoSourceProvider() {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(CommonProperties.PROVIDER_DEFAULT_DISABLE, "RENDEREDIMAGE, SOURCE");
+
+        DisabledProvidersChecker checker = new DisabledProvidersChecker(properties, RuntimeType.CLIENT);
+        checker.configure();
+        Assert.assertFalse(checker.bindSet.contains(RenderedImageProvider.class));
+        Assert.assertFalse(checker.bindSet.contains(SourceProvider.SourceWriter.class));
+        Assert.assertTrue(checker.bindSet.contains(SourceProvider.StreamSourceReader.class));
+        Assert.assertTrue(checker.bindSet.contains(SourceProvider.SaxSourceReader.class));
+        Assert.assertTrue(checker.bindSet.contains(SourceProvider.DomSourceReader.class));
+    }
+
+    @Test
+    public void testNoDisabledProvider() {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(CommonProperties.PROVIDER_DEFAULT_DISABLE, "ALL");
+
+        DisabledProvidersChecker checker = new DisabledProvidersChecker(properties, RuntimeType.CLIENT);
+        checker.configure();
+        Assert.assertFalse(checker.bindSet.contains(RenderedImageProvider.class));
+        Assert.assertFalse(checker.bindSet.contains(SourceProvider.StreamSourceReader.class));
+        Assert.assertFalse(checker.bindSet.contains(SourceProvider.SourceWriter.class));
+        Assert.assertFalse(checker.bindSet.contains(SourceProvider.SaxSourceReader.class));
+        Assert.assertFalse(checker.bindSet.contains(SourceProvider.DomSourceReader.class));
+    }
+
+
+}