summaryrefslogtreecommitdiffstats
path: root/container-jersey2
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-jersey2
Publish
Diffstat (limited to 'container-jersey2')
-rw-r--r--container-jersey2/.gitignore1
-rw-r--r--container-jersey2/OWNERS1
-rw-r--r--container-jersey2/pom.xml92
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/Component.java17
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/package-info.java10
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/util/ResourceConfigUtil.java17
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala40
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala16
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala105
-rw-r--r--container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala74
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/AbstractResource.java12
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/DummyAnnotation.java11
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InnerClass.java12
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InterfaceResource.java12
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NestedClass.java12
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NonPublicNestedClass.java12
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Provider.java9
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Resource.java11
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala75
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceWithMultipleAnnotations.java13
20 files changed, 552 insertions, 0 deletions
diff --git a/container-jersey2/.gitignore b/container-jersey2/.gitignore
new file mode 100644
index 00000000000..ea8c4bf7f35
--- /dev/null
+++ b/container-jersey2/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/container-jersey2/OWNERS b/container-jersey2/OWNERS
new file mode 100644
index 00000000000..3b2ba1ede81
--- /dev/null
+++ b/container-jersey2/OWNERS
@@ -0,0 +1 @@
+gjoranv
diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml
new file mode 100644
index 00000000000..f8e8b7f7d67
--- /dev/null
+++ b/container-jersey2/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>container-jersey2</artifactId>
+ <version>6-SNAPSHOT</version>
+ <packaging>container-plugin</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespa_jersey2</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>provided-dependencies</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>container-disc</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>5.0.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.scala-tools</groupId>
+ <artifactId>maven-scala-plugin</artifactId>
+ <version>2.14.1</version>
+ <executions>
+ <execution>
+ <id>compile</id>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <phase>compile</phase>
+ </execution>
+ <execution>
+ <id>test-compile</id>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ <phase>test-compile</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/Component.java b/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/Component.java
new file mode 100644
index 00000000000..0dc18350bb0
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/Component.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.jaxrs.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for injecting jdisc container components into jaxrs resources and providers
+ * @author tonytv
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Component {}
+
+
diff --git a/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/package-info.java b/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/package-info.java
new file mode 100644
index 00000000000..4adb642ddeb
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/jaxrs/annotation/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author tonytv
+ */
+@PublicApi
+@ExportPackage
+package com.yahoo.container.jaxrs.annotation;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/util/ResourceConfigUtil.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/util/ResourceConfigUtil.java
new file mode 100644
index 00000000000..e1246d0a125
--- /dev/null
+++ b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/util/ResourceConfigUtil.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.util;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * @author tonytv
+ */
+public class ResourceConfigUtil {
+ /**
+ * Solves ambiguous reference to overloaded definition, see
+ * http://stackoverflow.com/questions/3313929/how-do-i-disambiguate-in-scala-between-methods-with-vararg-and-without
+ */
+ public static void registerComponent(ResourceConfig config, Object component) {
+ config.register(component);
+ }
+}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala
new file mode 100644
index 00000000000..8a18821ed7b
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey
+
+import javax.inject.Singleton
+
+import com.yahoo.container.di.config.{ResolveDependencyException, RestApiContext}
+import com.yahoo.container.jaxrs.annotation.Component
+import org.glassfish.hk2.api.{ServiceHandle, Injectee, InjectionResolver}
+
+/**
+ * Resolves jdisc container components for jersey 2 components.
+ * Similar to Gjoran's ComponentGraphProvider for jersey 1.
+ * @author tonytv
+ */
+@Singleton //jersey2 requirement: InjectionResolvers must be in the Singleton scope
+class ComponentGraphProvider(injectables: Traversable[RestApiContext.Injectable]) extends InjectionResolver[Component] {
+ override def resolve(injectee: Injectee, root: ServiceHandle[_]): AnyRef = {
+ val wantedClass = injectee.getRequiredType match {
+ case c: Class[_] => c
+ case unsupported => throw new UnsupportedOperationException("Only classes are supported, got " + unsupported)
+ }
+
+ val componentsWithMatchingType = injectables.filter{ injectable =>
+ wantedClass.isInstance(injectable.instance) }
+
+ val injectionDescription =
+ s"class '$wantedClass' to inject into Jersey resource/provider '${injectee.getInjecteeClass}')"
+
+ if (componentsWithMatchingType.size > 1)
+ throw new ResolveDependencyException(s"Multiple components found of $injectionDescription: " +
+ componentsWithMatchingType.map(_.id).mkString(","))
+
+ componentsWithMatchingType.headOption.map(_.instance).getOrElse {
+ throw new ResolveDependencyException(s"Could not find a component of $injectionDescription.")
+ }
+ }
+
+ override def isMethodParameterIndicator: Boolean = true
+ override def isConstructorParameterIndicator: Boolean = true
+}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala
new file mode 100644
index 00000000000..44e3fb2e767
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey
+
+import javax.ws.rs.core.Application
+
+import scala.collection.convert.wrapAsJava._
+
+/**
+ * @author tonytv
+ */
+class JerseyApplication(resourcesAndProviderClasses: Set[Class[_]]) extends Application {
+ private val classes: java.util.Set[Class[_]] = resourcesAndProviderClasses
+
+ override def getClasses = classes
+ override def getSingletons = super.getSingletons
+}
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala
new file mode 100644
index 00000000000..30868f916a3
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey
+
+import java.io.{IOException, InputStream}
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
+import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
+import com.yahoo.container.di.componentgraph.Provider
+import com.yahoo.container.di.config.RestApiContext
+import com.yahoo.container.di.config.RestApiContext.BundleInfo
+import com.yahoo.container.jaxrs.annotation.Component
+import com.yahoo.container.servlet.jersey.util.ResourceConfigUtil.registerComponent
+import org.eclipse.jetty.servlet.ServletHolder
+import org.glassfish.hk2.api.{TypeLiteral, InjectionResolver}
+import org.glassfish.hk2.utilities.binding.AbstractBinder
+import org.glassfish.hk2.utilities.Binder
+import org.glassfish.jersey.media.multipart.MultiPartFeature
+import org.glassfish.jersey.server.ResourceConfig
+import org.glassfish.jersey.servlet.ServletContainer
+import org.objectweb.asm.ClassReader
+
+import scala.collection.convert.wrapAsScala._
+import scala.util.control.Exception
+
+
+/**
+ * @author tonytv
+ */
+class JerseyServletProvider(restApiContext: RestApiContext) extends Provider[ServletHolder] {
+ private val jerseyServletHolder = new ServletHolder(new ServletContainer(resourceConfig(restApiContext)))
+
+ private def resourceConfig(restApiContext: RestApiContext) = {
+ val resourceConfig = ResourceConfig.forApplication(
+ new JerseyApplication(resourcesAndProviders(restApiContext.getBundles)))
+
+ registerComponent(resourceConfig, componentInjectorBinder(restApiContext))
+ registerComponent(resourceConfig, jacksonDatatypeJdk8Provider)
+ resourceConfig.register(classOf[MultiPartFeature])
+
+ resourceConfig
+ }
+
+ def resourcesAndProviders(bundles: Traversable[BundleInfo]) =
+ (for {
+ bundle <- bundles.view
+ classEntry <- bundle.getClassEntries
+ className <- detectResourceOrProvider(bundle.classLoader, classEntry)
+ } yield loadClass(bundle.symbolicName, bundle.classLoader, className)).toSet
+
+
+ def detectResourceOrProvider(bundleClassLoader: ClassLoader, classEntry: String): Option[String] = {
+ using(getResourceAsStream(bundleClassLoader, classEntry)) { inputStream =>
+ val visitor = ResourceOrProviderClassVisitor.visit(new ClassReader(inputStream))
+ visitor.getJerseyClassName
+ }
+ }
+
+ private def getResourceAsStream(bundleClassLoader: ClassLoader, classEntry: String) = {
+ bundleClassLoader.getResourceAsStream(classEntry) match {
+ case null => throw new RuntimeException(s"No entry $classEntry in bundle $bundleClassLoader")
+ case stream => stream
+ }
+
+ }
+
+ def using[T <: InputStream, R](stream: T)(f: T => R): R = {
+ try {
+ f(stream)
+ } finally {
+ Exception.ignoring(classOf[IOException]) {
+ stream.close()
+ }
+ }
+ }
+
+ def loadClass(bundleSymbolicName: String, classLoader: ClassLoader, className: String) = {
+ try {
+ classLoader.loadClass(className)
+ } catch {
+ case e: Exception => throw new RuntimeException(s"Failed loading class $className from bundle $bundleSymbolicName", e)
+ }
+ }
+
+ def componentInjectorBinder(restApiContext: RestApiContext): Binder = {
+ val componentGraphProvider = new ComponentGraphProvider(restApiContext.getInjectableComponents.toTraversable)
+ val componentAnnotationType = new TypeLiteral[InjectionResolver[Component]] {}
+
+ new AbstractBinder {
+ override def configure() {
+ bind(componentGraphProvider).to(componentAnnotationType)
+ }
+ }
+ }
+
+ def jacksonDatatypeJdk8Provider: JacksonJaxbJsonProvider = {
+ val provider = new JacksonJaxbJsonProvider()
+ provider.setMapper(new ObjectMapper().registerModule(new Jdk8Module))
+ provider
+ }
+
+ override def get() = jerseyServletHolder
+ override def deconstruct() {}
+}
+
diff --git a/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala
new file mode 100644
index 00000000000..a8c1afe3d7b
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey
+
+import javax.ws.rs.Path
+import javax.ws.rs.ext.Provider
+
+import org.objectweb.asm.{ClassVisitor, Opcodes, Type, AnnotationVisitor, ClassReader}
+
+
+/**
+ * @author tonytv
+ */
+class ResourceOrProviderClassVisitor private () extends ClassVisitor(Opcodes.ASM5) {
+ private var className: String = null
+ private var isPublic: Boolean = false
+ private var isAbstract = false
+
+ private var isInnerClass: Boolean = false
+ private var isStatic: Boolean = false
+
+ private var isAnnotated: Boolean = false
+
+ def getJerseyClassName: Option[String] = {
+ if (isJerseyClass) Some(getClassName)
+ else None
+ }
+
+ def isJerseyClass: Boolean = {
+ isAnnotated && isPublic && !isAbstract &&
+ (!isInnerClass || isStatic)
+ }
+
+ def getClassName = {
+ assert (className != null)
+ Type.getObjectType(className).getClassName
+ }
+
+ override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) {
+ isPublic = ResourceOrProviderClassVisitor.isPublic(access)
+ className = name
+ isAbstract = ResourceOrProviderClassVisitor.isAbstract(access)
+ }
+
+ override def visitInnerClass(name: String, outerName: String, innerName: String, access: Int) {
+ assert (className != null)
+
+ if (name == className) {
+ isInnerClass = true
+ isStatic = ResourceOrProviderClassVisitor.isStatic(access)
+ }
+ }
+
+ override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
+ isAnnotated |= ResourceOrProviderClassVisitor.annotationClassDescriptors(desc)
+ null
+ }
+}
+
+
+object ResourceOrProviderClassVisitor {
+ val annotationClassDescriptors = Set(classOf[Path], classOf[Provider]) map Type.getDescriptor
+
+ def isPublic = isSet(Opcodes.ACC_PUBLIC) _
+ def isStatic = isSet(Opcodes.ACC_STATIC) _
+ def isAbstract = isSet(Opcodes.ACC_ABSTRACT) _
+
+ private def isSet(bits: Int)(access: Int): Boolean = (access & bits) == bits
+
+ def visit(classReader: ClassReader): ResourceOrProviderClassVisitor = {
+ val visitor = new ResourceOrProviderClassVisitor
+ classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES)
+ visitor
+ }
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/AbstractResource.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/AbstractResource.java
new file mode 100644
index 00000000000..9fa77b4753a
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/AbstractResource.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+@Path("ignored")
+public abstract class AbstractResource {
+
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/DummyAnnotation.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/DummyAnnotation.java
new file mode 100644
index 00000000000..296615fead4
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/DummyAnnotation.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @author tonytv
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DummyAnnotation {}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InnerClass.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InnerClass.java
new file mode 100644
index 00000000000..d4bcadde3fa
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InnerClass.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+public class InnerClass {
+ @Path("ignored")
+ public class Inner {}
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InterfaceResource.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InterfaceResource.java
new file mode 100644
index 00000000000..f969fa2d478
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/InterfaceResource.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+@Path("ignored")
+public interface InterfaceResource {
+
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NestedClass.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NestedClass.java
new file mode 100644
index 00000000000..e386f4c3cdd
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NestedClass.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+public class NestedClass {
+ @Path("ignored")
+ public static class Nested {}
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NonPublicNestedClass.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NonPublicNestedClass.java
new file mode 100644
index 00000000000..3af7c0be20f
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/NonPublicNestedClass.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+public class NonPublicNestedClass {
+ @Path("ignored")
+ static class Nested {}
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Provider.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Provider.java
new file mode 100644
index 00000000000..a641d934032
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Provider.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+/**
+ * @author tonytv
+ */
+@javax.ws.rs.ext.Provider
+public class Provider {
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Resource.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Resource.java
new file mode 100644
index 00000000000..65d696aed49
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/Resource.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+@Path("ignored")
+public class Resource {
+}
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala
new file mode 100644
index 00000000000..3d4bbcecd48
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor
+
+import com.yahoo.container.servlet.jersey.{ResourceOrProviderClassVisitor, classvisitor}
+import org.junit.{Assert, Test}
+import org.objectweb.asm.ClassReader
+
+import Assert.assertThat
+import org.hamcrest.CoreMatchers.is
+
+import scala.reflect.ClassTag
+
+class ResourceOrProviderClassVisitorTest {
+ @Test
+ def resource_is_detected() {
+ assert_is_accepted[classvisitor.Resource]
+ }
+
+ @Test
+ def provider_is_detected() {
+ assert_is_accepted[classvisitor.Provider]
+ }
+
+ @Test
+ def inner_class_is_ignored() {
+ assert_is_ignored[classvisitor.InnerClass#Inner]
+ }
+
+ @Test
+ def nested_public_class_is_detected() {
+ assert_is_accepted[classvisitor.NestedClass.Nested]
+ }
+
+ @Test
+ def nested_non_public_class_is_ignored() {
+ assert_is_ignored[classvisitor.NonPublicNestedClass.Nested]
+ }
+
+ @Test
+ def resource_with_multiple_annotations_is_detected() {
+ assert_is_accepted[classvisitor.ResourceWithMultipleAnnotations]
+ }
+
+ def interface_is_ignored() {
+ assert_is_ignored[classvisitor.InterfaceResource]
+ }
+
+ @Test
+ def abstract_class_is_ignored() {
+ assert_is_ignored[classvisitor.AbstractResource]
+ }
+
+ @Test
+ def className_is_equal_to_getName() {
+ assertThat(analyzeClass[classvisitor.Resource].getClassName, is(classOf[classvisitor.Resource].getName))
+ }
+
+ def assert_is_accepted[T: ClassTag] {
+ Assert.assertTrue(className[T] + " was not accepted",
+ analyzeClass[T].isJerseyClass)
+ }
+
+ def assert_is_ignored[T: ClassTag] {
+ Assert.assertFalse(className[T] + " was not ignored",
+ analyzeClass[T].isJerseyClass)
+ }
+
+ def analyzeClass[T: ClassTag] = {
+ ResourceOrProviderClassVisitor.visit(new ClassReader(className[T]))
+ }
+
+ def className[T: ClassTag] = implicitly[ClassTag[T]].runtimeClass.getName
+}
+
+
diff --git a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceWithMultipleAnnotations.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceWithMultipleAnnotations.java
new file mode 100644
index 00000000000..fbdb4cae726
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceWithMultipleAnnotations.java
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.servlet.jersey.classvisitor;
+
+import javax.ws.rs.Path;
+
+/**
+ * @author tonytv
+ */
+@Path("ignored")
+@DummyAnnotation
+public class ResourceWithMultipleAnnotations {
+
+}