Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download or redirect with error message to another controller action in Spring web MVC

Idea: I have a Spring web MVC action that should accomplish one or both of these taks:

  • Download a file from a remote server and write the input stream to the response output stream
  • Or catch the exception of the download, set one of multiple error messages and redirect to the /addresses page. The address page will display the error

Problem: Spring is unable to download a file and redirect in case of a problem - somehow flash attributes don't work because the get lost in the redirect:

@ResponseBody
@RequestMapping(value = "/download/{fileaddress}", method = RequestMethod.GET)
public void download(HttpServletRequest request, HttpServletResponse response, @PathVariable(value = "fileaddress") String fileaddress) throws Exception
{
    if(fileaddress != null && fileaddress.length() > 0)
    {
        try
        {
            // Get the remove file based on the fileaddress
            RemoteFile remotefile = new RemoteFile(fileaddress);

            // Set the input stream
            InputStream inputstream = remotefile.getInputStream();

            // Write the input stream to the output stream or throw an exception
            Utils.writeTo(inputstream, response.getOutputStream());
        }
        catch(MyExceptionA)
        {
            // TODO: Define error message a and pass it to /addresses
            // PROBLEM: Flash attributes that contain all critical error information don't work
            response.sendRedirect(request.getContextPath() + "/addresses");
        }
        catch(MyExceptionB)
        {
            // TODO: Add another error message and redirect
            response.sendRedirect(request.getContextPath() + "/addresses");
        }
        catch(MyExceptionC)
        {
            // TODO: Add another error message and redirect
            response.sendRedirect(request.getContextPath() + "/addresses");
        }
        catch(MyExceptionN)
        {
            // TODO: Add another error message and redirect
            response.sendRedirect(request.getContextPath() + "/addresses");
        }
    }
    else
    {
        // TODO: Add error message
        response.sendRedirect(request.getContextPath() + "/addresses");
    }
}

JSP page of /addresses:

<%@ page pageEncoding="UTF-8" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<%@ taglib prefix="core" uri="http://java.sun.com/jsp/jstl/core" %>

<tags:index>
    <jsp:attribute name="content">
        <core:if test="${not empty error}">
            <div class="alert alert-danger">
                <p>${error}</p>
            </div>
        </core:if>
        <p>Page under construction!</p>
    </jsp:attribute>
</tags:index>

Question: How I am able to display the error message (Simple string for example) in the /addresses site? Working with different URL parameter (error=errora, error=errorb ...) is a huge pain, if there are multiple error types and passing the error message as GET parameter looks unprofessional and is the root of encoding problems.

like image 594
swaechter Avatar asked Nov 09 '15 20:11

swaechter


2 Answers

What you need is the RedirectAttributes a specialization of the Model which controllers can use to select attributes for a redirect scenario. So for a working example see below code:

@ResponseBody
@RequestMapping(value = "/download/{fileaddress}", method = RequestMethod.GET)
public Object download(@PathVariable(value = "fileaddress") String fileaddress, RedirectAttributes redirectAttrs) throws Exception {
    if(StringUtils.hasText(fileaddress)){
        try{
            // Get the remove file based on the fileaddress
            RemoteFile remotefile = new RemoteFile(fileaddress);

            // Set the input stream
            InputStream inputstream = remotefile.getInputStream();
            // asume that it was a PDF file
            HttpHeaders responseHeaders = new HttpHeaders();
            InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
            responseHeaders.setContentLength(contentLengthOfStream);
            responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
            return new ResponseEntity<InputStreamResource> (inputStreamResource,
                                       responseHeaders,
                                       HttpStatus.OK);
         } catch (MyExceptionA | MyExceptionB | MyExceptionC | MyExceptionD ex) {
           redirectAttrs.addFlashAttribute("error", ex.getMessage());
         }        
    } else {
        redirectAttrs.addFlashAttribute("error", "File name is required");
    }
    return "redirect:/addresses";
}
like image 109
Babl Avatar answered Oct 08 '22 21:10

Babl


Update: I've thought the question is about situations where RedirectAttributes are not available because otherwise, the solution is pretty obvious (use RedirectAttributes).

Orignal answer:

I'm using the following code to bind messages to the flash map in situations where Spring doesn't support RedirectAttributes (e.g. in ExceptionHandler methods):

public static Feedback getInstance(HttpServletRequest request, HttpServletResponse response) throws IllegalArgumentException {
    FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
    Object o = flashMap.get(KEY);
    if (o != null) {
        if (o instanceof Feedback) {
            return Feedback.class.cast(o);
        } else {
            throw new IllegalArgumentException(...);
        }
    } else {
        FeedbackContainer feedbackContainer = new FeedbackContainer();
        flashMap.put(KEY, feedbackContainer);
        FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
        flashMapManager.saveOutputFlashMap(flashMap, request, response);
        return feedbackContainer;
    }

where Feedback / FeedbackContainer is a container for messages which is then accessed in JPSs via JSON serialization. In your case you may use a mere String with key "error" and access it directly in the JSP:

void storeErrorMsg(HttpServletRequest request, HttpServletResponse response, String message) {
   FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
   flashMap.put("error", message);
   FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
   flashMapManager.saveOutputFlashMap(flashMap, request, response);
}

The main reason for using my own message container is the possibility to have multiple messages with different levels and additional getInstance methods for RedirectAttributes, Model or ModelMap, so I don't have to care about duplicate feedback bindings and/or the different binding code.

like image 26
Robin Avatar answered Oct 08 '22 21:10

Robin