Asked  7 Months ago    Answers:  5   Viewed   149 times

I am uploading a file in JSF. I am using Tomahawk's <t:inputFileUpload> for this, but the same question is applicable on e.g. PrimeFaces <p:fileUpload> and JSF 2.2 <h:inputFile>.

I have the below backing bean code:

private UploadedFile uploadedFile; // +getter+setter

public String save() throws IOException {
    String name = uploadedFile.getName();
    System.out.println("File name: " + name);

    String type = uploadedFile.getContentType();
    System.out.println("File type: " + type);

    long size = uploadedFile.getSize();
    System.out.println("File size: " + size);  

    InputStream stream = uploadedFile.getInputStream();
    byte[] buffer = new byte[(int) size];  
    stream.read(buffer, 0, (int) size);  
    stream.close();  
}

I am able to get the file name, type and size, but I am unable to save this file at a specific path. I can't figure out the proper way to save the uploaded file. How can I achieve this?

 Answers

56

The getInputStream() method of the uploaded file represents the file content.

InputStream input = uploadedFile.getInputStream();

You need to copy it to a file. You should first prepare a folder on the local disk file system where the uploaded files should be stored. For example, /path/to/uploads (on Windows, that would be on the same disk as where the server runs). Note that you should absolutely not store the files in expanded WAR folder or even the IDE project's folder by using a hardcoded path or a web-relative path or getRealPath() for the reasons mentioned here Uploaded image only available after refreshing the page.

Then, you need to autogenerate the filename. Otherwise, when someone else uploads a file with coincidentally the same name later, it would be overwritten. You could use Files#createTempFile() facility to get an autogenerated filename.

Path folder = Paths.get("/path/to/uploads");
String filename = FilenameUtils.getBaseName(uploadedFile.getName()); 
String extension = FilenameUtils.getExtension(uploadedFile.getName());
Path file = Files.createTempFile(folder, filename + "-", "." + extension);

The path to uploads could if necessary be parameterized based on one of several ways shown in this Q&A: Recommended way to save uploaded files in a servlet application. The FilenameUtils is part of Apache Commons IO which you should already have in your classpath as it's a dependency of the Tomahawk file upload component.

Finally, just stream the uploaded file to that file (assuming Java 7):

try (InputStream input = uploadedFile.getInputStream()) {
    Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING);
}

System.out.println("Uploaded file successfully saved in " + file);

Then, to download it back, easiest would be to register /path/to/uploads as a new webapp context or a virtual host so that all those files are available by an URL. See also Load images from outside of webapps / webcontext / deploy folder using <h:graphicImage> or <img> tag.

Tuesday, June 1, 2021
 
rblarsen
answered 7 Months ago
14

