JAX-RS (Jersey 2 implementation) content negotiation with URL extension .xml or .json

妖精的绣舞 提交于 2021-02-19 03:47:07

问题


I've seen a Java RESTFUL webservice, that allowed the content-type to be requested in the URL with an extension at the end, such as

  • .xml
  • .json

This is the style of content negotiation I am striving to achieve in my own Web Service.

I am aware of the @Produces annotation, and the fact a method can resolve multiple types with the (value = {}) syntax, by adding an Accept header, say with Postman, the Chrome extension.

But I'm not sure how to effectively extract out the information in one method, and delegate to another method.

I'm assuming REGEX's can be use with @Path and @PathParam, but my attempts to do this have yet to be fruitful.

Can anyone provide an example?


This is my attempt thus far:

package com.extratechnology.caaews;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.extratechnology.caaews.model.Log;


@Path("et")
@Produces(MediaType.APPLICATION_JSON)
public class CAAEWS {


    @GET
    @Path("\\.{format}")
    @Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
    public Log getLog(
            @PathParam("format") String format
    ){
        Log result = null;
        switch (format) {
        case "json":
            result = this.getJSON();
        case "xml":
            result = this.getXML();
        }
        return result;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Log getJSON() {
        return new Log("JSON!");
    }

    @GET
    @Produces(MediaType.TEXT_XML)
    public Log getXML() {
        return new Log("XML!");
    }

}

package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Log {
    private String log;

    public Log(String log) {
        this.log = log;
    }

    public String getLog() {
        return log;
    }

    public void setLog(String log) {
        this.log = log;
    }

}

The project can be setup from Spring Tool Suite/Eclipse, by creating a Maven project (similar, but more up to date than here circa 4:50) using the following:

  • org.glassfish.jersey.archetypes
  • jersey.quickstart.webapp
  • 2.26

Then you uncomment the part of the pom.xml provided to enable JSON support, which effectively adds a few more JARS to your WAR.

I found I had some nasty BCEL errors too, and had to append some entries to the catalina.properties file, under the key:

tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
....
javax.json-api-1.1.jar, javax.json.bind-api-1.0.jar, javax.json-1.1.jar, \
yasson-1.0.jar

http://localhost:18080/caaews/webapi/et

yields:

{"log":"JSON!"}

http://localhost:18080/caaews/webapi/et.xml or

http://localhost:18080/caaews/webapi/et.json

yields:

HTTP Status 404 - Not Found

I'm also wondering if there's some sort of HTTP Interceptor type approach to this. My Java is a bit rusty, but is it servlet filters, or something akin to an AOP before advice.


Thanks to @user1803551 I have put breaks in switch statements.

Thanks to @callmepills I have tweaked code a bit.

The class level @Path annotation now has this. @Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})

The getLog @Path annotation is ".{format}".

In order to have the getLog be called and delegate, you have to use this syntax for the URL:

http://localhost:18080/caaews/webapi/et

http://localhost:18080/caaews/webapi/et/.xml

http://localhost:18080/caaews/webapi/et/.json

The need to have a '/' in the path isn't what I'm after, so I think I will probably have to resolve to servlet filters rather than @PathParam approach..


回答1:


Even though you didn't tag jersey, your question shows you are using Jersey, so I am going to post a Jersey-specific solution. What Jersey offers is a property that you can use to set the media type mappings

ServerPropeties.MEDIA_TYPE_MAPPINGS

public static final String MEDIA_TYPE_MAPPINGS

Defines mapping of URI extensions to media types. The property is used by UriConnegFilter. See it's javadoc for more information on media type mappings.

The property value MUST be an instance of String, String[] or Map<String, MediaType>. Each String instance represents one or more uri-extension-to-media-type map entries separated by a comma (","). Each map entry is a key-value pair separated by a colon (":"). Here is an example of an acceptable String value mapping txt extension to text/plain and xml extension to application/xml:

txt : text/plain, xml : application/xml

A default value is not set.

The name of the configuration property is "jersey.config.server.mediaTypeMappings".

Example with Java config

final Map<String, MediaType> mediaTypeMappings = new HashMap<>();
mediaTypeMappings.put("xml", MediaType.APPLICATION_XML_TYPE);
mediaTypeMappings.put("json", MediaType.APPLICATION_JSON_TYPE);

final ResourceConfig rc = new ResourceConfig()
        .packages("com.example.jersey")
        .property(ServerProperties.MEDIA_TYPE_MAPPINGS, mediaTypeMappings);

Example with web.xml config

<servlet>
    <servlet-name>JerseyApplication</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.example</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.mediaTypeMappings</param-name>
        <param-value>xml:application/xml, json:application/json</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>



回答2:


There are several problems with your JAX-RS code:

Regex in @Path

The value of the @Path annotation parses regex only inside a parameter template and after a : character. You're trying to use a regex outside of the parameter template, "\\.{format}", so it's not parsing it as a regex.

Path resolution

A method's path includes the segment of the class path followed by its own path segment. Your code suggests the paths /et/.{format} and /et while you're trying to call /et.{format}, which isn't defined anywhere, hence the 404.


