Issue
I've implemented a Spring controller that generates a PDF file, consuming my service layer. The problem is that when some exception is thrown during the execution of the method, even if I get the exception with a try-catch block, spring tries to resolve the view based on the current URL.
I honestly have no idea on what is causing this behavior.
@RequestMapping("/cliente")
public void relatorioCliente(EdicaoMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
try {
gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);
} catch (Exception e) {
e.printStackTrace();
// TODO alguma coisa
}
}
the relatorioParaCliente
method generates the PDF and exports it to the response's OutputStream.
Expected: the exception gets caught, the stack trace gets printed and nothing happens to the user.
Actual result: Spring redirects the user to views/cliente.jsp
UPDATE
I've tried changing the return type of the method so it looks like this now:
@RequestMapping("/cliente")
public ModelAndView relatorioCliente(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
try {
gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);
} catch (Exception e) {
e.printStackTrace();
return new ModelAndView("/contas/" + wrapper.getContaId());
}
return null;
}
But this has no effect on my code. I suppose that this does not affect the code because the outputStream gets used on the service. take a look:
@Service
public class ExportarReportPdfService {
@Autowired
private HttpServletResponse res;
public void exportar(List<JasperPrint> jasperPrintList) throws IOException {
JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(res.getOutputStream()));
SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();
configuration.setCreatingBatchModeBookmarks(true);
exporter.setConfiguration(configuration);
res.setContentType("application/x-pdf");
res.setHeader("Content-disposition", "inline; filename=relatorio.pdf" );
try {
exporter.exportReport();
} catch (Exception e) {
e.printStackTrace();
throw new CriacaoReportException("Erro ao exportar para PDF");
} finally {
OutputStream out = res.getOutputStream();
out.flush();
out.close();
}
}
Solution
Here is what I did to solve the problem:
instead of Autowiring the response on the service and exporting the pdf from there, I generate a byteArray and return it to the controller:
@Service
public class ExportarReportPdfService {
public byte[] exportar(List<JasperPrint> jasperPrintList) {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));
[OMITED CODE]
try {
exporter.exportReport();
return outputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
throw new CriacaoReportException("Erro ao exportar para PDF");
}
}
}
And the controller sends the response as a ResponseEntity :
@RequestMapping("/geral")
public ResponseEntity<InputStreamResource> relatorioAdministrador(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
byte[] arquivo = gerarReportService.relatorioParaAdmin(wrapper.getContaId(), usuario);
return ResponseEntity
.ok()
.contentLength(arquivo.length)
.contentType(MediaType.parseMediaType("application/pdf"))
.header("Content-Disposition", "attachment; filename=relatorio.pdf")
.body(new InputStreamResource(new ByteArrayInputStream(arquivo)));
}
So if any exception occurs it will be caught in the ExceptionHandler
as @cmoetzing explained in the comments:
@ExceptionHandler(CriacaoReportException.class)
public ModelAndView trataGeracaoRelatorio(CriacaoReportException exception) {
return new ModelAndView("redirect:/");
}
Answered By - Leonardo Meinerz Ramos