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.extensions.ajax; 018 019import java.util.ArrayList; 020import java.util.List; 021 022import org.apache.commons.fileupload2.core.FileItem; 023import org.apache.commons.fileupload2.core.FileUploadException; 024import org.apache.wicket.Component; 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.ajax.AjaxEventBehavior; 027import org.apache.wicket.ajax.AjaxRequestTarget; 028import org.apache.wicket.ajax.attributes.AjaxCallListener; 029import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 030import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method; 031import org.apache.wicket.core.util.string.CssUtils; 032import org.apache.wicket.markup.head.IHeaderResponse; 033import org.apache.wicket.markup.head.JavaScriptHeaderItem; 034import org.apache.wicket.markup.html.form.upload.FileUpload; 035import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequest; 036import org.apache.wicket.protocol.http.servlet.ServletWebRequest; 037import org.apache.wicket.request.resource.PackageResourceReference; 038import org.apache.wicket.request.resource.ResourceReference; 039import org.apache.wicket.util.lang.Args; 040import org.apache.wicket.util.lang.Bytes; 041 042/** 043 * Uploads files from a drop event. 044 * 045 * @author Andrew Kondratev 046 * @author svenmeier 047 */ 048public class AjaxFileDropBehavior extends AjaxEventBehavior 049{ 050 051 public static final String DRAG_OVER_CLASS_KEY = CssUtils.key(AjaxFileDropBehavior.class, "dragover"); 052 053 private static final ResourceReference JS = new PackageResourceReference( 054 AjaxFileDropBehavior.class, "wicket-ajaxupload.js"); 055 056 /** 057 * Maximum size of all uploaded files in bytes in a request. 058 */ 059 private Bytes maxSize; 060 061 /** 062 * Maximum size of file of upload in bytes (if there are more than one) in a request. 063 */ 064 private Bytes fileMaxSize; 065 066 /** 067 * Maximum amount of files in request. 068 * A value of -1 indicates no maximum. 069 */ 070 private long fileCountMax = -1L; 071 072 private String parameterName = "f"; 073 074 /** 075 * Listen for 'dragover' and 'drop' events and prevent them, only 'drop' will initiate 076 * an Ajax request. 077 */ 078 public AjaxFileDropBehavior() 079 { 080 super("dragenter dragover dragleave drop"); 081 } 082 083 @Override 084 public void renderHead(Component component, IHeaderResponse response) 085 { 086 super.renderHead(component, response); 087 088 response.render(JavaScriptHeaderItem.forReference(JS)); 089 } 090 091 @Override 092 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 093 { 094 super.updateAjaxAttributes(attributes); 095 096 attributes.setMultipart(true); 097 attributes.setMethod(Method.POST); 098 // default must be prevented, otherwise browser will consume the dataTransfer 099 attributes.setPreventDefault(true); 100 101 attributes.getAjaxCallListeners().add(new AjaxCallListener() { 102 @Override 103 public CharSequence getPrecondition(Component component) 104 { 105 String css = getComponent().getString(DRAG_OVER_CLASS_KEY); 106 107 return String.format("jQuery('#' + attrs.c).toggleClass('%s', attrs.event.type === 'dragover'); return (attrs.event.type === 'drop');", css); 108 } 109 }); 110 111 attributes.getDynamicExtraParameters() 112 .add(String.format( 113 "return Wicket.DataTransfer.getFilesAsParamArray(attrs.event.originalEvent, '%s');", 114 parameterName)); 115 } 116 117 @Override 118 protected void onEvent(AjaxRequestTarget target) 119 { 120 try 121 { 122 ServletWebRequest request = (ServletWebRequest)getComponent().getRequest(); 123 final MultipartServletWebRequest multipartWebRequest = request 124 .newMultipartWebRequest(getMaxSize(), getComponent().getPage().getId()); 125 multipartWebRequest.setFileMaxSize(getFileMaxSize()); 126 multipartWebRequest.setFileCountMax(getFileCountMax()); 127 multipartWebRequest.parseFileParts(); 128 129 // TODO: Can't this be detected from header? 130 getComponent().getRequestCycle().setRequest(multipartWebRequest); 131 132 ArrayList<FileUpload> fileUploads = new ArrayList<>(); 133 134 // Get the item for the path 135 final List<FileItem> fileItems = multipartWebRequest.getFile(parameterName); 136 137 if (fileItems != null) 138 { 139 for (FileItem item : fileItems) 140 { 141 fileUploads.add(new FileUpload(item)); 142 } 143 } 144 145 onFileUpload(target, fileUploads); 146 } 147 catch (final FileUploadException fux) 148 { 149 onError(target, fux); 150 } 151 } 152 153 public Bytes getMaxSize() 154 { 155 if (maxSize == null) 156 { 157 maxSize = getComponent().getApplication().getApplicationSettings() 158 .getDefaultMaximumUploadSize(); 159 } 160 return maxSize; 161 } 162 163 /** 164 * Set the maximum upload size. 165 * 166 * @param maxSize maximum size, must not be null 167 */ 168 public void setMaxSize(Bytes maxSize) 169 { 170 Args.notNull(maxSize, "maxSize"); 171 this.maxSize = maxSize; 172 } 173 174 public Bytes getFileMaxSize() 175 { 176 return fileMaxSize; 177 } 178 179 /** 180 * Set an optional maximum size per file. 181 * 182 * @param fileMaxSize maximum size for each uploaded file 183 */ 184 public void setFileMaxSize(Bytes fileMaxSize) 185 { 186 this.fileMaxSize = fileMaxSize; 187 } 188 189 /** 190 * Gets maximum count of files 191 * 192 * @return 193 */ 194 public long getFileCountMax() 195 { 196 return fileCountMax; 197 } 198 199 /** 200 * Sets maximum amount of files in upload request. 201 * 202 * @param fileCountMax 203 */ 204 public void setFileCountMax(long fileCountMax) 205 { 206 this.fileCountMax = fileCountMax; 207 } 208 209 /** 210 * Hook method called after a file was uploaded. 211 * <p> 212 * Note: {@link #onError(AjaxRequestTarget, FileUploadException)} is called instead when 213 * uploading failed 214 * 215 * @param target 216 * the current request handler 217 * @param files 218 * uploaded files 219 */ 220 protected void onFileUpload(AjaxRequestTarget target, List<FileUpload> files) 221 { 222 } 223 224 /** 225 * Hook method called to handle any error during uploading of the file. 226 * <p> 227 * Default implementation re-throws the exception. 228 * 229 * @param target 230 * the current request handler 231 * @param fux 232 * the error that occurred 233 */ 234 protected void onError(AjaxRequestTarget target, FileUploadException fux) 235 { 236 throw new WicketRuntimeException(fux); 237 } 238}