Here is an example that would work in accordance with your code:

@Path("et")
public class Resource {

    private static final String JSON = "json";
    private static final String XML = "xml";

    @GET
    @Path(".{format:(" + JSON + "|" + XML + ")}")
    @Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) // not XML?
    public String getLog(@PathParam("format") String format) {
        switch (format) {
            case JSON:
                this.getJSON();
                break;
            case XML:
                this.getXML();
        }
        return format;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public void getJSON() {
        System.out.println("in JSON");
    }

    @GET
    @Path("otherPath")
    @Produces(MediaType.APPLICATION_XML)
    public void getXML() {
        System.out.println("in XML");
    }
}

Your valid requests will now be:

  • http://localhost:18080/caaews/webapi/et (JSON through getJSON)
  • http://localhost:18080/caaews/webapi/et/otherPath (XML through getXML)
  • http://localhost:18080/caaews/webapi/et/.xml (XML through getLog)
  • http://localhost:18080/caaews/webapi/et/.json (JSON through getLog)

Change the paths according to what you want. I used "otherPath" for the XML method because it can't conflict with the empty path JSON method. I do not recommend this convention.

Notes:

  • Use a break in the switch statement.
  • To reduce the chances for bugs, use constants for reusable strings and the like as I did with your custom format types. An enum would be even better.

Edit:

The request is now to have a path /et/<something>.{format}. That can be achieved if we expand the scope of the path parameter to include the whole segment <something>.{format} and then programmatically extract the format:

