summaryrefslogtreecommitdiffstats
path: root/container-jersey2
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2018-06-04 17:41:04 +0200
committerGitHub <noreply@github.com>2018-06-04 17:41:04 +0200
commit7f385ad3515184d0fa4099cc57e85a2060cfbd80 (patch)
treedacb621d2def964e2a001de927f28b3a18770f3d /container-jersey2
parentf16999e04017a5c38cdafed505498189cb24a66c (diff)
Revert "Container-jersey2: Scala code replaced with Java"
Diffstat (limited to 'container-jersey2')
-rw-r--r--container-jersey2/pom.xml24
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java73
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java25
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java118
-rw-r--r--container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java103
-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.scala109
-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/ResourceOrProviderClassVisitorTest.java75
-rw-r--r--container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala75
11 files changed, 338 insertions, 394 deletions
diff --git a/container-jersey2/pom.xml b/container-jersey2/pom.xml
index eb94b20700f..26dfa762032 100644
--- a/container-jersey2/pom.xml
+++ b/container-jersey2/pom.xml
@@ -54,10 +54,34 @@
<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>net.alchim31.maven</groupId>
+ <artifactId>scala-maven-plugin</artifactId>
+ <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>
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java
deleted file mode 100644
index 7ff9646cb27..00000000000
--- a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ComponentGraphProvider.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey;
-
-import com.yahoo.container.di.config.ResolveDependencyException;
-import com.yahoo.container.di.config.RestApiContext;
-import com.yahoo.container.jaxrs.annotation.Component;
-import org.glassfish.hk2.api.Injectee;
-import org.glassfish.hk2.api.InjectionResolver;
-import org.glassfish.hk2.api.ServiceHandle;
-
-import javax.inject.Singleton;
-
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Resolves jdisc container components for jersey 2 components.
- *
- * @author Tony Vaagenes
- * @author ollivir
- */
-@Singleton // jersey2 requirement: InjectionResolvers must be in the Singleton scope
-public class ComponentGraphProvider implements InjectionResolver<Component> {
- private Collection<RestApiContext.Injectable> injectables;
-
- public ComponentGraphProvider(Collection<RestApiContext.Injectable> injectables) {
- this.injectables = injectables;
- }
-
- @Override
- public Object resolve(Injectee injectee, ServiceHandle<?> root) {
- Class<?> wantedClass;
- Type type = injectee.getRequiredType();
- if (type instanceof Class) {
- wantedClass = (Class<?>) type;
- } else {
- throw new UnsupportedOperationException("Only classes are supported, got " + type);
- }
-
- List<RestApiContext.Injectable> componentsWithMatchingType = new ArrayList<>();
- for (RestApiContext.Injectable injectable : injectables) {
- if (wantedClass.isInstance(injectable.instance)) {
- componentsWithMatchingType.add(injectable);
- }
- }
-
- if (componentsWithMatchingType.size() == 1) {
- return componentsWithMatchingType.get(0).instance;
- } else {
- String injectionDescription = "class '" + wantedClass + "' to inject into Jersey resource/provider '"
- + injectee.getInjecteeClass() + "')";
- if (componentsWithMatchingType.size() > 1) {
- String ids = componentsWithMatchingType.stream().map(c -> c.id.toString()).collect(Collectors.joining(","));
- throw new ResolveDependencyException("Multiple components found of " + injectionDescription + ": " + ids);
- } else {
- throw new ResolveDependencyException("Could not find a component of " + injectionDescription + ".");
- }
- }
- }
-
- @Override
- public boolean isMethodParameterIndicator() {
- return true;
- }
-
- @Override
- public boolean isConstructorParameterIndicator() {
- return true;
- }
-}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java
deleted file mode 100644
index 4c4e43bc8d5..00000000000
--- a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyApplication.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 Yahoo Holdings. 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 java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author Tony Vaagenes
- * @author ollivir
- */
-public class JerseyApplication extends Application {
- private Set<Class<?>> classes;
-
- public JerseyApplication(Collection<Class<?>> resourcesAndProviderClasses) {
- this.classes = new HashSet<>(resourcesAndProviderClasses);
- }
-
- @Override
- public Set<Class<?>> getClasses() {
- return classes;
- }
-}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java
deleted file mode 100644
index ab094e77650..00000000000
--- a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/JerseyServletProvider.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-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 org.eclipse.jetty.servlet.ServletHolder;
-import org.glassfish.hk2.api.InjectionResolver;
-import org.glassfish.hk2.api.TypeLiteral;
-import org.glassfish.hk2.utilities.Binder;
-import org.glassfish.hk2.utilities.binding.AbstractBinder;
-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 java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-
-import static com.yahoo.container.servlet.jersey.util.ResourceConfigUtil.registerComponent;
-
-/**
- * @author Tony Vaagenes
- * @author ollivir
- */
-public class JerseyServletProvider implements Provider<ServletHolder> {
- private final ServletHolder jerseyServletHolder;
-
- public JerseyServletProvider(RestApiContext restApiContext) {
- this.jerseyServletHolder = new ServletHolder(new ServletContainer(resourceConfig(restApiContext)));
- }
-
- private ResourceConfig resourceConfig(RestApiContext restApiContext) {
- final ResourceConfig resourceConfig = ResourceConfig
- .forApplication(new JerseyApplication(resourcesAndProviders(restApiContext.getBundles())));
-
- registerComponent(resourceConfig, componentInjectorBinder(restApiContext));
- registerComponent(resourceConfig, jacksonDatatypeJdk8Provider());
- resourceConfig.register(MultiPartFeature.class);
-
- return resourceConfig;
- }
-
- private static Collection<Class<?>> resourcesAndProviders(Collection<BundleInfo> bundles) {
- final List<Class<?>> ret = new ArrayList<>();
-
- for (BundleInfo bundle : bundles) {
- for (String classEntry : bundle.getClassEntries()) {
- Optional<String> className = detectResourceOrProvider(bundle.classLoader, classEntry);
- className.ifPresent(cname -> ret.add(loadClass(bundle.symbolicName, bundle.classLoader, cname)));
- }
- }
- return ret;
- }
-
- private static Optional<String> detectResourceOrProvider(ClassLoader bundleClassLoader, String classEntry) {
- try (InputStream inputStream = getResourceAsStream(bundleClassLoader, classEntry)) {
- ResourceOrProviderClassVisitor visitor = ResourceOrProviderClassVisitor.visit(new ClassReader(inputStream));
- return Optional.ofNullable(visitor.getClassName());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static InputStream getResourceAsStream(ClassLoader bundleClassLoader, String classEntry) {
- InputStream is = bundleClassLoader.getResourceAsStream(classEntry);
- if (is == null) {
- throw new RuntimeException("No entry " + classEntry + " in bundle " + bundleClassLoader);
- } else {
- return is;
- }
- }
-
- private static Class<?> loadClass(String bundleSymbolicName, ClassLoader classLoader, String className) {
- try {
- return classLoader.loadClass(className);
- } catch (Exception e) {
- throw new RuntimeException("Failed loading class " + className + " from bundle " + bundleSymbolicName, e);
- }
- }
-
- private static Binder componentInjectorBinder(RestApiContext restApiContext) {
- final ComponentGraphProvider componentGraphProvider = new ComponentGraphProvider(restApiContext.getInjectableComponents());
- final TypeLiteral<InjectionResolver<Component>> componentAnnotationType = new TypeLiteral<InjectionResolver<Component>>() {
- };
-
- return new AbstractBinder() {
- @Override
- public void configure() {
- bind(componentGraphProvider).to(componentAnnotationType);
- }
- };
- }
-
- private static JacksonJaxbJsonProvider jacksonDatatypeJdk8Provider() {
- JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
- provider.setMapper(new ObjectMapper().registerModule(new Jdk8Module()).registerModule(new JavaTimeModule()));
- return provider;
- }
-
- @Override
- public ServletHolder get() {
- return jerseyServletHolder;
- }
-
- @Override
- public void deconstruct() {
- }
-}
diff --git a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java b/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java
deleted file mode 100644
index 9abf573c73a..00000000000
--- a/container-jersey2/src/main/java/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.servlet.jersey;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import javax.ws.rs.Path;
-import javax.ws.rs.ext.Provider;
-
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * @author Tony Vaagenes
- * @author ollivir
- */
-public class ResourceOrProviderClassVisitor extends ClassVisitor {
- private String className = null;
- private boolean isPublic = false;
- private boolean isAbstract = false;
-
- private boolean isInnerClass = false;
- private boolean isStatic = false;
-
- private boolean isAnnotated = false;
-
- public ResourceOrProviderClassVisitor() {
- super(Opcodes.ASM5);
- }
-
- public Optional<String> getJerseyClassName() {
- if (isJerseyClass()) {
- return Optional.of(getClassName());
- } else {
- return Optional.empty();
- }
- }
-
- public boolean isJerseyClass() {
- return isAnnotated && isPublic && !isAbstract && (!isInnerClass || isStatic);
- }
-
- public String getClassName() {
- assert (className != null);
- return org.objectweb.asm.Type.getObjectType(className).getClassName();
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
- isPublic = isPublic(access);
- className = name;
- isAbstract = isAbstract(access);
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- assert (className != null);
-
- if (name.equals(className)) {
- isInnerClass = true;
- isStatic = isStatic(access);
- }
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- isAnnotated |= annotationClassDescriptors.contains(desc);
- return null;
- }
-
- private static Set<String> annotationClassDescriptors = new HashSet<>();
-
- static {
- annotationClassDescriptors.add(Type.getDescriptor(Path.class));
- annotationClassDescriptors.add(Type.getDescriptor(Provider.class));
- }
-
- private static boolean isPublic(int access) {
- return isSet(Opcodes.ACC_PUBLIC, access);
- }
-
- private static boolean isStatic(int access) {
- return isSet(Opcodes.ACC_STATIC, access);
- }
-
- private static boolean isAbstract(int access) {
- return isSet(Opcodes.ACC_ABSTRACT, access);
- }
-
- private static boolean isSet(int bits, int access) {
- return (access & bits) == bits;
- }
-
- public static ResourceOrProviderClassVisitor visit(ClassReader classReader) {
- ResourceOrProviderClassVisitor visitor = new ResourceOrProviderClassVisitor();
- classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
- return visitor;
- }
-}
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..cabde3680a4
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ComponentGraphProvider.scala
@@ -0,0 +1,40 @@
+// Copyright 2017 Yahoo Holdings. 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..eea41003984
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyApplication.scala
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. 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.JavaConverters._
+
+/**
+ * @author tonytv
+ */
+class JerseyApplication(resourcesAndProviderClasses: Set[Class[_]]) extends Application {
+ private val classes: java.util.Set[Class[_]] = resourcesAndProviderClasses.asJava
+
+ 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..f0eff54dc16
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/JerseyServletProvider.scala
@@ -0,0 +1,109 @@
+// Copyright 2017 Yahoo Holdings. 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.datatype.jsr310.JavaTimeModule
+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.{InjectionResolver, TypeLiteral}
+import org.glassfish.hk2.utilities.Binder
+import org.glassfish.hk2.utilities.binding.AbstractBinder
+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.JavaConverters._
+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.asScala)))
+
+ registerComponent(resourceConfig, componentInjectorBinder(restApiContext))
+ registerComponent(resourceConfig, jacksonDatatypeJdk8Provider)
+ resourceConfig.register(classOf[MultiPartFeature])
+
+ resourceConfig
+ }
+
+ def resourcesAndProviders(bundles: Traversable[BundleInfo]) =
+ (for {
+ bundle <- bundles.view
+ classEntry <- bundle.getClassEntries.asScala
+ 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.asScala)
+ 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)
+ .registerModule(new JavaTimeModule))
+ 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..c015f11360e
--- /dev/null
+++ b/container-jersey2/src/main/scala/com/yahoo/container/servlet/jersey/ResourceOrProviderClassVisitor.scala
@@ -0,0 +1,74 @@
+// Copyright 2017 Yahoo Holdings. 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/ResourceOrProviderClassVisitorTest.java b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.java
deleted file mode 100644
index a4f186c3c78..00000000000
--- a/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 Yahoo Holdings. 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;
-import org.junit.Assert;
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-
-import java.io.IOException;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-public class ResourceOrProviderClassVisitorTest {
- @Test
- public void resource_is_detected() throws IOException {
- assert_is_accepted(Resource.class);
- }
-
- @Test
- public void provider_is_detected() throws IOException {
- assert_is_accepted(Provider.class);
- }
-
- @Test
- public void inner_class_is_ignored() throws IOException {
- assert_is_ignored(InnerClass.Inner.class);
- }
-
- @Test
- public void nested_public_class_is_detected() throws IOException {
- assert_is_accepted(NestedClass.Nested.class);
- }
-
- @Test
- public void nested_non_public_class_is_ignored() throws IOException {
- assert_is_ignored(NonPublicNestedClass.Nested.class);
- }
-
- @Test
- public void resource_with_multiple_annotations_is_detected() throws IOException {
- assert_is_accepted(ResourceWithMultipleAnnotations.class);
- }
-
- @Test
- public void interface_is_ignored() throws IOException {
- assert_is_ignored(InterfaceResource.class);
- }
-
- @Test
- public void abstract_class_is_ignored() throws IOException {
- assert_is_ignored(AbstractResource.class);
- }
-
- @Test
- public void className_is_equal_to_getName() throws IOException {
- assertThat(analyzeClass(Resource.class).getClassName(), is(Resource.class.getName()));
- }
-
- private static void assert_is_accepted(Class<?> clazz) throws IOException {
- Assert.assertTrue(className(clazz) + " was not accepted", analyzeClass(clazz).isJerseyClass());
- }
-
- private static void assert_is_ignored(Class<?> clazz) throws IOException {
- Assert.assertFalse(className(clazz) + " was not ignored", analyzeClass(clazz).isJerseyClass());
- }
-
- private static ResourceOrProviderClassVisitor analyzeClass(Class<?> clazz) throws IOException {
- return ResourceOrProviderClassVisitor.visit(new ClassReader(className(clazz)));
- }
-
- private static String className(Class<?> clazz) {
- return clazz.getName();
- }
-}
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..f20c5e02e62
--- /dev/null
+++ b/container-jersey2/src/test/java/com/yahoo/container/servlet/jersey/classvisitor/ResourceOrProviderClassVisitorTest.scala
@@ -0,0 +1,75 @@
+// Copyright 2017 Yahoo Holdings. 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
+}
+
+