/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.rest.impl.resource;

import org.exoplatform.services.rest.BaseTest;
import org.exoplatform.services.rest.ConstructorDescriptor;
import org.exoplatform.services.rest.FieldInjector;
import org.exoplatform.services.rest.impl.header.MediaTypeHelper;
import org.exoplatform.services.rest.method.MethodParameter;
import org.exoplatform.services.rest.resource.AbstractResourceDescriptor;
import org.exoplatform.services.rest.resource.ResourceMethodDescriptor;
import org.exoplatform.services.rest.resource.ResourceMethodMap;
import org.exoplatform.services.rest.resource.SubResourceLocatorDescriptor;
import org.exoplatform.services.rest.resource.SubResourceLocatorMap;
import org.exoplatform.services.rest.resource.SubResourceMethodDescriptor;
import org.exoplatform.services.rest.resource.SubResourceMethodMap;
import org.exoplatform.services.rest.uri.UriPattern;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;

/**
 * @author <a href="mailto:andrew00x@gmail.com">Andrey Parfonov</a>
 * @version $Id: $
 */
public class ResourceDescriptorTest extends BaseTest
{

   /* 
      public void testFailedCreation1()
      {
         try
         {
            new AbstractResourceDescriptorImpl(Resource1.class).accept(ResourceDescriptorValidator.getInstance());
            fail("Should be failed here, resource does not contains JAX-RS methods");
         }
         catch (RuntimeException e)
         {
         }
      }
   */
   public void testFailedCreation2()
   {
      try
      {
         new AbstractResourceDescriptorImpl(Resource2.class);
         fail("Should be failed here, resource does not have public constructor");
      }
      catch (RuntimeException e)
      {
      }
   }

   public void testFailedCreation3()
   {
      try
      {
         new AbstractResourceDescriptorImpl(Resource3.class);
         fail("Should be failed here, resource has two methods that have tha same HTTP method, consumes and produces annotation");
         new AbstractResourceDescriptorImpl(Resource4.class);
         fail("Should be failed here, resource has two methods that have tha same HTTP method, consumes and produces annotation");
         new AbstractResourceDescriptorImpl(Resource5.class);
         fail("Should be failed here, resource has two methods that have tha same HTTP method, path, consumes and produces annotation");
         new AbstractResourceDescriptorImpl(Resource6.class);
         fail("Should be failed here, resource has two methods that have tha same HTTP method, path, consumes and produces annotation");
         new AbstractResourceDescriptorImpl(Resource7.class);
         fail("Should be failed here, resource has two methods that have tha same path");
      }
      catch (RuntimeException e)
      {
      }
   }

   public void testFailedCreation4()
   {
      try
      {
         new AbstractResourceDescriptorImpl(Resource8.class);
         fail("Should be failed here, method has two JAX-RS annotation on the same parameter");
      }
      catch (RuntimeException e)
      {
      }
      // must warn
      new AbstractResourceDescriptorImpl(Resource9.class);
   }

   public void testFailedCreation5()
   {
      try
      {
         new AbstractResourceDescriptorImpl(Resource10.class);
         fail("Should be failed here, constructor of per-request resource has two JAX-RS annotation on the same parameter");
      }
      catch (RuntimeException e)
      {
      }
      // must warn
      new AbstractResourceDescriptorImpl(Resource11.class);
   }

   public void testFailedCreation6()
   {
      try
      {
         new AbstractResourceDescriptorImpl(Resource12.class);
         fail("Should be failed here, fields of per-request resource has two JAX-RS annotation on the same parameter");
      }
      catch (RuntimeException e)
      {
      }
   }

   public void testNotPublicMethodAnnotated()
   {
      // TODO Mechanism for checking log messages. There is some sections in
      // JAX-RS specification that said 'should warn...'. Need control this
      // messages in some way.
      new AbstractResourceDescriptorImpl(Resource14.class);
   }

   // ====================== all of this resource are not valid =========================

   @Path("a")
   public static class Resource1
   {
      public void m1()
      {
         // no JAX-RS methods
      }
   }

   @Path("a")
   public static class Resource2
   {
      Resource2()
      {
      }

      @GET
      public void m1()
      {
      }
   }

   @Path("a")
   public static class Resource3
   {
      @GET
      public void m1()
      {
      }

      @GET
      public void m2()
      {
      }
   }

   @Path("a")
   public static class Resource4
   {
      @GET
      @Consumes({"text/xml", "application/xml", "application/xml+xhtml"})
      @Produces("text/plain")
      public void m1()
      {
      }