A couple of notes first: when you use Data/data1.txt as an argument, should it really be /Data/data1.txt (with a leading slash)? Also, should the outer loop scan only for .txt files, or all files in /Data? Here's an answer, assuming /Data/data1.txt and .txt files only:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Notes:

  • /Data/*.txt expands to the paths of the text files in /Data (including the /Data/ part)
  • $( ... ) runs a shell command and inserts its output at that point in the command line
  • basename somepath .txt outputs the base part of somepath, with .txt removed from the end (e.g. /Data/file.txt -> file)

If you needed to run MyProgram with Data/file.txt instead of /Data/file.txt, use "${filename#/}" to remove the leading slash. On the other hand, if it's really Data not /Data you want to scan, just use for filename in Data/*.txt.

Tuesday, June 1, 2021
 
nighter
answered 7 Months ago
51

Don't know if I get you right, but baseNumber is not bound to any property in any managed bean. It only exists in the scope of ui:repeat.

You should do something like:

<ui:repeat value="#{editableBaseSetList}" var="myVariable">
    <h:inputText value="#{managedBean.property}" />
</ui:repeat>
Monday, September 13, 2021
 
Paul Stanley
answered 3 Months ago
15

I'm not sure what's going on as I haven't seen this before. The following construct works for me when using the today's Mojarra 2.2.1 snapshot which you can download from the "implementation jar" link mentioned in What's new in JSF 2.2?

<h:form enctype="multipart/form-data">
    <h:inputFile value="#{bean.file}" required="true">
        <f:ajax listener="#{bean.handleFileUpload}" render="@form" />
    </h:inputFile>
    <h:messages />
</h:form>

with

private Part file;

public void handleFileUpload(AjaxBehaviorEvent event) {
    System.out.println("file size: " + file.getSize());
    System.out.println("file type: " + file.getContentType());
    System.out.println("file info: " + file.getHeader("Content-Disposition"));
}

// ...

I recommend to give the newer Mojarra version a try. Apparently there was a bug in an older Mojarra version which failed to create a proper multipart/form-data request using the <iframe> hack which ultimately caused this error. The mXX versions are beta versions anyway and should not be relied upon for production. This error could theoretically also have been browser-specific, but it works currently fine for me in Chrome 26, Firefox 20 and IE 10.

The only issue which I'm seeing is that the hidden <iframe> is still visible in Chrome and Firefox as below:

enter image description here

It appears that they're forgotten to set frameborder attribute to 0 in the generated <iframe>. I've reported issue 2861 about that.

Wednesday, September 29, 2021
 
Andro Selva
answered 2 Months ago
64

You basically need to do two things:

  1. Create a Filter which puts the multipart/form-data items in a custom map and replace the original request parameter map with it so that the normal request.getParameter() process keeps working.

  2. Create a JSF 2.0 custom component which renders a input type="file" and which is aware of this custom map and can obtain the uploaded files from it.

@taher has already given a link where you could find insights and code snippets. The JSF 2.0 snippets should be reuseable. You yet have to modify the MultipartMap to use the good 'ol Apache Commons FileUpload API instead of the Servlet 3.0 API.

If I have time, I will by end of day rewrite it and post it here.


Update: I almost forgot you, I did a quick update to replace Servlet 3.0 API by Commons FileUpload API, it should work:

package net.balusc.http.multipart;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

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;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

public class MultipartMap extends HashMap<String, Object> {

    // Constants ----------------------------------------------------------------------------------

    private static final String ATTRIBUTE_NAME = "parts";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.

    // Vars ---------------------------------------------------------------------------------------

    private String encoding;
    private String location;

    // Constructors -------------------------------------------------------------------------------

    /**
     * Construct multipart map based on the given multipart request and file upload location. When
     * the encoding is not specified in the given request, then it will default to <tt>UTF-8</tt>.
     * @param multipartRequest The multipart request to construct the multipart map for.
     * @param location The location to save uploaded files in.
     * @throws ServletException If something fails at Servlet level.
     * @throws IOException If something fails at I/O level.
     */
    @SuppressWarnings("unchecked") // ServletFileUpload#parseRequest() isn't parameterized.
    public MultipartMap(HttpServletRequest multipartRequest, String location)
        throws ServletException, IOException
    {
        multipartRequest.setAttribute(ATTRIBUTE_NAME, this);

        this.encoding = multipartRequest.getCharacterEncoding();
        if (this.encoding == null) {
            multipartRequest.setCharacterEncoding(this.encoding = DEFAULT_ENCODING);
        }
        this.location = location;

        try {
            List<FileItem> parts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(multipartRequest);
            for (FileItem part : parts) {
                if (part.isFormField()) {
                    processFormField(part);
                } else if (!part.getName().isEmpty()) {
                    processFileField(part);
                }
            }
        } catch (FileUploadException e) {
            throw new ServletException("Parsing multipart/form-data request failed.", e);
        }
    }

    // Actions ------------------------------------------------------------------------------------

    @Override
    public Object get(Object key) {
        Object value = super.get(key);
        if (value instanceof String[]) {
            String[] values = (String[]) value;
            return values.length == 1 ? values[0] : Arrays.asList(values);
        } else {
            return value; // Can be File or null.
        }
    }

    /**
     * @see ServletRequest#getParameter(String)
     * @throws IllegalArgumentException If this field is actually a File field.
     */
    public String getParameter(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
        }
        String[] values = (String[]) value;
        return values != null ? values[0] : null;
    }

    /**
     * @see ServletRequest#getParameterValues(String)
     * @throws IllegalArgumentException If this field is actually a File field.
     */
    public String[] getParameterValues(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
        }
        return (String[]) value;
    }

    /**
     * @see ServletRequest#getParameterNames()
     */
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(keySet());
    }

    /**
     * @see ServletRequest#getParameterMap()
     */
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> map = new HashMap<String, String[]>();
        for (Entry<String, Object> entry : entrySet()) {
            Object value = entry.getValue();
            if (value instanceof String[]) {
                map.put(entry.getKey(), (String[]) value);
            } else {
                map.put(entry.getKey(), new String[] { ((File) value).getName() });
            }
        }
        return map;
    }

    /**
     * Returns uploaded file associated with given request parameter name.
     * @param name Request parameter name to return the associated uploaded file for.
     * @return Uploaded file associated with given request parameter name.
     * @throws IllegalArgumentException If this field is actually a Text field.
     */
    public File getFile(String name) {
        Object value = super.get(name);
        if (value instanceof String[]) {
            throw new IllegalArgumentException("This is a Text field. Use #getParameter() instead.");
        }
        return (File) value;
    }

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Process given part as Text part.
     */
    private void processFormField(FileItem part) {
        String name = part.getFieldName();
        String[] values = (String[]) super.get(name);

        if (values == null) {
            // Not in parameter map yet, so add as new value.
            put(name, new String[] { part.getString() });
        } else {
            // Multiple field values, so add new value to existing array.
            int length = values.length;
            String[] newValues = new String[length + 1];
            System.arraycopy(values, 0, newValues, 0, length);
            newValues[length] = part.getString();
            put(name, newValues);
        }
    }

    /**
     * Process given part as File part which is to be saved in temp dir with the given filename.
     */
    private void processFileField(FileItem part) throws IOException {

        // Get filename prefix (actual name) and suffix (extension).
        String filename = FilenameUtils.getName(part.getName());
        String prefix = filename;
        String suffix = "";
        if (filename.contains(".")) {
            prefix = filename.substring(0, filename.lastIndexOf('.'));
            suffix = filename.substring(filename.lastIndexOf('.'));
        }

        // Write uploaded file.
        File file = File.createTempFile(prefix + "_", suffix, new File(location));
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new BufferedInputStream(part.getInputStream(), DEFAULT_BUFFER_SIZE);
            output = new BufferedOutputStream(new FileOutputStream(file), DEFAULT_BUFFER_SIZE);
            IOUtils.copy(input, output);
        } finally {
            IOUtils.closeQuietly(output);
            IOUtils.closeQuietly(input);
        }

        put(part.getFieldName(), file);
        part.delete(); // Cleanup temporary storage.
    }

}

You still need both the MultipartFilter and MultipartRequest classes as described in this article. You only need to remove the @WebFilter annotation and map the filter on an url-pattern of /* along with an <init-param> of location wherein you specify the absolute path where the uploaded files are to be stored. You can use the JSF 2.0 custom file upload component as described in this article unchanged.

Sunday, October 24, 2021
 
Maya
answered 1 Month ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share