1

I have a question regarding an REST-Interface Iam currently building. Iam normally a frontend specialist, due to no resources available, now handling backend stuff. So please patient with me :-).

Here is the Class Iam speaking of:

/**
 *
 * @author User
 */
@Path("/excel")
public class Excel {

     private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
     private static final String SERVER_UPLOAD_LOCATION_FOLDER = "C://tmp/";


    @POST
    @Path("/export")
    //@Consumes(MediaType.APPLICATION_JSON)
    @Consumes({"application/xml","application/json"})
    public Response writeFile(FileContent fileContent){
        //Check if anything is null, if so return a server error code 500
        String startDateStr = new Date().toString();

        try {       
            JsonParser parser = new JsonParser();
            JsonObject element = (JsonObject) parser.parse(fileContent.getContent());
            String fileName = startDateStr + Integer.toString(new Random().nextInt(100)) + ".xls";
            FileInformation fi = new FileInformation(
                    fileName, 
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            );
            File file = new File(fileName);
            WritableWorkbook workbook = Workbook.createWorkbook(file);
            WritableSheet sheet = workbook.createSheet(fileName, 0);
            int i = 0;
            while(element.get(Integer.toString(i)) != null && !element.get(Integer.toString(i)).getAsJsonArray().isJsonNull()) {
                JsonArray line = element.get(Integer.toString(i)).getAsJsonArray();
                System.out.println(i);
                for(int n = 0; n < line.size(); n++)
                {
                    System.out.println(line.get(n).toString());
                    Label label = new Label(n,i, line.get(n).toString());
                    sheet.addCell(label);
                }
                i++;
            }
            workbook.write();
            workbook.close();
            Runnable r = new DeleteThread(file);
            new Thread(r).start();

            return Response.status(201).entity(fi).header("Access-Control-Allow-Origin", "*").build();

        } catch (NullPointerException | IOException | WriteException e ) {
            System.out.println(e);
            return Response.status(500).build();
        }

    }

    @GET
    @Path("/export")
    @Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    public Response serveFile(@QueryParam("fileName") String fileName){
         File file = new File(fileName);  
         Response.ResponseBuilder response = Response.ok((Object) file);
            response.header("Content-Disposition",
                    "attachment; filename=" + fileName);
            return response.build();
    }

    @PUT
    @Path("/import")
    @Produces
    public Response putFile() {
        /*@FormDataParam("file") FormDataContentDisposition contentDispositionHeader) {
        String filePath = SERVER_UPLOAD_LOCATION_FOLDER + contentDispositionHeader.getFileName();
        FileHelper.saveFile(fileInputStream, filePath);*/
        return Response.status(200).entity("Hallo Friend").build();
    }
}

Here is my POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.range_plan_viz.rest</groupId>
  <artifactId>jerseyrangeplan</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jerseyrangeplan Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>net.sourceforge.jexcelapi</groupId>
      <artifactId>jxl</artifactId>
      <version>2.6.12</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.10</version>
    </dependency>
     <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet-core</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.2.4</version>
    </dependency>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>6.0</version>
        <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>rangePlanVizRest</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <server>TomcatServer</server>
          <path>/restRangePlan</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

</project>

Here is the mapper Class for the request payload:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.range_plan_viz.wrapper;

/**
 *
 * @author User
 */
public class FileContent {
    private String content;

    public FileContent(String content){
        setContent(content);
    }

    /**
     * @return the content
     */
    public String getContent() {
        return content;
    }

    /**
     * @param content the content to set
     */
    public void setContent(String content) {
        this.content = content;
    }


}

Here is the mapper for the response payload:

package org.range_plan_viz.wrapper;

import javax.xml.bind.annotation.XmlRootElement;

/**
 * Created by User on 12.02.2015.
 */
@XmlRootElement
public final class FileInformation {
    private String fileName;
    private String contentType;


    /**
     * Default constructor
     * @param content The content of the excel file published by the UI
     * @param fileName The name of the file that should be generated published by the UI
     * @param contentType The contenType of the file
     */
    public FileInformation(
            String fileName,
            String contentType
    ){
        setFileName(fileName);
        setContentType(contentType);
    }



    /**
     * Getter of the fileName
     * @return {String} The fileName of the file that should be generated
     */
    public String getFileName() {
        return fileName;
    }

    /**
     * The setter of the fileName
     * @param {String} fileName The fileName of the file that should be
     * generated
     */
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    /**
     * The getter of the content type
     * @return {String} The contentType of the file that should be generated
     */
    public String getContentType() {
        return contentType;
    }