      @GET
      @Consumes({"application/xml", "text/xml", "application/xml+xhtml"})
      @Produces("text/plain")
      public void m2()
      {
      }
   }

   @Path("a")
   public static class Resource5
   {
      @GET
      @Path("b")
      public void m1()
      {
      }

      @GET
      @Path("b")
      public void m2()
      {
      }
   }

   @Path("a")
   public static class Resource6
   {
      @GET
      @Consumes({"text/xml", "application/xml", "application/xml+xhtml"})
      @Produces("text/plain")
      @Path("b")
      public void m1()
      {
      }

      @GET
      @Consumes({"application/xml", "text/xml", "application/xml+xhtml"})
      @Produces("text/plain")
      @Path("b")
      public void m2()
      {
      }
   }

   @Path("/a")
   public static class Resource7
   {
      @Path("b")
      public Object m1()
      {
         return new Object();
      }

      @Path("/b")
      public Object m2()
      {
         return new Object();
      }
   }

   @Path("/a")
   public static class Resource8
   {
      @GET
      @Path("b")
      public void m1(@PathParam("b") @HeaderParam("head1") String b)
      {
      }
   }

   @Path("/a")
   public static class Resource9
   {
      @GET
      @Path("c")
      public void m1(@Test @HeaderParam("head1") String b)
      {
      }
   }

   @Target(ElementType.PARAMETER)
   @Retention(RetentionPolicy.RUNTIME)
   public @interface Test {

   }

   @Path("/a")
   public static class Resource10
   {

      public Resource10(@PathParam("b") @HeaderParam("head1") String b)
      {
      }

      @GET
      public void m1()
      {
      }
   }

   @Path("/a")
   public static class Resource11
   {

      // must warn about two constructors with the same number of parameters

      public Resource11(@PathParam("b") String b, @QueryParam("c") int c)
      {
      }

      public Resource11(@HeaderParam("head1") int b, @PathParam("c") String c)
      {
      }

      @GET
      public void m1()
      {
      }
   }

   public static class Resource12
   {

      @SuppressWarnings("unused")
      @Context
      private UriInfo uriInfo;

      @PathParam("b")
      @QueryParam("query")
      String b;

      @GET
      public void m1()
      {
      }
   }

   @Path("/")
   public static class Resource14
   {
      @GET
      void get()
      {
         // not public method annotated
      }

      @POST
      public void post()
      {
      }
   }

   // ===================================== end =================================

