001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.form.upload; 018 019 020import java.util.ArrayList; 021import java.util.List; 022 023import org.apache.commons.fileupload.FileItem; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.html.form.FormComponent; 026import org.apache.wicket.model.IModel; 027import org.apache.wicket.protocol.http.IMultipartWebRequest; 028import org.apache.wicket.request.Request; 029import org.apache.wicket.util.convert.ConversionException; 030import org.apache.wicket.util.string.Strings; 031 032/** 033 * Form component that corresponds to a <input type="file">. When a FileInput 034 * component is nested in a {@link org.apache.wicket.markup.html.form.Form}, that has multipart == 035 * true, its model is updated with the {@link org.apache.wicket.markup.html.form.upload.FileUpload} 036 * for this component. 037 * <p> 038 * <strong>NOTE</strong>The model of this component is reset with {@code null} at the end of the 039 * request because {@link FileUpload} instances do not survive across requests since the input 040 * streams they point to will be closed. Because of this, the {@link FileUpload} instance should be 041 * processed within the same request as the form containing it was submitted. 042 * </p> 043 * 044 * @author Eelco Hillenius 045 */ 046public class FileUploadField extends FormComponent<List<FileUpload>> 047{ 048 private static final long serialVersionUID = 1L; 049 050 private transient List<FileUpload> fileUploads; 051 052 /** 053 * @see org.apache.wicket.Component#Component(String) 054 */ 055 public FileUploadField(final String id) 056 { 057 super(id); 058 } 059 060 /** 061 * @param id 062 * See Component 063 * @param model 064 * the model holding the uploaded {@link FileUpload}s 065 */ 066 @SuppressWarnings("unchecked") 067 public FileUploadField(final String id, IModel<? extends List<FileUpload>> model) 068 { 069 super(id, (IModel<List<FileUpload>>)model); 070 } 071 072 /** 073 * @return the first uploaded file if HTML5 <input type="file" <strong>multiple</strong> 074 * /> is used and the browser supports <em>multiple</em>, otherwise returns the 075 * single uploaded file. 076 * @see #getFileUploads() 077 */ 078 public FileUpload getFileUpload() 079 { 080 List<FileUpload> fileUploads = getFileUploads(); 081 082 return fileUploads.isEmpty() ? null : fileUploads.get(0) ; 083 } 084 085 /** 086 * @return a list of all uploaded files. The list is empty if no files were selected. It will return more than one files if: 087 * <ul> 088 * <li>HTML5 <input type="file" <strong>multiple</strong> /> is used</li> 089 * <li>the browser supports <em>multiple</em> attribute</li> 090 * <li>the user has selected more than one files from the <em>Select file</em> dialog</li> 091 * </ul> 092 */ 093 public List<FileUpload> getFileUploads() 094 { 095 if (fileUploads != null) 096 { 097 return fileUploads; 098 } 099 100 fileUploads = new ArrayList<>(); 101 102 // Get request 103 final Request request = getRequest(); 104 105 // If we successfully installed a multipart request 106 if (request instanceof IMultipartWebRequest) 107 { 108 // Get the item for the path 109 final List<FileItem> fileItems = ((IMultipartWebRequest)request).getFile(getInputName()); 110 111 if (fileItems != null) 112 { 113 for (FileItem item : fileItems) 114 { 115 // WICKET-6270 detect empty field by missing file name 116 if (Strings.isEmpty(item.getName()) == false) { 117 fileUploads.add(new FileUpload(item)); 118 } 119 } 120 } 121 } 122 123 return fileUploads; 124 } 125 126 @Override 127 public void updateModel() 128 { 129 if (getModel() != null) 130 { 131 super.updateModel(); 132 } 133 } 134 135 @Override 136 public String[] getInputAsArray() 137 { 138 List<FileUpload> fileUploads = getFileUploads(); 139 if (fileUploads.isEmpty() == false) 140 { 141 List<String> clientFileNames = new ArrayList<>(); 142 for (FileUpload fu : fileUploads) 143 { 144 clientFileNames.add(fu.getClientFileName()); 145 } 146 return clientFileNames.toArray(new String[clientFileNames.size()]); 147 } 148 return null; 149 } 150 151 @Override 152 protected List<FileUpload> convertValue(String[] value) throws ConversionException 153 { 154 final String[] filenames = getInputAsArray(); 155 if (filenames == null) 156 { 157 return null; 158 } 159 return getFileUploads(); 160 } 161 162 @Override 163 public boolean isMultiPart() 164 { 165 return true; 166 } 167 168 @Override 169 protected void onComponentTag(ComponentTag tag) 170 { 171 // Must be attached to an input tag 172 checkComponentTag(tag, "input"); 173 174 // Check for file type 175 checkComponentTagAttribute(tag, "type", "file"); 176 177 // Default handling for component tag 178 super.onComponentTag(tag); 179 } 180 181 /** 182 * Clean up at the end of the request. This means closing all inputstreams which might have been 183 * opened from the fileUpload. 184 * 185 * @see org.apache.wicket.Component#onDetach() 186 */ 187 @Override 188 protected void onDetach() 189 { 190 if (fileUploads != null) 191 { 192 if (forceCloseStreamsOnDetach()) { 193 for (FileUpload fu : fileUploads) 194 { 195 fu.closeStreams(); 196 } 197 198 if (getModel() != null) 199 { 200 getModel().setObject(null); 201 } 202 } 203 204 fileUploads = null; 205 } 206 super.onDetach(); 207 } 208 209 /** 210 * The FileUploadField will close any input streams you have opened in its FileUpload by 211 * default. If you wish to manage the stream yourself (e.g. you want to use it in another 212 * thread) then you can override this method to prevent this behavior. 213 * 214 * @return <code>true</code> if stream should be closed at the end of request 215 */ 216 protected boolean forceCloseStreamsOnDetach() 217 { 218 return true; 219 } 220}