I am trying to get the whole body from the HttpServletRequest object.
The code I am following looks like this:
if ( request.getMethod().equals("POST") )
{
StringBuffer sb = new StringBuffer();
BufferedReader bufferedReader = null;
String content = "";
try {
//InputStream inputStream = request.getInputStream();
//inputStream.available();
//if (inputStream != null) {
bufferedReader = request.getReader() ; //new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead;
while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
sb.append(charBuffer, 0, bytesRead);
}
//} else {
// sb.append("");
//}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
test = sb.toString();
}
and I am testing the functionality with curl and wget as follows:
curl --header "MD5: abcd" -F "fileupload=#filename.txt http://localhost:8080/abcd.html"
wget --header="MD5: abcd" --post-data='{"imei":"351553012623446","hni":"310150","wdp":false}' http://localhost:8080/abcd.html"
But the while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) does not return anything, and so I get nothing appended on StringBuffer.
In Java 8, you can do it in a simpler and clean way :
if ("POST".equalsIgnoreCase(request.getMethod()))
{
test = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
Easy way with commons-io.
IOUtils.toString(request.getReader());
https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html
Be aware, that your code is quite noisy.
I know the thread is old, but a lot of people will read it anyway.
You could do the same thing using the guava library with:
if ("POST".equalsIgnoreCase(request.getMethod())) {
test = CharStreams.toString(request.getReader());
}
If all you want is the POST request body, you could use a method like this:
static String extractPostRequestBody(HttpServletRequest request) throws IOException {
if ("POST".equalsIgnoreCase(request.getMethod())) {
Scanner s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
return "";
}
Credit to: https://stackoverflow.com/a/5445161/1389219
This works for both GET and POST:
#Context
private HttpServletRequest httpRequest;
private void printRequest(HttpServletRequest httpRequest) {
System.out.println(" \n\n Headers");
Enumeration headerNames = httpRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = (String)headerNames.nextElement();
System.out.println(headerName + " = " + httpRequest.getHeader(headerName));
}
System.out.println("\n\nParameters");
Enumeration params = httpRequest.getParameterNames();
while(params.hasMoreElements()){
String paramName = (String)params.nextElement();
System.out.println(paramName + " = " + httpRequest.getParameter(paramName));
}
System.out.println("\n\n Row data");
System.out.println(extractPostRequestBody(httpRequest));
}
static String extractPostRequestBody(HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
Scanner s = null;
try {
s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
} catch (IOException e) {
e.printStackTrace();
}
return s.hasNext() ? s.next() : "";
}
return "";
}
If the request body is empty, then it simply means that it's already been consumed beforehand. For example, by a request.getParameter(), getParameterValues() or getParameterMap() call. Just remove the lines doing those calls from your code.
This will work for all HTTP method.
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public HttpRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toString(request.getReader());
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getBody().getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
public String getBody() {
return this.body;
}
}
Easiest way I could think of:
request.getReader().lines().reduce("",String::concat)
However, this will be one long string which you will have to parse. IF you send a username of tim and a password of 12345. The output of the code above would look like this:
{ "username":"tim", "password": "12345"}
Please be aware
Please be aware that with the reduce() method we are performing a Mutable Reduction which does a great deal of string copying and has a runtime of O(N^2) with N being the number of characters. Please check the Mutable Reduction documentation if you need a more performant result.
I resolved that situation in this way. I created a util method that return a object extracted from request body, using the readValue method of ObjectMapper that is capable of receiving a Reader.
public static <T> T getBody(ResourceRequest request, Class<T> class) {
T objectFromBody = null;
try {
ObjectMapper objectMapper = new ObjectMapper();
HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
objectFromBody = objectMapper.readValue(httpServletRequest.getReader(), class);
} catch (IOException ex) {
log.error("Error message", ex);
}
return objectFromBody;
}
I personnally use this code (on a dev server, not in production). Seems to work. The main difficulty is that once you read the request body, it will be lost and not transferred to the app. So you have to "cache" it first.
/* Export this filter as a jar and place it under directory ".../tomcat/lib" on your Tomcat server/
In the lib directory, also place the dependencies you need
(ex. org.apache.commons.io => commons-io-2.8.0.jar)
Once this is done, in order to activate the filter, on the Tomcat server:
o in .../tomcat/conf/server.xml, add:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" [%{postdata}r] %s %b"/>
=> the server will log the "postdata" attribute we generate in the Java code.
o in .../tomcat/conf/web.xml, add:
<filter>
<filter-name>post-data-dumper-filter</filter-name>
<filter-class>filters.PostDataDumperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>post-data-dumper-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Once you've done this, restart your tomcat server. You will get extra infos in file "localhost_access_log.<date>.txt"
*/
package filters;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.stream.Collectors;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times.
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An input stream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
//---------------------
#Override
public int read() throws IOException {
return input.read();
}
#Override
public boolean isFinished() {
return input.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
//---------------------
#Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
// Ex. : throw new RuntimeException("Not implemented");
}
}
}
public final class PostDataDumperFilter implements Filter {
private FilterConfig filterConfig = null;
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (filterConfig == null)
return;
StringBuffer output = new StringBuffer();
output.append("PostDataDumperFilter-");
/* Wrap the request in order to be able to read its body multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
// TODO : test the method in order not to log the body when receiving GET/DELETE requests ?
// I finally leave it "as it", since I've seen GET requests containing bodies (hell...).
output.append("Content-type=" + multiReadRequest.getContentType());
output.append(" - HTTP Method=" + multiReadRequest.getMethod());
output.append(" - REQUEST BODY = " + multiReadRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator())));
// Log the request parameters:
Enumeration names = multiReadRequest.getParameterNames();
if (names.hasMoreElements()) {
output.append("- REQUEST PARAMS = ");
}
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
output.append(name + "=");
String values[] = multiReadRequest.getParameterValues(name);
for (int i = 0; i < values.length; i++) {
if (i > 0) output.append("' ");
output.append(values[i]);
}
if (names.hasMoreElements()) output.append("&");
}
multiReadRequest.setAttribute("postdata", output);
chain.doFilter(multiReadRequest, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
}
Related
I have written a controller which is a default for MototuploadService(for Motor Upload), but I need to make one Factory Design so that
based on parentPkId, need to call HealUploadService, TempUploadService, PersonalUploadService etc which will have separate file processing stages.
controller is below.
#RequestMapping(value = "/csvUpload", method = RequestMethod.POST)
public List<String> csvUpload(#RequestParam String parentPkId, #RequestParam List<MultipartFile> files)
throws IOException, InterruptedException, ExecutionException, TimeoutException {
log.info("Entered method csvUpload() of DaoController.class");
List<String> response = new ArrayList<String>();
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletionService<String> compService = new ExecutorCompletionService<String>(executor);
List< Future<String> > futureList = new ArrayList<Future<String>>();
for (MultipartFile f : files) {
compService.submit(new ProcessMutlipartFile(f ,parentPkId,uploadService));
futureList.add(compService.take());
}
for (Future<String> f : futureList) {
long timeout = 0;
System.out.println(f.get(timeout, TimeUnit.SECONDS));
response.add(f.get());
}
executor.shutdown();
return response;
}
Here is ProcessMutlipartFile class which extends the callable interface, with CompletionService's compService.submit() invoke this class, which in turn executes call() method, which will process a file.
public class ProcessMutlipartFile implements Callable<String>
{
private MultipartFile file;
private String temp;
private MotorUploadService motUploadService;
public ProcessMutlipartFile(MultipartFile file,String temp, MotorUploadService motUploadService )
{
this.file=file;
this.temp=temp;
this.motUploadService=motUploadService;
}
public String call() throws Exception
{
return motUploadService.csvUpload(temp, file);
}
}
Below is MotorUploadService class, where I'm processing uploaded CSV file, line by line and then calling validateCsvData() method to validate Data,
which returns ErrorObject having line number and Errors associated with it.
if csvErrorRecords is null, then error-free and proceed with saving to Db.
else save errorList to Db and return Upload Failure.
#Component
public class MotorUploadService {
#Value("${external.resource.folder}")
String resourceFolder;
public String csvUpload(String parentPkId, MultipartFile file) {
String OUT_PATH = resourceFolder;
try {
DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
String filename = file.getOriginalFilename().split(".")[0] + df.format(new Date()) + file.getOriginalFilename().split(".")[1];
Path path = Paths.get(OUT_PATH,fileName)
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
}
catch(IOException e){
e.printStackTrace();
return "Failed to Upload File...try Again";
}
List<TxnMpMotSlaveRaw> txnMpMotSlvRawlist = new ArrayList<TxnMpMotSlaveRaw>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream()));
String line = "";
int header = 0;
int lineNum = 1;
TxnMpSlaveErrorNew txnMpSlaveErrorNew = new TxnMpSlaveErrorNew();
List<CSVErrorRecords> errList = new ArrayList<CSVErrorRecords>();
while ((line = br.readLine()) != null) {
// TO SKIP HEADER
if (header == 0) {
header++;
continue;
}
lineNum++;
header++;
// Use Comma As Separator
String[] csvDataSet = line.split(",");
CSVErrorRecords csvErrorRecords = validateCsvData(lineNum, csvDataSet);
System.out.println("Errors from csvErrorRecords is " + csvErrorRecords);
if (csvErrorRecords.equals(null) || csvErrorRecords.getRecordNo() == 0) {
//Function to Save to Db
} else {
// add to errList
continue;
}
}
if (txnMpSlaveErrorNew.getErrRecord().size() == 0) {
//save all
return "Successfully Uploaded " + file.getOriginalFilename();
}
else {
// save the error in db;
return "Failure as it contains Faulty Information" + file.getOriginalFilename();
}
} catch (IOException ex) {
ex.printStackTrace();
return "Failure Uploaded " + file.getOriginalFilename();
}
}
private TxnMpMotSlaveRaw saveCsvData(String[] csvDataSet, String parentPkId) {
/*
Mapping csvDataSet to PoJo
returning Mapped Pojo;
*/
}
private CSVErrorRecords validateCsvData(int lineNum, String[] csvDataSet) {
/*
Logic for Validation goes here
*/
}
}
How to make it as a factory design pattern from controller,
so that if
parentPkId='Motor' call MotorUploadService,
parentPkId='Heal' call HealUploadService
I'm not so aware of the Factory Design pattern, please help me out.
Thanks in advance.
If I understood the question, in essence you would create an interface, and then return a specific implementation based upon the desired type.
So
public interface UploadService {
void csvUpload(String temp, MultipartFile file) throws IOException;
}
The particular implementations
public class MotorUploadService implements UploadService
{
public void csvUpload(String temp, MultipartFile file) {
...
}
}
public class HealUploadService implements UploadService
{
public void csvUpload(String temp, MultipartFile file) {
...
}
}
Then a factory
public class UploadServiceFactory {
public UploadService getService(String type) {
if ("Motor".equals(type)) {
return new MotorUploadService();
}
else if ("Heal".equals(type)) {
return new HealUploadService();
}
}
}
The factory might cache the particular implementations. One can also use an abstract class rather than an interface if appropriate.
I think you currently have a class UploadService but that is really the MotorUploadService if I followed your code, so I would rename it to be specific.
Then in the controller, presumably having used injection for the UploadServiceFactory
...
for (MultipartFile f : files) {
UploadService uploadSrvc = uploadServiceFactory.getService(parentPkId);
compService.submit(new ProcessMutlipartFile(f ,parentPkId,uploadService));
futureList.add(compService.take());
}
So with some additional reading in your classes:
public class ProcessMutlipartFile implements Callable<String>
{
private MultipartFile file;
private String temp;
private UploadService uploadService;
// change to take the interface UploadService
public ProcessMutlipartFile(MultipartFile file,String temp, UploadService uploadService )
{
this.file=file;
this.temp=temp;
this.uploadService=uploadService;
}
public String call() throws Exception
{
return uploadService.csvUpload(temp, file);
}
}
Spring portlet JSP, Making ajax request and in controller trying to get jsp page so that i can pass and generate pdf output.
But problem is didn't get any string data but html contents are returned on jsp page please check code as follwoing
#Controller("exportSummaryController")
#RequestMapping(value = "VIEW")
public class ExportSummaryController implements PortletConfigAware {
#ResourceMapping("exportAccRequest")
public void accountRollupAction(#RequestParam("accNum") String accNum,
#RequestParam("sourceId") String sourceId, #RequestParam("serviceId") String serviceId,
#RequestParam("summaryExport") String strExport, ResourceRequest request, ResourceResponse response) throws Exception {
//processing data
ResourceResponseWrapper responseWrapper = new ResourceResponseWrapper(response) {
private final StringWriter sw = new StringWriter();
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(sw);
}
#Override
public OutputStream getPortletOutputStream() throws IOException {
return(new StringOutputStream(sw));
}
#Override
public String toString() {
return sw.toString();
}
};
portletConfig.getPortletContext().getRequestDispatcher("/WEB-INF/jsp/account_summary.jsp").include(request, responseWrapper);
String content = responseWrapper.toString();
System.out.println("Output : " + content); // here i found empty output on command line but output is returned to jsp page.
}
}
public class StringOutputStream extends OutputStream {
private StringWriter stringWriter;
public StringOutputStream(StringWriter stringWriter) {
this.stringWriter = stringWriter;
}
public void write(int c) {
stringWriter.write(c);
}
}
In your code the output is cosumed by only one OutputStream.
Try this,
ResourceResponseWrapper responseWrapper = new ResourceResponseWrapper(response) {
private final StringWriter sw = new StringWriter();
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(sw){
#Override
public void write(String s, int off, int len)
{
try
{ sw.write(s, off, len);
response.getWriter().write(s, off, len);
}
catch (IOException e)
{
e.printStackTrace();
}
}
};
}
#Override
public String toString() {
return sw.toString();
}
};
I'm trying to filter out a query parameter named 'reason' using a Filter in java/jsp.
Basically, the filter is in place to ensure that a user has entered a 'reason' for viewing a page. If they have not, it needs to redirect them to the 'enter reason' page. Once they have entered a valid reason, they can continue on to the page they requested.
So the basics of it work. However, the 'reason' is sent via a query paremter (i.e. GET parameter). Once the user selects a reason, the reason parameter is being forwarded on to the page they wanted to see. This is a problem, since checking if the reason paremeter exists is one of the main ways the filter determines if the user can move on.
I've tried extending HttpServletRequestWrapper, and overrode a bunch of methods (i.e. getPameter, etc) in an effort to remove the 'reason' parameter. However, I haven't been able to see the parameter get removed. Once the Filter forwards on to the requested page, the 'reason' parameter is always in the query string (i.e. the url in the browser url bar) as a GET parameter.
My filter class looks like:
public final class AccessRequestFilter implements Filter {
public class FilteredRequest extends HttpServletRequestWrapper {
public FilteredRequest(ServletRequest request) {
super((HttpServletRequest)request);
}
#Override
public String getParameter(String paramName) {
String value = super.getParameter(paramName);
if ("reason".equals(paramName)) {
value = null;
}
return value;
}
#Override
public String[] getParameterValues(String paramName) {
String[] values = super.getParameterValues(paramName);
if ("reason".equals(paramName)) {
values = null;
}
return values;
}
#Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
#Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> params = new HashMap<String, String[]>();
Map<String, String[]> originalParams = super.getParameterMap();
for(Object o : originalParams.entrySet()) {
Map.Entry<String, String[]> pairs = (Map.Entry<String, String[]>) o;
params.put(pairs.getKey(), pairs.getValue());
}
params.remove("reason");
return params;
}
#Override
public String getQueryString() {
String qs = super.getQueryString();
return qs.replaceAll("reason=", "old_reason=");
}
#Override
public StringBuffer getRequestURL() {
String qs = super.getRequestURL().toString();
return new StringBuffer( qs.replaceAll("reason=", "old_reason=") );
}
}
private FilterConfig filterConfig = null;
private static final Logger logger = MiscUtils.getLogger();
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.debug("Entering AccessRequestFilter.doFilter()");
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession();
boolean canView = false;
long echartAccessTime = 0L;
String demographicNo = "";
String reason = "";
Date current = new Date();
String user_no = (String) session.getAttribute("user");
ProgramProviderDAO programProviderDAO = (ProgramProviderDAO)SpringUtils.getBean("programProviderDAO");
ProgramQueueDao programQueueDao = (ProgramQueueDao)SpringUtils.getBean("programQueueDao");
// Check to see if user has submitted a reason
reason = request.getParameter("reason");
demographicNo = request.getParameter("demographicNo");
Long demographicNoAsLong = 0L;
try {
demographicNoAsLong = Long.parseLong( demographicNo );
} catch (Exception e) {
logger.error("Unable to parse demographic number.", e);
}
if (reason == null) {
// If no reason was submitted, see if user still has time remaining on previous submission (if there was one)
try {
echartAccessTime = (Long)session.getServletContext().getAttribute("echartAccessTime_" + demographicNo);
} catch (Exception e) {
logger.warn("No access time found");
}
if (current.getTime() - echartAccessTime < 30000) {
canView = true;
}
} else if (!reason.equals("")) {
// TODO: validate reason
canView = true;
session.getServletContext().setAttribute("echartAccessTime_" + demographicNo, current.getTime());
String ip = request.getRemoteAddr();
// Log the access request and the reason given for access
LogAction.addLog(user_no, "access", "eChart", demographicNo, ip, demographicNo, reason);
}
if (!canView) {
// Check if provider is part of circle of care
List<Long> programIds = new ArrayList<Long>();
List<ProgramQueue> programQueues = programQueueDao.getAdmittedProgramQueuesByDemographicId( demographicNoAsLong );
if (programQueues != null && programQueues.size() > 0) {
for (ProgramQueue pq : programQueues) {
programIds.add( pq.getProgramId() );
}
List<ProgramProvider> programProviders = programProviderDAO.getProgramProviderByProviderProgramId(user_no, programIds);
if (programProviders != null && programProviders.size() > 0) {
canView = true;
}
}
}
String useNewCaseMgmt;
if((useNewCaseMgmt = request.getParameter("newCaseManagement")) != null ) {
session.setAttribute("newCaseManagement", useNewCaseMgmt);
ArrayList<String> users = (ArrayList<String>)session.getServletContext().getAttribute("CaseMgmtUsers");
if( users != null ) {
users.add(request.getParameter("providerNo"));
session.getServletContext().setAttribute("CaseMgmtUsers", users);
}
}
else {
useNewCaseMgmt = (String)session.getAttribute("newCaseManagement");
}
String requestURI = httpRequest.getRequestURI();
String contextPath = httpRequest.getContextPath();
if (!canView && !requestURI.startsWith(contextPath + "/casemgmt/accessRequest.jsp")) {
httpResponse.sendRedirect(contextPath + "/casemgmt/accessRequest.jsp?" + httpRequest.getQueryString());
return;
}
logger.debug("AccessRequestFilter chainning");
chain.doFilter( new FilteredRequest(request), response);
}
}
The filter is setup to intercept all request and forwards coming into a subdirectory called casemgmt. The filter in web.xml is like:
<filter>
<filter-name>AccessRequestFilter</filter-name>
<filter-class>org.oscarehr.casemgmt.filter.AccessRequestFilter</filter-class>
</filter>
...
<filter-mapping>
<filter-name>AccessRequestFilter</filter-name>
<url-pattern>/casemgmt/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Anyone have any ideas how I can actually remove the 'reason' parameter?
Wrapping and manipulating the HttpServletRequest in the server side absolutely doesn't magically affect the URL as you see in browser's address bar. That URL stands as-is, as it's the one which the browser used to request the desired resource. The wrapped request would only affect the server side code which is running after the filter on the same request.
If you want to change the URL in browser's address bar, then you should be sending a redirect to exactly the desired URL.
Basically,
if (reasonParameterIsIn(queryString)) {
response.sendRedirect(requestURL + "?" + removeReasonParameterFrom(queryString));
return;
}
I think my scenario is pretty common. I have a database and I want my Spring MVC app to accept a request in the controller, invoke the DB service to get data and send that data to the client as a CSV file. I'm using the JavaCSV library found here to assist in the process: http://sourceforge.net/projects/javacsv/
I've found several examples of people doing similar things and cobbled together something that looks correct-ish. When I hit the method, though, nothing is really happening.
I thought writing the data to the HttpServletResponse's outputStream would be sufficient, but apparently, I'm missing something.
Here's my controller code:
#RequestMapping(value="/getFullData.html", method = RequestMethod.GET)
public void getFullData(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException{
List<CompositeRequirement> allRecords = compReqServ.getFullDataSet((String)session.getAttribute("currentProject"));
response.setContentType("data:text/csv;charset=utf-8");
response.setHeader("Content-Disposition","attachment; filename=\yourData.csv\"");
OutputStream resOs= response.getOutputStream();
OutputStream buffOs= new BufferedOutputStream(resOs);
OutputStreamWriter outputwriter = new OutputStreamWriter(buffOs);
CsvWriter writer = new CsvWriter(outputwriter, '\u0009');
for(int i=1;i <allRecords.size();i++){
CompositeRequirement aReq=allRecords.get(i);
writer.write(aReq.toString());
}
outputwriter.flush();
outputwriter.close();
};
What step am I missing here? Basically, the net effect is... nothing. I would have thought setting the header and content type would cause my browser to pick up on the response and trigger a file download action.
It seems to be because your Content-type is set incorrectly, it should be response.setContentType("text/csv;charset=utf-8") instead of response.setContentType("data:text/csv;charset=utf-8").
Additionally, if you are using Spring 3, you should probably use a #ResponseBody HttpMessageConverter for code reuse. For example:
In the controller:
#RequestMapping(value = "/getFullData2.html", method = RequestMethod.GET, consumes = "text/csv")
#ResponseBody // indicate to use a compatible HttpMessageConverter
public CsvResponse getFullData(HttpSession session) throws IOException {
List<CompositeRequirement> allRecords = compReqServ.getFullDataSet((String) session.getAttribute("currentProject"));
return new CsvResponse(allRecords, "yourData.csv");
}
plus a simple HttpMessageConverter:
public class CsvMessageConverter extends AbstractHttpMessageConverter<CsvResponse> {
public static final MediaType MEDIA_TYPE = new MediaType("text", "csv", Charset.forName("utf-8"));
public CsvMessageConverter() {
super(MEDIA_TYPE);
}
protected boolean supports(Class<?> clazz) {
return CsvResponse.class.equals(clazz);
}
protected void writeInternal(CsvResponse response, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
output.getHeaders().setContentType(MEDIA_TYPE);
output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + response.getFilename() + "\"");
OutputStream out = output.getBody();
CsvWriter writer = new CsvWriter(new OutputStreamWriter(out), '\u0009');
List<CompositeRequirement> allRecords = response.getRecords();
for (int i = 1; i < allRecords.size(); i++) {
CompositeRequirement aReq = allRecords.get(i);
writer.write(aReq.toString());
}
writer.close();
}
}
and a simple object to bind everything together:
public class CsvResponse {
private final String filename;
private final List<CompositeRequirement> records;
public CsvResponse(List<CompositeRequirement> records, String filename) {
this.records = records;
this.filename = filename;
}
public String getFilename() {
return filename;
}
public List<CompositeRequirement> getRecords() {
return records;
}
}
Based on Pierre answer, i did a converter. Here is the full code, that works with any Object passed:
TsvMessageConverter.java
public class TsvMessageConverter extends AbstractHttpMessageConverter<TsvResponse> {
public static final MediaType MEDIA_TYPE = new MediaType("text", "tsv", Charset.forName("utf-8"));
private static final Logger logger = LoggerFactory.getLogger(TsvMessageConverter.class);
public TsvMessageConverter() {
super(MEDIA_TYPE);
}
protected boolean supports(Class<?> clazz) {
return TsvResponse.class.equals(clazz);
}
#Override
protected TsvResponse readInternal(Class<? extends TsvResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
protected void writeInternal(TsvResponse tsvResponse, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
output.getHeaders().setContentType(MEDIA_TYPE);
output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + tsvResponse.getFilename() + "\"");
final OutputStream out = output.getBody();
writeColumnTitles(tsvResponse, out);
if (tsvResponse.getRecords() != null && tsvResponse.getRecords().size() != 0) {
writeRecords(tsvResponse, out);
}
out.close();
}
private void writeRecords(TsvResponse response, OutputStream out) throws IOException {
List<String> getters = getObjectGetters(response);
for (final Object record : response.getRecords()) {
for (String getter : getters) {
try {
Method method = ReflectionUtils.findMethod(record.getClass(), getter);
out.write(method.invoke(record).toString().getBytes(Charset.forName("utf-8")));
out.write('\t');
} catch (IllegalAccessException | InvocationTargetException e) {
logger.error("Erro ao transformar em CSV", e);
}
}
out.write('\n');
}
}
private List<String> getObjectGetters(TsvResponse response) {
List<String> getters = new ArrayList<>();
for (Method method : ReflectionUtils.getAllDeclaredMethods(response.getRecords().get(0).getClass())) {
String methodName = method.getName();
if (methodName.startsWith("get") && !methodName.equals("getClass")) {
getters.add(methodName);
}
}
sort(getters);
return getters;
}
private void writeColumnTitles(TsvResponse response, OutputStream out) throws IOException {
for (String columnTitle : response.getColumnTitles()) {
out.write(columnTitle.getBytes());
out.write('\t');
}
out.write('\n');
}
}
TsvResponse.java
public class TsvResponse {
private final String filename;
private final List records;
private final String[] columnTitles;
public TsvResponse(List records, String filename, String ... columnTitles) {
this.records = records;
this.filename = filename;
this.columnTitles = columnTitles;
}
public String getFilename() {
return filename;
}
public List getRecords() {
return records;
}
public String[] getColumnTitles() {
return columnTitles;
}
}
And on SpringContext.xml add the following:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.mypackage.TsvMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
So, you can use on your controller like this:
#RequestMapping(value="/tsv", method= RequestMethod.GET, produces = "text/tsv")
#ResponseBody
public TsvResponse tsv() {
return new TsvResponse(myListOfPojos, "fileName.tsv",
"Name", "Email", "Phone", "Mobile");
}
I have this action class, this class takes care of my response
Update now passing response from DownloadStatus class, but it looks like it is null
public final class DownloadStatus extends ActionSupport implements ServletRequestAware,ServletResponseAware
{
static Logger logger = Logger.getLogger(DownloadStatus.class);
private HttpServletRequest request;
private HttpServletResponse response;
private File cfile;
private String cfileFileName;
#Override
public String execute()
{
logger.debug("Inside DownloadStatus.execute method")
try {
ChainsInvoker invoker = new ChainsInvoker()
def executionResponse = invoker.invoke(request, MYChains.download, cfile, cfileFileName)
if(executionResponse == null || ErrorHandler.checkIfError(executionResponse))
{
return ERROR
}
response.setContentType("APPLICATION/xml")
logger.debug("filename: $cfileFileName")
response.addHeader("Content-Disposition", "attachment; filename=\""+cfileFileName+"\"")
response.getWriter().print(executionResponse)
logger.debug("executionResponse :" + executionResponse)
invoker.invoke(MYChains.clean)
}catch (Exception exp) {
logger.error("Exception while Creating Status ")
logger.error(exp.printStackTrace())
}
return NONE
}
#Override
public void setServletRequest(HttpServletRequest request) { this.request = request; }
#Override
public void setServletResponse(HttpServletResponse response) { this.response = response; }
public File getcfile() { cfile }
public void setcfile(File cfile) { this.cfile = cfile }
public String getcfileFileName() { cfileFileName }
public void setcfileFileName(String cfileFileName){ this.cfileFileName = cfileFileName }
}
and below class to write stream into response
class DownloadStatusResponse implements Command {
static Logger logger = Logger.getLogger(DownloadStatusResponse.class);
#Override
public boolean execute(Context ctx) throws Exception
{
logger.debug("Inside DownloadStatusResponse.execute() method")
OutputStream response = null;
if(ctx.get(ContextParams.absFileName) != null && ctx.get(ContextParams.absFileName).toString().trim().length() != 0 )
{
HttpServletResponse resp = ctx.get(ContextParams.response)
/*I am trying to get Response here*/
response=downloadStatusFile(ctx.get(ContextParams.absFileName).toString(),resp)
}
logger.debug("Response: " + response)
ctx.put(ContextParams.response,response); /*ContextParams is a enum of keywords, having response*/
return false;
}
private OutputStream downloadStatusFile(String filename,HttpServletResponse resp)
{
logger.info("Inside downloadStatusFile() method")
File fname = new File(filename)
if(!fname.exists())
{
logger.info("$filename does not exists")
return null
}
else
{
resp.setContentType("APPLICATION/xml")
/*Exception: cannot setContentType on null object*/
resp.addHeader("Content-Disposition", "attachment; filename=\""+fname.getName()+"\"")
FileInputStream istr = new FileInputStream(fname)
OutputStream ostr = resp.getOutputStream()
/*I need to use resp.getOutputStream() for ostr*/
int curByte=-1;
while( (curByte=istr.read()) !=-1)
ostr.write(curByte)
ostr.flush();
}
return ostr
}
}
My question is how can ostr be returned to the response in DownloadStatus class?
Update (working test servlet)
I have this below servlet which does the job of getting file content into a stream and giving it back to the HttpServletResponse, but i want to use it in above code
public class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String fileName = req.getParameter("zipFile");
if(fileName == null) return;
File fname = new File(fileName);
System.out.println("filename");
if(!fname.exists()) {System.out.println("Does not exists"); return;}
FileInputStream istr = null;
OutputStream ostr = null;
//resp.setContentType("application/x-download");
resp.setContentType("APPLICATION/ZIP");
resp.addHeader("Content-Disposition", "attachment; filename=\""+fname.getName()+"\"");
System.out.println(fname.getName());
try {
istr = new FileInputStream(fname);
ostr = resp.getOutputStream();
int curByte=-1;
while( (curByte=istr.read()) !=-1)
ostr.write(curByte);
ostr.flush();
} catch(Exception ex){
ex.printStackTrace(System.out);
} finally{
try {
if(istr!=null) istr.close();
if(ostr!=null) ostr.close();
} catch(Exception ex){
ex.printStackTrace();
System.out.println(ex.getMessage());
}
}
try {
resp.flushBuffer();
} catch(Exception ex){
ex.printStackTrace();
System.out.println(ex.getMessage());
}
}
}
As far as I understand all you require is how to download a file using Struts2.
You need something like this is your struts.xml file
<action name="downloadfile" class="DownloadAction">
<result name="success" type="stream">
<param name="contentType">application/pdf</param>
<param name="inputName">inputStream</param>
<param name="contentDisposition">attachment;filename="document.pdf"</param>
<param name="bufferSize">1024</param>
</result>
</action>
Code:
public class DownloadAction extends ActionSupport {
private InputStream inputStream;
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String execute() throws FileNotFoundException {
String filePath = ServletActionContext.getServletContext().getRealPath("/uploads");
File f = new File(filePath + "/nn.pdf");
System.out.println(f.exists());
inputStream = new FileInputStream(f);
return SUCCESS;
}
}