1

I have two services returning two different ResponseEntity.

public ResponseEntity<InputStreamResource> getA(...) {
return ResponseEntity
            .ok()
            .headers(headers)
            .contentType(MediaType.APPLICATION_PDF)
            .contentLength(out.size())
            .body(new InputStreamResource(bis)); }
public ResponseEntity<InputStreamResource> getB(...) {
return ResponseEntity
            .ok()
            .headers(headers)
            .contentType(MediaType.APPLICATION_PDF)
            .contentLength(out.size())
            .body(new InputStreamResource(bis)); }

Each service has a controller that calls and returns.

public ResponseEntity<InputStreamResource> getA(...) {
return aService.getA(...) }
public ResponseEntity<InputStreamResource> getB(...) {
return bService.getB(...) }

I'm trying to create another controller which does and return both services at once.

public ResponseEntity<InputStreamResource> getAB(...) {
return aService.getA(...) *and* bService.getB(...) ?????? }

not sure how to combine two ResponseEntities returns into one.

user12527433
  • 23
  • 1
  • 5

3 Answers3

2

Returning ResponseEntity from a Service method is not a good idea.

It's the Controller layer which should be responsible for generating ResponseEntity Object. That is his business not of the Service Layer. Similarly, the responsibility of the Service Layer is to prepare some kind of a DTO object based on the given input and then the Controller will wrap around that DTO and send it as a response.

So, I suggest to do some structural change here.

Service Layer

public InputStreamResource getA(...) {
  return A 
}
public InputStreamResource getB(...) {
  return B 
}

Controller Layer

public ResponseEntity<InputStreamResource> getA(...) {
  return new ResponseEntity<>(aService.getA(...) (
}
public ResponseEntity<InputStreamResource> getB(...) {
  return new ResponseEntity<>(bService.getB(...) )
}

To merge 2 streams


If you are targeting to stream 2 different pdf documents one by one, then I think the option will be to merge pdf documents in memory first with whatever pdf library you might be using. Then create a single InputStreamResource as Response.

But, If the streams can be run in sequence, then below is a working example to merge 2 streams using SequenceInputStream -

@RequestMapping(
        path = "/sayHello",
        method = RequestMethod.GET,
        produces = MediaType.TEXT_PLAIN_VALUE
)
public ResponseEntity<InputStreamResource> get() {
        byte[] inputBytes1 = "Hello".getBytes();
        // 1st stream has "Hello" text
        ByteArrayInputStream baos1 = new ByteArrayInputStream(inputBytes1);

        byte[] inputBytes2 = "World".getBytes();
        // 2nd stream has "World" text
        ByteArrayInputStream baos2 = new ByteArrayInputStream(inputBytes2);

        // combined stream will have "HelloWorld" text
        SequenceInputStream combinedStream = new SequenceInputStream(baos1, baos2);
        InputStreamResource inputStreamResource = new InputStreamResource(combinedStream);
        return ResponseEntity.ok().body(inputStreamResource);
    }

Output ->

 curl -X GET  http://localhost:8083/sayHello
 HelloWorld
SKumar
  • 1,940
  • 1
  • 7
  • 12
  • ResponseEntity works. ResponseEntity doesn't work. – user12527433 Oct 14 '20 at 22:18
  • @user12527433 what is not working ? Is there some exception ? Can you post that exception or the issue. – SKumar Oct 14 '20 at 22:40
  • HttpMessageNotReadableException getA and getB is returning InputStreamResource type but getAB is returning object CombinedInputStreamDTO type – user12527433 Oct 14 '20 at 22:44
  • Thank you for your help. Now.... using sequenceinputstream, i'm able only generate baos2. It seems like the baos1 is being overwritten or something. – user12527433 Oct 15 '20 at 21:15
  • @user12527433 Had it been a String stream merging (like in the example), it would be possible using SequenceInputStream. That's working as expected. But, since you want to merge pdf stream, see if this is useful for you - https://stackoverflow.com/questions/3585329/how-to-merge-two-pdf-files-into-one-in-java – SKumar Oct 15 '20 at 21:34
  • Is there a way to combine two different responseEntity controller calls from front-end? – user12527433 Oct 16 '20 at 20:23
  • @user12527433 I don't think there will be any easy way. If the front end can invoke request 2 times, then you can have a request parameter (e.g streamId) to control which pdf document to send as a response. – SKumar Oct 17 '20 at 14:19
0

You can try via this way:

public ResponseEntity<List<InputStreamResource> getAAndB(...) {
    
                  private List<InputStreamResource> result = new ArrayList<>();
                  result.add(aService.getA(...));
                  result.add(bService.getB(...));
           return ResponseEntity.ok().body(result)); }  
    }
0
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfReader;

@RestController
public class ReportController {
private static final Logger LOGGER = ...

@Autowired
private ReportManager manager;

@GetMapping("/reportcard/students")
public ResponseEntity<InputStreamResource> getStudentReportCard(parameters...) throws ServiceException {
    List<InputStream> studentReportList = manager.getStudentReports(parameters.... );
            
    HttpHeaders headers = getHeaders(Constants.STUDENTS_REPORT_CARD);

    Document document = new Document(PageSize.LETTER);
    ByteArrayOutputStream outputStream = null;

    try {
        outputStream = new ByteArrayOutputStream();
        PdfCopy copy = new PdfCopy(document, outputStream);

        document.open();

        for (InputStream file : studentReportList) {
            copy.addDocument(new PdfReader(file)); // writes directly to the output stream
        }
        outputStream.flush();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (document.isOpen()) {
            document.close();
        }
        try {
            if (outputStream != null) {
                outputStream.close();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(outputStream.toByteArray()));
    return ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_PDF).body(inputStreamResource);
}

private HttpHeaders getHeaders(String fileName) {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
    headers.add("Pragma", "no-cache");
    headers.add("Expires", "0");
    headers.set("Content-disposition", "attachment; filename=" + fileName);
    return headers;
}


public class ReportManagerImpl implements ReportManager {
private static final Logger LOGGER = ...

@Autowired
private HttpClient httpClient;

@Override
public List<InputStream> getStudentReports(parameters...)
        throws ServiceException {
    
    List<InputStream> studentReportList = new Vector<InputStream>();
    
    List<String> learnersList = //list of students
    
    String reportUrl = //url
        
    for (String learner : learnersList) {
        studentReportList.add(getHttpResponse(reportUrl + learner));
    }
    
    return studentReportList;
}

private InputStream getHttpResponse(String url) throws ServiceException {
    try {
        HttpResponse response = httpClient.execute(new HttpGet(url));
        HttpEntity entity = response.getEntity();

        return entity.getContent();
    } catch (IOException e) {
        throw new ServiceException(e.getMessage());
    }

}