2

I would like to upload images onto a folder inside the server.

For some reason i cant. I don't understand why my filter is not being triggered.And why the file does not get uploaded. Could someone have a look at my code and help me find the reason why the files don't get uploaded?

I will paste all i did till now so you can help me find the mistake:

1.Added commons-fileupload-1.2.1.jar and commons-io-1.4.jar to the lib folder(Automatically get added to the classpath)

enter image description here

2.Created an xml that wil make the tag library available(This is placed inside WEB-INF folder)

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib version="2.0"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
   <namespace>http://corejsf.com</namespace>
   <tag>
      <tag-name>upload</tag-name>
      <component>
         <component-type>javax.faces.Input</component-type>
         <renderer-type>com.corejsf.Upload</renderer-type>
      </component>
   </tag>
</facelet-taglib>

3.Create a package for the implementation of the tag and place in a new package called com.corejsf;

enter image description here

Here is the source:

package com.corejsf;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;

@FacesRenderer(componentFamily="javax.faces.Input",
   rendererType="com.corejsf.Upload")
public class UploadRenderer extends Renderer {
   public void encodeBegin(FacesContext context, UIComponent component)  
      throws IOException {
      if (!component.isRendered()) return;
      ResponseWriter writer = context.getResponseWriter();

      String clientId = component.getClientId(context);

      writer.startElement("input", component);
      writer.writeAttribute("type", "file", "type");
      writer.writeAttribute("name", clientId, "clientId");
      writer.endElement("input");
      writer.flush();
   }

   public void decode(FacesContext context, UIComponent component) {
      ExternalContext external = context.getExternalContext(); 
      HttpServletRequest request = (HttpServletRequest) external.getRequest();
      String clientId = component.getClientId(context);
      FileItem item = (FileItem) request.getAttribute(clientId);

      Object newValue;
      ValueExpression valueExpr = component.getValueExpression("value");
      if (valueExpr != null) {
         Class<?> valueType = valueExpr.getType(context.getELContext());
         if (valueType == byte[].class) {
            newValue = item.get();
         }
         else if (valueType == InputStream.class) {
            try {
               newValue = item.getInputStream();
            } catch (IOException ex) {
               throw new FacesException(ex);
            }
         }
         else {
            String encoding = request.getCharacterEncoding();
            if (encoding != null)
               try {
                  newValue = item.getString(encoding);
               } catch (UnsupportedEncodingException ex) {
                  newValue = item.getString(); 
               }
            else 
               newValue = item.getString(); 
         }
         ((EditableValueHolder) component).setSubmittedValue(newValue);  
         ((EditableValueHolder) component).setValid(true);  
      }

      Object target = component.getAttributes().get("target");

      if (target != null) {
         File file;
         if (target instanceof File)
            file = (File) target;
         else {
            ServletContext servletContext 
               = (ServletContext) external.getContext();
            String realPath = servletContext.getRealPath(target.toString());
            file = new File(realPath); 
         }

         try { // ugh--write is declared with "throws Exception"
            item.write(file);
         } catch (Exception ex) { 
            throw new FacesException(ex);
         }
      }
   }   
}

4.Then I added a servlet filter, to distinguish to intercept the requests and placed it in the same package as the custom tag implementation

enter image description here

This is its source:

