diff --git a/api/src/main/java/io/jsonwebtoken/lang/Services.java b/api/src/main/java/io/jsonwebtoken/lang/Services.java new file mode 100644 index 00000000..fde9a603 --- /dev/null +++ b/api/src/main/java/io/jsonwebtoken/lang/Services.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.lang; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +/** + * Helper class for loading services from the classpath, using a {@link ServiceLoader}. Decouples loading logic for + * better separation of concerns and testability. + */ +public final class Services { + + private Services() {} + + /** + * Loads and instantiates all service implementation of the given SPI class and returns them as a List. + * + * @param spi The class of the Service Provider Interface + * @param The type of the SPI + * @return An unmodifiable list with an instance of all available implementations of the SPI. No guarantee is given + * on the order of implementations, if more than one. + */ + public static List loadAll(Class spi) { + Assert.notNull(spi, "Parameter 'spi' must not be null."); + ServiceLoader serviceLoader = ServiceLoader.load(spi); + + List implementations = new ArrayList<>(); + + for (T implementation : serviceLoader) { + implementations.add(implementation); + } + + // fail if no implementations were found + if (implementations.isEmpty()) { + throw new UnavailableImplementationException(spi); + } + + return Collections.unmodifiableList(implementations); + } + + /** + * Loads the first available implementation the given SPI class from the classpath. Uses the {@link ServiceLoader} + * to find implementations. When multiple implementations are available it will return the first one that it + * encounters. There is no guarantee with regard to ordering. + * + * @param spi The class of the Service Provider Interface + * @param The type of the SPI + * @return A new instance of the service. + * @throws UnavailableImplementationException When no implementation the SPI is available on the classpath. + */ + public static T loadFirst(Class spi) { + Assert.notNull(spi, "Parameter 'spi' must not be null."); + ServiceLoader serviceLoader = ServiceLoader.load(spi); + if (serviceLoader.iterator().hasNext()) { + return serviceLoader.iterator().next(); + } else { + throw new UnavailableImplementationException(spi); + } + } +} diff --git a/api/src/main/java/io/jsonwebtoken/lang/UnavailableImplementationException.java b/api/src/main/java/io/jsonwebtoken/lang/UnavailableImplementationException.java new file mode 100644 index 00000000..6fedbf08 --- /dev/null +++ b/api/src/main/java/io/jsonwebtoken/lang/UnavailableImplementationException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.lang; + +/** + * Exception indicating that no implementation of an jjwt-api SPI was found on the classpath. + * @since 0.11.0 + */ +public final class UnavailableImplementationException extends RuntimeException { + + private static final String DEFAULT_NOT_FOUND_MESSAGE = "Unable to find an implementation for %s using java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, for example jjwt-impl.jar, or your own .jar for custom implementations."; + + UnavailableImplementationException(final Class klass) { + super(String.format(DEFAULT_NOT_FOUND_MESSAGE, klass)); + } +} diff --git a/api/src/main/java/io/jsonwebtoken/lang/UnknownClassException.java b/api/src/main/java/io/jsonwebtoken/lang/UnknownClassException.java index 9a4f395d..07b44d9f 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/UnknownClassException.java +++ b/api/src/main/java/io/jsonwebtoken/lang/UnknownClassException.java @@ -48,16 +48,17 @@ public class UnknownClassException extends RuntimeException { public UnknownClassException(Throwable cause) { super(cause); } + */ /** * Constructs a new UnknownClassException. * * @param message the reason for the exception * @param cause the underlying Throwable that caused this exception to be thrown. - * + */ public UnknownClassException(String message, Throwable cause) { + // TODO: remove in v1.0, this constructor is only exposed to allow for backward compatible behavior super(message, cause); } - */ } \ No newline at end of file diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/InstanceLocator.java b/api/src/test/groovy/io/jsonwebtoken/DefaultStubService.groovy similarity index 78% rename from impl/src/main/java/io/jsonwebtoken/impl/io/InstanceLocator.java rename to api/src/test/groovy/io/jsonwebtoken/DefaultStubService.groovy index 4c1e1224..52b831a9 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/InstanceLocator.java +++ b/api/src/test/groovy/io/jsonwebtoken/DefaultStubService.groovy @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 jsonwebtoken.io + * Copyright (C) 2019 jsonwebtoken.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jsonwebtoken.impl.io; +package io.jsonwebtoken -/** - * @since 0.10.0 - */ -public interface InstanceLocator { - - T getInstance(); +class DefaultStubService implements StubService { } diff --git a/api/src/test/groovy/io/jsonwebtoken/StubService.groovy b/api/src/test/groovy/io/jsonwebtoken/StubService.groovy new file mode 100644 index 00000000..c64661c7 --- /dev/null +++ b/api/src/test/groovy/io/jsonwebtoken/StubService.groovy @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken + +interface StubService { +} diff --git a/api/src/test/groovy/io/jsonwebtoken/lang/ServicesTest.groovy b/api/src/test/groovy/io/jsonwebtoken/lang/ServicesTest.groovy new file mode 100644 index 00000000..9f9aed35 --- /dev/null +++ b/api/src/test/groovy/io/jsonwebtoken/lang/ServicesTest.groovy @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.lang + +import io.jsonwebtoken.DefaultStubService +import io.jsonwebtoken.StubService +import org.junit.Test +import org.junit.runner.RunWith +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner + +import static org.junit.Assert.assertEquals +import static org.junit.Assert.assertNotNull + +@RunWith(PowerMockRunner.class) +@PrepareForTest([Services]) +class ServicesTest { + + @Test + void testSuccessfulLoading() { + def factory = Services.loadFirst(StubService) + assertNotNull factory + assertEquals(DefaultStubService, factory.class) + } + + @Test(expected = UnavailableImplementationException) + void testLoadFirstUnavailable() { + NoServicesClassLoader.runWith { + Services.loadFirst(StubService.class) + } + } + + @Test(expected = UnavailableImplementationException) + void testLoadAllUnavailable() { + NoServicesClassLoader.runWith { + Services.loadAll(StubService.class) + } + } + + static class NoServicesClassLoader extends ClassLoader { + private NoServicesClassLoader(ClassLoader parent) { + super(parent) + } + + @Override + Enumeration getResources(String name) throws IOException { + if (name.startsWith("META-INF/services/")) { + return java.util.Collections.emptyEnumeration() + } else { + return super.getResources(name) + } + } + + static void runWith(Closure closure) { + ClassLoader originalClassloader = Thread.currentThread().getContextClassLoader() + try { + Thread.currentThread().setContextClassLoader(new NoServicesClassLoader(originalClassloader)) + closure.run() + } finally { + if (originalClassloader != null) { + Thread.currentThread().setContextClassLoader(originalClassloader) + } + } + } + } +} diff --git a/api/src/test/resources/META-INF/services/io.jsonwebtoken.StubService b/api/src/test/resources/META-INF/services/io.jsonwebtoken.StubService new file mode 100644 index 00000000..067b1cca --- /dev/null +++ b/api/src/test/resources/META-INF/services/io.jsonwebtoken.StubService @@ -0,0 +1 @@ +io.jsonwebtoken.DefaultStubService \ No newline at end of file diff --git a/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Deserializer b/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Deserializer new file mode 100644 index 00000000..d0a65718 --- /dev/null +++ b/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Deserializer @@ -0,0 +1 @@ +io.jsonwebtoken.gson.io.GsonDeserializer \ No newline at end of file diff --git a/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Serializer b/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Serializer new file mode 100644 index 00000000..7530fd82 --- /dev/null +++ b/extensions/gson/src/resources/META-INF/services/io.jsonwebtoken.io.Serializer @@ -0,0 +1 @@ +io.jsonwebtoken.gson.io.GsonSerializer \ No newline at end of file diff --git a/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer b/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer new file mode 100644 index 00000000..75aeb9fb --- /dev/null +++ b/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer @@ -0,0 +1 @@ +io.jsonwebtoken.jackson.io.JacksonDeserializer \ No newline at end of file diff --git a/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer b/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer new file mode 100644 index 00000000..295fb694 --- /dev/null +++ b/extensions/jackson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer @@ -0,0 +1 @@ +io.jsonwebtoken.jackson.io.JacksonSerializer \ No newline at end of file diff --git a/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer b/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer new file mode 100644 index 00000000..d8240c44 --- /dev/null +++ b/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer @@ -0,0 +1 @@ +io.jsonwebtoken.orgjson.io.OrgJsonDeserializer \ No newline at end of file diff --git a/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer b/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer new file mode 100644 index 00000000..6eb2035b --- /dev/null +++ b/extensions/orgjson/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer @@ -0,0 +1 @@ +io.jsonwebtoken.orgjson.io.OrgJsonSerializer \ No newline at end of file diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index d1b5c2a6..df2d2215 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -24,14 +24,13 @@ import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.crypto.DefaultJwtSigner; import io.jsonwebtoken.impl.crypto.JwtSigner; -import io.jsonwebtoken.impl.io.InstanceLocator; +import io.jsonwebtoken.impl.lang.LegacyServices; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.InvalidKeyException; @@ -295,10 +294,10 @@ public class DefaultJwtBuilder implements JwtBuilder { public String compact() { if (this.serializer == null) { - //try to find one based on the runtime environment: - InstanceLocator>> locator = - Classes.newInstance("io.jsonwebtoken.impl.io.RuntimeClasspathSerializerLocator"); - this.serializer = locator.getInstance(); + // try to find one based on the services available + // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0 + // use the previous commented out line instead + this.serializer = LegacyServices.loadFirst(Serializer.class); } if (payload == null && Collections.isEmpty(claims)) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index af7352a2..497c66b9 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -39,13 +39,12 @@ import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver; import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator; import io.jsonwebtoken.impl.crypto.JwtSignatureValidator; -import io.jsonwebtoken.impl.io.InstanceLocator; +import io.jsonwebtoken.impl.lang.LegacyServices; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; @@ -107,13 +106,6 @@ public class DefaultJwtParser implements JwtParser { this.base64UrlDecoder = base64UrlDecoder; this.deserializer = deserializer; this.compressionCodecResolver = compressionCodecResolver; - - if (this.deserializer == null) { - //try to find one based on the runtime environment: - InstanceLocator>> locator = - Classes.newInstance("io.jsonwebtoken.impl.io.RuntimeClasspathDeserializerLocator"); - this.deserializer = locator.getInstance(); - } } @Override @@ -255,12 +247,12 @@ public class DefaultJwtParser implements JwtParser { @Override public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException { - // TODO move this to constructor before 1.0 + // TODO, this logic is only need for a now deprecated code path + // remove this block in v1.0 (the equivalent is already in DefaultJwtParserBuilder) if (this.deserializer == null) { - //try to find one based on the runtime environment: - InstanceLocator>> locator = - Classes.newInstance("io.jsonwebtoken.impl.io.RuntimeClasspathDeserializerLocator"); - this.deserializer = locator.getInstance(); + // try to find one based on the services available + // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0 + this.deserializer = LegacyServices.loadFirst(Deserializer.class); } Assert.hasText(jwt, "JWT String argument cannot be null or empty."); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java index 82a7c888..3189500e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java @@ -26,6 +26,8 @@ import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Services; + import java.security.Key; import java.util.Date; import java.util.Map; @@ -170,6 +172,14 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder { @Override public JwtParser build() { + + // Only lookup the deserializer IF it is null. It is possible a Deserializer implementation was set + // that is NOT exposed as a service and no other implementations are available for lookup. + if (this.deserializer == null) { + // try to find one based on the services available: + this.deserializer = Services.loadFirst(Deserializer.class); + } + return new ImmutableJwtParser( new DefaultJwtParser(signingKeyResolver, key, diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java index fc281886..88602dac 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java @@ -17,11 +17,17 @@ package io.jsonwebtoken.impl.compression; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodecResolver; +import io.jsonwebtoken.CompressionCodecs; import io.jsonwebtoken.CompressionException; import io.jsonwebtoken.Header; import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Services; import io.jsonwebtoken.lang.Strings; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * Default implementation of {@link CompressionCodecResolver} that supports the following: *

@@ -45,6 +51,22 @@ import io.jsonwebtoken.lang.Strings; */ public class DefaultCompressionCodecResolver implements CompressionCodecResolver { + private static final String MISSING_COMPRESSION_MESSAGE = "Unable to find an implementation for compression algorithm [%s] using java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, for example jjwt-impl.jar, or your own .jar for custom implementations."; + + private final Map codecs; + + public DefaultCompressionCodecResolver() { + Map codecMap = new HashMap<>(); + for (CompressionCodec codec : Services.loadAll(CompressionCodec.class)) { + codecMap.put(codec.getAlgorithmName().toUpperCase(), codec); + } + + codecMap.put(CompressionCodecs.DEFLATE.getAlgorithmName().toUpperCase(), CompressionCodecs.DEFLATE); + codecMap.put(CompressionCodecs.GZIP.getAlgorithmName().toUpperCase(), CompressionCodecs.GZIP); + + codecs = Collections.unmodifiableMap(codecMap); + } + @Override public CompressionCodec resolveCompressionCodec(Header header) { String cmpAlg = getAlgorithmFromHeader(header); @@ -54,14 +76,7 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver if (!hasCompressionAlgorithm) { return null; } - if (io.jsonwebtoken.CompressionCodecs.DEFLATE.getAlgorithmName().equalsIgnoreCase(cmpAlg)) { - return io.jsonwebtoken.CompressionCodecs.DEFLATE; - } - if (io.jsonwebtoken.CompressionCodecs.GZIP.getAlgorithmName().equalsIgnoreCase(cmpAlg)) { - return io.jsonwebtoken.CompressionCodecs.GZIP; - } - - throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'"); + return byName(cmpAlg); } private String getAlgorithmFromHeader(Header header) { @@ -69,4 +84,15 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver return header.getCompressionAlgorithm(); } + + private CompressionCodec byName(String name) { + Assert.hasText(name, "'name' must not be empty"); + + CompressionCodec codec = codecs.get(name.toUpperCase()); + if (codec == null) { + throw new CompressionException(String.format(MISSING_COMPRESSION_MESSAGE, name)); + } + + return codec; + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocator.java b/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocator.java deleted file mode 100644 index c02785f0..00000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed 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 io.jsonwebtoken.impl.io; - -import io.jsonwebtoken.io.Deserializer; -import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Classes; - -import java.util.concurrent.atomic.AtomicReference; - -/** - * @since 0.10.0 - */ -public class RuntimeClasspathDeserializerLocator implements InstanceLocator> { - - private static final AtomicReference DESERIALIZER = new AtomicReference<>(); - - @SuppressWarnings("unchecked") - @Override - public Deserializer getInstance() { - Deserializer deserializer = DESERIALIZER.get(); - if (deserializer == null) { - deserializer = locate(); - Assert.state(deserializer != null, "locate() cannot return null."); - if (!compareAndSet(deserializer)) { - deserializer = DESERIALIZER.get(); - } - } - Assert.state(deserializer != null, "deserializer cannot be null."); - return deserializer; - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected Deserializer locate() { - if (isAvailable("io.jsonwebtoken.jackson.io.JacksonDeserializer")) { - return Classes.newInstance("io.jsonwebtoken.jackson.io.JacksonDeserializer"); - } else if (isAvailable("io.jsonwebtoken.orgjson.io.OrgJsonDeserializer")) { - return Classes.newInstance("io.jsonwebtoken.orgjson.io.OrgJsonDeserializer"); - } else if (isAvailable("io.jsonwebtoken.gson.io.GsonDeserializer")) { - return Classes.newInstance("io.jsonwebtoken.gson.io.GsonDeserializer"); - } else { - throw new IllegalStateException("Unable to discover any JSON Deserializer implementations on the classpath."); - } - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected boolean compareAndSet(Deserializer d) { - return DESERIALIZER.compareAndSet(null, d); - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected boolean isAvailable(String fqcn) { - return Classes.isAvailable(fqcn); - } -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocator.java b/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocator.java deleted file mode 100644 index dfb5aae3..00000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed 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 io.jsonwebtoken.impl.io; - -import io.jsonwebtoken.io.Serializer; -import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Classes; - -import java.util.concurrent.atomic.AtomicReference; - -/** - * @since 0.10.0 - */ -public class RuntimeClasspathSerializerLocator implements InstanceLocator { - - private static final AtomicReference> SERIALIZER = new AtomicReference<>(); - - @SuppressWarnings("unchecked") - @Override - public Serializer getInstance() { - Serializer serializer = SERIALIZER.get(); - if (serializer == null) { - serializer = locate(); - Assert.state(serializer != null, "locate() cannot return null."); - if (!compareAndSet(serializer)) { - serializer = SERIALIZER.get(); - } - } - Assert.state(serializer != null, "serializer cannot be null."); - return serializer; - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected Serializer locate() { - if (isAvailable("io.jsonwebtoken.jackson.io.JacksonSerializer")) { - return Classes.newInstance("io.jsonwebtoken.jackson.io.JacksonSerializer"); - } else if (isAvailable("io.jsonwebtoken.orgjson.io.OrgJsonSerializer")) { - return Classes.newInstance("io.jsonwebtoken.orgjson.io.OrgJsonSerializer"); - } else if (isAvailable("io.jsonwebtoken.gson.io.GsonSerializer")) { - return Classes.newInstance("io.jsonwebtoken.gson.io.GsonSerializer"); - } else { - throw new IllegalStateException("Unable to discover any JSON Serializer implementations on the classpath."); - } - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected boolean compareAndSet(Serializer s) { - return SERIALIZER.compareAndSet(null, s); - } - - @SuppressWarnings("WeakerAccess") //to allow testing override - protected boolean isAvailable(String fqcn) { - return Classes.isAvailable(fqcn); - } -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/LegacyServices.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/LegacyServices.java new file mode 100644 index 00000000..aa75fca2 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/LegacyServices.java @@ -0,0 +1,28 @@ +package io.jsonwebtoken.impl.lang; + +import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Services; +import io.jsonwebtoken.lang.UnavailableImplementationException; +import io.jsonwebtoken.lang.UnknownClassException; + +/** + * A backward compatibility {@link Services} utility to help migrate away from {@link Classes#newInstance(String)}. + * TODO: remove before v1.0 + * @deprecated use {@link Services} directly + */ +@Deprecated +public final class LegacyServices { + + /** + * Wraps {@code Services.loadFirst} and throws a {@link UnknownClassException} instead of a + * {@link UnavailableImplementationException} to retain the previous behavior. This method should be used when + * to retain the previous behavior of methods that throw an unchecked UnknownClassException. + */ + public static T loadFirst(Class spi) { + try { + return Services.loadFirst(spi); + } catch (UnavailableImplementationException e) { + throw new UnknownClassException(e.getMessage(), e); + } + } +} diff --git a/impl/src/main/resources/META-INF/services/io.jsonwebtoken.CompressionCodec b/impl/src/main/resources/META-INF/services/io.jsonwebtoken.CompressionCodec new file mode 100644 index 00000000..ead2efe6 --- /dev/null +++ b/impl/src/main/resources/META-INF/services/io.jsonwebtoken.CompressionCodec @@ -0,0 +1,2 @@ +io.jsonwebtoken.impl.compression.DeflateCompressionCodec +io.jsonwebtoken.impl.compression.GzipCompressionCodec \ No newline at end of file diff --git a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy index 40dc970c..6cf72035 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy @@ -19,8 +19,9 @@ import io.jsonwebtoken.impl.DefaultHeader import io.jsonwebtoken.impl.DefaultJwsHeader import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver import io.jsonwebtoken.impl.compression.GzipCompressionCodec -import io.jsonwebtoken.impl.io.RuntimeClasspathSerializerLocator import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.io.Serializer +import io.jsonwebtoken.lang.Services import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.WeakKeyException @@ -43,7 +44,7 @@ class DeprecatedJwtsTest { } protected static String toJson(o) { - def serializer = new RuntimeClasspathSerializerLocator().getInstance() + def serializer = Services.loadFirst(Serializer) byte[] bytes = serializer.serialize(o) return new String(bytes, Strings.UTF_8) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 35fddd42..39d08424 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -19,8 +19,9 @@ import io.jsonwebtoken.impl.DefaultHeader import io.jsonwebtoken.impl.DefaultJwsHeader import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver import io.jsonwebtoken.impl.compression.GzipCompressionCodec -import io.jsonwebtoken.impl.io.RuntimeClasspathSerializerLocator import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.io.Serializer +import io.jsonwebtoken.lang.Services import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.WeakKeyException @@ -43,7 +44,7 @@ class JwtsTest { } protected static String toJson(o) { - def serializer = new RuntimeClasspathSerializerLocator().getInstance() + def serializer = Services.loadFirst(Serializer) byte[] bytes = serializer.serialize(o) return new String(bytes, Strings.UTF_8) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolverTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolverTest.groovy new file mode 100644 index 00000000..b1441ed7 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolverTest.groovy @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.impl.compression + +import io.jsonwebtoken.CompressionCodec +import io.jsonwebtoken.CompressionException +import io.jsonwebtoken.impl.DefaultHeader +import io.jsonwebtoken.impl.io.FakeServiceDescriptorClassLoader +import io.jsonwebtoken.lang.Services +import org.junit.Assert +import org.junit.Test + +import io.jsonwebtoken.CompressionCodecs + +import static org.hamcrest.CoreMatchers.hasItem +import static org.hamcrest.CoreMatchers.instanceOf +import static org.hamcrest.CoreMatchers.is +import static org.hamcrest.CoreMatchers.nullValue +import static org.hamcrest.MatcherAssert.assertThat + +class DefaultCompressionCodecResolverTest { + + @Test + void resolveHeaderTest() { + assertThat new DefaultCompressionCodecResolver().resolveCompressionCodec( + new DefaultHeader()), nullValue() + assertThat new DefaultCompressionCodecResolver().resolveCompressionCodec( + new DefaultHeader().setCompressionAlgorithm("def")), is(CompressionCodecs.DEFLATE) + assertThat new DefaultCompressionCodecResolver().resolveCompressionCodec( + new DefaultHeader().setCompressionAlgorithm("gzip")), is(CompressionCodecs.GZIP) + } + + @Test + void invalidCompressionNameTest() { + try { + new DefaultCompressionCodecResolver().resolveCompressionCodec( + new DefaultHeader().setCompressionAlgorithm("expected-missing")) + Assert.fail("Expected CompressionException to be thrown") + } catch (CompressionException e) { + assertThat e.message, is(String.format(DefaultCompressionCodecResolver.MISSING_COMPRESSION_MESSAGE, "expected-missing")) + } + } + + @Test + void overrideDefaultCompressionImplTest() { + FakeServiceDescriptorClassLoader.runWithFake "io.jsonwebtoken.io.compression.CompressionCodec.test.override", { + + // first make sure the service loader actually resolves the test class + assertThat Services.loadAll(CompressionCodec), hasItem(instanceOf(YagCompressionCodec)) + + // now we know the class is loadable, make sure we ALWAYS return the GZIP impl + assertThat new DefaultCompressionCodecResolver().byName("gzip"), instanceOf(GzipCompressionCodec) + } + } + + @Test + void emptyCompressionAlgInHeaderTest() { + try { + new DefaultCompressionCodecResolver().byName("") + Assert.fail("Expected IllegalArgumentException to be thrown") + } catch (IllegalArgumentException e) { + // expected + } + } +} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/compression/YagCompressionCodec.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/YagCompressionCodec.groovy new file mode 100644 index 00000000..52ffe875 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/compression/YagCompressionCodec.groovy @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.impl.compression + +import io.jsonwebtoken.CompressionCodec +import io.jsonwebtoken.CompressionException + +/** + * Yet Another GZIP CompressionCodec. This codec has the same name as the Official GZIP impl. The DefaultCompressionCodecResolver will NOT resolve this class. + */ +class YagCompressionCodec implements CompressionCodec { + + @Override + String getAlgorithmName() { + return new GzipCompressionCodec().getAlgorithmName(); + } + + @Override + byte[] compress(byte[] payload) throws CompressionException { + return new byte[0] + } + + @Override + byte[] decompress(byte[] compressed) throws CompressionException { + return new byte[0] + } +} \ No newline at end of file diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/FakeServiceDescriptorClassLoader.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/FakeServiceDescriptorClassLoader.groovy new file mode 100644 index 00000000..4e2ea508 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/FakeServiceDescriptorClassLoader.groovy @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.impl.io + +class FakeServiceDescriptorClassLoader extends ClassLoader { + private String serviceDescriptor + + FakeServiceDescriptorClassLoader(ClassLoader parent, String serviceDescriptor) { + super(parent) + this.serviceDescriptor = serviceDescriptor + } + + @Override + Enumeration getResources(String name) throws IOException { + if (name.startsWith("META-INF/services/")) { + return super.getResources(serviceDescriptor) + } else { + return super.getResources(name) + } + } + + static void runWithFake(String fakeDescriptor, Closure closure) { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader() + try { + Thread.currentThread().setContextClassLoader(new FakeServiceDescriptorClassLoader(originalClassLoader, fakeDescriptor)) + closure.run() + } finally { + if(originalClassLoader != null) { + Thread.currentThread().setContextClassLoader(originalClassLoader) + } + } + } +} \ No newline at end of file diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/NoServiceDescriptorClassLoader.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/NoServiceDescriptorClassLoader.groovy new file mode 100644 index 00000000..4dc95e52 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/NoServiceDescriptorClassLoader.groovy @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed 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 io.jsonwebtoken.impl.io + +class NoServiceDescriptorClassLoader extends ClassLoader { + NoServiceDescriptorClassLoader(ClassLoader parent) { + super(parent) + } + + @Override + Enumeration getResources(String name) throws IOException { + if (name.startsWith("META-INF/services/")) { + return Collections.emptyEnumeration() + } else { + return super.getResources(name) + } + } + + static void runWith(Closure closure) { + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader() + try { + Thread.currentThread().setContextClassLoader(new NoServiceDescriptorClassLoader(originalClassLoader)) + closure.run() + } finally { + if(originalClassLoader != null) { + Thread.currentThread().setContextClassLoader(originalClassLoader) + } + } + } +} \ No newline at end of file diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocatorTest.groovy deleted file mode 100644 index 41bffbb8..00000000 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathDeserializerLocatorTest.groovy +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed 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 io.jsonwebtoken.impl.io - - -import io.jsonwebtoken.io.Deserializer -import io.jsonwebtoken.jackson.io.JacksonDeserializer -import io.jsonwebtoken.orgjson.io.OrgJsonDeserializer -import io.jsonwebtoken.gson.io.GsonDeserializer -import org.junit.After -import org.junit.Before -import org.junit.Test - -import static org.easymock.EasyMock.createMock -import static org.junit.Assert.* - -class RuntimeClasspathDeserializerLocatorTest { - - @Before - void setUp() { - RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null) - } - - @After - void teardown() { - RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null) - } - - @Test - void testClassIsNotAvailable() { - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - return false - } - } - try { - locator.getInstance() - } catch (Exception ex) { - assertEquals 'Unable to discover any JSON Deserializer implementations on the classpath.', ex.message - } - } - - @Test - void testCompareAndSetFalse() { - Deserializer deserializer = createMock(Deserializer) - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected boolean compareAndSet(Deserializer d) { - RuntimeClasspathDeserializerLocator.DESERIALIZER.set(deserializer) - return false - } - } - - def returned = locator.getInstance() - assertSame deserializer, returned - } - - @Test(expected = IllegalStateException) - void testLocateReturnsNull() { - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected Deserializer locate() { - return null - } - } - locator.getInstance() - } - - @Test(expected = IllegalStateException) - void testCompareAndSetFalseWithNullReturn() { - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected boolean compareAndSet(Deserializer d) { - return false - } - } - locator.getInstance() - } - - @Test - void testJackson() { - def deserializer = new RuntimeClasspathDeserializerLocator().getInstance() - assertTrue deserializer instanceof JacksonDeserializer - } - - @Test - void testOrgJson() { - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - if (JacksonDeserializer.class.getName().equals(fqcn)) { - return false; //skip it to allow the OrgJson impl to be created - } - return super.isAvailable(fqcn) - } - } - - def deserializer = locator.getInstance() - assertTrue deserializer instanceof OrgJsonDeserializer - } - - @Test - void testGson() { - def locator = new RuntimeClasspathDeserializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - if (JacksonDeserializer.class.getName().equals(fqcn)) { - return false; //skip it to allow the Gson impl to be created - } - if (OrgJsonDeserializer.class.getName().equals(fqcn)) { - return false; //skip it to allow the Gson impl to be created - } - return super.isAvailable(fqcn) - } - } - - def deserializer = locator.getInstance() - assertTrue deserializer instanceof GsonDeserializer - } -} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocatorTest.groovy deleted file mode 100644 index 335e2b79..00000000 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/RuntimeClasspathSerializerLocatorTest.groovy +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2014 jsonwebtoken.io - * - * Licensed 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 io.jsonwebtoken.impl.io - -import io.jsonwebtoken.io.Serializer -import io.jsonwebtoken.jackson.io.JacksonSerializer -import io.jsonwebtoken.orgjson.io.OrgJsonSerializer -import io.jsonwebtoken.gson.io.GsonSerializer -import org.junit.After -import org.junit.Before -import org.junit.Test - -import static org.easymock.EasyMock.createMock -import static org.junit.Assert.* - -class RuntimeClasspathSerializerLocatorTest { - - @Before - void setUp() { - RuntimeClasspathSerializerLocator.SERIALIZER.set(null) - } - - @After - void teardown() { - RuntimeClasspathSerializerLocator.SERIALIZER.set(null) - } - - @Test - void testClassIsNotAvailable() { - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - return false - } - } - try { - locator.getInstance() - } catch (Exception ex) { - assertEquals 'Unable to discover any JSON Serializer implementations on the classpath.', ex.message - } - } - - @Test - void testCompareAndSetFalse() { - Serializer serializer = createMock(Serializer) - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected boolean compareAndSet(Serializer s) { - RuntimeClasspathSerializerLocator.SERIALIZER.set(serializer) - return false - } - } - - def returned = locator.getInstance() - assertSame serializer, returned - } - - @Test(expected = IllegalStateException) - void testLocateReturnsNull() { - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected Serializer locate() { - return null - } - } - locator.getInstance() - } - - @Test(expected = IllegalStateException) - void testCompareAndSetFalseWithNullReturn() { - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected boolean compareAndSet(Serializer s) { - return false - } - } - locator.getInstance() - } - - @Test - void testJackson() { - def serializer = new RuntimeClasspathSerializerLocator().getInstance() - assertTrue serializer instanceof JacksonSerializer - } - - @Test - void testOrgJson() { - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - if (JacksonSerializer.class.getName().equals(fqcn)) { - return false //skip it to allow the OrgJson impl to be created - } - return super.isAvailable(fqcn) - } - } - - def serializer = locator.getInstance() - assertTrue serializer instanceof OrgJsonSerializer - } - - @Test - void testGson() { - def locator = new RuntimeClasspathSerializerLocator() { - @Override - protected boolean isAvailable(String fqcn) { - if (JacksonSerializer.class.getName().equals(fqcn)) { - return false //skip it to allow the Gson impl to be created - } - if (OrgJsonSerializer.class.getName().equals(fqcn)) { - return false //skip it to allow the Gson impl to be created - } - return super.isAvailable(fqcn) - } - } - - def serializer = locator.getInstance() - assertTrue serializer instanceof GsonSerializer - } -} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/LegacyServicesTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/LegacyServicesTest.groovy new file mode 100644 index 00000000..60cd3cb3 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/LegacyServicesTest.groovy @@ -0,0 +1,13 @@ +package io.jsonwebtoken.impl.lang + +import io.jsonwebtoken.lang.UnknownClassException +import org.junit.Test + +class LegacyServicesTest { + + @Test(expected = UnknownClassException) + void serviceNotFoundTest() { + // try to load a class that will NOT have any services, i.e. this test class. + LegacyServices.loadFirst(LegacyServicesTest) + } +} diff --git a/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.gson b/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.gson new file mode 100644 index 00000000..d0a65718 --- /dev/null +++ b/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.gson @@ -0,0 +1 @@ +io.jsonwebtoken.gson.io.GsonDeserializer \ No newline at end of file diff --git a/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.orgjson b/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.orgjson new file mode 100644 index 00000000..d8240c44 --- /dev/null +++ b/impl/src/test/resources/io.jsonwebtoken.io.Deserializer.test.orgjson @@ -0,0 +1 @@ +io.jsonwebtoken.orgjson.io.OrgJsonDeserializer \ No newline at end of file diff --git a/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.gson b/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.gson new file mode 100644 index 00000000..7530fd82 --- /dev/null +++ b/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.gson @@ -0,0 +1 @@ +io.jsonwebtoken.gson.io.GsonSerializer \ No newline at end of file diff --git a/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.orgjson b/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.orgjson new file mode 100644 index 00000000..6eb2035b --- /dev/null +++ b/impl/src/test/resources/io.jsonwebtoken.io.Serializer.test.orgjson @@ -0,0 +1 @@ +io.jsonwebtoken.orgjson.io.OrgJsonSerializer \ No newline at end of file diff --git a/impl/src/test/resources/io.jsonwebtoken.io.compression.CompressionCodec.test.override b/impl/src/test/resources/io.jsonwebtoken.io.compression.CompressionCodec.test.override new file mode 100644 index 00000000..6b8eaf96 --- /dev/null +++ b/impl/src/test/resources/io.jsonwebtoken.io.compression.CompressionCodec.test.override @@ -0,0 +1 @@ +io.jsonwebtoken.impl.compression.YagCompressionCodec \ No newline at end of file