diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 0000000..12f6284 --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,25 @@ +version: 1 +update_configs: + - package_manager: "java:maven" + directory: "/" + update_schedule: "daily" + automerged_updates: + - match: + dependency_name: "org.apache.maven.plugins:*" + - match: + dependency_name: "org.codehaus.mojo:*" + - match: + dependency_name: "org.gaul:modernizer-maven-plugin" + - match: + dependency_name: "org.jacoco:jacoco-maven-plugin" + - match: + dependency_name: "org.checkerframework:checker-qual" + - match: + dependency_name: "org.assertj:assertj-core" + - match: + dependency_name: "org.openjdk.jmh:*" + - match: + dependency_name: "com.akathist.maven.plugins.launch4j:launch4j-maven-plugin" + - match: + dependency_name: "net.nicoulaj.maven.plugins:checksum-maven-plugin" + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 923a966..2780ff5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,49 @@ +dist: xenial language: java jdk: - - oraclejdk8 + - openjdk8 + - openjdk11 + - openjdk-ea -cache: - directories: - - $HOME/.m2 +matrix: + allow_failures: + - jdk: openjdk-ea + +env: + global: + - DEPLOY_JDK=openjdk11 + - DEPLOY_REPO=nbbrd/java-net-proxy deploy: - # SNAPSHOTS from develop & jdk8 + # Maven snapshots from develop branch - provider: script script: mvn deploy -Dmaven.test.skip -s .travis.settings.xml -P deploy-snapshots skip_cleanup: true on: branch: develop - jdk: oraclejdk8 - condition: $TRAVIS_PULL_REQUEST = "false" - - # RELEASES from master & jdk8 + tags: false + repo: "${DEPLOY_REPO}" + condition: $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_JDK_VERSION == $DEPLOY_JDK + + # Maven releases from tags - provider: script script: mvn deploy -Dmaven.test.skip -s .travis.settings.xml -P deploy-releases skip_cleanup: true on: - branch: master - jdk: oraclejdk8 - condition: $TRAVIS_PULL_REQUEST = "false" + tags: true + repo: "${DEPLOY_REPO}" + condition: $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_JDK_VERSION == $DEPLOY_JDK + + # Github releases from tags + - provider: releases + api_key: "${GITHUB_KEY}" + skip_cleanup: true + draft: true + on: + tags: true + repo: "${DEPLOY_REPO}" + condition: $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_JDK_VERSION == $DEPLOY_JDK + +cache: + directories: + - $HOME/.m2 diff --git a/pom.xml b/pom.xml index 3da881a..f0cd268 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ be.nbb.rd java-net-proxy - 1.0.0-SNAPSHOT + 1.0.0 jar java-net-proxy @@ -21,44 +21,29 @@ https://joinup.ec.europa.eu/page/eupl-text-11-12 - - - - UTF-8 - 1.8 - 1.8 - 2.4 - 0.7.9 - - - 1.16.18 - RELEASE82 - 4.12 - 3.11.1 - 3.0.2 - - - 1.1.0 - org.projectlombok lombok - ${lombok.version} provided - org.netbeans.api - org-openide-util-lookup - ${netbeans.version} + be.nbb.rd + java-service-processor provided - - com.google.code.findbugs - jsr305 - ${jsr305.version} + + org.checkerframework + checker-qual + 3.4.1 + provided + + + com.github.stephenc.jcip + jcip-annotations + 1.0-1 provided @@ -66,70 +51,281 @@ com.github.tuupertunut powershell-lib-java - ${powershell-lib-java.version} + 2.0.0 junit junit - ${junit.version} + 4.13 test org.assertj assertj-core - ${assertj-core.version} + 3.16.1 test - - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar - - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - - - - org.jacoco - jacoco-maven-plugin - - - - + + + base-java8 + + + !skipBaseJava8 + + + + UTF-8 + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + + + + + base-processors + + + !skipBaseProcessors + + + + 1.18.12 + 1.3.0 + + + + + org.projectlombok + lombok + ${lombok.version} + + + be.nbb.rd + java-service-annotation + ${java-service.version} + + + be.nbb.rd + java-service-processor + ${java-service.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + ${lombok.version} + + + be.nbb.rd + java-service-processor + ${java-service.version} + + + + + + + + + + + java8-with-jpms + + [9,) + + + + 9 + 9 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + default-compile + + 9 + + + + + base-compile + + compile + + + 8 + 8 + + module-info.java + + + + + + + + + + + + java8-without-jpms + + 1.8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + + module-info.java + + + + + + + + + + + + enforce-dependency-rules + + + !skipEnforceDependencyRules + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + enforce + + + + 3.3.9 + + + + true + + + + + enforce + + + + + + + + + + + enforce-modern-api + + + !skipEnforceModernAPI + + + + + + org.gaul + modernizer-maven-plugin + 2.1.0 + + 1.8 + + + + modernizer + verify + + modernizer + + + + + + + + + + + enforce-code-coverage + + + !skipEnforceCodeCoverage + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + + deploy-snapshots @@ -141,9 +337,23 @@ + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + org.apache.maven.plugins maven-source-plugin + 3.2.1 + + + verify + + jar-no-fork + + + @@ -160,9 +370,23 @@ + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + org.apache.maven.plugins maven-source-plugin + 3.2.1 + + + verify + + jar-no-fork + + + @@ -171,8 +395,11 @@ - netbeans - http://bits.netbeans.org/maven2/ - + oss-jfrog-artifactory-releases + https://oss.jfrog.org/artifactory/oss-release-local + + false + + \ No newline at end of file diff --git a/src/main/java/internal/net/proxy/FailsafeSystemProxySpi.java b/src/main/java/internal/net/proxy/FailsafeSystemProxySpi.java new file mode 100644 index 0000000..84b0efa --- /dev/null +++ b/src/main/java/internal/net/proxy/FailsafeSystemProxySpi.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package internal.net.proxy; + +import java.net.Proxy; +import java.net.URI; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import nbbrd.net.proxy.SystemProxySelector; + +/** + * + * @author Philippe Charles + */ +@lombok.extern.java.Log +@lombok.AllArgsConstructor +public final class FailsafeSystemProxySpi implements SystemProxySelector.Spi { + + public static SystemProxySelector.Spi wrap(SystemProxySelector.Spi delegate) { + return new FailsafeSystemProxySpi(delegate, FailsafeSystemProxySpi::logUnexpectedError); + } + + @lombok.NonNull + private final SystemProxySelector.Spi delegate; + + @lombok.NonNull + private final BiConsumer onUnexpectedError; + + @Override + public Proxy getProxyOrNull(URI uri) { + Objects.requireNonNull(uri); + try { + return delegate.getProxyOrNull(uri); + } catch (RuntimeException ex) { + onUnexpectedError.accept("While calling 'getProxyOrNull' on '" + delegate + "'", ex); + return null; + } + } + + static void logUnexpectedError(String msg, RuntimeException ex) { + if (log.isLoggable(Level.WARNING)) { + log.log(Level.WARNING, msg, ex); + } + } +} diff --git a/src/main/java/com/github/tuupertunut/powershelllibjava/PowerShell.java b/src/main/java/internal/net/proxy/x/FixedPowerShell.java similarity index 87% rename from src/main/java/com/github/tuupertunut/powershelllibjava/PowerShell.java rename to src/main/java/internal/net/proxy/x/FixedPowerShell.java index b78a9f9..e6a53dc 100644 --- a/src/main/java/com/github/tuupertunut/powershelllibjava/PowerShell.java +++ b/src/main/java/internal/net/proxy/x/FixedPowerShell.java @@ -21,8 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.github.tuupertunut.powershelllibjava; +package internal.net.proxy.x; +import com.github.tuupertunut.powershelllibjava.AsyncReaderRecorder; +import com.github.tuupertunut.powershelllibjava.PowerShellExecutionException; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Closeable; @@ -43,7 +45,7 @@ * * @author Tuupertunut */ -public class PowerShell implements Closeable { +public class FixedPowerShell implements Closeable { private static final String DEFAULT_WIN_EXECUTABLE = "powershell"; private static final String DEFAULT_CORE_EXECUTABLE = "pwsh"; @@ -64,7 +66,7 @@ public class PowerShell implements Closeable { private boolean closed; - private PowerShell(String psExecutable) throws IOException { + private FixedPowerShell(String psExecutable) throws IOException { psSession = createProcessBuilder(psExecutable).start(); @@ -73,10 +75,10 @@ private PowerShell(String psExecutable) throws IOException { outputRecorder = new AsyncReaderRecorder(commandOutput); errorOutputRecorder = new AsyncReaderRecorder(commandErrorOutput); - executor = Executors.newFixedThreadPool(2, r -> { - Thread result = Executors.defaultThreadFactory().newThread(r); - result.setDaemon(true); - return result; + executor = Executors.newFixedThreadPool(2, (Runnable r) -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setDaemon(true); + return thread; }); executor.execute(outputRecorder); executor.execute(errorOutputRecorder); @@ -107,7 +109,7 @@ private static ProcessBuilder createProcessBuilder(String psExecutable) { * * -Command - : Read commands from standard input stream of the * process. */ - return new ProcessBuilder("cmd", "/c", "chcp", "65001", ">", "NUL", "&", psExecutable, "-ExecutionPolicy", "Bypass", "-NoExit", "-Command", "-"); + return new ProcessBuilder("cmd", "/c", "chcp 65001 > NUL", "&", psExecutable, "-ExecutionPolicy", "Bypass", "-NoExit", "-Command", "-"); } else { return new ProcessBuilder(psExecutable, "-ExecutionPolicy", "Bypass", "-NoExit", "-Command", "-"); } @@ -135,8 +137,8 @@ private static String getDefaultExecutable() { * @return a new PowerShell session. * @throws IOException if an IOException occurred on process creation. */ - public static PowerShell open() throws IOException { - return new PowerShell(getDefaultExecutable()); + public static FixedPowerShell open() throws IOException { + return new FixedPowerShell(getDefaultExecutable()); } /** @@ -147,8 +149,8 @@ public static PowerShell open() throws IOException { * @return a new PowerShell session. * @throws IOException if an IOException occurred on process creation. */ - public static PowerShell open(String customExecutable) throws IOException { - return new PowerShell(customExecutable); + public static FixedPowerShell open(String customExecutable) throws IOException { + return new FixedPowerShell(customExecutable); } /** @@ -159,6 +161,10 @@ public static PowerShell open(String customExecutable) throws IOException { public void close() { closed = true; if (commandInput != null) { + + /* Sending a shutdown signal to PowerShell. */ + commandInput.println("exit"); + commandInput.close(); } if (executor != null) { @@ -176,9 +182,6 @@ public void close() { } catch (IOException ex) { } } - if (psSession != null) { - psSession.destroy(); - } } /** @@ -205,13 +208,16 @@ public void close() { * @throws IOException if an IOException occurred while reading the output * of the commands. * @throws IllegalStateException if this PowerShell session was already - * closed. - * @throws RuntimeException if the output stream ended too early, or if the - * current thread was interrupted while executing. + * closed, or the process or its output stream has terminated too early. + * @throws RuntimeException if the current thread was interrupted while + * executing. */ public String executeCommands(String... commands) throws PowerShellExecutionException, IOException { if (closed) { throw new IllegalStateException("This PowerShell session has been closed."); + } else if (!psSession.isAlive()) { + close(); + throw new IllegalStateException("The PowerShell process has terminated before it should."); } StringBuilder commandChainBuilder = new StringBuilder(); @@ -243,7 +249,8 @@ public String executeCommands(String... commands) throws PowerShellExecutionExce * circumstances. */ Optional optionalOutput = outputRecorder.consumeToNextDelimiter(END_OF_COMMAND + System.lineSeparator()); if (!optionalOutput.isPresent()) { - throw new RuntimeException("PowerShell output stream ended too early."); + close(); + throw new IllegalStateException("PowerShell output stream ended too early."); } String output = optionalOutput.get().replace(END_OF_COMMAND + System.lineSeparator(), ""); diff --git a/src/main/java/internal/net/SinglePowerShell.java b/src/main/java/internal/net/proxy/x/SharedPowerShell.java similarity index 91% rename from src/main/java/internal/net/SinglePowerShell.java rename to src/main/java/internal/net/proxy/x/SharedPowerShell.java index 27b7aeb..7112c4a 100644 --- a/src/main/java/internal/net/SinglePowerShell.java +++ b/src/main/java/internal/net/proxy/x/SharedPowerShell.java @@ -14,29 +14,30 @@ * See the Licence for the specific language governing permissions and * limitations under the Licence. */ -package internal.net; +package internal.net.proxy.x; -import com.github.tuupertunut.powershelllibjava.PowerShell; import com.github.tuupertunut.powershelllibjava.PowerShellExecutionException; import java.io.IOException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import net.jcip.annotations.ThreadSafe; /** * * @author Philippe Charles */ -public final class SinglePowerShell { +@ThreadSafe +public final class SharedPowerShell { private static final long MAIN_TIMEOUT_MILLIS = 1000 * 10; private static final int FALLBACK_MAX_INSTANCES = 3; private final ReentrantLock lock; private final Semaphore fallbackInstances; - private PowerShell ps; + private FixedPowerShell ps; - public SinglePowerShell() { + public SharedPowerShell() { this.lock = new ReentrantLock(); this.fallbackInstances = new Semaphore(FALLBACK_MAX_INSTANCES); this.ps = null; @@ -62,7 +63,7 @@ public String executeCommands(String cmd) throws IOException, PowerShellExecutio private String execOnMain(String cmd) throws IOException, PowerShellExecutionException { try { if (ps == null) { - ps = PowerShell.open(); + ps = FixedPowerShell.open(); } else { } return ps.executeCommands(cmd); @@ -74,7 +75,7 @@ private String execOnMain(String cmd) throws IOException, PowerShellExecutionExc private String execOnFallback(String cmd) throws IOException, PowerShellExecutionException { if (fallbackInstances.tryAcquire()) { - try (PowerShell temp = PowerShell.open()) { + try (FixedPowerShell temp = FixedPowerShell.open()) { return temp.executeCommands(cmd); } finally { fallbackInstances.release(); diff --git a/src/main/java/internal/net/TtlCache.java b/src/main/java/internal/net/proxy/x/TtlCache.java similarity index 84% rename from src/main/java/internal/net/TtlCache.java rename to src/main/java/internal/net/proxy/x/TtlCache.java index 1b9f651..6cad488 100644 --- a/src/main/java/internal/net/TtlCache.java +++ b/src/main/java/internal/net/proxy/x/TtlCache.java @@ -14,7 +14,7 @@ * See the Licence for the specific language governing permissions and * limitations under the Licence. */ -package internal.net; +package internal.net.proxy.x; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -22,8 +22,8 @@ import java.util.function.Function; import java.util.function.LongSupplier; import java.util.logging.Level; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * @@ -35,13 +35,13 @@ @lombok.Builder(builderClassName = "Builder", toBuilder = true) public final class TtlCache { - @Nonnull + @NonNull public static TtlCache of() { return builder() .minTtlInMillis(10) .maxTtlInMillis(1000 * 60) .ttlFactor(10) - .cache(new ConcurrentHashMap<>()) + .storage(new ConcurrentHashMap<>()) .clock(System::currentTimeMillis) .onEvent(TtlCache::logEvent) .build(); @@ -50,15 +50,15 @@ public static TtlCache of() { private final long minTtlInMillis; private final long maxTtlInMillis; private final long ttlFactor; - private final ConcurrentMap> cache; + private final ConcurrentMap> storage; private final LongSupplier clock; private final BiConsumer onEvent; private final int evictThreshold = 1000; @Nullable - public V get(@Nonnull K key, @Nonnull Function loader) { + public V get(@NonNull K key, @NonNull Function loader) { long now = clock.getAsLong(); - Entry entry = cache.get(key); + Entry entry = storage.get(key); if (entry != null) { if (!entry.hasExpired(now)) { onEvent.accept(key, Event.HIT); @@ -77,22 +77,22 @@ private V load(K key, Function loader, long before, boolean miss) { if (ttl >= minTtlInMillis) { onEvent.accept(key, miss ? Event.MISS_SLOW : Event.EXP_SLOW); evictExpiredEntries(after); - cache.put(key, new Entry<>(after + Math.min(maxTtlInMillis, ttl), result)); + storage.put(key, new Entry<>(after + Math.min(maxTtlInMillis, ttl), result)); } else { onEvent.accept(key, miss ? Event.MISS_FAST : Event.EXP_FAST); if (!miss) { - cache.remove(key); + storage.remove(key); } } return result; } private void evictExpiredEntries(long currentTimeInMillis) { - if (cache.size() > evictThreshold) { - cache.entrySet() + if (storage.size() > evictThreshold) { + storage.entrySet() .stream() .filter(o -> o.getValue().hasExpired(currentTimeInMillis)) - .forEach(cache::remove); + .forEach(storage::remove); } } diff --git a/src/main/java/internal/net/WinPowerShellProxySelector.java b/src/main/java/internal/net/proxy/x/WinPowerShellProxySelector.java similarity index 95% rename from src/main/java/internal/net/WinPowerShellProxySelector.java rename to src/main/java/internal/net/proxy/x/WinPowerShellProxySelector.java index a3c5998..9571bf2 100644 --- a/src/main/java/internal/net/WinPowerShellProxySelector.java +++ b/src/main/java/internal/net/proxy/x/WinPowerShellProxySelector.java @@ -14,7 +14,7 @@ * See the Licence for the specific language governing permissions and * limitations under the Licence. */ -package internal.net; +package internal.net.proxy.x; import com.github.tuupertunut.powershelllibjava.PowerShellExecutionException; import java.io.IOException; @@ -28,14 +28,14 @@ import java.util.function.UnaryOperator; import java.util.logging.Level; import lombok.AccessLevel; -import nbbrd.net.SystemProxySelector; -import org.openide.util.lookup.ServiceProvider; +import nbbrd.net.proxy.SystemProxySelector; +import nbbrd.service.ServiceProvider; /** * * @author Philippe Charles */ -@ServiceProvider(service = SystemProxySelector.Spi.class) +@ServiceProvider(SystemProxySelector.Spi.class) @lombok.extern.java.Log @lombok.AllArgsConstructor(access = AccessLevel.PACKAGE) public final class WinPowerShellProxySelector implements SystemProxySelector.Spi { @@ -112,7 +112,7 @@ private static void logCacheEvent(Object key, TtlCache.Event e) { public static final class GetSystemWebProxyCommand implements Function { - private final SinglePowerShell ps = new SinglePowerShell(); + private final SharedPowerShell ps = new SharedPowerShell(); @Override public String apply(URI uri) { diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..d07d106 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ + +module nbbrd.net.proxy { + requires static lombok; + requires static nbbrd.service; + requires static org.checkerframework.checker.qual; + requires static jcip.annotations; + + requires java.logging; + requires com.github.tuupertunut.powershelllibjava; + + exports nbbrd.net.proxy; + + provides nbbrd.net.proxy.SystemProxySelector.Spi with + internal.net.proxy.x.WinPowerShellProxySelector; + + uses nbbrd.net.proxy.SystemProxySelector.Spi; +} diff --git a/src/main/java/nbbrd/net/SystemProxySelector.java b/src/main/java/nbbrd/net/proxy/SystemProxySelector.java similarity index 66% rename from src/main/java/nbbrd/net/SystemProxySelector.java rename to src/main/java/nbbrd/net/proxy/SystemProxySelector.java index f099a3d..cfdc328 100644 --- a/src/main/java/nbbrd/net/SystemProxySelector.java +++ b/src/main/java/nbbrd/net/proxy/SystemProxySelector.java @@ -14,8 +14,10 @@ * See the Licence for the specific language governing permissions and * limitations under the Licence. */ -package nbbrd.net; +package nbbrd.net.proxy; +import internal.net.proxy.FailsafeSystemProxySpi; +import internal.net.proxy.SystemProxySpiLoader; import java.io.IOException; import java.net.Proxy; import java.net.ProxySelector; @@ -24,35 +26,27 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.ServiceLoader; -import java.util.function.BiConsumer; import java.util.function.UnaryOperator; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.ThreadSafe; +import nbbrd.service.Quantifier; +import nbbrd.service.ServiceDefinition; +import net.jcip.annotations.ThreadSafe; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * * @author Philippe Charles */ @ThreadSafe -@lombok.extern.java.Log @lombok.Builder(builderClassName = "Builder", toBuilder = true) public final class SystemProxySelector extends ProxySelector { - @Nonnull + @NonNull public static SystemProxySelector ofServiceLoader() { - List providers = StreamSupport - .stream(ServiceLoader.load(Spi.class).spliterator(), false) - .collect(Collectors.toList()); return builder() - .providers(providers) + .providers(new SystemProxySpiLoader().get()) .systemProperties(System::getProperty) .fallback(ProxySelector.getDefault()) - .onUnexpectedError(SystemProxySelector::logUnexpectedError) .build(); } @@ -65,9 +59,6 @@ public static SystemProxySelector ofServiceLoader() { @lombok.NonNull private final ProxySelector fallback; - @lombok.NonNull - private final BiConsumer onUnexpectedError; - @Override public List select(URI uri) { if (uri == null) { @@ -77,7 +68,7 @@ public List select(URI uri) { return fallback.select(uri); } return providers.stream() - .map(o -> tryGetProxyOrNull(o, uri)) + .map(provider -> provider.getProxyOrNull(uri)) .filter(Objects::nonNull) .findFirst() .map(Collections::singletonList) @@ -89,15 +80,6 @@ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { fallback.connectFailed(uri, sa, ioe); } - private Proxy tryGetProxyOrNull(Spi o, URI uri) { - try { - return o.getProxyOrNull(uri); - } catch (RuntimeException ex) { - onUnexpectedError.accept("While calling 'getProxyOrNull' on '" + o + "'", ex); - return null; - } - } - private boolean hasStaticProxyProperties() { return hasProperty("https.proxyPort") || hasProperty("https.proxyHost") @@ -110,16 +92,14 @@ private boolean hasProperty(String property) { return systemProperties.apply(property) != null; } - private static void logUnexpectedError(String msg, RuntimeException ex) { - if (log.isLoggable(Level.WARNING)) { - log.log(Level.WARNING, msg, ex); - } - } - @ThreadSafe + @ServiceDefinition( + quantifier = Quantifier.MULTIPLE, + wrapper = FailsafeSystemProxySpi.class, + loaderName = "internal.net.proxy.SystemProxySpiLoader") public interface Spi { @Nullable - Proxy getProxyOrNull(@Nonnull URI uri); + Proxy getProxyOrNull(@NonNull URI uri); } } diff --git a/src/test/java/_demo/PowerShellDemo.java b/src/test/java/_demo/SharedPowerShellDemo.java similarity index 93% rename from src/test/java/_demo/PowerShellDemo.java rename to src/test/java/_demo/SharedPowerShellDemo.java index 177779a..8143555 100644 --- a/src/test/java/_demo/PowerShellDemo.java +++ b/src/test/java/_demo/SharedPowerShellDemo.java @@ -16,7 +16,7 @@ */ package _demo; -import internal.net.SinglePowerShell; +import internal.net.proxy.x.SharedPowerShell; import java.util.List; import java.util.function.ToLongFunction; import java.util.stream.Collectors; @@ -26,7 +26,7 @@ * * @author Philippe Charles */ -public class PowerShellDemo { +public class SharedPowerShellDemo { public static void main(String[] args) throws Exception { List cmds = IntStream.range(0, 10).mapToObj(i -> "echo " + i).collect(Collectors.toList()); @@ -38,7 +38,7 @@ public static void main(String[] args) throws Exception { } private static ToLongFunction newTimer() { - SinglePowerShell ps = new SinglePowerShell(); + SharedPowerShell ps = new SharedPowerShell(); // try { // ps.executeCommands("echo 'warming'"); // } catch (IOException | PowerShellExecutionException ex) { diff --git a/src/test/java/_demo/SystemProxySelectorDemo.java b/src/test/java/_demo/SystemProxySelectorDemo.java new file mode 100644 index 0000000..4d772d4 --- /dev/null +++ b/src/test/java/_demo/SystemProxySelectorDemo.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package _demo; + +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.stream.Stream; +import nbbrd.net.proxy.SystemProxySelector; + +/** + * + * @author Philippe Charles + */ +public class SystemProxySelectorDemo { + + public static void main(String[] args) throws URISyntaxException { + URI[] x = { + new URI("http://ec.europa.eu"), + new URI("http://dataservices.imf.org"), + new URI("http://data.un.org"), + new URI("https://sdmxcentral.imf.org"), + new URI("http://andmebaas.stat.ee"), + new URI("http://www.ilo.org"), + new URI("http://bdm.insee.fr"), + new URI("https://sdw-wsrest.ecb.europa.eu"), + new URI("http://sdmx.snieg.mx"), + new URI("https://stats.oecd.org"), + new URI("http://stat.data.abs.gov.au"), + new URI("https://stat.nbb.be"), + new URI("http://wits.worldbank.org"), + new URI("http://data.uis.unesco.org"), + new URI("https://api.worldbank.org"), + new URI("http://sdmx.istat.it") + }; + + SystemProxySelector proxySelector = SystemProxySelector.ofServiceLoader(); + Stream.of(x).parallel().forEach(uri -> fetchAndPrint(uri, proxySelector)); + } + + private static void fetchAndPrint(URI uri, ProxySelector proxySelector) { + long start = System.currentTimeMillis(); + List proxies = proxySelector.select(uri); + long stop = System.currentTimeMillis(); + System.out.println(uri + " >>> " + proxies + " >>> " + (stop - start)); + } +} diff --git a/src/test/java/_demo/WinMapDemo.java b/src/test/java/_demo/WinMapDemo.java index cdc1e36..01a7793 100644 --- a/src/test/java/_demo/WinMapDemo.java +++ b/src/test/java/_demo/WinMapDemo.java @@ -16,7 +16,7 @@ */ package _demo; -import internal.net.WinPowerShellProxySelector; +import internal.net.proxy.x.WinPowerShellProxySelector; import java.net.URI; import java.net.URISyntaxException; diff --git a/src/test/java/_demo/WinParallelDemo.java b/src/test/java/_demo/WinParallelDemo.java index f09e115..f1aa583 100644 --- a/src/test/java/_demo/WinParallelDemo.java +++ b/src/test/java/_demo/WinParallelDemo.java @@ -16,7 +16,7 @@ */ package _demo; -import internal.net.WinPowerShellProxySelector; +import internal.net.proxy.x.WinPowerShellProxySelector; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; diff --git a/src/test/java/_test/ProxyMap.java b/src/test/java/_test/ProxyMap.java new file mode 100644 index 0000000..a7ffdea --- /dev/null +++ b/src/test/java/_test/ProxyMap.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package _test; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * + * @author Philippe Charles + */ +@lombok.Builder +public final class ProxyMap extends ProxySelector { + + @lombok.Singular + private final Map proxies; + + @Override + public List select(URI uri) { + Proxy result = proxies.get(uri); + return result != null ? Collections.singletonList(result) : Collections.emptyList(); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + } +} diff --git a/src/test/java/internal/net/proxy/FailsafeSystemProxySpiTest.java b/src/test/java/internal/net/proxy/FailsafeSystemProxySpiTest.java new file mode 100644 index 0000000..9171aec --- /dev/null +++ b/src/test/java/internal/net/proxy/FailsafeSystemProxySpiTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package internal.net.proxy; + +import _test.LogCollector; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedList; +import java.util.Queue; +import java.util.function.BiConsumer; +import static org.assertj.core.api.Assertions.*; +import org.junit.Test; + +/** + * + * @author Philippe Charles + */ +public class FailsafeSystemProxySpiTest { + + @Test + public void testFactories() throws URISyntaxException { + assertThatNullPointerException() + .isThrownBy(() -> new FailsafeSystemProxySpi(null, this::doNothing)); + + assertThatNullPointerException() + .isThrownBy(() -> new FailsafeSystemProxySpi(this::pass, null)); + + assertThatNullPointerException() + .isThrownBy(() -> FailsafeSystemProxySpi.wrap(null)); + } + + @Test + public void testGetProxyOrNull() throws URISyntaxException { + URI uri = new URI("https://www.nbb.be"); + + Queue errorStack = new LinkedList<>(); + BiConsumer pushError = (m, e) -> errorStack.add(e); + + assertThatNullPointerException() + .isThrownBy(() -> new FailsafeSystemProxySpi(this::pass, pushError).getProxyOrNull(null)); + assertThat(errorStack).isEmpty(); + + assertThat(new FailsafeSystemProxySpi(this::pass, pushError).getProxyOrNull(uri)).isEqualTo(mainProxy); + assertThat(errorStack).isEmpty(); + + assertThat(new FailsafeSystemProxySpi(this::fail, pushError).getProxyOrNull(uri)).isNull(); + assertThat(errorStack).hasSize(1); + } + + @Test + public void testLogUnexpectedError() throws URISyntaxException { + URI uri = new URI("https://www.nbb.be"); + + try (LogCollector logs = LogCollector.of(FailsafeSystemProxySpi.class)) { + new FailsafeSystemProxySpi(this::fail, FailsafeSystemProxySpi::logUnexpectedError).getProxyOrNull(uri); + assertThat(logs) + .hasSize(1) + .element(0) + .extracting(record -> record.getThrown().getMessage()) + .asString() + .contains("boom"); + } + } + + private final Proxy mainProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("main", 1234)); + + private Proxy pass(URI o) { + return mainProxy; + } + + private Proxy fail(URI o) { + throw new RuntimeException("boom"); + } + + private void doNothing(String msg, Exception ex) { + } +} diff --git a/src/test/java/internal/net/TtlCacheTest.java b/src/test/java/internal/net/proxy/x/TtlCacheTest.java similarity index 85% rename from src/test/java/internal/net/TtlCacheTest.java rename to src/test/java/internal/net/proxy/x/TtlCacheTest.java index 811599d..f216284 100644 --- a/src/test/java/internal/net/TtlCacheTest.java +++ b/src/test/java/internal/net/proxy/x/TtlCacheTest.java @@ -14,7 +14,7 @@ * See the Licence for the specific language governing permissions and * limitations under the Licence. */ -package internal.net; +package internal.net.proxy.x; import _test.LogCollector; import java.util.concurrent.ConcurrentHashMap; @@ -35,7 +35,7 @@ public class TtlCacheTest { @Test @SuppressWarnings("null") public void test() { - ConcurrentMap> map = new ConcurrentHashMap<>(); + ConcurrentMap> storage = new ConcurrentHashMap<>(); AtomicLong clock = new AtomicLong(0); AtomicInteger counter = new AtomicInteger(); AtomicReference event = new AtomicReference<>(); @@ -46,7 +46,7 @@ public void test() { .minTtlInMillis(10) .maxTtlInMillis(1000) .ttlFactor(1) - .cache(map) + .storage(storage) .clock(clock::get) .onEvent((k, e) -> event.set(e)) .build(); @@ -62,40 +62,44 @@ public void test() { duration.set(5); assertThat(cache.get("a", loader)).isEqualTo(1); assertThat(event).hasValue(TtlCache.Event.MISS_FAST); - assertThat(map).doesNotContainKey("a"); + assertThat(storage).doesNotContainKey("a"); assertThat(clock).hasValue(5); duration.set(10); assertThat(cache.get("a", loader)).isEqualTo(2); assertThat(event).hasValue(TtlCache.Event.MISS_SLOW); - assertThat(map).containsKey("a"); + assertThat(storage).containsKey("a"); assertThat(clock).hasValue(15); duration.set(10); assertThat(cache.get("a", loader)).isEqualTo(2); assertThat(event).hasValue(TtlCache.Event.HIT); - assertThat(map).containsKey("a"); + assertThat(storage).containsKey("a"); assertThat(clock).hasValue(15); clock.addAndGet(1000); duration.set(10); assertThat(cache.get("a", loader)).isEqualTo(3); assertThat(event).hasValue(TtlCache.Event.EXP_SLOW); - assertThat(map).containsKey("a"); + assertThat(storage).containsKey("a"); assertThat(clock).hasValue(1025); clock.addAndGet(1000); duration.set(5); assertThat(cache.get("a", loader)).isEqualTo(4); assertThat(event).hasValue(TtlCache.Event.EXP_FAST); - assertThat(map).doesNotContainKey("a"); + assertThat(storage).doesNotContainKey("a"); assertThat(clock).hasValue(2030); } @Test public void testLog() { try (LogCollector logs = LogCollector.of(TtlCache.class)) { - TtlCache.of().get("hello", o -> "world"); + TtlCache.of() + .toBuilder() + .clock(() -> 0) + .build() + .get("hello", o -> "world"); assertThat(logs) .hasSize(1) .element(0) diff --git a/src/test/java/internal/net/proxy/x/WinPowerShellProxySelectorTest.java b/src/test/java/internal/net/proxy/x/WinPowerShellProxySelectorTest.java new file mode 100644 index 0000000..f09fb40 --- /dev/null +++ b/src/test/java/internal/net/proxy/x/WinPowerShellProxySelectorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package internal.net.proxy.x; + +import java.util.ServiceLoader; +import nbbrd.net.proxy.SystemProxySelector; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + +/** + * + * @author Philippe Charles + */ +public class WinPowerShellProxySelectorTest { + + @Test + public void testRegistration() { + assertThat(ServiceLoader.load(SystemProxySelector.Spi.class)) + .anyMatch(WinPowerShellProxySelector.class::isInstance); + } +} diff --git a/src/test/java/nbbrd/net/SystemProxySelectorTest.java b/src/test/java/nbbrd/net/SystemProxySelectorTest.java deleted file mode 100644 index 64dbe92..0000000 --- a/src/test/java/nbbrd/net/SystemProxySelectorTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2018 National Bank of Belgium - * - * Licensed under the EUPL, Version 1.1 or - as soon they will be approved - * by the European Commission - subsequent versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: - * - * http://ec.europa.eu/idabc/eupl - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the Licence is distributed on an "AS IS" basis, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and - * limitations under the Licence. - */ -package nbbrd.net; - -import _test.LogCollector; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.function.BiConsumer; -import java.util.function.UnaryOperator; -import static org.assertj.core.api.Assertions.*; -import org.junit.Test; - -/** - * - * @author Philippe Charles - */ -public class SystemProxySelectorTest { - - @Test - public void test() throws URISyntaxException { - URI uri = new URI("https://www.nbb.be"); - Queue errorStack = new LinkedList<>(); - ProxySelector fallbackSelector = new FallbackProxySelector(fallbackProxy); - UnaryOperator noProperties = o -> null; - BiConsumer pushError = (m, e) -> errorStack.add(e); - - assertThatIllegalArgumentException() - .isThrownBy( - () -> SystemProxySelector - .builder() - .systemProperties(noProperties) - .fallback(fallbackSelector) - .onUnexpectedError(pushError) - .build() - .select(null) - ); - - assertThat(SystemProxySelector - .builder() - .systemProperties(noProperties) - .fallback(fallbackSelector) - .onUnexpectedError(pushError) - .build() - .select(uri) - ).containsExactly(fallbackProxy); - assertThat(errorStack).isEmpty(); - - assertThat(SystemProxySelector - .builder() - .provider(o -> mainProxy) - .systemProperties(noProperties) - .fallback(fallbackSelector) - .onUnexpectedError(pushError) - .build() - .select(uri) - ).containsExactly(mainProxy); - assertThat(errorStack).isEmpty(); - - assertThat(SystemProxySelector - .builder() - .provider(o -> mainProxy) - .systemProperties(o -> "http.proxyHost") - .fallback(fallbackSelector) - .onUnexpectedError(pushError) - .build() - .select(uri) - ).containsExactly(fallbackProxy); - assertThat(errorStack).isEmpty(); - - assertThat(SystemProxySelector - .builder() - .provider(this::boom) - .systemProperties(noProperties) - .fallback(fallbackSelector) - .onUnexpectedError(pushError) - .build() - .select(uri) - ).containsExactly(fallbackProxy); - assertThat(errorStack).hasSize(1); - } - - @Test - public void testLog() throws URISyntaxException { - try (LogCollector logs = LogCollector.of(SystemProxySelector.class)) { - SystemProxySelector.ofServiceLoader() - .toBuilder() - .clearProviders() - .provider(this::boom) - .build() - .select(new URI("https://www.nbb.be")); - assertThat(logs) - .hasSize(1) - .element(0) - .extracting(o -> o.getThrown().getMessage()) - .asString() - .contains("boom"); - } - } - - private final Proxy mainProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("main", 1234)); - private final Proxy fallbackProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("fallback", 1234)); - - private Proxy boom(URI o) { - throw new RuntimeException("boom"); - } - - @lombok.AllArgsConstructor - private static final class FallbackProxySelector extends ProxySelector { - - private final Proxy proxy; - - @Override - public List select(URI uri) { - return Collections.singletonList(proxy); - } - - @Override - public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { - } - } -} diff --git a/src/test/java/nbbrd/net/proxy/SystemProxySelectorTest.java b/src/test/java/nbbrd/net/proxy/SystemProxySelectorTest.java new file mode 100644 index 0000000..fb8d57d --- /dev/null +++ b/src/test/java/nbbrd/net/proxy/SystemProxySelectorTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 National Bank of Belgium + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved + * by the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * http://ec.europa.eu/idabc/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ +package nbbrd.net.proxy; + +import _test.ProxyMap; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.function.UnaryOperator; +import static org.assertj.core.api.Assertions.*; +import org.junit.Test; + +/** + * + * @author Philippe Charles + */ +public class SystemProxySelectorTest { + + @Test + public void test() throws URISyntaxException { + URI uri = new URI("https://www.nbb.be"); + + ProxySelector fallbackSelector = ProxyMap.builder().proxy(uri, fallbackProxy).build(); + UnaryOperator noProperties = o -> null; + + assertThatIllegalArgumentException() + .isThrownBy( + () -> SystemProxySelector + .builder() + .systemProperties(noProperties) + .fallback(fallbackSelector) + .build() + .select(null) + ); + + assertThat(SystemProxySelector + .builder() + .systemProperties(noProperties) + .fallback(fallbackSelector) + .build() + .select(uri) + ).containsExactly(fallbackProxy); + + assertThat(SystemProxySelector + .builder() + .provider(o -> mainProxy) + .systemProperties(noProperties) + .fallback(fallbackSelector) + .build() + .select(uri) + ).containsExactly(mainProxy); + + assertThat(SystemProxySelector + .builder() + .provider(o -> mainProxy) + .systemProperties(o -> "http.proxyHost") + .fallback(fallbackSelector) + .build() + .select(uri) + ).containsExactly(fallbackProxy); + } + + private final Proxy mainProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("main", 1234)); + private final Proxy fallbackProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("fallback", 1234)); +}