You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jl...@apache.org on 2018/04/16 22:45:21 UTC

[17/38] tomee git commit: some refactoring

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
new file mode 100644
index 0000000..d69df02
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueProducer.java
@@ -0,0 +1,75 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Optional;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+
+/**
+ * A producer for the ClaimValue<T> wrapper injection sites.
+ * @param <T> the raw claim type
+ */
+public class ClaimValueProducer<T> {
+
+    @Produces
+    @Claim("")
+    ClaimValue<T> produce(InjectionPoint ip) {
+        String name = getName(ip);
+        ClaimValue<Optional<T>> cv = MPJWTProducer.generalClaimValueProducer(name);
+        ClaimValue<T> returnValue = (ClaimValue<T>) cv;
+        Optional<T> value = cv.getValue();
+        // Pull out the ClaimValue<T> T type,
+        Type matchType = ip.getType();
+        Type actualType = Object.class;
+        boolean isOptional = false;
+        if (matchType instanceof ParameterizedType) {
+            actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+            isOptional = matchType.getTypeName().equals(Optional.class.getTypeName());
+            if (isOptional) {
+                actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+            }
+        }
+
+        if (!actualType.getTypeName().startsWith(Optional.class.getTypeName())) {
+            T nestedValue = value.orElse(null);
+            ClaimValueWrapper<T> wrapper = new ClaimValueWrapper<>(cv.getName());
+            wrapper.setValue(nestedValue);
+            returnValue = wrapper;
+        }
+        return returnValue;
+    }
+
+    String getName(InjectionPoint ip) {
+        String name = null;
+        for (Annotation ann : ip.getQualifiers()) {
+            if (ann instanceof Claim) {
+                Claim claim = (Claim) ann;
+                name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+            }
+        }
+        return name;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
new file mode 100644
index 0000000..6776191
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
@@ -0,0 +1,54 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.ClaimValue;
+
+/**
+ * An implementation of the ClaimValue interface
+ *
+ * @param <T> the claim value type
+ */
+public class ClaimValueWrapper<T> implements ClaimValue<T> {
+    private String name;
+
+    private T value;
+
+    public ClaimValueWrapper(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ClaimValueWrapper[@%s], name=%s, value[%s]=%s", Integer.toHexString(hashCode()),
+                name, value.getClass(), value);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
new file mode 100644
index 0000000..2f991b2
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonValueProducer.java
@@ -0,0 +1,111 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ * A producer for JsonValue injection types
+ */
+public class JsonValueProducer {
+    private static Logger log = Logger.getLogger(JsonValueProducer.class.getName());
+
+    @Produces
+    @Claim("")
+    public JsonString getJsonString(InjectionPoint ip) {
+        return getValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public Optional<JsonString> getOptionalJsonString(InjectionPoint ip) {
+        return getOptionalValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public JsonNumber getJsonNumber(InjectionPoint ip) {
+        return getValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public Optional<JsonNumber> getOptionalJsonNumber(InjectionPoint ip) {
+        return getOptionalValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public JsonArray getJsonArray(InjectionPoint ip) {
+        return getValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public Optional<JsonArray> getOptionalJsonArray(InjectionPoint ip) {
+        return getOptionalValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public JsonObject getJsonObject(InjectionPoint ip) {
+        return getValue(ip);
+    }
+
+    @Produces
+    @Claim("")
+    public Optional<JsonObject> getOptionalJsonObject(InjectionPoint ip) {
+        return getOptionalValue(ip);
+    }
+
+    public <T extends JsonValue> T getValue(InjectionPoint ip) {
+        log.fine(String.format("JsonValueProducer(%s).produce", ip));
+        String name = getName(ip);
+        T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name);
+        return jsonValue;
+    }
+
+    public <T extends JsonValue> Optional<T> getOptionalValue(InjectionPoint ip) {
+        log.fine(String.format("JsonValueProducer(%s).produce", ip));
+        String name = getName(ip);
+        T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name);
+        return Optional.ofNullable(jsonValue);
+    }
+
+    String getName(InjectionPoint ip) {
+        String name = null;
+        for (Annotation ann : ip.getQualifiers()) {
+            if (ann instanceof Claim) {
+                Claim claim = (Claim) ann;
+                name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+            }
+        }
+        return name;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
new file mode 100644
index 0000000..95e1aea
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
@@ -0,0 +1,395 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfoProvider;
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
+import org.apache.tomee.microprofile.jwt.MPJWTInitializer;
+import org.apache.tomee.microprofile.jwt.TCKTokenParser;
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.SessionScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.BeanAttributes;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessBeanAttributes;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.inject.spi.ProcessProducer;
+import javax.inject.Provider;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * A CDI extension that provides a producer for the current authenticated JsonWebToken based on a thread
+ * local value that is managed by the {@link JWTAuthMechanism} request
+ * authentication handler.
+ * <p>
+ * This also installs the producer methods for the discovered:
+ * <ul>
+ * <li>@Claim ClaimValue<T> injection sites.</li>
+ * <li>@Claim raw type<T> injection sites.</li>
+ * <li>@Claim JsonValue injection sites.</li>
+ * </ul>
+ *
+ * @see JWTAuthMechanism
+ */
+public class MPJWTCDIExtension implements Extension {
+    private static Logger log = Logger.getLogger(MPJWTCDIExtension.class.getName());
+
+    /**
+     * Register the MPJWTProducer JsonWebToken producer bean
+     *
+     * @param bbd         before discovery event
+     * @param beanManager cdi bean manager
+     */
+    public void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager beanManager) {
+        log.fine("MPJWTExtension(), added JWTPrincipalProducer");
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(TCKTokenParser.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTFilter.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTInitializer.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(JWTAuthContextInfoProvider.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTProducer.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(RawClaimTypeProducer.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(ClaimValueProducer.class));
+        bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonValueProducer.class));
+    }
+
+    /**
+     * Replace the general producer method BeanAttributes with one bound to the collected injection site
+     * types to properly reflect all of the type locations the producer method applies to.
+     *
+     * @param pba the ProcessBeanAttributes
+     * @see ClaimProviderBeanAttributes
+     */
+    public void addTypeToClaimProducer(@Observes ProcessBeanAttributes pba) {
+        if (pba.getAnnotated().isAnnotationPresent(Claim.class)) {
+            Claim claim = pba.getAnnotated().getAnnotation(Claim.class);
+            if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) {
+                log.fine(String.format("addTypeToClaimProducer: %s\n", pba.getAnnotated()));
+                BeanAttributes delegate = pba.getBeanAttributes();
+                String name = delegate.getName();
+                if (delegate.getTypes().contains(Optional.class)) {
+                    if (providerOptionalTypes.size() == 0) {
+                        providerOptionalTypes.add(Optional.class);
+                    }
+                    pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, providerOptionalTypes, providerQualifiers));
+                    // This is
+                } else if (name != null && name.startsWith("RawClaimTypeProducer#")) {
+                    if (rawTypes.size() == 0) {
+                        rawTypes.add(Object.class);
+                    }
+                    pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, rawTypes, rawTypeQualifiers));
+                    log.fine(String.format("Setup RawClaimTypeProducer BeanAttributes"));
+                }
+            }
+        }
+    }
+
+    public void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager beanManager) {
+    }
+
+    void doProcessProducers(@Observes ProcessProducer pp) {
+    }
+
+    /**
+     * Handle the non-{@linkplain Provider}, {@linkplain org.eclipse.microprofile.jwt.ClaimValue}, and
+     * {@linkplain javax.json.JsonValue} claim injection types.
+     *
+     * @param pip - the injection point event information
+     * @see RawClaimTypeProducer
+     */
+    void processClaimInjections(@Observes ProcessInjectionPoint pip) {
+        log.fine(String.format("pipRaw: %s", pip.getInjectionPoint()));
+        InjectionPoint ip = pip.getInjectionPoint();
+        if (ip.getAnnotated().isAnnotationPresent(Claim.class)) {
+            Claim claim = ip.getAnnotated().getAnnotation(Claim.class);
+            if (ip.getType() instanceof Class) {
+                Class rawClass = (Class) ip.getType();
+                // Primative types
+                if (Modifier.isFinal(rawClass.getModifiers())) {
+                    rawTypes.add(ip.getType());
+                    rawTypeQualifiers.add(claim);
+                    log.fine(String.format("+++ Added Claim raw type: %s", ip.getType()));
+                    Class declaringClass = ip.getMember().getDeclaringClass();
+                    Annotation[] appScoped = declaringClass.getAnnotationsByType(ApplicationScoped.class);
+                    Annotation[] sessionScoped = declaringClass.getAnnotationsByType(SessionScoped.class);
+                    if ((appScoped != null && appScoped.length > 0) || (sessionScoped != null && sessionScoped.length > 0)) {
+                        String err = String.format("A raw type cannot be injected into application/session scope: IP=%s", ip);
+                        pip.addDefinitionError(new DeploymentException(err));
+                    }
+                }
+                // This handles collections of primative types
+            } else if (isRawParameterizedType(ip.getType())) {
+                log.fine(String.format("+++ Added Claim ParameterizedType: %s", ip.getType()));
+                rawTypes.add(ip.getType());
+                rawTypeQualifiers.add(claim);
+            }
+        } else {
+            log.fine(String.format("Skipping pip: %s, type: %s/%s", ip, ip.getType(), ip.getType().getClass()));
+        }
+    }
+
+    /**
+     * Collect the types of all {@linkplain Provider} injection points annotated with {@linkplain Claim}.
+     *
+     * @param pip - the injection point event information
+     */
+    void processClaimProviderInjections(@Observes ProcessInjectionPoint<?, ? extends Provider> pip) {
+        log.fine(String.format("pip: %s", pip.getInjectionPoint()));
+        final InjectionPoint ip = pip.getInjectionPoint();
+        if (ip.getAnnotated().isAnnotationPresent(Claim.class)) {
+            Claim claim = ip.getAnnotated().getAnnotation(Claim.class);
+            if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) {
+                pip.addDefinitionError(new DeploymentException("@Claim at: " + ip + " has no name or valid standard enum setting"));
+            }
+            boolean usesEnum = claim.standard() != Claims.UNKNOWN;
+            final String claimName = usesEnum ? claim.standard().name() : claim.value();
+            log.fine(String.format("Checking Provider Claim(%s), ip: %s", claimName, ip));
+            ClaimIP claimIP = claims.get(claimName);
+            Type matchType = ip.getType();
+            // The T from the Provider<T> injection site
+            Type actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0];
+            // Don't add Optional or JsonValue as this is handled specially
+            if (!optionalOrJsonValue(actualType)) {
+                rawTypes.add(actualType);
+            } else if (!actualType.getTypeName().startsWith("javax.json.Json")) {
+                // Validate that this is not an Optional<JsonValue>
+                Type innerType = ((ParameterizedType) actualType).getActualTypeArguments()[0];
+                if (!innerType.getTypeName().startsWith("javax.json.Json")) {
+                    providerOptionalTypes.add(actualType);
+                    providerQualifiers.add(claim);
+                }
+            }
+            rawTypeQualifiers.add(claim);
+            ClaimIPType key = new ClaimIPType(claimName, actualType);
+            if (claimIP == null) {
+                claimIP = new ClaimIP(actualType, actualType, false, claim);
+                claimIP.setProviderSite(true);
+                claims.put(key, claimIP);
+            }
+            claimIP.getInjectionPoints().add(ip);
+            log.fine(String.format("+++ Added Provider Claim(%s) ip: %s", claimName, ip));
+
+        }
+    }
+
+    /**
+     * Create producer methods for each ClaimValue injection site
+     *
+     * @param event       - AfterBeanDiscovery
+     * @param beanManager - CDI bean manager
+     */
+    void observesAfterBeanDiscovery(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) {
+        log.fine(String.format("observesAfterBeanDiscovery, %s", claims));
+        installClaimValueProducerMethodsViaSyntheticBeans(event, beanManager);
+
+        //installClaimValueProducesViaTemplateType(event, beanManager);
+    }
+
+    /**
+     * Create a synthetic bean with a custom Producer for the non-Provider injection sites.
+     *
+     * @param event       - AfterBeanDiscovery
+     * @param beanManager - CDI bean manager
+     */
+    private void installClaimValueProducerMethodsViaSyntheticBeans(final AfterBeanDiscovery event, final BeanManager beanManager) {
+
+    }
+
+    private boolean optionalOrJsonValue(Type type) {
+        boolean isOptionOrJson = type.getTypeName().startsWith(Optional.class.getTypeName())
+                | type.getTypeName().startsWith("javax.json.Json");
+        return isOptionOrJson;
+    }
+
+    private boolean isRawParameterizedType(Type type) {
+        boolean isRawParameterizedType = false;
+        if (type instanceof ParameterizedType) {
+            ParameterizedType ptype = ParameterizedType.class.cast(type);
+            Type rawType = ptype.getRawType();
+            String rawTypeName = rawType.getTypeName();
+            isRawParameterizedType = !rawTypeName.startsWith("org.eclipse.microprofile.jwt");
+        }
+        return isRawParameterizedType;
+    }
+
+    /**
+     * A map of claim,type pairs to the injection site information
+     */
+    private HashMap<ClaimIPType, ClaimIP> claims = new HashMap<>();
+
+    private Set<Type> providerOptionalTypes = new HashSet<>();
+
+    private Set<Type> providerTypes = new HashSet<>();
+
+    private Set<Type> rawTypes = new HashSet<>();
+
+    private Set<Annotation> rawTypeQualifiers = new HashSet<>();
+
+    private Set<Annotation> providerQualifiers = new HashSet<>();
+
+    /**
+     * A key for a claim,injection site type pair
+     */
+    public static class ClaimIPType implements Comparable<ClaimIPType> {
+        public ClaimIPType(String claimName, Type ipType) {
+            this.claimName = claimName;
+            this.ipType = ipType;
+        }
+
+        /**
+         * Order the @Claim ClaimValue<T> on the @Claim.value and then T type name
+         *
+         * @param o - ClaimIP to compare to
+         * @return the ordering of this claim relative to o
+         */
+        @Override
+        public int compareTo(ClaimIPType o) {
+            int compareTo = claimName.compareTo(o.claimName);
+            if (compareTo == 0) {
+                compareTo = ipType.getTypeName().compareTo(o.ipType.getTypeName());
+            }
+            return compareTo;
+        }
+
+        private String claimName;
+
+        private Type ipType;
+    }
+
+    /**
+     * The representation of an @Claim annotated injection site
+     */
+    public static class ClaimIP {
+        /**
+         * Create a ClaimIP from the injection site information
+         *
+         * @param matchType  - the outer type of the injection site
+         * @param valueType  - the parameterized type of the injection site
+         * @param isOptional - is the injection site an Optional
+         * @param claim      - the Claim qualifier
+         */
+        public ClaimIP(Type matchType, Type valueType, boolean isOptional, Claim claim) {
+            this.matchType = matchType;
+            this.valueType = valueType;
+            this.claim = claim;
+        }
+
+        public Type getMatchType() {
+            return matchType;
+        }
+
+        public String getClaimName() {
+            return claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+        }
+
+        public Claim getClaim() {
+            return claim;
+        }
+
+        public Type getValueType() {
+            return valueType;
+        }
+
+        public boolean isOptional() {
+            return isOptional;
+        }
+
+        public boolean isProviderSite() {
+            return isProviderSite;
+        }
+
+        public void setProviderSite(boolean providerSite) {
+            this.isProviderSite = providerSite;
+        }
+
+        public boolean isNonStandard() {
+            return isNonStandard;
+        }
+
+        public void setNonStandard(boolean nonStandard) {
+            isNonStandard = nonStandard;
+        }
+
+        public boolean isJsonValue() {
+            return isJsonValue;
+        }
+
+        public void setJsonValue(boolean jsonValue) {
+            isJsonValue = jsonValue;
+        }
+
+        public Set<InjectionPoint> getInjectionPoints() {
+            return injectionPoints;
+        }
+
+        @Override
+        public String toString() {
+            return "ClaimIP{" +
+                    "type=" + matchType +
+                    ", claim=" + claim +
+                    ", ips=" + injectionPoints +
+                    '}';
+        }
+
+        /**
+         * The injection site value type
+         */
+        private Type matchType;
+
+        /**
+         * The actual type of of the ParameterizedType matchType
+         */
+        private Type valueType;
+
+        /**
+         * Is valueType actually wrapped in an Optional
+         */
+        private boolean isOptional;
+
+        private boolean isProviderSite;
+
+        private boolean isNonStandard;
+
+        private boolean isJsonValue;
+
+        /**
+         * The injection site @Claim annotation value
+         */
+        private Claim claim;
+
+        /**
+         * The location that share the @Claim/type combination
+         */
+        private HashSet<InjectionPoint> injectionPoints = new HashSet<>();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
new file mode 100644
index 0000000..f267437
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
@@ -0,0 +1,196 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Produces;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ * A class that tracks the current validated MP-JWT and associated JsonWebToken via a thread
+ * local to provide a @RequestScoped JsonWebToken producer method.
+ * <p>
+ * It also provides utility methods for access the current JsonWebToken claim values.
+ */
+@ApplicationScoped
+public class MPJWTProducer {
+    private static Logger log = Logger.getLogger(MPJWTProducer.class.getName());
+    private static final String TMP = "tmp";
+    private static ThreadLocal<JsonWebToken> currentPrincipal = new ThreadLocal<>();
+
+    public static void setJWTPrincipal(JsonWebToken principal) {
+        currentPrincipal.set(principal);
+    }
+
+    public static JsonWebToken getJWTPrincpal() {
+        return currentPrincipal.get();
+    }
+
+    @PostConstruct
+    void init() {
+        log.fine("MPJWTProducer initialized");
+    }
+
+    void observeRequestInitialized(@Observes @Initialized(RequestScoped.class) Object event) {
+        log.finest(String.format("observeRequestInitialized, event=%s", event));
+    }
+
+    void observeRequestDestroyed(@Observes @Destroyed(RequestScoped.class) Object event) {
+        log.finest(String.format("observeRequestDestroyed, event=%s", event));
+    }
+
+    /**
+     * The @RequestScoped producer method for the current JsonWebToken
+     *
+     * @return
+     */
+    @Produces
+    @RequestScoped
+    JsonWebToken currentPrincipalOrNull() {
+        return currentPrincipal.get();
+    }
+
+    /**
+     * A utility method for accessing a claim from the current JsonWebToken as a ClaimValue<Optional<T>> object.
+     *
+     * @param name - name of the claim
+     * @param <T>  expected actual type of the claim
+     * @return the claim value wrapper object
+     */
+    static <T> ClaimValue<Optional<T>> generalClaimValueProducer(String name) {
+        ClaimValueWrapper<Optional<T>> wrapper = new ClaimValueWrapper<>(name);
+        T value = getValue(name, false);
+        Optional<T> optValue = Optional.ofNullable(value);
+        wrapper.setValue(optValue);
+        return wrapper;
+    }
+
+    /**
+     * Return the indicated claim value as a JsonValue
+     *
+     * @param name - name of the claim
+     * @return a JsonValue wrapper
+     */
+    static JsonValue generalJsonValueProducer(String name) {
+        Object value = getValue(name, false);
+        JsonValue jsonValue = wrapValue(value);
+        return jsonValue;
+    }
+
+    public static <T> T getValue(String name, boolean isOptional) {
+        JsonWebToken jwt = getJWTPrincpal();
+        if (jwt == null) {
+            log.fine(String.format("getValue(%s), null JsonWebToken", name));
+            return null;
+        }
+
+        Optional<T> claimValue = jwt.claim(name);
+        if (!isOptional && !claimValue.isPresent()) {
+            log.fine(String.format("Failed to find Claim for: %s", name));
+        }
+        log.fine(String.format("getValue(%s), isOptional=%s, claimValue=%s", name, isOptional, claimValue));
+        return claimValue.orElse(null);
+    }
+
+    static JsonObject replaceMap(Map<String, Object> map) {
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object entryValue = entry.getValue();
+            if (entryValue instanceof Map) {
+                JsonObject entryJsonObject = replaceMap((Map<String, Object>) entryValue);
+                builder.add(entry.getKey(), entryJsonObject);
+            } else if (entryValue instanceof List) {
+                JsonArray array = (JsonArray) wrapValue(entryValue);
+                builder.add(entry.getKey(), array);
+            } else if (entryValue instanceof Long || entryValue instanceof Integer) {
+                long lvalue = ((Number) entryValue).longValue();
+                builder.add(entry.getKey(), lvalue);
+            } else if (entryValue instanceof Double || entryValue instanceof Float) {
+                double dvalue = ((Number) entryValue).doubleValue();
+                builder.add(entry.getKey(), dvalue);
+            } else if (entryValue instanceof Boolean) {
+                boolean flag = ((Boolean) entryValue).booleanValue();
+                builder.add(entry.getKey(), flag);
+            } else if (entryValue instanceof String) {
+                builder.add(entry.getKey(), entryValue.toString());
+            }
+        }
+        return builder.build();
+    }
+
+    static JsonValue wrapValue(Object value) {
+        JsonValue jsonValue = null;
+        if (value instanceof JsonValue) {
+            // This may already be a JsonValue
+            jsonValue = (JsonValue) value;
+        } else if (value instanceof String) {
+            jsonValue = Json.createObjectBuilder()
+                    .add(TMP, value.toString())
+                    .build()
+                    .getJsonString(TMP);
+        } else if (value instanceof Number) {
+            Number number = (Number) value;
+            if ((number instanceof Long) || (number instanceof Integer)) {
+                jsonValue = Json.createObjectBuilder()
+                        .add(TMP, number.longValue())
+                        .build()
+                        .getJsonNumber(TMP);
+            } else {
+                jsonValue = Json.createObjectBuilder()
+                        .add(TMP, number.doubleValue())
+                        .build()
+                        .getJsonNumber(TMP);
+            }
+        } else if (value instanceof Boolean) {
+            Boolean flag = (Boolean) value;
+            jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE;
+        } else if (value instanceof Collection) {
+            JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            Collection list = (Collection) value;
+            for (Object element : list) {
+                if (element instanceof String) {
+                    arrayBuilder.add(element.toString());
+                } else {
+                    JsonValue jvalue = wrapValue(element);
+                    arrayBuilder.add(jvalue);
+                }
+            }
+            jsonValue = arrayBuilder.build();
+        } else if (value instanceof Map) {
+            jsonValue = replaceMap((Map) value);
+        }
+        return jsonValue;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
new file mode 100644
index 0000000..1ab817e
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/cdi/RawClaimTypeProducer.java
@@ -0,0 +1,69 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.inject.Named;
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+public class RawClaimTypeProducer {
+    private static Logger log = Logger.getLogger(RawClaimTypeProducer.class.getName());
+
+    @Produces
+    @Claim("")
+    @Named("RawClaimTypeProducer#getValue")
+    public Object getValue(InjectionPoint ip) {
+        log.fine(String.format("getValue(%s)", ip));
+        String name = getName(ip);
+        ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name);
+        Optional<Object> value = cv.getValue();
+        Object returnValue = value.orElse(null);
+        return returnValue;
+    }
+
+    @Produces
+    @Claim("")
+    @Named("RawClaimTypeProducer#getOptionalValue")
+    public Optional getOptionalValue(InjectionPoint ip) {
+        log.fine(String.format("getOptionalValue(%s)", ip));
+        String name = getName(ip);
+        ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name);
+        Optional<Object> value = cv.getValue();
+        return value;
+    }
+
+    String getName(InjectionPoint ip) {
+        String name = null;
+        for (Annotation ann : ip.getQualifiers()) {
+            if (ann instanceof Claim) {
+                Claim claim = (Claim) ann;
+                name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+            }
+        }
+        return name;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
new file mode 100644
index 0000000..ef8c0b0
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
@@ -0,0 +1,66 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.config;
+
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * The public key and expected issuer needed to validate a token.
+ */
+public class JWTAuthContextInfo {
+    private RSAPublicKey signerKey;
+    private String issuedBy;
+    private int expGracePeriodSecs = 60;
+
+    public JWTAuthContextInfo() {
+    }
+
+    public JWTAuthContextInfo(RSAPublicKey signerKey, String issuedBy) {
+        this.signerKey = signerKey;
+        this.issuedBy = issuedBy;
+    }
+
+    public JWTAuthContextInfo(JWTAuthContextInfo orig) {
+        this.signerKey = orig.signerKey;
+        this.issuedBy = orig.issuedBy;
+        this.expGracePeriodSecs = orig.expGracePeriodSecs;
+    }
+
+    public RSAPublicKey getSignerKey() {
+        return signerKey;
+    }
+
+    public void setSignerKey(RSAPublicKey signerKey) {
+        this.signerKey = signerKey;
+    }
+
+    public String getIssuedBy() {
+        return issuedBy;
+    }
+
+    public void setIssuedBy(String issuedBy) {
+        this.issuedBy = issuedBy;
+    }
+
+    public int getExpGracePeriodSecs() {
+        return expGracePeriodSecs;
+    }
+
+    public void setExpGracePeriodSecs(int expGracePeriodSecs) {
+        this.expGracePeriodSecs = expGracePeriodSecs;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
new file mode 100644
index 0000000..b810fcf
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java
@@ -0,0 +1,61 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.config;
+
+import org.apache.tomee.microprofile.jwt.KeyUtils;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Produces;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Optional;
+
+@Dependent
+public class JWTAuthContextInfoProvider {
+
+    @Produces
+    Optional<JWTAuthContextInfo> getOptionalContextInfo() {
+        JWTAuthContextInfo contextInfo = new JWTAuthContextInfo();
+
+        // todo use MP Config to load the configuration
+        contextInfo.setIssuedBy("https://server.example.com");
+        RSAPublicKey pk = null;
+        try {
+            pk = (RSAPublicKey) KeyUtils.decodePublicKey("-----BEGIN RSA PUBLIC KEY-----\n" +
+                    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq\n" +
+                    "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR\n" +
+                    "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e\n" +
+                    "UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9\n" +
+                    "AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn\n" +
+                    "sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x\n" +
+                    "nQIDAQAB\n" +
+                    "-----END RSA PUBLIC KEY-----\n");
+
+        } catch (final Exception e) {
+            e.printStackTrace();
+            // todo better handling
+            throw new RuntimeException(e);
+        }
+        contextInfo.setSignerKey(pk);
+
+        return Optional.of(contextInfo);
+    }
+
+    @Produces
+    JWTAuthContextInfo getContextInfo() {
+        return getOptionalContextInfo().get();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
new file mode 100644
index 0000000..120058d
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
@@ -0,0 +1,334 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.principal;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import javax.security.auth.Subject;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A default implementation of JWTCallerPrincipal using jose4j
+ * Another implementation could use nimbus and another plain JSON-P
+ */
+public class DefaultJWTCallerPrincipal extends JWTCallerPrincipal {
+
+    private static Logger logger = Logger.getLogger(DefaultJWTCallerPrincipal.class.getName());
+    private String jwt;
+    private String type;
+    private JwtClaims claimsSet;
+
+    /**
+     * Create the DefaultJWTCallerPrincipal from the parsed JWT token and the extracted principal name
+     *
+     * @param jwt  - the parsed JWT token representation
+     * @param name - the extracted unqiue name to use as the principal name; from "upn", "preferred_username" or "sub" claim
+     */
+    public DefaultJWTCallerPrincipal(String jwt, String type, JwtClaims claimsSet, String name) {
+        super(name);
+        this.jwt = jwt;
+        this.type = type;
+        this.claimsSet = claimsSet;
+        fixJoseTypes();
+    }
+
+    @Override
+    public Set<String> getAudience() {
+        Set<String> audSet = new HashSet<>();
+        try {
+            List<String> audList = claimsSet.getStringListClaimValue("aud");
+            if (audList != null) {
+                audSet.addAll(audList);
+            }
+        } catch (MalformedClaimException e) {
+            try {
+                String aud = claimsSet.getStringClaimValue("aud");
+                audSet.add(aud);
+            } catch (MalformedClaimException e1) {
+            }
+        }
+        return audSet;
+    }
+
+    @Override
+    public Set<String> getGroups() {
+        HashSet<String> groups = new HashSet<>();
+        try {
+            List<String> globalGroups = claimsSet.getStringListClaimValue("groups");
+            if (globalGroups != null) {
+                groups.addAll(globalGroups);
+            }
+        } catch (MalformedClaimException e) {
+            e.printStackTrace();
+        }
+        return groups;
+    }
+
+
+    @Override
+    public Set<String> getClaimNames() {
+        return new HashSet<>(claimsSet.getClaimNames());
+    }
+
+    @Override
+    public Object getClaim(String claimName) {
+        Claims claimType = Claims.UNKNOWN;
+        Object claim = null;
+        try {
+            claimType = Claims.valueOf(claimName);
+        } catch (IllegalArgumentException e) {
+        }
+        // Handle the jose4j NumericDate types and
+        switch (claimType) {
+            case exp:
+            case iat:
+            case auth_time:
+            case nbf:
+            case updated_at:
+                try {
+                    claim = claimsSet.getClaimValue(claimType.name(), Long.class);
+                    if (claim == null) {
+                        claim = new Long(0);
+                    }
+                } catch (MalformedClaimException e) {
+                }
+                break;
+            case groups:
+                claim = getGroups();
+                break;
+            case aud:
+                claim = getAudience();
+                break;
+            case UNKNOWN:
+                claim = claimsSet.getClaimValue(claimName);
+                break;
+            default:
+                claim = claimsSet.getClaimValue(claimType.name());
+        }
+        return claim;
+    }
+
+    @Override
+    public boolean implies(Subject subject) {
+        return false;
+    }
+
+    public String toString() {
+        return toString(false);
+    }
+
+    /**
+     * TODO: showAll is ignored and currently assumed true
+     *
+     * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the
+     *                JsonWebToken interface be displayed.
+     * @return JWTCallerPrincipal string view
+     */
+    @Override
+    public String toString(boolean showAll) {
+        String toString = "DefaultJWTCallerPrincipal{" +
+                "id='" + getTokenID() + '\'' +
+                ", name='" + getName() + '\'' +
+                ", expiration=" + getExpirationTime() +
+                ", notBefore=" + getClaim(Claims.nbf.name()) +
+                ", issuedAt=" + getIssuedAtTime() +
+                ", issuer='" + getIssuer() + '\'' +
+                ", audience=" + getAudience() +
+                ", subject='" + getSubject() + '\'' +
+                ", type='" + type + '\'' +
+                ", issuedFor='" + getClaim("azp") + '\'' +
+                ", authTime=" + getClaim("auth_time") +
+                ", givenName='" + getClaim("given_name") + '\'' +
+                ", familyName='" + getClaim("family_name") + '\'' +
+                ", middleName='" + getClaim("middle_name") + '\'' +
+                ", nickName='" + getClaim("nickname") + '\'' +
+                ", preferredUsername='" + getClaim("preferred_username") + '\'' +
+                ", email='" + getClaim("email") + '\'' +
+                ", emailVerified=" + getClaim(Claims.email_verified.name()) +
+                ", allowedOrigins=" + getClaim("allowedOrigins") +
+                ", updatedAt=" + getClaim("updated_at") +
+                ", acr='" + getClaim("acr") + '\'';
+        StringBuilder tmp = new StringBuilder(toString);
+        tmp.append(", groups=[");
+        for (String group : getGroups()) {
+            tmp.append(group);
+            tmp.append(',');
+        }
+        tmp.setLength(tmp.length() - 1);
+        tmp.append("]}");
+        return tmp.toString();
+    }
+
+    /**
+     * Convert the types jose4j uses for address, sub_jwk, and jwk
+     */
+    private void fixJoseTypes() {
+        if (claimsSet.hasClaim(Claims.address.name())) {
+            replaceMap(Claims.address.name());
+        }
+        if (claimsSet.hasClaim(Claims.jwk.name())) {
+            replaceMap(Claims.jwk.name());
+        }
+        if (claimsSet.hasClaim(Claims.sub_jwk.name())) {
+            replaceMap(Claims.sub_jwk.name());
+        }
+        // Handle custom claims
+        Set<String> customClaimNames = filterCustomClaimNames(claimsSet.getClaimNames());
+        for (String name : customClaimNames) {
+            Object claimValue = claimsSet.getClaimValue(name);
+            Class claimType = claimValue.getClass();
+            if (claimValue instanceof List) {
+                replaceList(name);
+            } else if (claimValue instanceof Map) {
+                replaceMap(name);
+            } else if (claimValue instanceof Number) {
+                replaceNumber(name);
+            }
+        }
+    }
+
+    /**
+     * Determine the custom claims in the set
+     *
+     * @param claimNames - the current set of claim names in this token
+     * @return the possibly empty set of names for non-Claims claims
+     */
+    private Set<String> filterCustomClaimNames(Collection<String> claimNames) {
+        HashSet<String> customNames = new HashSet<>(claimNames);
+        for (Claims claim : Claims.values()) {
+            customNames.remove(claim.name());
+        }
+        return customNames;
+    }
+
+    /**
+     * Replace the jose4j Map<String,Object> with a JsonObject
+     *
+     * @param name - claim name
+     */
+    private void replaceMap(String name) {
+        try {
+            Map<String, Object> map = claimsSet.getClaimValue(name, Map.class);
+            JsonObject jsonObject = replaceMap(map);
+            claimsSet.setClaim(name, jsonObject);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceMap failure for: " + name, e);
+        }
+    }
+
+    private JsonObject replaceMap(Map<String, Object> map) {
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object entryValue = entry.getValue();
+            if (entryValue instanceof Map) {
+                JsonObject entryJsonObject = replaceMap((Map<String, Object>) entryValue);
+                builder.add(entry.getKey(), entryJsonObject);
+            } else if (entryValue instanceof List) {
+                JsonArray array = (JsonArray) wrapValue(entryValue);
+                builder.add(entry.getKey(), array);
+            } else if (entryValue instanceof Long || entryValue instanceof Integer) {
+                long lvalue = ((Number) entryValue).longValue();
+                builder.add(entry.getKey(), lvalue);
+            } else if (entryValue instanceof Double || entryValue instanceof Float) {
+                double dvalue = ((Number) entryValue).doubleValue();
+                builder.add(entry.getKey(), dvalue);
+            } else if (entryValue instanceof Boolean) {
+                boolean flag = ((Boolean) entryValue).booleanValue();
+                builder.add(entry.getKey(), flag);
+            } else if (entryValue instanceof String) {
+                builder.add(entry.getKey(), entryValue.toString());
+            }
+        }
+        return builder.build();
+    }
+
+    private JsonValue wrapValue(Object value) {
+        JsonValue jsonValue = null;
+        if (value instanceof Number) {
+            Number number = (Number) value;
+            if ((number instanceof Long) || (number instanceof Integer)) {
+                jsonValue = Json.createObjectBuilder()
+                        .add("tmp", number.longValue())
+                        .build()
+                        .getJsonNumber("tmp");
+            } else {
+                jsonValue = Json.createObjectBuilder()
+                        .add("tmp", number.doubleValue())
+                        .build()
+                        .getJsonNumber("tmp");
+            }
+        } else if (value instanceof Boolean) {
+            Boolean flag = (Boolean) value;
+            jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE;
+        } else if (value instanceof List) {
+            JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            List list = (List) value;
+            for (Object element : list) {
+                if (element instanceof String) {
+                    arrayBuilder.add(element.toString());
+                } else {
+                    JsonValue jvalue = wrapValue(element);
+                    arrayBuilder.add(jvalue);
+                }
+            }
+            jsonValue = arrayBuilder.build();
+        }
+        return jsonValue;
+    }
+
+
+    /**
+     * Replace the jose4j List<?> with a JsonArray
+     *
+     * @param name - claim name
+     */
+    private void replaceList(String name) {
+        try {
+            List list = claimsSet.getClaimValue(name, List.class);
+            JsonArray array = (JsonArray) wrapValue(list);
+            claimsSet.setClaim(name, array);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceList failure for: " + name, e);
+        }
+    }
+
+    private void replaceNumber(String name) {
+        try {
+            Number number = claimsSet.getClaimValue(name, Number.class);
+            JsonNumber jsonNumber = (JsonNumber) wrapValue(number);
+            claimsSet.setClaim(name, jsonNumber);
+        } catch (MalformedClaimException e) {
+            logger.log(Level.WARNING, "replaceNumber failure for: " + name, e);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..a420dde
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
@@ -0,0 +1,88 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.principal;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.apache.tomee.microprofile.jwt.ParseException;
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+import org.jose4j.jwt.NumericDate;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
+import org.jose4j.jwt.consumer.JwtContext;
+
+/**
+ * A default implementation of the abstract JWTCallerPrincipalFactory that uses the Keycloak token parsing classes.
+ */
+public class DefaultJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory {
+
+    /**
+     * Tries to load the JWTAuthContextInfo from CDI if the class level authContextInfo has not been set.
+     */
+    public DefaultJWTCallerPrincipalFactory() {
+    }
+
+    @Override
+    public JWTCallerPrincipal parse(final String token, final JWTAuthContextInfo authContextInfo) throws ParseException {
+        JWTCallerPrincipal principal = null;
+
+        try {
+            JwtConsumerBuilder builder = new JwtConsumerBuilder()
+                    .setRequireExpirationTime()
+                    .setRequireSubject()
+                    .setSkipDefaultAudienceValidation()
+                    .setExpectedIssuer(authContextInfo.getIssuedBy())
+                    .setVerificationKey(authContextInfo.getSignerKey())
+                    .setJwsAlgorithmConstraints(
+                            new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
+                                    AlgorithmIdentifiers.RSA_USING_SHA256));
+            if (authContextInfo.getExpGracePeriodSecs() > 0) {
+                builder.setAllowedClockSkewInSeconds(authContextInfo.getExpGracePeriodSecs());
+            } else {
+                builder.setEvaluationTime(NumericDate.fromSeconds(0));
+            }
+
+            JwtConsumer jwtConsumer = builder.build();
+            JwtContext jwtContext = jwtConsumer.process(token);
+            String type = jwtContext.getJoseObjects().get(0).getHeader("typ");
+            //  Validate the JWT and process it to the Claims
+            jwtConsumer.processContext(jwtContext);
+            JwtClaims claimsSet = jwtContext.getJwtClaims();
+
+            // We have to determine the unique name to use as the principal name. It comes from upn, preferred_username, sub in that order
+            String principalName = claimsSet.getClaimValue("upn", String.class);
+            if (principalName == null) {
+                principalName = claimsSet.getClaimValue("preferred_username", String.class);
+                if (principalName == null) {
+                    principalName = claimsSet.getSubject();
+                }
+            }
+            claimsSet.setClaim(Claims.raw_token.name(), token);
+            principal = new DefaultJWTCallerPrincipal(token, type, claimsSet, principalName);
+        } catch (InvalidJwtException e) {
+            throw new ParseException("Failed to verify token", e);
+        } catch (MalformedClaimException e) {
+            throw new ParseException("Failed to verify token claims", e);
+        }
+
+        return principal;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
new file mode 100644
index 0000000..26d9406
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipal.java
@@ -0,0 +1,59 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.principal;
+
+
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import java.util.Optional;
+
+/**
+ * An abstract CallerPrincipal implementation that provides access to the JWT claims that are required by
+ * the microprofile token.
+ */
+public abstract class JWTCallerPrincipal implements JsonWebToken {
+
+    private String name;
+
+    /**
+     * Create a JWTCallerPrincipal with the caller's name
+     *
+     * @param name - caller's name
+     */
+    public JWTCallerPrincipal(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Generate a human readable version of the caller principal and associated JWT.
+     *
+     * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the
+     *                JsonWebToken interface be displayed.
+     * @return human readable presentation of the caller principal and associated JWT.
+     */
+    public abstract String toString(boolean showAll);
+
+    public <T> Optional<T> claim(final String claimName) {
+        final T claim = (T) getClaim(claimName);
+        return Optional.ofNullable(claim);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..3aec7f0
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/principal/JWTCallerPrincipalFactory.java
@@ -0,0 +1,127 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You 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 org.apache.tomee.microprofile.jwt.principal;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.apache.tomee.microprofile.jwt.ParseException;
+
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ServiceLoader;
+
+/**
+ * The factory class that provides the token string to JWTCallerPrincipal parsing for a given implementation.
+ */
+public abstract class JWTCallerPrincipalFactory {
+    private static JWTCallerPrincipalFactory instance;
+
+    /**
+     * Obtain the JWTCallerPrincipalFactory that has been set or by using the ServiceLoader pattern.
+     *
+     * @return the factory instance
+     * @see #setInstance(JWTCallerPrincipalFactory)
+     */
+    public static JWTCallerPrincipalFactory instance() {
+        if (instance == null) {
+            synchronized (JWTCallerPrincipalFactory.class) {
+                if (instance != null) {
+                    return instance;
+                }
+
+                ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                    @Override
+                    public ClassLoader run() {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+                });
+                if (cl == null) {
+                    cl = JWTCallerPrincipalFactory.class.getClassLoader();
+                }
+
+                JWTCallerPrincipalFactory newInstance = loadSpi(cl);
+
+                if (newInstance == null && cl != JWTCallerPrincipalFactory.class.getClassLoader()) {
+                    cl = JWTCallerPrincipalFactory.class.getClassLoader();
+                    newInstance = loadSpi(cl);
+                }
+                if (newInstance == null) {
+                    throw new IllegalStateException("No JWTCallerPrincipalFactory implementation found!");
+                }
+
+                instance = newInstance;
+            }
+        }
+
+        return instance;
+    }
+
+    /**
+     * Look for a JWTCallerPrincipalFactory service implementation using the ServiceLoader.
+     *
+     * @param cl - the ClassLoader to pass into the {@link ServiceLoader#load(Class, ClassLoader)} method.
+     * @return the JWTCallerPrincipalFactory if found, null otherwise
+     */
+    private static JWTCallerPrincipalFactory loadSpi(ClassLoader cl) {
+        if (cl == null) {
+            return null;
+        }
+
+        // start from the root CL and go back down to the TCCL
+        JWTCallerPrincipalFactory instance = loadSpi(cl.getParent());
+
+        if (instance == null) {
+            ServiceLoader<JWTCallerPrincipalFactory> sl = ServiceLoader.load(JWTCallerPrincipalFactory.class, cl);
+            URL u = cl.getResource("/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory");
+            System.out.printf("JWTCallerPrincipalFactory, cl=%s, u=%s, sl=%s\n", cl, u, sl);
+            try {
+                for (JWTCallerPrincipalFactory spi : sl) {
+                    if (instance != null) {
+                        throw new IllegalStateException(
+                                "Multiple JWTCallerPrincipalFactory implementations found: "
+                                        + spi.getClass().getName() + " and "
+                                        + instance.getClass().getName());
+                    } else {
+                        System.out.printf("sl=%s, loaded=%s\n", sl, spi);
+                        instance = spi;
+                    }
+                }
+            } catch (Throwable e) {
+                System.err.printf("Warning: %s\n", e.getMessage());
+            }
+        }
+        return instance;
+    }
+
+    /**
+     * Set the instance. It is used by OSGi environment where service loader pattern is not supported.
+     *
+     * @param resolver the instance to use.
+     */
+    public static void setInstance(JWTCallerPrincipalFactory resolver) {
+        instance = resolver;
+    }
+
+    /**
+     * Parse the given bearer token string into a JWTCallerPrincipal instance.
+     *
+     * @param token - the bearer token provided for authorization
+     * @return A JWTCallerPrincipal representation for the token.
+     * @throws ParseException on parse or verification failure.
+     */
+    public abstract JWTCallerPrincipal parse(String token, JWTAuthContextInfo authContextInfo) throws ParseException;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index 5e3bccc..d5eea47 100644
--- a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -1 +1 @@
-org.apache.tomee.microprofile.jwt.MPJWTCDIExtension
\ No newline at end of file
+org.apache.tomee.microprofile.jwt.cdi.MPJWTCDIExtension
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
deleted file mode 100644
index 67f39db..0000000
--- a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.tomee.microprofile.jwt.DefaultJWTCallerPrincipalFactory
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/c5964e07/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
new file mode 100644
index 0000000..21c9831
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory
@@ -0,0 +1 @@
+org.apache.tomee.microprofile.jwt.principal.DefaultJWTCallerPrincipalFactory
\ No newline at end of file