org.apache.wicket.markup.html.form.upload.MultiFileUploadField.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.markup.html.form.upload.MultiFileUploadField.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.markup.html.form.upload;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.fileupload.FileItem;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.FormComponentPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.IMultipartWebRequest;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.string.Strings;

/**
 * Form component that allows the user to select multiple files to upload via a single <input
 * type="file"/> field.
 * 
 * Notice that this component clears its model at the end of the request, so the uploaded files MUST
 * be processed within the request they were uploaded.
 * 
 * Uses javascript implementation from
 * http://the-stickman.com/web-development/javascript/upload-multiple-files-with-a-single-file-element/
 * 
 * For customizing caption text see {@link #RESOURCE_LIMITED} and {@link #RESOURCE_UNLIMITED}
 * 
 * For an example of styling using CSS see the upload example in wicket-examples
 * 
 * @author Igor Vaynberg (ivaynberg)
 */
public class MultiFileUploadField extends FormComponentPanel<Collection<FileUpload>> {
    private static final long serialVersionUID = 1L;

    /**
     * Represents an unlimited max count of uploads
     */
    public static final int UNLIMITED = -1;

    /**
     * Resource key used to retrieve caption message for when a limit on the number of uploads is
     * specified. The limit is represented via ${max} variable.
     * 
     * Example: org.apache.wicket.mfu.caption.limited=Files (maximum ${max}):
     */
    public static final String RESOURCE_LIMITED = "org.apache.wicket.mfu.caption.limited";

    /**
     * Resource key used to retrieve caption message for when no limit on the number of uploads is
     * specified.
     * 
     * Example: org.apache.wicket.mfu.caption.unlimited=Files:
     */
    public static final String RESOURCE_UNLIMITED = "org.apache.wicket.mfu.caption.unlimited";

    private static final String NAME_ATTR = "name";

    public static final String MAGIC_SEPARATOR = "_mf_";

    public static final ResourceReference JS = new JavaScriptResourceReference(MultiFileUploadField.class,
            "MultiFileUploadField.js");

    private final WebComponent upload;
    private final WebMarkupContainer container;

    private final int max;

    private final boolean useMultipleAttr;

    private transient String[] inputArrayCache = null;

    /**
     * Constructor
     * 
     * @param id
     */
    public MultiFileUploadField(String id) {
        this(id, null, UNLIMITED);
    }

    /**
     * Constructor
     * 
     * @param id
     * @param max
     *            max number of files a user can upload
     */
    public MultiFileUploadField(String id, int max) {
        this(id, null, max);
    }

    /**
     * Constructor
     * 
     * @param id
     * @param model
     */
    public MultiFileUploadField(String id, IModel<? extends Collection<FileUpload>> model) {
        this(id, model, UNLIMITED);
    }

    /**
     * Constructor
     * 
     * @param id
     * @param model
     * @param max
     *            max number of files a user can upload
     */
    public MultiFileUploadField(String id, IModel<? extends Collection<FileUpload>> model, int max) {
        this(id, model, max, false);
    }

    /**
     * Constructor
     *
     * @param id
     * @param model
     * @param max
     *            max number of files a user can upload
     * @param useMultipleAttr
     *            true in order to use the new HTML5 "multiple" &lt;input&gt; attribute. It will allow
     *            the users to select multiple files at once for multiple times if the browser
     *            supports it, otherwise it will work just as before - one file multiple times.
     * 
     */
    @SuppressWarnings("unchecked")
    public MultiFileUploadField(String id, IModel<? extends Collection<FileUpload>> model, int max,
            boolean useMultipleAttr) {
        super(id, (IModel<Collection<FileUpload>>) model);

        this.max = max;
        this.useMultipleAttr = useMultipleAttr;

        upload = new WebComponent("upload") {
            @Override
            protected void onComponentTag(ComponentTag tag) {
                super.onComponentTag(tag);

                if (!isEnabledInHierarchy()) {
                    onDisabled(tag);
                }
            }
        };
        upload.setOutputMarkupId(true);
        add(upload);

        container = new WebMarkupContainer("container");
        container.setOutputMarkupId(true);
        add(container);

        container.add(new Label("caption", new CaptionModel()));
    }

