mirror of https://github.com/jwtk/jjwt.git
Use ServiceLoader instead of reflection to resolve implementation classes.
By using ServiceLoader the hardcoded dependency of implementation classes becomes obsolete, so that the API will be truly independent from the implementation. Also this approach paves the way for migration to JPMS modules, as these also leverage the ServiceLoader API. Use ServiceLoader instead of reflection to resolve CompressionCodec implementation classes. Isolate key- and key-pair generators and use ServiceLoader instead of reflection to invert dependencies. Move FactoryLoader logic to Services class and improve package layout. Resolve Deserializer using the ServiceLoader instead of reflection and hardcoded reference. Resolve Serializer using the ServiceLoader instead of reflection and hardcoded reference.
This commit is contained in:
parent
bf7e300d6b
commit
ef32a1386d
|
@ -0,0 +1,28 @@
|
|||
package io.jsonwebtoken;
|
||||
|
||||
/**
|
||||
* Factory for {@link CompressionCodec} implementations. Backs the {@link io.jsonwebtoken.CompressionCodecs} constants
|
||||
* class. Implementations of jjwt-api should provide their own implementation and make it available via a provider
|
||||
* configuration file in META-INF/services.
|
||||
*/
|
||||
public interface CompressionCodecFactory {
|
||||
|
||||
/**
|
||||
* Creates a new instance of a CompressionCodec implementing the <a href="https://tools.ietf.org/html/rfc7518">JWA</a>
|
||||
* standard <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
|
||||
*
|
||||
* @return A new instance of a deflate CompressionCodec
|
||||
*/
|
||||
CompressionCodec deflateCodec();
|
||||
|
||||
/**
|
||||
* Creates a new instance of a CompressionCodec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a>
|
||||
* compression algorithm. * <h3>Compatibility Warning</h3> * <p><b>This is not a standard JWA compression
|
||||
* algorithm</b>. Be sure to use this only when you are confident * that all parties accessing the token support
|
||||
* the gzip algorithm.</p> * <p>If you're concerned about compatibility, the {@link #deflateCodec()} code is JWA
|
||||
* standards-compliant.</p>
|
||||
*
|
||||
* @return A new instance of a gzip CompressionCodec
|
||||
*/
|
||||
CompressionCodec gzipCodec();
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.lang.Classes;
|
||||
import io.jsonwebtoken.lang.Services;
|
||||
|
||||
/**
|
||||
* Provides default implementations of the {@link CompressionCodec} interface.
|
||||
|
@ -26,6 +26,8 @@ import io.jsonwebtoken.lang.Classes;
|
|||
*/
|
||||
public final class CompressionCodecs {
|
||||
|
||||
private static final CompressionCodecFactory FACTORY = Services.loadFirst(CompressionCodecFactory.class);
|
||||
|
||||
private CompressionCodecs() {
|
||||
} //prevent external instantiation
|
||||
|
||||
|
@ -33,8 +35,7 @@ public final class CompressionCodecs {
|
|||
* Codec implementing the <a href="https://tools.ietf.org/html/rfc7518">JWA</a> standard
|
||||
* <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
|
||||
*/
|
||||
public static final CompressionCodec DEFLATE =
|
||||
Classes.newInstance("io.jsonwebtoken.impl.compression.DeflateCompressionCodec");
|
||||
public static final CompressionCodec DEFLATE = FACTORY.deflateCodec();
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> compression algorithm.
|
||||
|
@ -43,7 +44,6 @@ public final class CompressionCodecs {
|
|||
* that all parties accessing the token support the gzip algorithm.</p>
|
||||
* <p>If you're concerned about compatibility, the {@link #DEFLATE DEFLATE} code is JWA standards-compliant.</p>
|
||||
*/
|
||||
public static final CompressionCodec GZIP =
|
||||
Classes.newInstance("io.jsonwebtoken.impl.compression.GzipCompressionCodec");
|
||||
public static final CompressionCodec GZIP = FACTORY.gzipCodec();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package io.jsonwebtoken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Factory for creating instances of JWT interfaces. Backs the {@link io.jsonwebtoken.Jwts} factory class.
|
||||
* Implementations of jjwt-api should provide their own implementation and make it available via a provider
|
||||
* configuration file in META-INF/services.
|
||||
*
|
||||
* @since 0.1
|
||||
*/
|
||||
public interface JwtFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. As this
|
||||
* is a less common use of JWTs, consider using the {@link #jwsHeader()} factory method instead if you will later
|
||||
* digitally sign the JWT.
|
||||
*
|
||||
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs.
|
||||
*/
|
||||
Header header();
|
||||
|
||||
/**
|
||||
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs, populated
|
||||
* with the specified name/value pairs. As this is a less common use of JWTs, consider using the
|
||||
* {@link #jwsHeader(java.util.Map)} factory method instead if you will later digitally sign the JWT.
|
||||
*
|
||||
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs.
|
||||
*/
|
||||
Header header(Map<String, Object> header);
|
||||
|
||||
/**
|
||||
* Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's).
|
||||
*
|
||||
* @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's).
|
||||
* @see JwtBuilder#setHeader(Header)
|
||||
*/
|
||||
JwsHeader jwsHeader();
|
||||
|
||||
/**
|
||||
* Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the
|
||||
* specified name/value pairs.
|
||||
*
|
||||
* @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the
|
||||
* specified name/value pairs.
|
||||
* @see JwtBuilder#setHeader(Header)
|
||||
*/
|
||||
JwsHeader jwsHeader(Map<String, Object> header);
|
||||
|
||||
/**
|
||||
* Returns a new {@link Claims} instance to be used as a JWT body.
|
||||
*
|
||||
* @return a new {@link Claims} instance to be used as a JWT body.
|
||||
*/
|
||||
Claims claims();
|
||||
|
||||
/**
|
||||
* Returns a new {@link Claims} instance populated with the specified name/value pairs.
|
||||
*
|
||||
* @param claims the name/value pairs to populate the new Claims instance.
|
||||
* @return a new {@link Claims} instance populated with the specified name/value pairs.
|
||||
*/
|
||||
Claims claims(Map<String, Object> claims);
|
||||
|
||||
/**
|
||||
* Returns a new {@link JwtParser} instance that can be configured and then used to parse JWT strings.
|
||||
*
|
||||
* @return a new {@link JwtParser} instance that can be configured and then used to parse JWT strings.
|
||||
*/
|
||||
JwtParser parser();
|
||||
|
||||
/**
|
||||
* Returns a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized
|
||||
* strings.
|
||||
*
|
||||
* @return a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized
|
||||
* strings.
|
||||
*/
|
||||
JwtBuilder builder();
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.lang.Classes;
|
||||
import io.jsonwebtoken.lang.Services;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -23,11 +23,11 @@ import java.util.Map;
|
|||
* Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good
|
||||
* alternative to tightly coupling your code to implementation classes.
|
||||
*
|
||||
* @since 0.1
|
||||
* @since 0.11
|
||||
*/
|
||||
public final class Jwts {
|
||||
|
||||
private static final Class[] MAP_ARG = new Class[]{Map.class};
|
||||
private static final JwtFactory FACTORY = Services.loadFirst(JwtFactory.class);
|
||||
|
||||
private Jwts() {
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public final class Jwts {
|
|||
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs.
|
||||
*/
|
||||
public static Header header() {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader");
|
||||
return FACTORY.header();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,7 +51,7 @@ public final class Jwts {
|
|||
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs.
|
||||
*/
|
||||
public static Header header(Map<String, Object> header) {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader", MAP_ARG, header);
|
||||
return FACTORY.header(header);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +61,7 @@ public final class Jwts {
|
|||
* @see JwtBuilder#setHeader(Header)
|
||||
*/
|
||||
public static JwsHeader jwsHeader() {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader");
|
||||
return FACTORY.jwsHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,7 +73,7 @@ public final class Jwts {
|
|||
* @see JwtBuilder#setHeader(Header)
|
||||
*/
|
||||
public static JwsHeader jwsHeader(Map<String, Object> header) {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader", MAP_ARG, header);
|
||||
return FACTORY.jwsHeader(header);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +82,7 @@ public final class Jwts {
|
|||
* @return a new {@link Claims} instance to be used as a JWT body.
|
||||
*/
|
||||
public static Claims claims() {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaims");
|
||||
return FACTORY.claims();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +92,7 @@ public final class Jwts {
|
|||
* @return a new {@link Claims} instance populated with the specified name/value pairs.
|
||||
*/
|
||||
public static Claims claims(Map<String, Object> claims) {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaims", MAP_ARG, claims);
|
||||
return FACTORY.claims(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,7 +118,7 @@ public final class Jwts {
|
|||
*/
|
||||
@Deprecated
|
||||
public static JwtParser parser() {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParser");
|
||||
return FACTORY.parser();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,6 +138,6 @@ public final class Jwts {
|
|||
* strings.
|
||||
*/
|
||||
public static JwtBuilder builder() {
|
||||
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder");
|
||||
return FACTORY.builder();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package io.jsonwebtoken.lang;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
|
||||
/**
|
||||
* Exception indicating that no implementation of an jjwt-api SPI was found on the classpath.
|
||||
*/
|
||||
public class ImplementationNotFoundException extends JwtException {
|
||||
|
||||
ImplementationNotFoundException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
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 {
|
||||
|
||||
/**
|
||||
* 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 <T> 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 <T> List<T> loadAllAvailableImplementations(Class<T> spi) {
|
||||
ServiceLoader<T> serviceLoader = ServiceLoader.load(spi);
|
||||
|
||||
List<T> implementations = new ArrayList<>();
|
||||
for (T implementation : serviceLoader) {
|
||||
implementations.add(implementation);
|
||||
}
|
||||
|
||||
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 <T> The type of the SPI
|
||||
* @return A new instance of the service.
|
||||
* @throws ImplementationNotFoundException When no implementation the SPI is available on the classpath.
|
||||
*/
|
||||
public static <T> T loadFirst(Class<T> spi) {
|
||||
ServiceLoader<T> serviceLoader = ServiceLoader.load(spi);
|
||||
if (serviceLoader.iterator().hasNext()) {
|
||||
return serviceLoader.iterator().next();
|
||||
} else {
|
||||
throw new ImplementationNotFoundException("No implementation of " + spi.getName() + " found on the classpath. Make sure to include an implementation of jjwt-api.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public interface KeyGenerator {
|
||||
boolean supports(SignatureAlgorithm alg);
|
||||
|
||||
/**
|
||||
* Generates a new secure-random secret key of a length suitable for creating and verifying HMAC signatures
|
||||
* according to the specified {@code SignatureAlgorithm}.
|
||||
*
|
||||
* @param alg the desired signature algorithm
|
||||
* @return a new secure-random secret key of a length suitable for creating and verifying HMAC signatures according
|
||||
* to the specified {@code SignatureAlgorithm}.
|
||||
*/
|
||||
SecretKey generateKey(SignatureAlgorithm alg);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
public interface KeyPairGenerator {
|
||||
boolean supports(SignatureAlgorithm alg);
|
||||
|
||||
/**
|
||||
* Generates a new secure-random key pair of sufficient strength for the specified {@link SignatureAlgorithm} using
|
||||
* JJWT's default SecureRandom instance.
|
||||
*
|
||||
* @param alg the algorithm indicating strength
|
||||
* @return a new secure-randomly generated key pair of sufficient strength for the specified {@link
|
||||
* SignatureAlgorithm}.
|
||||
*/
|
||||
KeyPair generateKeyPair(SignatureAlgorithm alg);
|
||||
}
|
|
@ -17,7 +17,7 @@ package io.jsonwebtoken.security;
|
|||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Classes;
|
||||
import io.jsonwebtoken.lang.Services;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
@ -32,12 +32,8 @@ import java.util.List;
|
|||
* @since 0.10.0
|
||||
*/
|
||||
public final class Keys {
|
||||
|
||||
private static final String MAC = "io.jsonwebtoken.impl.crypto.MacProvider";
|
||||
private static final String RSA = "io.jsonwebtoken.impl.crypto.RsaProvider";
|
||||
private static final String EC = "io.jsonwebtoken.impl.crypto.EllipticCurveProvider";
|
||||
|
||||
private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class};
|
||||
private static final List<KeyGenerator> KEY_GENERATORS = Services.loadAllAvailableImplementations(KeyGenerator.class);
|
||||
private static final List<KeyPairGenerator> KEY_PAIR_GENERATORS = Services.loadAllAvailableImplementations(KeyPairGenerator.class);
|
||||
|
||||
//purposefully ordered higher to lower:
|
||||
private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
|
||||
|
@ -129,15 +125,14 @@ public final class Keys {
|
|||
*/
|
||||
public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgumentException {
|
||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||
switch (alg) {
|
||||
case HS256:
|
||||
case HS384:
|
||||
case HS512:
|
||||
return Classes.invokeStatic(MAC, "generateKey", SIG_ARG_TYPES, alg);
|
||||
default:
|
||||
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
for (KeyGenerator keyGenerator : KEY_GENERATORS) {
|
||||
if (keyGenerator.supports(alg)) {
|
||||
return keyGenerator.generateKey(alg);
|
||||
}
|
||||
}
|
||||
|
||||
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,21 +206,14 @@ public final class Keys {
|
|||
*/
|
||||
public static KeyPair keyPairFor(SignatureAlgorithm alg) throws IllegalArgumentException {
|
||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||
switch (alg) {
|
||||
case RS256:
|
||||
case PS256:
|
||||
case RS384:
|
||||
case PS384:
|
||||
case RS512:
|
||||
case PS512:
|
||||
return Classes.invokeStatic(RSA, "generateKeyPair", SIG_ARG_TYPES, alg);
|
||||
case ES256:
|
||||
case ES384:
|
||||
case ES512:
|
||||
return Classes.invokeStatic(EC, "generateKeyPair", SIG_ARG_TYPES, alg);
|
||||
default:
|
||||
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
|
||||
for (KeyPairGenerator keyPairGenerator : KEY_PAIR_GENERATORS) {
|
||||
if (keyPairGenerator.supports(alg)) {
|
||||
return keyPairGenerator.generateKeyPair(alg);
|
||||
}
|
||||
}
|
||||
|
||||
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,41 +15,41 @@
|
|||
*/
|
||||
package io.jsonwebtoken
|
||||
|
||||
import io.jsonwebtoken.lang.Classes
|
||||
import io.jsonwebtoken.lang.Services
|
||||
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.easymock.EasyMock.createMock
|
||||
import static org.easymock.EasyMock.eq
|
||||
import static org.easymock.EasyMock.expect
|
||||
import static org.junit.Assert.assertSame
|
||||
import static org.powermock.api.easymock.PowerMock.mockStatic
|
||||
import static org.powermock.api.easymock.PowerMock.replay
|
||||
import static org.powermock.api.easymock.PowerMock.verify
|
||||
import static org.powermock.api.easymock.PowerMock.*
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest([Classes, CompressionCodecs])
|
||||
@PrepareForTest([Services, CompressionCodecs])
|
||||
class CompressionCodecsTest {
|
||||
|
||||
@Test
|
||||
void testStatics() {
|
||||
|
||||
mockStatic(Classes)
|
||||
mockStatic(Services)
|
||||
|
||||
def factory = createMock(CompressionCodecFactory)
|
||||
|
||||
expect(Services.loadFirst(CompressionCodecFactory)).andReturn(factory)
|
||||
|
||||
def deflate = createMock(CompressionCodec)
|
||||
def gzip = createMock(CompressionCodec)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.compression.DeflateCompressionCodec"))).andReturn(deflate)
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.compression.GzipCompressionCodec"))).andReturn(gzip)
|
||||
expect(factory.deflateCodec()).andReturn(deflate)
|
||||
expect(factory.gzipCodec()).andReturn(gzip)
|
||||
|
||||
replay Classes, deflate, gzip
|
||||
replay Services, factory, deflate, gzip
|
||||
|
||||
assertSame deflate, CompressionCodecs.DEFLATE
|
||||
assertSame gzip, CompressionCodecs.GZIP
|
||||
|
||||
verify Classes, deflate, gzip
|
||||
verify Services, factory, deflate, gzip
|
||||
|
||||
//test coverage for private constructor:
|
||||
new CompressionCodecs()
|
||||
|
|
|
@ -15,26 +15,44 @@
|
|||
*/
|
||||
package io.jsonwebtoken
|
||||
|
||||
import io.jsonwebtoken.lang.Classes
|
||||
import io.jsonwebtoken.lang.Services
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
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.easymock.EasyMock.createMock
|
||||
import static org.easymock.EasyMock.eq
|
||||
import static org.easymock.EasyMock.expect
|
||||
import static org.easymock.EasyMock.mock
|
||||
import static org.easymock.EasyMock.reset
|
||||
import static org.easymock.EasyMock.same
|
||||
import static org.junit.Assert.assertSame
|
||||
import static org.powermock.api.easymock.PowerMock.createMock
|
||||
import static org.powermock.api.easymock.PowerMock.mockStatic
|
||||
import static org.powermock.api.easymock.PowerMock.replay
|
||||
import static org.powermock.api.easymock.PowerMock.reset
|
||||
import static org.powermock.api.easymock.PowerMock.verify
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest([Classes, Jwts])
|
||||
@PrepareForTest([Services])
|
||||
class JwtsTest {
|
||||
|
||||
static JwtFactory factory = mock(JwtFactory)
|
||||
|
||||
@BeforeClass
|
||||
static void prepareFactory() {
|
||||
mockStatic(Services)
|
||||
|
||||
expect(Services.loadFirst(JwtFactory)).andReturn(factory).anyTimes()
|
||||
|
||||
replay Services
|
||||
}
|
||||
|
||||
@Before
|
||||
void resetFactoryMock() {
|
||||
reset(factory)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrivateCtor() { //for code coverage only
|
||||
new Jwts()
|
||||
|
@ -43,146 +61,118 @@ class JwtsTest {
|
|||
@Test
|
||||
void testHeader() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def instance = createMock(Header)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultHeader"))).andReturn(instance)
|
||||
expect(factory.header()).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.header()
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHeaderFromMap() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def map = [:]
|
||||
|
||||
def instance = createMock(Header)
|
||||
|
||||
expect(Classes.newInstance(
|
||||
eq("io.jsonwebtoken.impl.DefaultHeader"),
|
||||
same(Jwts.MAP_ARG),
|
||||
same(map))
|
||||
).andReturn(instance)
|
||||
expect(factory.header(same(map) as Map<String, Object>)).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.header(map)
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJwsHeader() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def instance = createMock(JwsHeader)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultJwsHeader"))).andReturn(instance)
|
||||
expect(factory.jwsHeader()).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.jwsHeader()
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJwsHeaderFromMap() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def map = [:]
|
||||
|
||||
def instance = createMock(JwsHeader)
|
||||
|
||||
expect(Classes.newInstance(
|
||||
eq("io.jsonwebtoken.impl.DefaultJwsHeader"),
|
||||
same(Jwts.MAP_ARG),
|
||||
same(map))
|
||||
).andReturn(instance)
|
||||
expect(factory.jwsHeader(same(map) as Map<String, Object>)).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.jwsHeader(map)
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClaims() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def instance = createMock(Claims)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultClaims"))).andReturn(instance)
|
||||
expect(factory.claims()).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.claims()
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClaimsFromMap() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def map = [:]
|
||||
|
||||
def instance = createMock(Claims)
|
||||
|
||||
expect(Classes.newInstance(
|
||||
eq("io.jsonwebtoken.impl.DefaultClaims"),
|
||||
same(Jwts.MAP_ARG),
|
||||
same(map))
|
||||
).andReturn(instance)
|
||||
expect(factory.claims(same(map) as Map<String, Object>)).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.claims(map)
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParser() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def instance = createMock(JwtParser)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultJwtParser"))).andReturn(instance)
|
||||
expect(factory.parser()).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.parser()
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilder() {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def instance = createMock(JwtBuilder)
|
||||
|
||||
expect(Classes.newInstance(eq("io.jsonwebtoken.impl.DefaultJwtBuilder"))).andReturn(instance)
|
||||
expect(factory.builder()).andReturn(instance)
|
||||
|
||||
replay Classes, instance
|
||||
replay factory, instance
|
||||
|
||||
assertSame instance, Jwts.builder()
|
||||
|
||||
verify Classes, instance
|
||||
verify factory, instance
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package io.jsonwebtoken
|
||||
|
||||
class TestJwtFactory implements JwtFactory {
|
||||
@Override
|
||||
Header header() {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
Header header(final Map<String, Object> header) {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
JwsHeader jwsHeader() {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
JwsHeader jwsHeader(final Map<String, Object> header) {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
Claims claims() {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
Claims claims(final Map<String, Object> claim) {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
JwtParser parser() {
|
||||
return null
|
||||
}
|
||||
|
||||
@Override
|
||||
JwtBuilder builder() {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.jsonwebtoken.lang
|
||||
|
||||
import io.jsonwebtoken.JwtFactory
|
||||
import io.jsonwebtoken.TestJwtFactory
|
||||
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.assertNotNull
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest([Services])
|
||||
class ServicesTest {
|
||||
|
||||
@Test
|
||||
void testSuccessfulLoading() {
|
||||
def factory = Services.loadFirst(JwtFactory.class)
|
||||
|
||||
assertNotNull factory
|
||||
|
||||
org.junit.Assert.assertEquals(TestJwtFactory, factory.class)
|
||||
}
|
||||
|
||||
@Test(expected = ImplementationNotFoundException)
|
||||
void testFailedLoading() {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader()
|
||||
|
||||
Thread.currentThread().setContextClassLoader(new NoServicesClassLoader(cl))
|
||||
|
||||
Services.loadFirst(JwtFactory.class)
|
||||
}
|
||||
|
||||
static class NoServicesClassLoader extends ClassLoader {
|
||||
private NoServicesClassLoader(ClassLoader parent) {
|
||||
super(parent)
|
||||
}
|
||||
|
||||
@Override
|
||||
Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (name.startsWith("META-INF/services/")) {
|
||||
return java.util.Collections.emptyEnumeration()
|
||||
} else {
|
||||
return super.getResources(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,9 @@
|
|||
package io.jsonwebtoken.security
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.lang.Classes
|
||||
import io.jsonwebtoken.lang.Services
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
|
@ -25,8 +27,8 @@ import org.powermock.modules.junit4.PowerMockRunner
|
|||
import javax.crypto.SecretKey
|
||||
import java.security.KeyPair
|
||||
|
||||
import static org.easymock.EasyMock.eq
|
||||
import static org.easymock.EasyMock.expect
|
||||
import static org.easymock.EasyMock.mock
|
||||
import static org.easymock.EasyMock.same
|
||||
import static org.junit.Assert.*
|
||||
import static org.powermock.api.easymock.PowerMock.*
|
||||
|
@ -37,9 +39,28 @@ import static org.powermock.api.easymock.PowerMock.*
|
|||
* The actual implementation assertions are done in KeysImplTest in the impl module.
|
||||
*/
|
||||
@RunWith(PowerMockRunner)
|
||||
@PrepareForTest([Classes, Keys])
|
||||
@PrepareForTest([Keys, Services])
|
||||
class KeysTest {
|
||||
|
||||
static KeyGenerator keyGenerator = mock(KeyGenerator)
|
||||
static KeyPairGenerator keyPairGenerator = mock(KeyPairGenerator)
|
||||
|
||||
@BeforeClass
|
||||
static void prepareServices() {
|
||||
mockStatic(Services)
|
||||
|
||||
expect(Services.loadAllAvailableImplementations(KeyGenerator)).andReturn([keyGenerator]).anyTimes()
|
||||
expect(Services.loadAllAvailableImplementations(KeyPairGenerator)).andReturn([keyPairGenerator]).anyTimes()
|
||||
|
||||
replay Services
|
||||
}
|
||||
|
||||
@Before
|
||||
void reset() {
|
||||
reset keyGenerator
|
||||
reset keyPairGenerator
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrivateCtor() { //for code coverage only
|
||||
new Keys()
|
||||
|
@ -80,27 +101,30 @@ class KeysTest {
|
|||
|
||||
if (name.startsWith('H')) {
|
||||
|
||||
mockStatic(Classes)
|
||||
|
||||
def key = createMock(SecretKey)
|
||||
expect(Classes.invokeStatic(eq(Keys.MAC), eq("generateKey"), same(Keys.SIG_ARG_TYPES), same(alg))).andReturn(key)
|
||||
expect(keyGenerator.supports(same(alg))).andReturn(true)
|
||||
expect(keyGenerator.generateKey(same(alg))).andReturn(key)
|
||||
|
||||
replay Classes, key
|
||||
replay keyGenerator, key
|
||||
|
||||
assertSame key, Keys.secretKeyFor(alg)
|
||||
|
||||
verify Classes, key
|
||||
verify keyGenerator, key
|
||||
|
||||
reset Classes, key
|
||||
reset keyGenerator, key
|
||||
|
||||
} else {
|
||||
expect(keyGenerator.supports(same(alg))).andReturn(false)
|
||||
|
||||
replay(keyGenerator)
|
||||
|
||||
try {
|
||||
Keys.secretKeyFor(alg)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertEquals "The $name algorithm does not support shared secret keys." as String, expected.message
|
||||
reset keyGenerator
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,27 +138,29 @@ class KeysTest {
|
|||
String name = alg.name()
|
||||
|
||||
if (name.equals('NONE') || name.startsWith('H')) {
|
||||
expect(keyPairGenerator.supports(alg)).andReturn(false)
|
||||
|
||||
replay keyPairGenerator
|
||||
|
||||
try {
|
||||
Keys.keyPairFor(alg)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertEquals "The $name algorithm does not support Key Pairs." as String, expected.message
|
||||
reset keyPairGenerator
|
||||
}
|
||||
} else {
|
||||
String fqcn = name.startsWith('E') ? Keys.EC : Keys.RSA
|
||||
|
||||
mockStatic Classes
|
||||
|
||||
def pair = createMock(KeyPair)
|
||||
expect(Classes.invokeStatic(eq(fqcn), eq("generateKeyPair"), same(Keys.SIG_ARG_TYPES), same(alg))).andReturn(pair)
|
||||
expect(keyPairGenerator.supports(same(alg))).andReturn(true)
|
||||
expect(keyPairGenerator.generateKeyPair(same(alg))).andReturn(pair)
|
||||
|
||||
replay Classes, pair
|
||||
replay keyPairGenerator, pair
|
||||
|
||||
assertSame pair, Keys.keyPairFor(alg)
|
||||
|
||||
verify Classes, pair
|
||||
verify keyPairGenerator, pair
|
||||
|
||||
reset Classes, pair
|
||||
reset keyPairGenerator, pair
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.TestJwtFactory
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.JacksonDeserializer
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.JacksonSerializer
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.OrgJsonDeserializer
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.OrgJsonSerializer
|
|
@ -0,0 +1,18 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import io.jsonwebtoken.CompressionCodec;
|
||||
import io.jsonwebtoken.CompressionCodecFactory;
|
||||
import io.jsonwebtoken.impl.compression.DeflateCompressionCodec;
|
||||
import io.jsonwebtoken.impl.compression.GzipCompressionCodec;
|
||||
|
||||
public class DefaultCompressionCodecFactory implements CompressionCodecFactory {
|
||||
@Override
|
||||
public CompressionCodec deflateCodec() {
|
||||
return new DeflateCompressionCodec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressionCodec gzipCodec() {
|
||||
return new GzipCompressionCodec();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.JwtFactory;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.impl.DefaultClaims;
|
||||
import io.jsonwebtoken.impl.DefaultHeader;
|
||||
import io.jsonwebtoken.impl.DefaultJwsHeader;
|
||||
import io.jsonwebtoken.impl.DefaultJwtBuilder;
|
||||
import io.jsonwebtoken.impl.DefaultJwtParser;
|
||||
|
||||
public class DefaultJwtFactory implements JwtFactory {
|
||||
|
||||
@Override
|
||||
public Header header() {
|
||||
return new DefaultHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header header(final Map<String, Object> map) {
|
||||
return new DefaultHeader(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwsHeader jwsHeader() {
|
||||
return new DefaultJwsHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwsHeader jwsHeader(final Map<String, Object> header) {
|
||||
return new DefaultJwsHeader(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Claims claims() {
|
||||
return new DefaultClaims();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Claims claims(final Map<String, Object> claims) {
|
||||
return new DefaultClaims(claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtBuilder builder() {
|
||||
return new DefaultJwtBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtParser parser() {
|
||||
return new DefaultJwtParser();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.KeyPairGenerator;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
public class EllipticCurveKeyPairGenerator implements KeyPairGenerator {
|
||||
@Override
|
||||
public boolean supports(SignatureAlgorithm alg) {
|
||||
return alg.isEllipticCurve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
||||
return EllipticCurveProvider.generateKeyPair(alg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.KeyGenerator;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public final class MacKeyGenerator implements KeyGenerator {
|
||||
|
||||
@Override
|
||||
public boolean supports(SignatureAlgorithm alg) {
|
||||
return alg.isHmac();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateKey(SignatureAlgorithm alg) {
|
||||
return MacProvider.generateKey(alg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.KeyPairGenerator;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
public class RsaKeyPairGenerator implements KeyPairGenerator {
|
||||
@Override
|
||||
public boolean supports(SignatureAlgorithm alg) {
|
||||
return alg.isRsa();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
||||
return RsaProvider.generateKeyPair(alg);
|
||||
}
|
||||
}
|
|
@ -132,7 +132,6 @@ public abstract class RsaProvider extends SignatureProvider {
|
|||
* @see #generateKeyPair(String, int, SecureRandom)
|
||||
* @since 0.10.0
|
||||
*/
|
||||
@SuppressWarnings("unused") //used by io.jsonwebtoken.security.Keys
|
||||
public static KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
||||
Assert.isTrue(alg.isRsa(), "Only RSA algorithms are supported by this method.");
|
||||
int keySizeInBits = 4096;
|
||||
|
|
|
@ -17,8 +17,9 @@ package io.jsonwebtoken.impl.io;
|
|||
|
||||
import io.jsonwebtoken.io.Deserializer;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Classes;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
|
@ -45,12 +46,10 @@ public class RuntimeClasspathDeserializerLocator<T> implements InstanceLocator<D
|
|||
|
||||
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||
protected Deserializer<T> 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");
|
||||
ServiceLoader<Deserializer> serviceLoader = ServiceLoader.load(Deserializer.class);
|
||||
Iterator<Deserializer> iterator = serviceLoader.iterator();
|
||||
if(iterator.hasNext()) {
|
||||
return (Deserializer<T>)iterator.next();
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to discover any JSON Deserializer implementations on the classpath.");
|
||||
}
|
||||
|
@ -60,9 +59,4 @@ public class RuntimeClasspathDeserializerLocator<T> implements InstanceLocator<D
|
|||
protected boolean compareAndSet(Deserializer<T> d) {
|
||||
return DESERIALIZER.compareAndSet(null, d);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||
protected boolean isAvailable(String fqcn) {
|
||||
return Classes.isAvailable(fqcn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ package io.jsonwebtoken.impl.io;
|
|||
|
||||
import io.jsonwebtoken.io.Serializer;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Classes;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
|
@ -43,14 +44,12 @@ public class RuntimeClasspathSerializerLocator implements InstanceLocator<Serial
|
|||
return serializer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||
@SuppressWarnings({"unchecked", "WeakerAccess"}) //to allow testing override
|
||||
protected Serializer<Object> 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");
|
||||
ServiceLoader<Serializer> serviceLoader = ServiceLoader.load(Serializer.class);
|
||||
Iterator<Serializer> iterator = serviceLoader.iterator();
|
||||
if(iterator.hasNext()) {
|
||||
return (Serializer<Object>)iterator.next();
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to discover any JSON Serializer implementations on the classpath.");
|
||||
}
|
||||
|
@ -60,9 +59,4 @@ public class RuntimeClasspathSerializerLocator implements InstanceLocator<Serial
|
|||
protected boolean compareAndSet(Serializer<Object> s) {
|
||||
return SERIALIZER.compareAndSet(null, s);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||
protected boolean isAvailable(String fqcn) {
|
||||
return Classes.isAvailable(fqcn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.impl.DefaultCompressionCodecFactory
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.impl.DefaultJwtFactory
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.impl.crypto.MacKeyGenerator
|
|
@ -0,0 +1,2 @@
|
|||
io.jsonwebtoken.impl.crypto.RsaKeyPairGenerator
|
||||
io.jsonwebtoken.impl.crypto.EllipticCurveKeyPairGenerator
|
|
@ -0,0 +1,26 @@
|
|||
package io.jsonwebtoken.impl
|
||||
|
||||
import io.jsonwebtoken.impl.DefaultCompressionCodecFactory
|
||||
import io.jsonwebtoken.impl.compression.DeflateCompressionCodec
|
||||
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
class DefaultCompressionCodecFactoryTest {
|
||||
@Test
|
||||
void testCreateDeflateCodec() {
|
||||
|
||||
def deflate = new DefaultCompressionCodecFactory().deflateCodec()
|
||||
|
||||
assertEquals(DeflateCompressionCodec, deflate.class)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateGzipCodec() {
|
||||
|
||||
def gzipCodec = new DefaultCompressionCodecFactory().gzipCodec()
|
||||
|
||||
assertEquals(GzipCompressionCodec, gzipCodec.class)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.jsonwebtoken.impl.io
|
||||
|
||||
class FakeServiceDescriptorClassLoader extends ClassLoader {
|
||||
private String serviceDescriptor
|
||||
|
||||
FakeServiceDescriptorClassLoader(ClassLoader parent, String serviceDescriptor) {
|
||||
super(parent)
|
||||
this.serviceDescriptor = serviceDescriptor
|
||||
}
|
||||
|
||||
@Override
|
||||
Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (name.startsWith("META-INF/services/")) {
|
||||
return super.getResources(serviceDescriptor)
|
||||
} else {
|
||||
return super.getResources(name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package io.jsonwebtoken.impl.io
|
||||
|
||||
class NoServiceDescriptorClassLoader extends ClassLoader {
|
||||
NoServiceDescriptorClassLoader(ClassLoader parent) {
|
||||
super(parent)
|
||||
}
|
||||
|
||||
@Override
|
||||
Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (name.startsWith("META-INF/services/")) {
|
||||
return Collections.emptyEnumeration()
|
||||
} else {
|
||||
return super.getResources(name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.io
|
||||
|
||||
|
||||
import io.jsonwebtoken.io.Deserializer
|
||||
import io.jsonwebtoken.jackson.io.JacksonDeserializer
|
||||
import io.jsonwebtoken.orgjson.io.OrgJsonDeserializer
|
||||
|
@ -29,6 +28,10 @@ import static org.junit.Assert.*
|
|||
|
||||
class RuntimeClasspathDeserializerLocatorTest {
|
||||
|
||||
private static final String TEST_SERVICE_DESCRIPTOR = "io.jsonwebtoken.io.Deserializer.test.orgjson"
|
||||
|
||||
private ClassLoader originalClassLoader
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null)
|
||||
|
@ -37,18 +40,23 @@ class RuntimeClasspathDeserializerLocatorTest {
|
|||
@After
|
||||
void teardown() {
|
||||
RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null)
|
||||
restoreOriginalClassLoader()
|
||||
}
|
||||
|
||||
private void restoreOriginalClassLoader() {
|
||||
if(originalClassLoader != null) {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader)
|
||||
originalClassLoader = null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClassIsNotAvailable() {
|
||||
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||
@Override
|
||||
protected boolean isAvailable(String fqcn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
prepareNoServiceDescriptorClassLoader()
|
||||
|
||||
try {
|
||||
locator.getInstance()
|
||||
new RuntimeClasspathDeserializerLocator().getInstance()
|
||||
fail 'Located Deserializer class, whereas none was expected.'
|
||||
} catch (Exception ex) {
|
||||
assertEquals 'Unable to discover any JSON Deserializer implementations on the classpath.', ex.message
|
||||
}
|
||||
|
@ -99,20 +107,12 @@ class RuntimeClasspathDeserializerLocatorTest {
|
|||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
prepareFakeServiceClassLoader()
|
||||
|
||||
def deserializer = locator.getInstance()
|
||||
def deserializer = new RuntimeClasspathDeserializerLocator().getInstance()
|
||||
assertTrue deserializer instanceof OrgJsonDeserializer
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testGson() {
|
||||
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||
|
@ -131,4 +131,14 @@ class RuntimeClasspathDeserializerLocatorTest {
|
|||
def deserializer = locator.getInstance()
|
||||
assertTrue deserializer instanceof GsonDeserializer
|
||||
}
|
||||
|
||||
private void prepareNoServiceDescriptorClassLoader() {
|
||||
originalClassLoader = Thread.currentThread().getContextClassLoader()
|
||||
Thread.currentThread().setContextClassLoader(new NoServiceDescriptorClassLoader(originalClassLoader))
|
||||
}
|
||||
|
||||
private void prepareFakeServiceClassLoader() {
|
||||
originalClassLoader = Thread.currentThread().getContextClassLoader()
|
||||
Thread.currentThread().setContextClassLoader(new FakeServiceDescriptorClassLoader(originalClassLoader, TEST_SERVICE_DESCRIPTOR))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ import static org.junit.Assert.*
|
|||
|
||||
class RuntimeClasspathSerializerLocatorTest {
|
||||
|
||||
private static final String TEST_SERVICE_DESCRIPTOR = "io.jsonwebtoken.io.Serializer.test.orgjson"
|
||||
|
||||
private ClassLoader originalClassLoader
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
RuntimeClasspathSerializerLocator.SERIALIZER.set(null)
|
||||
|
@ -36,18 +40,23 @@ class RuntimeClasspathSerializerLocatorTest {
|
|||
@After
|
||||
void teardown() {
|
||||
RuntimeClasspathSerializerLocator.SERIALIZER.set(null)
|
||||
restoreOriginalClassLoader()
|
||||
}
|
||||
|
||||
private void restoreOriginalClassLoader() {
|
||||
if(originalClassLoader != null) {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader)
|
||||
originalClassLoader = null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClassIsNotAvailable() {
|
||||
def locator = new RuntimeClasspathSerializerLocator() {
|
||||
@Override
|
||||
protected boolean isAvailable(String fqcn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
prepareNoServiceDescriptorClassLoader()
|
||||
|
||||
try {
|
||||
locator.getInstance()
|
||||
new RuntimeClasspathSerializerLocator().getInstance()
|
||||
fail 'Located Deserializer class, whereas none was expected.'
|
||||
} catch (Exception ex) {
|
||||
assertEquals 'Unable to discover any JSON Serializer implementations on the classpath.', ex.message
|
||||
}
|
||||
|
@ -98,20 +107,12 @@ class RuntimeClasspathSerializerLocatorTest {
|
|||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
prepareFakeServiceClassLoader()
|
||||
|
||||
def serializer = locator.getInstance()
|
||||
def serializer = new RuntimeClasspathSerializerLocator().getInstance()
|
||||
assertTrue serializer instanceof OrgJsonSerializer
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testGson() {
|
||||
def locator = new RuntimeClasspathSerializerLocator() {
|
||||
|
@ -130,4 +131,14 @@ class RuntimeClasspathSerializerLocatorTest {
|
|||
def serializer = locator.getInstance()
|
||||
assertTrue serializer instanceof GsonSerializer
|
||||
}
|
||||
|
||||
private void prepareNoServiceDescriptorClassLoader() {
|
||||
originalClassLoader = Thread.currentThread().getContextClassLoader()
|
||||
Thread.currentThread().setContextClassLoader(new NoServiceDescriptorClassLoader(originalClassLoader))
|
||||
}
|
||||
|
||||
private void prepareFakeServiceClassLoader() {
|
||||
originalClassLoader = Thread.currentThread().getContextClassLoader()
|
||||
Thread.currentThread().setContextClassLoader(new FakeServiceDescriptorClassLoader(originalClassLoader, TEST_SERVICE_DESCRIPTOR))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.OrgJsonDeserializer
|
|
@ -0,0 +1 @@
|
|||
io.jsonwebtoken.io.OrgJsonSerializer
|
Loading…
Reference in New Issue