@GET
@Path("{segment:[a-zA-Z0-9_]*\\.(" + JSON + "|" + XML + ")}")
@Produces(value = { MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public String getLog(@PathParam("segment") String segment) {
    String format = segment.substring(segment.indexOf('.') + 1);
    switch (format) {
        case JSON:
            this.getJSON();
            break;
        case XML:
            this.getXML();
    }
    return format;
}

The regex [a-zA-Z0-9_]* means any alphanumeric or underscore once or more. You can replace that part with whatever restriction you want. Consult the URL specifications for allowed characters.




回答3:


Have you tried getting rid of the @Path annotation at the class level? Your method level annotation would then be:

@Path("et.{format}")

I think your current implementation is creating a sub-resource which matches a path like:

/et/{format}



回答4:


When I Googled on "servlet filter with jax-rs example", this was at the top of the list. From a cursory scan at the code, I think this suits my needs.


Here is my solution (so far... see footnote caveat)

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
     see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <filter>
        <filter-name>accept-filter</filter-name>
        <filter-class>com.extratechnology.filters.AcceptFilter</filter-class>
        <init-param>
            <param-name>xml</param-name>
            <param-value>text/xml</param-value>
        </init-param>
        <init-param>
            <param-name>json</param-name>
            <param-value>application/json</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>accept-filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.extratechnology.caaews</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
</web-app>

AcceptFilter.java

package com.extratechnology.filters;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AcceptFilter implements Filter  {
    private final Map<String,String> extensions = new HashMap<String,String>();

    public void init(FilterConfig config) throws ServletException {
        Enumeration<String> exts = config.getInitParameterNames();
        while (exts.hasMoreElements()) {
            String ext = exts.nextElement();
            if (ext != null && !ext.isEmpty()) {
                this.extensions.put(ext.toLowerCase(), config.getInitParameter(ext));
            }
        }
    }

    public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        String uri = httpRequest.getRequestURI();
        String ext = this.getExtension(uri);
        String accept = this.extensions.get(ext);

        if (accept == null) {
            accept = httpRequest.getHeader("accept");
            if (accept != null && accept.indexOf("text/html") > 0) {
                // patch WebKit-style Accept headers by elevating "text/html"
                accept = "text/html,"+accept;
                request = new RequestWrapper(httpRequest, uri, accept);
            }
        } else {
            // remove extension and remap the Accept header
            uri = uri.substring(0, uri.length() - ext.length()-1);
            request = new RequestWrapper(httpRequest, uri, accept);
        }

        // add "Vary: accept" to the response headers
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        httpResponse.addHeader("Vary", "accept");

        chain.doFilter(request, response);
    }

    private String getExtension(String path) {
        String result = "";
        int index = path.lastIndexOf('.');
        if (!(index < 0 || path.lastIndexOf('/') > index)) {
            result =  path.substring(index+1).toLowerCase();
        }
        return result;
    }

    private static class RequestWrapper extends HttpServletRequestWrapper {

        private final String uri;
        private final String accept;

        public RequestWrapper(HttpServletRequest request, String uri, String accept) {
            super(request);

            this.uri = uri;
            this.accept = accept;
        }

        @Override
        public String getRequestURI() {
            return this.uri;
        }
        @Override
        public Enumeration<String> getHeaders(String name) {
            Enumeration<String> result;
            if ("accept".equalsIgnoreCase(name)) {
                Vector<String> values = new Vector<String>(1);
                values.add(this.accept);
                result = values.elements();
            } else {
                result =  super.getHeaders(name);       
            }
            return result;
        }
    }
}

CAAEWS.java

package com.extratechnology.caaews;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.extratechnology.caaews.model.Log;


@Path("et")
@Produces(value = {MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public class CAAEWS {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Log getJSON() {
        return new Log("JSON!");
    }

    @GET
    @Produces(MediaType.TEXT_XML)
    public Log getXML() {
        return new Log("XML!");
    }

}

Log.java

package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Log {
    private String log;

    public Log(String log) {
        this.log = log;
    }

    public String getLog() {
        return log;
    }

    public void setLog(String log) {
        this.log = log;
    }

}

The only thing that intrigued me slightly is that HTTP has two content types for XML.

  • text/xml
  • application/xml

It's configurable in the web.xml, but I'd have to tweak the annotations. Why the two?

--

Footnote:

After writing this, I'm now finding I'm getting HTTP 500 errors. The logs seem to be in some obscure folder when you run the server in Eclipse:

Documents\workspace-sts-3.8.3.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\logs

And I get this written to log:

0:0:0:0:0:0:0:1 - - [25/Nov/2017:16:56:00 +0000] "GET /caaews/webapi/et.xml HTTP/1.1" 500 1082

Does anyone have an idea how to get more sensible log information? Or what I need to do to trap more meaningful stack traces?


It appears the Log class needs a no argument constructor to overcome this. But I concede, the @peeskillet answer is far less cumbersome and uses built in Jersey functionality.


I'm also wondering if javax.servlet.filters don't play well with JAX-RS 2.0 after looking at examples here...


Per other related answers/comments for this question, I ended up implementing an exception handler, so you get more info on the HTTP 500 messages in Jersey..

Here's the code that helps point way to Log.java needing a no argument constructor..

ErrorMessage

package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ErrorMessage {
    private String errorMessage;
    private String errorStackTrace;
    private String cause;
    private String causeStackTrace;
    private int    errorCode;

    public ErrorMessage() {
    }

    public ErrorMessage(
        String errorMessage, 
        String errorStackTrace, 
        String cause,
        String causeStackTrace,
        int errorCode
    ) {
        this.errorMessage = errorMessage;
        this.errorStackTrace = errorStackTrace;
        this.cause = cause;
        this.causeStackTrace = causeStackTrace;
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public String getErrorStackTrace() {
        return errorStackTrace;
    }

    public void setErrorStackTrace(String errorStackTrace) {
        this.errorStackTrace = errorStackTrace;
    }

    public String getCause() {
        return cause;
    }

    public void setCause(String cause) {
        this.cause = cause;
    }

    public String getCauseStackTrace() {
        return causeStackTrace;
    }

    public void setCauseStackTrace(String causeStackTrace) {
        this.causeStackTrace = causeStackTrace;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

}

GenericExceptionMapper.java

package com.extratechnology.caaews.exception;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.extratechnology.caaews.model.ErrorMessage;

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>{

    @Override
    public Response toResponse(Throwable ex) {
        System.out.println("Stack Trace:");
        ex.printStackTrace();
        System.out.println("Cause:");
        Throwable cause = ex.getCause();
        if (cause != null) {
            cause.printStackTrace();    
        }
        ErrorMessage message = new ErrorMessage(
                ex.getMessage(),
                GenericExceptionMapper.getStackTrack(ex),
                cause.getMessage(),
                GenericExceptionMapper.getStackTrack(cause),
                Status.INTERNAL_SERVER_ERROR.getStatusCode()
                );
        return Response
                .status(Status.INTERNAL_SERVER_ERROR)
                .entity(message)
                .build();
    }

    private static String getStackTrack(Throwable ex) {
        StringBuilder sb = new StringBuilder();
        String ls = System.lineSeparator();
        if (ex != null) {
            StackTraceElement[] steAll = ex.getStackTrace();
            for (StackTraceElement ste : steAll) {
                sb.append(ste.toString());
                sb.append(ls);
            }   
        }
        return sb.toString();
    }
}

The system.out.println gives console messages when debugging and you get a payload back in the web browser too on an error.

eg:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<errorMessage>
<cause>1 counts of IllegalAnnotationExceptions</cause>
<causeStackTrace>
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(Unknown Source) com.sun.xml.internal.bind.v2.ContextFactory.createContext(Unknown Source) sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.find(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getStoredJaxbContext(AbstractJaxbProvider.java:312) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getJAXBContext(AbstractJaxbProvider.java:297) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:264) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:231) org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:175) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors$1.call(Errors.java:272) org.glassfish.jersey.internal.Errors$1.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</causeStackTrace>
<errorCode>500</errorCode>
<errorMessage>HTTP 500 Internal Server Error</errorMessage>
<errorStackTrace>
org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:183) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors$1.call(Errors.java:272) org.glassfish.jersey.internal.Errors$1.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</errorStackTrace>
</errorMessage>


来源:https://stackoverflow.com/questions/47479511/jax-rs-jersey-2-implementation-content-negotiation-with-url-extension-xml-or

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!