package com.corejsf;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadFilter implements Filter {
   private int sizeThreshold = -1;
   private String repositoryPath;

   public void init(FilterConfig config) throws ServletException {
      repositoryPath = config.getInitParameter(
         "com.corejsf.UploadFilter.repositoryPath");
      try {
         String paramValue = config.getInitParameter(
            "com.corejsf.UploadFilter.sizeThreshold");
         if (paramValue != null) 
            sizeThreshold = Integer.parseInt(paramValue);
      }
      catch (NumberFormatException ex) {
         ServletException servletEx = new ServletException();
         servletEx.initCause(ex);
         throw servletEx;
      }
   }

   public void destroy() {
   }

   public void doFilter(ServletRequest request, 
      ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {

      if (!(request instanceof HttpServletRequest)) {
         chain.doFilter(request, response);
         return;
      }

      HttpServletRequest httpRequest = (HttpServletRequest) request;

      boolean isMultipartContent 
         = ServletFileUpload.isMultipartContent(httpRequest);
      if (!isMultipartContent) {
         chain.doFilter(request, response);
         return;
      }

      DiskFileItemFactory factory = new DiskFileItemFactory();
      if (sizeThreshold >= 0)
         factory.setSizeThreshold(sizeThreshold);
      if (repositoryPath != null) 
         factory.setRepository(new File(repositoryPath));
      ServletFileUpload upload = new ServletFileUpload(factory);

      try {
         @SuppressWarnings("unchecked") List<FileItem> items 
            = (List<FileItem>) upload.parseRequest(httpRequest);
         final Map<String, String[]> map = new HashMap<String, String[]>();
         for (FileItem item : items) {
            String str = item.getString();
            if (item.isFormField())
               map.put(item.getFieldName(), new String[] { str });
            else
               httpRequest.setAttribute(item.getFieldName(), item);
         }

         chain.doFilter(new 
            HttpServletRequestWrapper(httpRequest) {
               public Map<String, String[]> getParameterMap() {
                  return map;
               }                   
               // busywork follows ... should have been part of the wrapper
               public String[] getParameterValues(String name) {
                  Map<String, String[]> map = getParameterMap();
                  return (String[]) map.get(name);
               }
               public String getParameter(String name) {
                  String[] params = getParameterValues(name);
                  if (params == null) return null;
                  return params[0];
               }
               public Enumeration<String> getParameterNames() {
                  Map<String, String[]> map = getParameterMap();
                  return Collections.enumeration(map.keySet());
               }
            }, response);
      } catch (FileUploadException ex) {
         ServletException servletEx = new ServletException();
         servletEx.initCause(ex);
         throw servletEx;
      }      
   }   
}

5.Then I registered the filter in the web.xml. (I wanted to use an annotation but I didn’t know how, does someon know how can I do that with an annotation?) Also added the corejsf.taglib.xml

<!-- NEEDED FOR FILE UPLOAD -->
<filter>
      <filter-name>Upload Filter</filter-name>
      <filter-class>com.corejsf.UploadFilter</filter-class>
      <init-param>
         <param-name>sizeThreshold</param-name>
         <param-value>1024</param-value>
      </init-param>
</filter>

   <filter-mapping>
      <filter-name>Upload Filter</filter-name>
      <url-pattern>/faces/upload/*</url-pattern>
   </filter-mapping> 

    <context-param>
      <param-name>javax.faces.PROJECT_STAGE</param-name>
      <param-value>Development</param-value>
   </context-param>
   <context-param>
      <param-name>facelets.LIBRARIES</param-name>
      <param-value>/WEB-INF/corejsf.taglib.xml</param-value>
   </context-param>   

6.On my WebContent folder I created a subfolder called upload(Destination of the uploaded files)

enter image description here

7.Inside a jsf page I use the tag for upload and submit and also use a managed bean method to create the file names:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:corejsf="http://corejsf.com">
            ....
    <h:form enctype="multipart/form-data">

     <corejsf:upload target="upload/#{placeAddController.prepareUniqueIdentifier}" />

         ....

        <h:commandButton value="Dalje" style=" font-weight: bold;  font-size:150%; action="/submittedImage" />  

    ...

    </h:form>

And the java managedbean:

@ManagedBean
@RequestScoped
public class PlaceAddControler {
…
public String prepareUniqueIdentifier() {
        return UUID.randomUUID().toString()+"png";
    }   

-All seems ok, but something is missing or wrong. What do you think, why is not uploading?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
javing
  • 12,307
  • 35
  • 138
  • 211

1 Answers1

1

The filter is apparently not been invoked. Put debug breakpoints on the doFilter() method or add Logger statements or poor man's System.out.println() statements to learn what code exactly get executed and what not and what variables exactly are been set.

The filter will only be invoked when the request URL matches the filter's <url-pattern>. It needs to match the URL pattern of the request URL as you see in the browser address bar of the JSF page with the upload form. As you have configured the URL pattern, /faces/upload/*, it will only be invoked when the request URL look like something this

http://localhost:8080/contextname/faces/upload/form.xhtml

As to the question how to annotate the filter, use @WebFilter.

@WebFilter(urlPatterns={"/faces/upload/*"})
public class UploadFilter implements Filter {
    // ...
}

Unrelated to the problem, there are some flaws in the code (yes, I know, the majority is not yours, I just want to warn you):

  1. This filter does not support request parameters with multiple values like foo=val1&foo=val2&foo=val3 as you can get when multi-select or multi-checkbox are been used in the forms. Only the last selected/checked value ends up in the parameter map this way. I'd recommend to fix the filter code accordingly.

  2. Storing uploaded files in webcontent folder is not useful if you want a permanent storage. Whenever you redeploy the webapp WAR/EAR file, then the originally expanded webapp folder will be entirely deleted, including the files which were been added during webapp's runtime. The webserver don't retain the changes in the freshly expanded webapp folder. If you want a more permanent storage, you should be storing the files outside the webapp folder, preferably on an absolute path. E.g. /var/webapp/upload.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I am trying to fix it. I placed println() calls in the filter methods, but i don't see the messages, i think i am not hitting it. How can i check if i am targeting them correctly(I see no changes in the URL). I noticed that since i use multipart form, the validation messages do not appear. Can the validation in some how disturb the file upload? – javing Jun 18 '11 at 22:19
  • That's because the filter is not invoked. The filter is responsible for parsing a `multipart/form-data` request and putting the parameters back in the request parameter map so that JSF can access it the usual way by `request.getParameter(name)`. As to the filter mapping, try replacing `/faces/upload/*` by `Faces Servlet` (which should be exactly the same as the servlet name of the real `FacesServlet` in your `web.xml`). – BalusC Jun 18 '11 at 22:43
  • I changed to but now i get File not found exception: `java.io.FileNotFoundException: C:\jeeAplicationServer\glassfishv3\glassfish\domains\domain1\eclipseApps\scarecrow1\upload (Access is denied) at java.io.FileOutputStream.open(Native Method)...` My upload folder is currently out of the WebContent folder, it is at the projects root – javing Jun 19 '11 at 10:30
  • The exception, i think is good news, i think now if reached the filter. It is the last line of code that makes trouble, it says: `item.write(file);` I think it has to do with the path, to the upload folder(At the project root). How could i know what is the correct path? – javing Jun 19 '11 at 11:18
  • That it reaches the filter now is a sign that the request URL did definitely not match the URL pattern in filter's mapping. As to the exception, you should definitely not use the project root either. It has to be completely out of the project and server. Use `/var/webapp/uploads` or something (which would on Windows end in `C:\var\webapp\uploads`). – BalusC Jun 19 '11 at 12:08
  • I understand. But i have a doubt now. If later i deploy in the real server, then i will store them in a folder outside of the project somewhere in the hosting right?(In the case i don't want to store images in the database) – javing Jun 19 '11 at 14:47
  • Also i had another doubt,almost forgot. Is there any way i could call the filter, from within a method in the managedbean and pass the request an response. Maybe that way is easier for me and i dont have to worry about matching the url? – javing Jun 19 '11 at 14:56
  • You should instruct the serveradmin that the webapp requires r/w access to a folder on path x. You could make path x configureable by a system property so that the serveradmin can set path x externally. E.g. by adding VM argument `-Dupload.location=/var/webapp/upload` so that you can retrieve it by `System.getProperty("upload.location")`. As to accessing the filter in bean, that's not possible and would also not work, it's too late anyway. The filter has to run before JSF runs otherwise JSF won't be able at all to execute the bean action. Do you understand in any way what the filter is doing? – BalusC Jun 19 '11 at 16:35
  • Honestly i partially understand the filter. What it does is take non URL encoded objects and transfform them in HTTP requests. I am following this tutorial: `http://www.d.umn.edu/~tcolburn/cs4531/assignments/team/2011/file_upload/file_upload.xhtml` I really don't understand why i cant do this same thing with a servlet just. I thought about reading a bit more amout servlets and filters and then come back to this task. I need to read more theory before implementing this one myself. – javing Jun 19 '11 at 20:16
  • 1
    The standard form encoding is `application/x-www-form-urlencoded`. Form farameters appears URL encoded in request body like `name1=value1&name2=value2&name3=value3`. However, this format does not support binary data like uploaded files. So you have to use `multipart/form-data` encoding instead. This way the paramters are very differently encoded in the request body. You can find an example here http://en.wikipedia.org/wiki/MIME#Multipart_messages. However, the standard Servlet API does not support this format. `request.getParameter()` calls returns nothing. JSF is relying on parameters being.. – BalusC Jun 19 '11 at 20:20
  • 1
    ..available by `request.getParameter()`. The Filter is thus parsing the `multipart/form-data` request and putting the parameters back in a custom `HttpServletRequest` object and passing it through so that JSF can use `request.getParameter()` the usual way. Truly you can also use a servlet instead, but this is not workable in combination with JSF as the `FacesServlet` is already doing the request/response controlling job. Hence the filter. You can also just forget JSF for that specific form/page, but that makes imo no sense. – BalusC Jun 19 '11 at 20:22
  • Thank you very much for this 2 comments. Now i see things more clear. I will read about filters in the JEE 6 tutorial(chapter 10) and i will create my own one to try to catch that request, i will transform it back to the form of a HttpRequest, then i will extract the images and store them in the file system. – javing Jun 19 '11 at 21:23
  • What's the problem you have with the current filter? (apart from that it doesn't handle multiple parameter values properly) It is working, right? You can find another example of the filter and the component in this article: http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html Note that you can also go ahead with Tomahawk 2.0 so that you end up with less custom code: http://stackoverflow.com/questions/5418292/jsf-2-0-file-upload – BalusC Jun 19 '11 at 23:14
  • Forgot to accept this answer :) I finally made it. I finally understood the reason why i use a filter to implement my own fileupload. Im happy for that, but i noticed, i don't have some advantages i had with my old primefaces fileupload component(Multiple file choice, progressbar). I think i prefer to use primefaces, than this approach, but i am happy i understood how it is done. – javing Jun 29 '11 at 22:17