    /**
     * The setter of the content type
     * @param {String} contentType The contentType of the app that should be
     * generated
     */
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    @Override
    public String toString() {
        return "FileInformation [fileName=" + getFileName() + ", contenType=" + getContentType() + "]";
    }

}

And here is the JavaScript to test it:

var a =[BigDataMock],
    url = "http://localhost:8084/jerseyrangeplan/rest/excel/export"
$.ajax({
    url: url,
    method: 'post',
    crossDomain: true,
    contentType: "application/json",
    dataType: "json",
    data:{
        content: JSON.stringify(a)
    },  
    success: function(result, status, xhr) {
        window.location.assign(url + "?fileName=" + result.fileName);
    },
    error: function (responseData, textStatus, errorThrown) {
        console.log('POST failed.');
    }
});

What I want to do is basically extract a big dataset that is in the frontend to Excel and then download the file. I allready tried the form approach but it worked only for small datasets. We have 10000 members in the object and the form cutted the stringyfied string which lead to problems in the backend. I also tried to use excelbuilder.js, that didn't work either. The bytestream I was sending after generating the excel file wasn't interpreted correctly from the Java backend. Now I want to save the excel file and then instantly trigger a download in the callback.

But I keep getting the 415 unsupported media type error. I also have trouble with CORS filter which seems not to work correctly.

Can anybody help?

Thanks in advance,

Sebastian

  • For the CORS problem, I would put the header in a [`ContainerResponseFilter`](https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9509), rather then in the method, as seen [here](http://stackoverflow.com/a/28067653/2587435). Not sure about the error response though. I would image that with this return status, there would be some exception being logged in the server. Do you see anything you can share with us? – Paul Samsotha Feb 21 '15 at 09:52
  • Hallo peeskillet unfortunately not. I allready set breakpoints, but it seems he crashes right in the starting or even don't execute the method. – Sebastian Zabel Feb 21 '15 at 16:03
  • Do you have a `MessageBodyWriter` for this type `"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"`? Without testing it, I would guess this is culprit. When you redirect to that method, the return value needs to be serialized into that type, but can't if it can't find a writer. See [How to Write Custom Entity Providers](https://jersey.java.net/documentation/latest/message-body-workers.html#d0e6686) – Paul Samsotha Feb 21 '15 at 16:12
  • Another option, if the media type is not important, just change to or add `"application/octet-stream"` – Paul Samsotha Feb 21 '15 at 16:15
  • Hello guys thanks for your answers. But the problem is not about the GET it is about the post. That is what he is not doing. – Sebastian Zabel Feb 22 '15 at 09:15
  • But doesn `window.location.assign(url + "?fileName=" + result.fileName);` open a new window with a GET request? That's what I was referring to. When you have a successful post, that is the success callback – Paul Samsotha Feb 22 '15 at 09:25
  • Write an `ExceptionMapper` (if there is no log to your server) to do some debug logging if you need to. It would help to know the cause, in the form of a stack trace. See [here](http://stackoverflow.com/q/28151049/2587435) – Paul Samsotha Feb 22 '15 at 09:28

1 Answers1

2

Seems to be the main problem is that you don't have a MessageBodyReader configured to handle JSON. You have the jersey-media-json-jackson on the classpath, but you will still need to register the provider. That's why the Unsupported Media Type. If Jersey can't find a provider to handle the conversion, this is the the status you will get.

If you are using web.xml, you can use

<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>
            jersey.web.stackoverflow,   <!-- your package -->
            org.codehaus.jackson.jaxrs  <!-- jackson package -->
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

If you are using a ResourceConfig class, then you can just do

public class AppConfig extends ResourceConfig {
    public AppConfig() {
        register(JacksonFeature.class);
    }
}

What you can expect after fixing this

  • Not sure how you are sending the data, I don't know what BigDataMock it, but if Jackson can't read it, you can expect to get a 400 Bad Request

  • Also mentioned in the comments, you will mostly likely get another problem with your @Produces in your GET redirect method. The content type will no be supported, and you will need to write custom MessageBodyWriter for it, or simple use application/octect-stream instead.

  • You will also need a default (no-arg) constructor for FileContent. Jackson needs this create the POJO.


UPDATE

So I guess the @Produces is not a problem. I was able to get it to work, just fixing the main problem, the first bullet, and last bullet. And also I changed

String startDateStr = new Date().toString();
// TO
long startDateStr = new Date().getTime();

I guess (for me with Windows at least) it was a bad file name format.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720