   public void testCreateAbstractResourceDescriptor()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      assertTrue(resource.isRootResource());
      assertEquals("/a/{b}/", resource.getPathValue().getPath());
      assertEquals(SampleResource.class, resource.getObjectClass());
      assertEquals(3, resource.getResourceMethods().size());
      assertEquals(1, resource.getSubResourceMethods().size());
      assertEquals(3, resource.getSubResourceMethods().values().iterator().next().size());
      assertEquals(1, resource.getSubResourceLocators().size());
   }

   public void testResourceMethods()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      // GET
      ResourceMethodDescriptor methodDescriptor = resource.getResourceMethods().getFirst("GET");
      assertEquals("GET", methodDescriptor.getHttpMethod());
      assertEquals(MediaTypeHelper.DEFAULT_TYPE, methodDescriptor.consumes().get(0));
      assertEquals(MediaType.valueOf("application/xml"), methodDescriptor.produces().get(0));
      assertEquals(SampleResource.class, methodDescriptor.getParentResource().getObjectClass());
      assertEquals(1, methodDescriptor.getMethodParameters().size());
      MethodParameter methodParameter = methodDescriptor.getMethodParameters().get(0);
      assertEquals("hello", methodParameter.getDefaultValue());
      assertEquals(String.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(2, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
      assertEquals(DefaultValue.class, methodParameter.getAnnotations()[1].annotationType());
      // the same must be for HEAD
      methodDescriptor = resource.getResourceMethods().getFirst("HEAD");
      assertEquals("HEAD", methodDescriptor.getHttpMethod());
      assertEquals(MediaTypeHelper.DEFAULT_TYPE, methodDescriptor.consumes().get(0));
      assertEquals(MediaType.valueOf("application/xml"), methodDescriptor.produces().get(0));
      assertEquals(SampleResource.class, methodDescriptor.getParentResource().getObjectClass());
      assertEquals(1, methodDescriptor.getMethodParameters().size());
      methodParameter = methodDescriptor.getMethodParameters().get(0);
      assertEquals("hello", methodParameter.getDefaultValue());
      assertEquals(String.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(2, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
      assertEquals(DefaultValue.class, methodParameter.getAnnotations()[1].annotationType());
   }

   public void testSubResourceMethods()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      Collection<ResourceMethodMap<SubResourceMethodDescriptor>> subRes = resource.getSubResourceMethods().values();
      // POST
      SubResourceMethodDescriptor subResourceMethodDescriptor = subRes.iterator().next().getFirst("POST");
      assertEquals("POST", subResourceMethodDescriptor.getHttpMethod());
      assertEquals("{c}", subResourceMethodDescriptor.getPathValue().getPath());
      assertEquals(MediaType.valueOf("text/plain"), subResourceMethodDescriptor.consumes().get(0));
      assertEquals(MediaType.valueOf("text/xml"), subResourceMethodDescriptor.consumes().get(1));
      assertEquals(MediaType.valueOf("text/html"), subResourceMethodDescriptor.produces().get(0));
      assertEquals(SampleResource.class, subResourceMethodDescriptor.getParentResource().getObjectClass());
      assertEquals(1, subResourceMethodDescriptor.getMethodParameters().size());
      MethodParameter methodParameter = subResourceMethodDescriptor.getMethodParameters().get(0);
      assertEquals(null, methodParameter.getDefaultValue());
      assertEquals(List.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(1, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
      // GET
      subResourceMethodDescriptor = subRes.iterator().next().getFirst("GET");
      assertEquals("GET", subResourceMethodDescriptor.getHttpMethod());
      assertEquals("{d}", subResourceMethodDescriptor.getPathValue().getPath());
      assertEquals(MediaType.valueOf("text/plain"), subResourceMethodDescriptor.consumes().get(0));
      assertEquals(MediaType.valueOf("text/xml"), subResourceMethodDescriptor.consumes().get(1));
      assertEquals(MediaType.valueOf("text/html"), subResourceMethodDescriptor.produces().get(0));
      assertEquals(SampleResource.class, subResourceMethodDescriptor.getParentResource().getObjectClass());
      assertEquals(1, subResourceMethodDescriptor.getMethodParameters().size());
      methodParameter = subResourceMethodDescriptor.getMethodParameters().get(0);
      assertEquals(null, methodParameter.getDefaultValue());
      assertEquals(List.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(1, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
      // HEAD
      subResourceMethodDescriptor = subRes.iterator().next().getFirst("HEAD");
      assertEquals("HEAD", subResourceMethodDescriptor.getHttpMethod());
      assertEquals("{d}", subResourceMethodDescriptor.getPathValue().getPath());
      assertEquals(MediaType.valueOf("text/plain"), subResourceMethodDescriptor.consumes().get(0));
      assertEquals(MediaType.valueOf("text/xml"), subResourceMethodDescriptor.consumes().get(1));
      assertEquals(MediaType.valueOf("text/html"), subResourceMethodDescriptor.produces().get(0));
      assertEquals(SampleResource.class, subResourceMethodDescriptor.getParentResource().getObjectClass());
      assertEquals(1, subResourceMethodDescriptor.getMethodParameters().size());
      methodParameter = subResourceMethodDescriptor.getMethodParameters().get(0);
      assertEquals(null, methodParameter.getDefaultValue());
      assertEquals(List.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(1, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
   }

   public void testSubResourceLocators()
   {
      // sub-resource method SampleResource#get2()
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      SubResourceLocatorDescriptor subResourceLocatorDescriptor =
         resource.getSubResourceLocators().values().iterator().next();
      assertEquals("{c}/d", subResourceLocatorDescriptor.getPathValue().getPath());
      assertEquals(SampleResource.class, subResourceLocatorDescriptor.getParentResource().getObjectClass());
      assertEquals(1, subResourceLocatorDescriptor.getMethodParameters().size());
      MethodParameter methodParameter = subResourceLocatorDescriptor.getMethodParameters().get(0);
      assertTrue(methodParameter.isEncoded());
      assertEquals(null, methodParameter.getDefaultValue());
      assertEquals(String.class, methodParameter.getParameterClass());
      assertEquals(PathParam.class, methodParameter.getAnnotation().annotationType());
      assertEquals(2, methodParameter.getAnnotations().length);
      assertEquals(PathParam.class, methodParameter.getAnnotations()[0].annotationType());
   }

   public void testFields()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      List<FieldInjector> fields = resource.getFieldInjectors();

      cleanupSonarRuntimeData(fields);

      assertEquals(1, fields.size());
      FieldInjector f = fields.get(0);
      assertEquals(String.class, f.getParameterClass());
      assertEquals(String.class, f.getGenericType());
      assertEquals("default", f.getDefaultValue());
      assertEquals(PathParam.class, f.getAnnotation().annotationType());
      assertEquals("b", ((PathParam)f.getAnnotation()).value());
      assertTrue(f.isEncoded());
   }

   public void testConstructors()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource.class);
      assertEquals(3, resource.getConstructorDescriptors().size());
      List<ConstructorDescriptor> c = resource.getConstructorDescriptors();
      assertEquals(2, c.get(0).getParameters().size());
      assertEquals(1, c.get(1).getParameters().size());
      assertEquals(0, c.get(2).getParameters().size());

      assertFalse(c.get(0).getParameters().get(0).isEncoded());
      assertTrue(c.get(0).getParameters().get(1).isEncoded());
      assertEquals(QueryParam.class, c.get(0).getParameters().get(0).getAnnotation().annotationType());
      assertEquals(PathParam.class, c.get(0).getParameters().get(1).getAnnotation().annotationType());
      assertEquals("test", ((QueryParam)c.get(0).getParameters().get(0).getAnnotation()).value());
      assertEquals("b", ((PathParam)c.get(0).getParameters().get(1).getAnnotation()).value());

      assertFalse(c.get(1).getParameters().get(0).isEncoded());
      assertEquals(PathParam.class, c.get(1).getParameters().get(0).getAnnotation().annotationType());
      assertEquals("b", ((PathParam)c.get(1).getParameters().get(0).getAnnotation()).value());
   }

   @SuppressWarnings("unused")
   @Path("/a/{b}/")
   public static class SampleResource
   {

      @DefaultValue("default")
      @PathParam("b")
      @Encoded
      private String field1;

      public SampleResource(@PathParam("b") String str)
      {
      }

      public SampleResource()
      {
      }

      public SampleResource(@QueryParam("test") int i, @Encoded @PathParam("b") String str)
      {
      }

      @POST
      @Path("{c}")
      @Consumes({"text/plain", "text/xml"})
      @Produces({"text/html"})
      public void post1(@PathParam("b") List<String> p)
      {
         // this is sub-resource method
      }

      @GET
      @Path("{d}")
      @Consumes({"text/plain", "text/xml"})
      @Produces({"text/html"})
      public void get1(@PathParam("b") List<String> p)
      {
         // this is sub-resource method
      }

      @Path("{c}/d")
      public void get2(@PathParam("b") @Encoded String p)
      {
         // this is sub-resource locator
      }

      @GET
      @Produces({"application/xml"})
      public void get3(@PathParam("b") @DefaultValue("hello") String p)
      {
         // this is resource method
      }
   }

   public void testResourceMethodSorting()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource1.class);
      List<ResourceMethodDescriptor> l = resource.getResourceMethods().get("GET");
      assertEquals("m4", l.get(0).getMethod().getName());
      assertEquals("m3", l.get(1).getMethod().getName());
      assertEquals("m2", l.get(2).getMethod().getName());
      assertEquals("m5", l.get(3).getMethod().getName());
      assertEquals("m0", l.get(4).getMethod().getName());
      assertEquals("m1", l.get(5).getMethod().getName());
   }

   @Path("a")
   public static class SampleResource1
   {
      @Consumes({"application/*", "application/xml", "text/*"})
      @Produces({"text/plain", "text/html", "text/*"})
      @GET
      public void m0()
      {
         // before last
      }

      @GET
      public void m1()
      {
         // last, weak defined consumes and produces
      }

      @Consumes({"application/*", "text/*"})
      @Produces({"text/plain", "text/html"})
      @GET
      public void m2()
      {
         // should be third, produces is strong defined
      }

      @Consumes({"application/xml", "text/plain"})
      @GET
      public void m3()
      {
         // should be second after sorting
      }

      @Consumes({"application/xml"})
      @GET
      public void m4()
      {
         // should be first after sorting, consumes type checked first and it is
         // strong defined and number of item in consumes less the for method m3
      }

      @Consumes({"text/*"})
      @Produces({"text/html", "text/*"})
      @GET
      public void m5()
      {
         // forth, less consumes and produces length then at method m0
      }
   }

   public void testSubResourceMethodSorting()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource2.class);
      SubResourceMethodMap srmm = resource.getSubResourceMethods();
      Collection<UriPattern> uris = srmm.keySet();
      Iterator<UriPattern> i = uris.iterator();
      // NOTE template will be normalized, first slash added
      assertEquals("/b/c/d", i.next().getTemplate());
      assertEquals("/b/c", i.next().getTemplate());
      assertEquals("/b/{c}", i.next().getTemplate());
      assertEquals("/b", i.next().getTemplate());

      i = uris.iterator();
      ResourceMethodMap<SubResourceMethodDescriptor> rmm = srmm.getMethodMap(i.next());
      assertEquals(1, rmm.size());
      assertEquals(1, rmm.get("GET").size());
      assertEquals("m4", rmm.get("GET").get(0).getMethod().getName());
      rmm = srmm.getMethodMap(i.next());
      assertEquals(1, rmm.size());
      assertEquals(1, rmm.get("GET").size());
      assertEquals("m1", rmm.get("GET").get(0).getMethod().getName());
      rmm = srmm.getMethodMap(i.next());
      assertEquals(1, rmm.size());
      assertEquals(1, rmm.get("GET").size());
      assertEquals("m3", rmm.get("GET").get(0).getMethod().getName());
      rmm = srmm.getMethodMap(i.next());
      assertEquals(1, rmm.size());
      assertEquals(3, rmm.get("GET").size());
      assertEquals("m2", rmm.get("GET").get(0).getMethod().getName());
      assertEquals("m5", rmm.get("GET").get(1).getMethod().getName());
      assertEquals("m0", rmm.get("GET").get(2).getMethod().getName());
   }

   @Path("a")
   public static class SampleResource2
   {
      @Consumes({"application/*", "application/xml", "text/*"})
      @Produces({"text/plain", "text/html", "text/*"})
      @GET
      @Path("b")
      public void m0()
      {
      }

      @GET
      @Path("b/c")
      public void m1()
      {
      }

      @Consumes({"application/*", "text/*"})
      @Produces({"text/plain", "text/html"})
      @GET
      @Path("b")
      public void m2()
      {
      }

      @Consumes({"application/xml", "text/plain"})
      @GET
      @Path("b/{c}")
      public void m3()
      {
      }

      @Consumes({"application/xml"})
      @GET
      @Path("b/c/d")
      public void m4()
      {
      }

      @Consumes({"text/*"})
      @Produces({"text/html", "text/*"})
      @GET
      @Path("b")
      public void m5()
      {
      }
   }

   public void testSubResourceLocatorSorting()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(SampleResource3.class);
      SubResourceLocatorMap locs = resource.getSubResourceLocators();
      Collection<UriPattern> uris = locs.keySet();
      Iterator<UriPattern> i = uris.iterator();
      // NOTE template will be normalized, first slash added
      assertEquals("/b/c/d", i.next().getTemplate());
      assertEquals("/b/c/z", i.next().getTemplate());
      assertEquals("/b/c", i.next().getTemplate());
      assertEquals("/b/{c}", i.next().getTemplate());
      assertEquals("/b", i.next().getTemplate());
      Iterator<SubResourceLocatorDescriptor> i2 = locs.values().iterator();
      assertEquals("m3", i2.next().getMethod().getName());
      assertEquals("m1", i2.next().getMethod().getName());
      assertEquals("m4", i2.next().getMethod().getName());
      assertEquals("m2", i2.next().getMethod().getName());
      assertEquals("m0", i2.next().getMethod().getName());
   }

   @Path("a")
   public static class SampleResource3
   {
      @Path("b")
      public void m0()
      {
      }

      @Path("b/c/z")
      public void m1()
      {
      }

      @Path("b/{c}")
      public void m2()
      {
      }

      @Path("b/c/d")
      public void m3()
      {
      }

      @Path("b/c")
      public void m4()
      {
      }

   }

   // =========================================

   public void testInitializeFieldSuperClass()
   {
      AbstractResourceDescriptor resource = new AbstractResourceDescriptorImpl(EndResource.class);

      List<FieldInjector> fields = resource.getFieldInjectors();
      cleanupSonarRuntimeData(fields);

      assertEquals(6, fields.size());
   }

   /**
    * Workaround for sonar.
    */
   private void cleanupSonarRuntimeData(List<FieldInjector> fields)
   {
      Iterator<FieldInjector> iter = fields.iterator();
      while (iter.hasNext())
      {
         FieldInjector field = iter.next();
         if (field.getName().equals("$jacocoData"))
         {
            iter.remove();
         }
      }
   }

   public abstract static class AbstractResource
   {
      @Context
      protected UriInfo uriInfo;

      @Context
      public Request request;

      @Context
      protected UriInfo something;
   }

   public abstract static class ExtResource extends AbstractResource
   {

      @Context
      protected SecurityContext sc;

   }

   public static class EndResource extends ExtResource
   {
      @SuppressWarnings("unused")
      @Context
      private HttpHeaders header;

      @SuppressWarnings("unused")
      @Context
      private UriInfo something;

      @GET
      public void m1()
      {
      }
   }
}
