0

I'm currently working on a plugin for Killbill, using Jooby for developing the servlet. The servlet will receive a notification from payment gateway and process the update into internal Killbill system

@Singleton
public class NotificationServlet {

    private final InvoiceApi invoiceApi;
    private static final Logger logger = LoggerFactory.getLogger(NotificationServlet .class);

    @Inject
    public NotificationServlet() {
        // initiate InvoiceApi
        this.invoiceApi = new InvoiceApi(new KillBillHttpClient()); 
    }

    @POST
    @Path("/notify")
    public Result notify(@Body String body) throws KillBillClientException {
        logger.info(body);
        JSONObject dataJson = new JSONObject(body);
        if (dataJson.getString("status_code").equalsIgnoreCase("1")) {
            String referenceId = dataJson.getString("reference_id");
            String[] referenceIdArray = referenceId.split("\\|");
            String apiKey = referenceIdArray[0];
            String apiSecret = referenceIdArray[1];
            String accountId = referenceIdArray[2];
            String invoiceId = referenceIdArray[3];
            String amount = referenceIdArray[4];

            InvoicePayment invoicePayment = new InvoicePayment();
            invoicePayment.setPurchasedAmount(new BigDecimal(amount));
            invoicePayment.setAccountId(UUID.fromString(accountId));
            invoicePayment.setTargetInvoiceId(UUID.fromString(invoiceId));

            RequestOptions requestOptions = RequestOptions.builder()
                                                          .withCreatedBy("createdBy")
                                                          .withReason("reason")
                                                          .withComment("comment")
                                                          .withQueryParams(ArrayListMultimap.create())
                                                          .withTenantApiKey(apiKey)
                                                          .withTenantApiSecret(apiSecret)
                                                          .build();

            InvoicePayment result = invoiceApi.createInstantPayment(UUID.fromString(invoiceId),
                                                                    invoicePayment,
                                                                    true,
                                                                    null,
                                                                    null,
                                                                    requestOptions);
        }

        return Results.with("", Status.OK)
                      .type(MediaType.json);
    }

When I tested this servlet using Postman, the servlet works fine. But when I tested this with the payment gateway, I got Jersey's ContainerException

2022-04-07T16:08:07,423+0000 lvl='ERROR', log='[default]', th='catalina-exec-10', xff='', rId='', tok='', aRId='', tRId='', Servlet.service() for servlet [default] in context with path [] threw exception [org.glassfish.jersey.server.ContainerException: java.io.IOException: Stream closed] with root cause
java.io.IOException: Stream closed
        at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:367)
        at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:152)
        at com.google.common.io.ByteStreams.copy(ByteStreams.java:109)
        at org.killbill.billing.jaxrs.resources.PluginResource.createInputStream(PluginResource.java:247)
        at org.killbill.billing.jaxrs.resources.PluginResource.serviceViaOSGIPlugin(PluginResource.java:185)
        at org.killbill.billing.jaxrs.resources.PluginResource.doFormPOST(PluginResource.java:140)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167)
        at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
        at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
        at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
        at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
        at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
        at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
        at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:680)
        at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394)
        at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:366)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:319)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
        at com.google.inject.servlet.ServletDefinition.doServiceImpl(ServletDefinition.java:290)
        at com.google.inject.servlet.ServletDefinition.doService(ServletDefinition.java:280)
        at com.google.inject.servlet.ServletDefinition.service(ServletDefinition.java:184)
        at com.google.inject.servlet.ManagedServletPipeline.service(ManagedServletPipeline.java:89)
        at org.killbill.billing.server.security.TenantFilter.handleAuthenticationError(TenantFilter.java:120)
        at org.killbill.billing.server.security.TenantFilter.doFilter(TenantFilter.java:89)
        at org.killbill.billing.server.filters.ResponseCorsFilter.doFilter(ResponseCorsFilter.java:75)
        at ch.qos.logback.classic.helpers.MDCInsertingServletFilter.doFilter(MDCInsertingServletFilter.java:49)
        at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:121)
        at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:133)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:44)
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:41)
        at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
        at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.shiro.guice.web.SimpleFilterChain.doFilter(SimpleFilterChain.java:41)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
        at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
        at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
        at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
        at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.codahale.metrics.servlet.AbstractInstrumentedFilter.doFilter(AbstractInstrumentedFilter.java:111)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:544)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
        at org.apache.catalina.valves.rewrite.RewriteValve.invoke(RewriteValve.java:305)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
        at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:747)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:616)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:818)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1626)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

I'm suspecting it might be because different type of body being sent, since I tested with Postman using raw data in JSON format meanwhile the gateway is sending x-www-form-urlencoded. I noticed this since there's a warning before this error appeared

2022-04-07T16:08:01,477+0000 lvl='WARN', log='WebComponent', th='catalina-exec-9', xff='', rId='', tok='', aRId='', tRId='', 
A servlet request to the URI http://path/to/my/notify-servlet contains form parameters in 
the request body but the request body has been consumed by the servlet or a servlet filter 
accessing the request parameters. Only resource methods using @FormParam will work as 
expected. Resource methods consuming the request body by other means will not work as expected.

I've tried adjusting the method according to Jooby's docs on form submission, but the issue still persists. So is the issue still in Jooby or it's in Jersey?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Muhamad Iqbal
  • 742
  • 1
  • 4
  • 17
  • Well yeah, if you know the gateway is going to send you form-urlencoded data, why are trying to use JSON? Have you tried using `@FormParam` like the warning suggests? – Paul Samsotha Apr 07 '22 at 17:09
  • @PaulSamsotha i did try using `@FormParam` from Jersey, but the error (and the warning) still persisted – Muhamad Iqbal Apr 08 '22 at 03:55
  • Yea so I've never used Jooby, but after looking at the documentation and starter projects, it doesn't use Jersey and the annotations they use are not JAX-RS annotations (`javax.rs.ws`), they are they're own annotations. See [the starter projects](https://github.com/search?q=topic%3Astarter+org%3Ajooby-project&type=Repositories) – Paul Samsotha Apr 08 '22 at 08:28
  • So I'm not really sure how it's supposed to work. I would just look at their starter projects and see how your differs. – Paul Samsotha Apr 08 '22 at 08:32

0 Answers0