    /**
     * @see org.apache.wicket.markup.html.form.FormComponentPanel#onComponentTag(org.apache.wicket.markup.ComponentTag)
     */
    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        // remove the name attribute added by the FormComponent
        if (tag.getAttributes().containsKey(NAME_ATTR)) {
            tag.getAttributes().remove(NAME_ATTR);
        }
    }

    /**
     * @see org.apache.wicket.Component#onBeforeRender()
     */
    @Override
    protected void onBeforeRender() {
        super.onBeforeRender();

        Form<?> form = findParent(Form.class);
        if (form == null) {
            // woops
            throw new IllegalStateException("Component " + getClass().getName() + " must have a "
                    + Form.class.getName() + " component above in the hierarchy");
        }
    }

    @Override
    public boolean isMultiPart() {
        return true;
    }

    @Override
    public void renderHead(IHeaderResponse response) {
        // initialize the javascript library
        response.render(JavaScriptHeaderItem.forReference(JS));
        response.render(OnDomReadyHeaderItem.forScript(
                "new MultiSelector('" + getInputName() + "', document.getElementById('" + container.getMarkupId()
                        + "'), " + max + ", " + useMultipleAttr + ", '" + getString("org.apache.wicket.mfu.delete")
                        + "').addElement(document.getElementById('" + upload.getMarkupId() + "'));"));
    }

    /**
     * @see org.apache.wicket.markup.html.form.FormComponent#getInputAsArray()
     */
    @Override
    public String[] getInputAsArray() {
        // fake the input array as if it contained an array of all uploaded file
        // names

        if (inputArrayCache == null) {
            // this array will aggregate all input names
            ArrayList<String> names = null;

            final Request request = getRequest();
            if (request instanceof IMultipartWebRequest) {
                // retrieve the filename->FileItem map from request
                final Map<String, List<FileItem>> itemNameToItem = ((IMultipartWebRequest) request).getFiles();
                for (Entry<String, List<FileItem>> entry : itemNameToItem.entrySet()) {
                    // iterate over the map and build the list of filenames

                    final String name = entry.getKey();
                    final List<FileItem> fileItems = entry.getValue();

                    if (!Strings.isEmpty(name) && name.startsWith(getInputName() + MAGIC_SEPARATOR)
                            && !fileItems.isEmpty() && !Strings.isEmpty(fileItems.get(0).getName())) {

                        // make sure the fileitem belongs to this component and
                        // is not empty

                        names = (names != null) ? names : new ArrayList<String>();
                        names.add(name);
                    }
                }
            }

            if (names != null) {
                inputArrayCache = names.toArray(new String[names.size()]);
            }
        }
        return inputArrayCache;

    }

    @Override
    protected Collection<FileUpload> convertValue(String[] value) throws ConversionException {
        // convert the array of filenames into a collection of FileItems

        Collection<FileUpload> uploads = null;

        final String[] filenames = getInputAsArray();

        if (filenames != null) {
            final IMultipartWebRequest request = (IMultipartWebRequest) getRequest();

            uploads = new ArrayList<>(filenames.length);

            for (String filename : filenames) {
                List<FileItem> fileItems = request.getFile(filename);
                for (FileItem fileItem : fileItems) {
                    uploads.add(new FileUpload(fileItem));
                }
            }
        }

        return uploads;

    }

    /**
     * See {@link FormComponent#updateCollectionModel(FormComponent)} for details on how the model
     * is updated.
     */
    @Override
    public void updateModel() {
        FormComponent.updateCollectionModel(this);
    }

    @Override
    protected void onDetach() {
        if (forceCloseStreamsOnDetach()) {
            // cleanup any opened filestreams
            Collection<FileUpload> uploads = getConvertedInput();
            if (uploads != null) {
                for (FileUpload upload : uploads) {
                    upload.closeStreams();
                }
            }

            // cleanup any caches
            inputArrayCache = null;

            // clean up the model because we don't want FileUpload objects in session
            Collection<FileUpload> modelObject = getModelObject();
            if (modelObject != null) {
                modelObject.clear();
            }
        }

        super.onDetach();
    }

    /**
     * The FileUploadField will close any input streams you have opened in its FileUpload by
     * default. If you wish to manage the stream yourself (e.g. you want to use it in another
     * thread) then you can override this method to prevent this behavior.
     *
     * @return <code>true</code> if stream should be closed at the end of request
     */
    protected boolean forceCloseStreamsOnDetach() {
        return true;
    }

    /**
     * Model that will construct the caption string
     * 
     * @author ivaynberg
     */
    private class CaptionModel implements IModel<String> {
        private static final long serialVersionUID = 1L;

        @Override
        public String getObject() {
            if (max == UNLIMITED) {
                return getString(RESOURCE_UNLIMITED);
            } else {
                HashMap<String, Object> vars = new HashMap<>(1);
                vars.put("max", max);
                return getString(RESOURCE_LIMITED, new Model<>(vars));
            }
        }

    }
}