Commits

Cezary Zawadka (Atlassian) committed 43371f5 Merge

Merge branch 'issue/FECRU-3966-xsrf-CommitHookREST' into jira-fisheye-plugin-6.1.x

  • Participants
  • Parent commits 6ad74e2, 19f445c

Comments (0)

Files changed (17)

             <version>20080701</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.sun.jersey.jersey-test-framework</groupId>
+            <artifactId>jersey-test-framework-inmemory</artifactId>
+            <version>1.8</version>
+            <scope>test</scope>
+        </dependency>
 
         <!-- integration test deps -->
         <dependency>

File src/main/java/com/atlassian/jirafisheyeplugin/commithook/CommitHookREST.java

 package com.atlassian.jirafisheyeplugin.commithook;
 
 import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.fugue.Iterables;
 import com.atlassian.jira.issue.Issue;
 import com.atlassian.jira.issue.IssueManager;
 import com.atlassian.jira.issue.MutableIssue;
 import com.atlassian.jira.issue.worklog.Worklog;
 import com.atlassian.jira.security.JiraAuthenticationContext;
 import com.atlassian.jira.util.I18nHelper;
+import com.atlassian.jirafisheyeplugin.commithook.handlers.CommandHandler;
 import com.atlassian.jirafisheyeplugin.commithook.handlers.CommentHandler;
 import com.atlassian.jirafisheyeplugin.commithook.handlers.TransitionHandler;
 import com.atlassian.jirafisheyeplugin.commithook.handlers.WorkLogHandler;
 import com.atlassian.jirafisheyeplugin.commithook.restbeans.CommitHookErrors;
 import com.atlassian.jirafisheyeplugin.util.Either;
 import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
+import com.google.common.base.Objects;
 import com.sun.jersey.spi.resource.Singleton;
 
 import javax.ws.rs.*;
-import javax.ws.rs.core.CacheControl;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import javax.ws.rs.core.*;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 @Singleton
 @Path("/issues")
     private static final String BAD_COMMAND = "fisheye.commithooks.commands.badcommand";
     private static final String UNKNOWN_ISSUE = "fisheye.commithooks.commands.unknownissue";
 
