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.fileupload.FileItem;
023import org.apache.commons.fileupload.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}