-    private CacheControl NO_CACHE;
+    private static final CacheControl NO_CACHE;
 
     private TransitionHandler transitionHandler;
     private CommentHandler commentHandler;
 
     public CommitHookREST(IssueManager issueManager, TransitionHandler transitionHandler, CommentHandler commentHandler,
                           WorkLogHandler workLogHandler, JiraAuthenticationContext jiraAuthenticationContext) {
-        NO_CACHE = new CacheControl();
-        NO_CACHE.setNoCache(true);
-
         this.issueManager = issueManager;
         this.transitionHandler = transitionHandler;
         this.commentHandler = commentHandler;
     @AnonymousAllowed
     @Path("/{issueKey}/transition/{actionName}")
     public Response transition(@PathParam("issueKey") final String issueKey,
-                             @PathParam("actionName") final String actionName) {
-        MutableIssue issue = issueManager.getIssueObject(issueKey);
-        if (issue == null) {
-            return formResponse(CommitHookErrors.fromSingleError(
-                    transitionHandler.getCommandType().getName(),
-                    issueKey,
-                    badIssueKey(issueKey)));
-        }
+                             @PathParam("actionName") final String actionName,
+                             @Context HttpHeaders headers) {
+
+        ensureRequestIsFromApplink(headers);
+
+        MutableIssue issue = getExistingIssue(issueKey, transitionHandler);
 
         Either<CommitHookErrors, Issue> result = transitionHandler.handle(
                 getUser(), issue, transitionHandler.getCommandType().getName(), Arrays.asList(actionName));
     @AnonymousAllowed
     @Path("/{issueKey}/comment")
     @Consumes(MediaType.TEXT_PLAIN)
-    public Response comment(@PathParam("issueKey") final String issueKey, final String text) {
-        MutableIssue issue = issueManager.getIssueObject(issueKey);
-        if (issue == null) {
-            return formResponse(CommitHookErrors.fromSingleError(
-                    commentHandler.getCommandType().getName(),
-                    issueKey,
-                    badIssueKey(issueKey)));
-        }
+    public Response comment(@PathParam("issueKey") final String issueKey, final String text,
+                            @Context HttpHeaders headers) {
+
+        ensureRequestIsFromApplink(headers);
+
+        MutableIssue issue = getExistingIssue(issueKey, commentHandler);
 
         Either<CommitHookErrors, Comment> result = commentHandler.handle(
                 getUser(), issue, commentHandler.getCommandType().getName(), Arrays.asList(text));
     @Path("/{issueKey}/time/{amount}")
     public Response logWork(@PathParam("issueKey") final String issueKey,
                             @PathParam("amount") final String timeToLog,
-                            String workComment) {
-        MutableIssue issue = issueManager.getIssueObject(issueKey);
-        if (issue == null) {
-            return formResponse(CommitHookErrors.fromSingleError(
-                    workLogHandler.getCommandType().getName(),
-                    issueKey,
-                    badIssueKey(issueKey)));
-        }
+                            String workComment, @Context HttpHeaders headers) {
+
+        ensureRequestIsFromApplink(headers);
+
+        MutableIssue issue = getExistingIssue(issueKey, workLogHandler);
 
         Either<CommitHookErrors, Worklog> result = workLogHandler.handle(
                 getUser(), issue, workLogHandler.getCommandType().getName(),
         return formResponse(errors);
     }
 
-    private Response formResponse(CommitHookErrors errors) {
+    private static Response formResponse(CommitHookErrors errors) {
         if (errors == null || errors.isEmpty()) {
             return Response.ok().cacheControl(NO_CACHE).build();
         } else {
     private User getUser() {
         return jiraAuthenticationContext.getLoggedInUser();
     }
+
+    private MutableIssue getExistingIssue(String issueKey, CommandHandler handler) {
+        MutableIssue issue = issueManager.getIssueObject(issueKey);
+        if (issue == null) {
+            throw new BadIssueException(handler.getCommandType().getName(), issueKey, badIssueKey(issueKey));
+        }
+        return issue;
+    }
+
+    /**
+     * Implementation of Option 1 from https://extranet.atlassian.com/pages/viewpage.action?pageId=2188216661
+     * Secured by ensuring request is an Application Link request (check User-Agent header)
+     */
+    private void ensureRequestIsFromApplink(HttpHeaders headers) {
+        List<String> userAgents = Objects.firstNonNull(headers.getRequestHeader(HttpHeaders.USER_AGENT), Collections.<String>emptyList());
+        String userAgent = Iterables.first(userAgents).getOrElse("");
+        if (!userAgent.toLowerCase().contains("httpclient")) { // applink request factory uses HttpClient.
+            throw new NonApplinkRequestException();
+        }
+    }
+
+    private static class BadIssueException extends WebApplicationException {
+        public BadIssueException(String commandType, String issueKey, String message) {
+            super(formResponse(CommitHookErrors.fromSingleError(commandType, issueKey, message)));
+        }
+    }
+
+    private static class NonApplinkRequestException extends WebApplicationException {
+        public NonApplinkRequestException() {
+            super(Response.status(Response.Status.FORBIDDEN).entity("Only AppLink requests are allowed").build());
+        }
+    }
+
+    static {
+        NO_CACHE = new CacheControl();
+        NO_CACHE.setNoCache(true);
+    }
 }

File src/test/java/com/atlassian/jirafisheyeplugin/RestTest.java

+package com.atlassian.jirafisheyeplugin;
+
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.LowLevelAppDescriptor;
+
+/**
+ * REST service integration test
+ *
+ * @tparam T REST service class
+ */
+abstract public class RestTest<T> extends JerseyTest {
+
+    @Override
+    protected AppDescriptor configure() {
+        T service = createService();
+
+        DefaultResourceConfig resourceConfig = new DefaultResourceConfig(service.getClass());
+        resourceConfig.getSingletons().add(service);
+        return new LowLevelAppDescriptor.Builder(resourceConfig).build();
+    }
+
+    /**
+     * Creates REST service instance
+     *
+     * Note: it's called from JerseyTest constructor so <code>this</code> isn't fully initialized
+     *
+     * @return Service instance
+     */
+    abstract protected T createService();
+}

File src/test/java/com/atlassian/jirafisheyeplugin/commithook/CommitHookRESTTest.java

+package com.atlassian.jirafisheyeplugin.commithook;
+
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.jira.issue.IssueManager;
+import com.atlassian.jira.issue.MutableIssue;
+import com.atlassian.jira.security.JiraAuthenticationContext;
+import com.atlassian.jirafisheyeplugin.RestTest;
+import com.atlassian.jirafisheyeplugin.commithook.handlers.CommandHandler;
+import com.atlassian.jirafisheyeplugin.commithook.handlers.CommentHandler;
+import com.atlassian.jirafisheyeplugin.commithook.handlers.TransitionHandler;
+import com.atlassian.jirafisheyeplugin.commithook.handlers.WorkLogHandler;
+import com.atlassian.jirafisheyeplugin.util.Either;
+import com.google.common.collect.ImmutableList;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import org.hamcrest.text.IsEmptyString;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.times;
+
+public class CommitHookRESTTest extends RestTest<CommitHookREST> {
+    private IssueManager issueManager;
+    private TransitionHandler transitionHandler;
+    private CommentHandler commentHandler;
+    private WorkLogHandler workLogHandler;
+    private JiraAuthenticationContext jiraAuthenticationContext;
+
+    @Override
+    protected CommitHookREST createService() {
+        issueManager = Mockito.mock(IssueManager.class);
+        transitionHandler = mockCommandHandler(TransitionHandler.class, CommandType.TRANSITION);
+        commentHandler = mockCommandHandler(CommentHandler.class, CommandType.COMMENT);
+        workLogHandler = mockCommandHandler(WorkLogHandler.class, CommandType.LOG_WORK);
+        jiraAuthenticationContext = Mockito.mock(JiraAuthenticationContext.class);
+
+        return new CommitHookREST(issueManager, transitionHandler, commentHandler, workLogHandler, jiraAuthenticationContext);
+    }
+
+    @Test
+    public void testTimeReturnsForbiddenIfNoApplinkRequest() throws Exception {
+        try {
+            resource().path("/issues/ISS-1/time/10m").post();
+            fail("Expecting response error");
+        } catch(UniformInterfaceException e) {
+            assertThat(e.getResponse().getStatus(), is(Response.Status.FORBIDDEN.getStatusCode()));
+        }
+    }
+
+    @Test
+    public void testTimeCallsHandler() throws Exception {
+        String issueKey = "ISS-1";
+        MutableIssue issue = mockMutableIssue(issueManager, issueKey);
+
+        String errors = resource()
+                .path("/issues/" + issueKey + "/time/10m")
+                .header(HttpHeaders.USER_AGENT, "Apache HttpClient/1.3")
+                .entity("comment 1")
+                .post(String.class);
+
+        assertThat(errors, IsEmptyString.isEmptyOrNullString());
+        Mockito.verify(workLogHandler, times(1)).handle(Matchers.any(User.class), eq(issue), anyString(), eq(ImmutableList.of("10m", "comment 1")));
+    }
+
+    @Test
+    public void testCommentReturnsForbiddenIfNoApplinkRequest() throws Exception {
+        try {
+            resource().path("/issues/ISS-1/comment").post();
+            fail("Expecting response error");
+        } catch(UniformInterfaceException e) {
+            assertThat(e.getResponse().getStatus(), is(Response.Status.FORBIDDEN.getStatusCode()));
+        }
+    }
+
+    @Test
+    public void testCommentCallsHandler() throws Exception {
+        String issueKey = "ISS-1";
+        MutableIssue issue = mockMutableIssue(issueManager, issueKey);
+
+        String errors = resource()
+                .path("/issues/" + issueKey + "/comment")
+                .header(HttpHeaders.USER_AGENT, "Apache HttpClient/1.3")
+                .entity("comment 1")
+                .post(String.class);
+
+        assertThat(errors, IsEmptyString.isEmptyOrNullString());
+        Mockito.verify(commentHandler, times(1)).handle(Matchers.any(User.class), eq(issue), anyString(), eq(ImmutableList.of("comment 1")));
+    }
+
+    @Test
+    public void testTransitionReturnsForbiddenIfNoApplinkRequest() throws Exception {
+        try {
+            resource().path("/issues/ISS-1/transition/close").post();
+            fail("Expecting response error");
+        } catch(UniformInterfaceException e) {
+            assertThat(e.getResponse().getStatus(), is(Response.Status.FORBIDDEN.getStatusCode()));
+        }
+    }
+
+    @Test
+    public void testTransitionCallsHandler() throws Exception {
+        String issueKey = "ISS-1";
+        MutableIssue issue = mockMutableIssue(issueManager, issueKey);
+
+        String errors = resource()
+                .path("/issues/" + issueKey + "/transition/close")
+                .header(HttpHeaders.USER_AGENT, "Apache HttpClient/1.3")
+                .post(String.class);
+
+        assertThat(errors, IsEmptyString.isEmptyOrNullString());
+        Mockito.verify(transitionHandler, times(1)).handle(Matchers.any(User.class), eq(issue), anyString(), eq(ImmutableList.of("close")));
+    }
+
+    private <T extends CommandHandler> T mockCommandHandler(Class<T> cls, CommandType commandType) {
+        T handler = Mockito.mock(cls);
+        Mockito.when(handler.getCommandType()).thenReturn(commandType);
+        Mockito.when(handler.handle(Matchers.any(User.class), Matchers.any(MutableIssue.class), anyString(), anyList()))
+                .thenReturn(Either.value(null));
+        return handler;
+    }
+
+    private MutableIssue mockMutableIssue(IssueManager issueManager, String issueKey) {
+        MutableIssue issue = Mockito.mock(MutableIssue.class);
+        Mockito.when(issue.getKey()).thenReturn(issueKey);
+        Mockito.when(issueManager.getIssueObject(issueKey)).thenReturn(issue);
+        return issue;
+    }
+
+}

File src/test/resources/META-INF/services/com.sun.jersey.spi.HeaderDelegateProvider

+com.sun.jersey.core.impl.provider.header.LocaleProvider
+com.sun.jersey.core.impl.provider.header.EntityTagProvider
+com.sun.jersey.core.impl.provider.header.MediaTypeProvider
+com.sun.jersey.core.impl.provider.header.CacheControlProvider
+com.sun.jersey.core.impl.provider.header.NewCookieProvider
+com.sun.jersey.core.impl.provider.header.CookieProvider
+com.sun.jersey.core.impl.provider.header.URIProvider
+com.sun.jersey.core.impl.provider.header.DateProvider
+com.sun.jersey.core.impl.provider.header.StringProvider

File src/test/resources/META-INF/services/com.sun.jersey.spi.StringReaderProvider

+com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderProviders$TypeFromStringEnum
+com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderProviders$TypeValueOf
+com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderProviders$TypeFromString
+com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderProviders$StringConstructor
+com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderProviders$DateProvider
+com.sun.jersey.server.impl.model.parameter.multivalued.JAXBStringReaderProviders$RootElementProvider

File src/test/resources/META-INF/services/com.sun.jersey.spi.container.ContainerProvider

+com.sun.jersey.server.impl.container.httpserver.HttpHandlerContainerProvider

File src/test/resources/META-INF/services/com.sun.jersey.spi.container.ContainerRequestFilter

+com.sun.jersey.server.impl.container.filter.NormalizeFilter

File src/test/resources/META-INF/services/com.sun.jersey.spi.container.ResourceMethodCustomInvokerDispatchProvider

+com.sun.jersey.server.impl.model.method.dispatch.VoidVoidDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.HttpReqResDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.MultipartFormDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.EntityParamDispatchProvider
+

File src/test/resources/META-INF/services/com.sun.jersey.spi.container.ResourceMethodDispatchProvider

+com.sun.jersey.server.impl.model.method.dispatch.VoidVoidDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.HttpReqResDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.MultipartFormDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider
+com.sun.jersey.server.impl.model.method.dispatch.EntityParamDispatchProvider

File src/test/resources/META-INF/services/com.sun.jersey.spi.container.WebApplicationProvider

+com.sun.jersey.server.impl.container.WebApplicationProviderImpl

File src/test/resources/META-INF/services/com.sun.jersey.spi.inject.InjectableProvider

+com.sun.jersey.core.impl.provider.xml.SAXParserContextProvider
+com.sun.jersey.core.impl.provider.xml.XMLStreamReaderContextProvider
+com.sun.jersey.core.impl.provider.xml.DocumentBuilderFactoryProvider
+com.sun.jersey.core.impl.provider.xml.TransformerFactoryProvider
+

File src/test/resources/META-INF/services/javax.enterprise.inject.spi.Extension

+com.sun.jersey.server.impl.cdi.CDIExtension

File src/test/resources/META-INF/services/javax.servlet.ServletContainerInitializer

+com.sun.jersey.server.impl.container.servlet.JerseyServletContainerInitializer

File src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader

+com.sun.jersey.core.impl.provider.entity.StringProvider
+com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
+com.sun.jersey.core.impl.provider.entity.FileProvider
+com.sun.jersey.core.impl.provider.entity.InputStreamProvider
+com.sun.jersey.core.impl.provider.entity.DataSourceProvider
+com.sun.jersey.core.impl.provider.entity.RenderedImageProvider
+com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider
+com.sun.jersey.core.impl.provider.entity.FormProvider
+com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
+com.sun.jersey.core.impl.provider.entity.ReaderProvider
+com.sun.jersey.core.impl.provider.entity.DocumentProvider
+com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader
+com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader
+com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader
+com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General
+com.sun.jersey.core.impl.provider.entity.EntityHolderReadercom.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$General
+com.sun.jersey.json.impl.provider.entity.JacksonProviderProxy
+

File src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter

+com.sun.jersey.core.impl.provider.entity.StringProvider
+com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
+com.sun.jersey.core.impl.provider.entity.FileProvider
+com.sun.jersey.core.impl.provider.entity.InputStreamProvider
+com.sun.jersey.core.impl.provider.entity.DataSourceProvider
+com.sun.jersey.core.impl.provider.entity.RenderedImageProvider
+com.sun.jersey.core.impl.provider.entity.MimeMultipartProvider
+com.sun.jersey.core.impl.provider.entity.FormProvider
+com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$Text
+com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
+com.sun.jersey.core.impl.provider.entity.ReaderProvider
+com.sun.jersey.core.impl.provider.entity.DocumentProvider
+com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider
+com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
+com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$App
+com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$General
+com.sun.jersey.json.impl.provider.entity.JSONWithPaddingProvider
+com.sun.jersey.json.impl.provider.entity.JacksonProviderProxy
+com.sun.jersey.server.impl.template.ViewableMessageBodyWriter

File src/test/resources/META-INF/services/javax.ws.rs.ext.RuntimeDelegate

+com.sun.jersey.server.impl.provider.RuntimeDelegateImpl