You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by re...@apache.org on 2023/04/14 08:50:54 UTC
[wicket] 01/01: [WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
This is an automated email from the ASF dual-hosted git repository.
reiern70 pushed a commit to branch bugix/reiern70/WICKET-7037-master
in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 0b36a50428a417e933988435599f987f3c78452d
Author: reiern70 <re...@gmail.com>
AuthorDate: Thu Apr 6 10:20:25 2023 +0300
[WICKET-7033] implementation of: 1) an upload field that allows uploading files into to a mounted resource, 2) a reusable mounted resource that process uploads and 3) adapting upload progress bar to work in such use case
---
.../java/org/apache/wicket/ajax/AjaxUtils.java | 55 +++
.../resource/AbstractFileUploadResource.java | 223 +++++++++++
.../resource/FileUploadResourceReference.java | 132 ++++++
.../upload/resource/FileUploadToResourceField.java | 446 +++++++++++++++++++++
.../upload/resource/FileUploadToResourceField.js | 104 +++++
.../resource/FileUploadToResourceField.properties | 16 +
.../upload/resource/FolderUploadsFileManager.java | 83 ++++
.../form/upload/resource/IUploadsFileManager.java | 52 +++
.../wicket/examples/upload/UploadApplication.java | 19 +
.../apache/wicket/examples/upload/UploadPage.java | 5 +-
.../examples/upload/UploadToResourcePage.html | 44 ++
.../examples/upload/UploadToResourcePage.java | 263 ++++++++++++
.../apache/wicket/examples/homepage/HomePage.html | 1 +
.../resources/org/apache/wicket/examples/style.css | 2 +-
.../markup/html/form/upload/UploadProgressBar.java | 78 +++-
.../ajax/markup/html/form/upload/progressbar.js | 50 ++-
16 files changed, 1543 insertions(+), 30 deletions(-)
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java
new file mode 100644
index 0000000000..7ed0253e1a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AjaxUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ajax;
+
+import java.util.Optional;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.danekja.java.util.function.serializable.SerializableConsumer;
+
+/**
+ * Some AJAX related utility functions.
+ */
+public class AjaxUtils
+{
+
+ /**
+ * Runs action if current request is of type "AJAX". Otherwise, nothing is done.
+ *
+ * @param ajaxAction
+ * The action to run a {@link SerializableConsumer}
+ */
+ public static void executeIfAjax(SerializableConsumer<AjaxRequestTarget> ajaxAction)
+ {
+ Optional<AjaxRequestTarget> target = RequestCycle.get().find(AjaxRequestTarget.class);
+ target.ifPresent(ajaxAction);
+ }
+
+
+ /**
+ * Runs action if current request is of type "AJAX" or a Websockets request. Otherwise, nothing is done.
+ *
+ * @param ajaxAction
+ * The action to run a {@link SerializableConsumer}
+ */
+ public static void executeIfAjaxOrWebSockets(SerializableConsumer<IPartialPageRequestHandler> ajaxAction)
+ {
+ Optional<IPartialPageRequestHandler> target = RequestCycle.get().find(IPartialPageRequestHandler.class);
+ target.ifPresent(ajaxAction);
+ }
+
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java
new file mode 100644
index 0000000000..6bbce26384
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java
@@ -0,0 +1,223 @@
+/*
+ * 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.resource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.fileupload2.FileItem;
+import org.apache.commons.fileupload2.pub.FileUploadSizeException;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequest;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.resource.AbstractResource;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.github.openjson.JSONObject;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * The resource that handles the file uploads.
+ * Reads the file items from the request parameters and uses {@link IUploadsFileManager}
+ * to store them.
+ * Additionally, cares about the response's content type and body.
+ * <p>
+ * This code was adapted from
+ * <p>
+ * <a href="https://github.com/martin-g/blogs/blob/master/file-upload/src/main/java/com/mycompany/fileupload/AbstractFileUploadResource.java">AbstractFileUploadResource.java</a>
+ * <p>
+ * The main difference is that there some JQuery plugin is used at client side (and it supports multiple uploads +
+ * some UI allowing to delete/preview files and so on).
+ * Here we are just using plain jQuery code at client side to upload a single file.
+ */
+public abstract class AbstractFileUploadResource extends AbstractResource
+{
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFileUploadResource.class);
+
+ public static final String PARAM_NAME = "WICKET-FILE-UPLOAD";
+
+ /**
+ * This resource is usually an application singleton. Thus, client side pass
+ * to the resource a unique ID identifying the upload field performing the upload.
+ * The upload file makes sure this is a unique identifier at application level, See
+ * {@link FileUploadToResourceField#generateAUniqueApplicationWiseId()}. So that, there are no clashes between
+ * different users/pages/sessions performing an upload.
+ */
+ public static final String UPLOAD_ID = "uploadId";
+ /**
+ * i18n key for case no files were selected.
+ */
+ public static final String NO_FILE_SELECTED = "wicket.no.files.selected";
+ /**
+ * i18n key for the case selcted files exceed size limit.
+ */
+ public static final String REQUEST_SIZE_LIMIT_EXCEEDED = "wicket.multipart.size.exceeded";
+
+ private final IUploadsFileManager fileManager;
+
+ public AbstractFileUploadResource(IUploadsFileManager fileManager)
+ {
+ this.fileManager = fileManager;
+ }
+
+ /**
+ * Reads and stores the uploaded files
+ *
+ * @param attributes
+ * Attributes
+ * @return ResourceResponse
+ */
+ @Override
+ protected ResourceResponse newResourceResponse(Attributes attributes)
+ {
+ final ResourceResponse resourceResponse = new ResourceResponse();
+
+ final ServletWebRequest webRequest = (ServletWebRequest) attributes.getRequest();
+
+ // get the ID of the upload field (it should be unique per application)
+ String uploadId = webRequest.getRequestParameters().getParameterValue(UPLOAD_ID).toString("resource");
+
+ try
+ {
+ MultipartServletWebRequest multiPartRequest = webRequest.newMultipartWebRequest(getMaxSize(), uploadId);
+ multiPartRequest.parseFileParts();
+
+ RequestCycle.get().setRequest(multiPartRequest);
+
+ // retrieve the files.
+ Map<String, List<FileItem>> files = multiPartRequest.getFiles();
+ List<FileItem> fileItems = files.get(PARAM_NAME);
+
+ if (fileItems != null)
+ {
+ List<FileUpload> fileUploads = new ArrayList<>();
+ for (FileItem fileItem : fileItems)
+ {
+ fileUploads.add(new FileUpload(fileItem));
+ }
+ saveFiles(fileUploads, uploadId);
+ prepareResponse(resourceResponse, webRequest, fileUploads);
+ }
+ else
+ {
+ resourceResponse.setContentType("application/json");
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ JSONObject json = new JSONObject();
+ json.put("error", true);
+ json.put("errorMessage", NO_FILE_SELECTED);
+ String error = json.toString();
+ attributes.getResponse().write(error);
+ }
+ });
+ }
+
+ }
+ catch (Exception fux)
+ {
+ LOG.error("An error occurred while uploading a file", fux);
+ if (fux instanceof FileUploadSizeException)
+ {
+ resourceResponse.setStatusCode(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+ return resourceResponse;
+ }
+ resourceResponse.setContentType("application/json");
+ JSONObject json = new JSONObject();
+ json.put("error", true);
+ String errorMessage = LOG.isDebugEnabled() ? fux.getMessage() : "An error occurred while uploading files";
+ json.put("errorMessage", errorMessage);
+ String error = json.toString();
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ attributes.getResponse().write(error);
+ }
+ });
+ }
+
+ return resourceResponse;
+ }
+
+ /**
+ * Sets the response's content type and body
+ *
+ * @param resourceResponse
+ * ResourceResponse
+ * @param webRequest
+ * ServletWebRequest
+ * @param fileItems
+ * List<FileUpload>
+ */
+ protected void prepareResponse(ResourceResponse resourceResponse, ServletWebRequest webRequest, List<FileUpload> fileItems)
+ {
+ resourceResponse.setContentType("application/json");
+ final String responseContent = generateJsonResponse(resourceResponse, webRequest, fileItems);
+
+ resourceResponse.setWriteCallback(new WriteCallback()
+ {
+ @Override
+ public void writeData(Attributes attributes) throws IOException
+ {
+ attributes.getResponse().write(responseContent);
+ }
+ });
+ }
+
+ /**
+ * Delegates to FileManager to store the uploaded files
+ *
+ * @param fileItems
+ * List<FileUpload>
+ */
+ protected void saveFiles(List<FileUpload> fileItems, String uploadId)
+ {
+ for (FileUpload fileItem : fileItems)
+ {
+ fileManager.save(fileItem, uploadId);
+ }
+ }
+
+ /**
+ * Defines what is the maximum size of the uploaded files.
+ *
+ * @return Bytes
+ */
+ protected abstract Bytes getMaxSize();
+
+ /**
+ * Should generate the response's body in JSON format
+ *
+ * @param resourceResponse
+ * ResourceResponse
+ * @param webRequest
+ * ServletWebRequest
+ * @param files
+ * List<FileUpload>
+ * @return The generated JSON
+ */
+ protected abstract String generateJsonResponse(ResourceResponse resourceResponse,
+ ServletWebRequest webRequest, List<FileUpload> files);
+
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java
new file mode 100644
index 0000000000..802ea5df88
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java
@@ -0,0 +1,132 @@
+/*
+ * 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.resource;
+
+import java.util.List;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.danekja.java.util.function.serializable.SerializableSupplier;
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONException;
+import com.github.openjson.JSONObject;
+
+/**
+ * A resource reference that provides default implementation of AbstractFileUploadResource.
+ * The implementation generates JSON response with data from the upload (this data is
+ * re-routed to the page for things like getting the client file name and file size).
+ */
+public class FileUploadResourceReference extends ResourceReference
+{
+
+ private final IUploadsFileManager uploadFileManager;
+
+ private static FileUploadResourceReference instance;
+
+ /**
+ * This method assumes {@link #createNewInstance(IUploadsFileManager, SerializableSupplier)} was called before
+ *
+ * @return FileUploadResourceReference
+ */
+ public static FileUploadResourceReference getInstance()
+ {
+ if (instance == null)
+ {
+ throw new IllegalStateException("An instance should be created via the createNewInstance method");
+ }
+ return instance;
+ }
+
+ /**
+ * Use this method in order to create an instance to be mounted at application level.
+ *
+ * @param fileManager The {@link IUploadsFileManager}
+ * @param maxSize The maxSize of the multipart request.
+ * @return FileUploadResourceReference
+ */
+ public static FileUploadResourceReference createNewInstance(IUploadsFileManager fileManager, SerializableSupplier<Bytes> maxSize)
+ {
+ if (instance == null)
+ {
+ instance = new FileUploadResourceReference(fileManager, maxSize);
+ }
+ return instance;
+ }
+
+ private final SerializableSupplier<Bytes> maxSize;
+
+ protected FileUploadResourceReference(IUploadsFileManager uploadFileManager, SerializableSupplier<Bytes> maxSize)
+ {
+ super(FileUploadResourceReference.class, "file-uploads");
+
+ Args.notNull(uploadFileManager, "uploadFileManager");
+
+ this.uploadFileManager = uploadFileManager;
+ this.maxSize = maxSize;
+ }
+
+ @Override
+ public IResource getResource()
+ {
+ return new AbstractFileUploadResource(uploadFileManager)
+ {
+ @Override
+ protected Bytes getMaxSize()
+ {
+ return maxSize.get();
+ }
+
+ @Override
+ protected String generateJsonResponse(ResourceResponse resourceResponse, ServletWebRequest webRequest, List<FileUpload> files)
+ {
+ JSONArray json = new JSONArray();
+
+ for (FileUpload fileItem : files)
+ {
+ JSONObject fileJson = new JSONObject();
+
+ try
+ {
+ generateFileInfo(fileJson, fileItem);
+ json.put(fileJson);
+ }
+ catch (JSONException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ return json.toString();
+ }
+
+ };
+ }
+
+ public IUploadsFileManager getUploadFileManager() {
+ return uploadFileManager;
+ }
+
+ protected void generateFileInfo(JSONObject fileJson, FileUpload fileItem)
+ {
+ fileJson.put("clientFileName", fileItem.getClientFileName());
+ fileJson.put("size", fileItem.getSize());
+ fileJson.put("contentType", fileItem.getContentType());
+ }
+
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java
new file mode 100644
index 0000000000..9f1054fbaf
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.java
@@ -0,0 +1,446 @@
+/*
+ * 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.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.AjaxUtils;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+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.form.upload.FileUpload;
+import org.apache.wicket.markup.html.form.upload.FileUploadField;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.resource.CoreLibrariesContributor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONObject;
+
+/**
+ * Implementation of FileUploadField capable to uploading a file into a wicket mounted resource.
+ * This field does not require a {@link org.apache.wicket.markup.html.form.Form}!
+ * The upload of the file id done via the {@link #startUpload(IPartialPageRequestHandler)} method.
+ */
+public abstract class FileUploadToResourceField extends FileUploadField
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadToResourceField.class);
+
+ /**
+ * Info regarding an upload.
+ */
+ public static final class UploadInfo
+ {
+ private File file;
+ private final String clientFileName;
+
+ private final long size;
+
+ private final String contentType;
+
+ public UploadInfo(String clientFileName, long size, String contentType)
+ {
+ this.clientFileName = clientFileName;
+ this.size = size;
+ this.contentType = contentType;
+ }
+
+ public File getFile()
+ {
+ return file;
+ }
+
+ public void setFile(File file)
+ {
+ this.file = file;
+ }
+
+ public String getClientFileName()
+ {
+ return clientFileName;
+ }
+
+ public long getSize()
+ {
+ return size;
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UploadInfo fileInfo = (UploadInfo) o;
+ return Objects.equals(clientFileName, fileInfo.clientFileName);
+ }
+
+ /**
+ * @return the bytes associated with the upload. Return null in case no file is present
+ * or some other error condition happens.
+ */
+ public byte[] get()
+ {
+ if (file == null)
+ {
+ return null;
+ }
+
+ byte[] fileData = new byte[(int) getSize()];
+ InputStream fis = null;
+
+ try
+ {
+ fis = new FileInputStream(file);
+ IOUtils.readFully(fis, fileData);
+ }
+ catch (IOException e)
+ {
+ LOGGER.debug("IOException at get", e);
+ fileData = null;
+ }
+ finally
+ {
+ IOUtils.closeQuietly(fis);
+ }
+
+ return fileData;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(clientFileName);
+ }
+
+ public static List<UploadInfo> fromJson(String json)
+ {
+ List<UploadInfo> infos = new ArrayList<>();
+ JSONArray jsonArray = new JSONArray(json);
+ for (int i = 0; i < jsonArray.length(); i++)
+ {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ infos.add(new UploadInfo(jsonObject.getString("clientFileName"), jsonObject.getLong("size"), jsonObject.getString("contentType")));
+ }
+ return infos;
+ }
+ }
+
+ private static final JavaScriptResourceReference JS = new JavaScriptResourceReference(FileUploadToResourceField.class, "FileUploadToResourceField.js");
+
+ public static String UPLOAD_CANCELED = "upload.canceled";
+
+
+ private static abstract class FileModel implements IModel<List<UploadInfo>>
+ {
+
+ @Override
+ public List<UploadInfo> getObject()
+ {
+ List<UploadInfo> fileInfos = getFileUploadInfos();
+ for (UploadInfo uploadInfo : fileInfos)
+ {
+ // at this point files were stored at the server side by resource
+ // UploadFieldId acts as a discriminator at application level
+ // so that uploaded files are isolated.
+ uploadInfo.setFile(fileManager().getFile(getUploadFieldId(), uploadInfo.clientFileName));
+ }
+ return fileInfos;
+ }
+
+ @Override
+ public void setObject(List<UploadInfo> object)
+ {
+ throw new UnsupportedOperationException("setObject not supported");
+ }
+
+ protected abstract IUploadsFileManager fileManager();
+
+ /*
+ This is an application unique ID assigned to upload field.
+ */
+ protected abstract String getUploadFieldId();
+
+ protected abstract List<UploadInfo> getFileUploadInfos();
+ }
+
+ private final AbstractDefaultAjaxBehavior ajaxBehavior;
+
+ private transient List<UploadInfo> fileUploadInfos;
+
+ public FileUploadToResourceField(String id) {
+ super(id);
+ setOutputMarkupId(true);
+ // generate a unique ID
+ setMarkupId(generateAUniqueApplicationWiseId());
+ setDefaultModel(new FileModel() {
+ @Override
+ protected IUploadsFileManager fileManager() {
+ return FileUploadToResourceField.this.fileManager();
+ }
+
+ @Override
+ protected String getUploadFieldId()
+ {
+ return FileUploadToResourceField.this.getMarkupId();
+ }
+
+ @Override
+ protected List<UploadInfo> getFileUploadInfos()
+ {
+ return fileUploadInfos;
+ }
+ });
+ ajaxBehavior = new AbstractDefaultAjaxBehavior()
+ {
+ @Override
+ protected void respond(AjaxRequestTarget target)
+ {
+ Request request = RequestCycle.get().getRequest();
+ boolean error = request.getRequestParameters().getParameterValue("error").toBoolean(true);
+ if (!error) {
+ String filesIfo = request.getRequestParameters().getParameterValue("filesInfo").toString();
+ fileUploadInfos = UploadInfo.fromJson(filesIfo);
+ onUploadSuccess(target, getFileUploadInfos());
+ }
+ else
+ {
+ String errorMessage = request.getRequestParameters().getParameterValue("errorMessage").toString(null);
+ if (UPLOAD_CANCELED.equals(errorMessage))
+ {
+ onUploadCanceled(target);
+ }
+ else
+ {
+ if (AbstractFileUploadResource.NO_FILE_SELECTED.equals(errorMessage))
+ {
+ errorMessage = getString(AbstractFileUploadResource.NO_FILE_SELECTED);
+ }
+ else if (isUploadTooBig(errorMessage))
+ {
+ errorMessage = getString(AbstractFileUploadResource.REQUEST_SIZE_LIMIT_EXCEEDED);
+ }
+ onUploadFailure(target, errorMessage);
+ }
+ }
+ }
+ };
+ add(ajaxBehavior);
+ }
+
+ /**
+ *
+ * @param errorMessage The error message
+ * @return true iff errorMessage is because upload exceeded max allowed size.
+ */
+ protected boolean isUploadTooBig(String errorMessage)
+ {
+ return "Payload Too Large".equals(errorMessage);
+ }
+
+ private List<UploadInfo> getFileUploadInfos()
+ {
+ // mind that this makes files to be added.
+ return (List<UploadInfo>)getDefaultModel().getObject();
+ }
+
+ @Override
+ public FileUpload getFileUpload()
+ {
+ throw new UnsupportedOperationException("FileUploadToResourceField does not support working with FileUpload");
+ }
+
+ @Override
+ public List<FileUpload> getFileUploads()
+ {
+ throw new UnsupportedOperationException("FileUploadToResourceField does not support working with FileUpload");
+ }
+
+ @Override
+ protected void onRemove()
+ {
+ super.onRemove();
+ // we clean any client side mess if component is removed via "partial" page replacement
+ AjaxUtils.executeIfAjaxOrWebSockets(target -> target.appendJavaScript("delete Wicket.Timer." + getMarkupId() + ";"));
+
+ }
+
+ /**
+ * Override to do something on a successful upload.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ * @param fileInfos The List<FileInfo
+ */
+ protected abstract void onUploadSuccess(AjaxRequestTarget target, List<UploadInfo> fileInfos);
+
+
+ /**
+ * Override to do something on a non successful upload
+ *
+ * @param target The {@link AjaxRequestTarget}
+ * @param errorInfo The cause of the failure
+ */
+ protected void onUploadFailure(AjaxRequestTarget target, String errorInfo)
+ {
+ // nothing by default
+ }
+
+ /**
+ * Override to do something in case user canceled upload
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ protected void onUploadCanceled(AjaxRequestTarget target)
+ {
+ // nothing by default
+ }
+
+ /**
+ * @return a unique application wise ID (it should be a valid HTML id).
+ */
+ protected String generateAUniqueApplicationWiseId()
+ {
+ return "WRFUF_" + UUID.randomUUID().toString().replace("-", "_");
+ }
+
+
+ @Override
+ public void renderHead(IHeaderResponse response)
+ {
+ CoreLibrariesContributor.contributeAjax(getApplication(), response);
+ response.render(JavaScriptHeaderItem.forReference(JS));
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("inputName", getMarkupId());
+ jsonObject.put("resourceUrl", urlFor(getFileUploadResourceReference(), new PageParameters()).toString());
+ jsonObject.put("ajaxCallBackUrl", ajaxBehavior.getCallbackUrl());
+ response.render(OnDomReadyHeaderItem.forScript("Wicket.Timer."
+ + getMarkupId() + " = new Wicket.FileUploadToResourceField("
+ + jsonObject + ","
+ + getClientBeforeSendCallBack() + ","
+ + getClientSideSuccessCallBack() + ","
+ + getClientSideCancelCallBack() + ","
+ + getClientSideUploadErrorCallBack() + ");"));
+ }
+
+ /**
+ * Override if you need to return a different instance of FileUploadResourceReference
+ * @return FileUploadResourceReference
+ */
+ protected FileUploadResourceReference getFileUploadResourceReference()
+ {
+ return FileUploadResourceReference.getInstance();
+ }
+
+ /**
+ * @return The JavaScript expression starting the upload.
+ */
+ public String getTriggerUploadScript()
+ {
+ return "Wicket.Timer." + getMarkupId() + ".upload();";
+ }
+
+ /**
+ * Starts the upload via an AJAX request.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ public void startUpload(IPartialPageRequestHandler target)
+ {
+ target.appendJavaScript(getTriggerUploadScript());
+ }
+
+ /**
+ * @return The JavaScript expression canceling the upload.
+ */
+ public String getTriggerCancelUploadScript()
+ {
+ return "Wicket.Timer." + getMarkupId() + ".cancel();";
+ }
+ /**
+ * Cancels the upload via an AJAX request.
+ *
+ * @param target The {@link AjaxRequestTarget}
+ */
+ public void cancelUpload(IPartialPageRequestHandler target)
+ {
+ target.appendJavaScript(getTriggerCancelUploadScript());
+ }
+
+ /**
+ * See jQuery.ajax documentation.
+ *
+ * @return A JavaScript function to be executed on beforeSend the upload request.
+ */
+ protected CharSequence getClientBeforeSendCallBack()
+ {
+ return "function (xhr, settings) { return true; }";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on successful upload. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadSuccess(AjaxRequestTarget, List)}).
+ */
+ protected CharSequence getClientSideSuccessCallBack()
+ {
+ return "function () {}";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on upload canceled. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadFailure(AjaxRequestTarget, String)}).
+ */
+ protected CharSequence getClientSideCancelCallBack()
+ {
+ return "function () {}";
+ }
+
+ /**
+ * @return A JavaScript function to be executed on upload canceled. This is, besides the normal wicket
+ * AJAX request (see {@link #onUploadFailure(AjaxRequestTarget, String)}). It receives as parameter a JSON object
+ * like <code>{'error': true, errorMessage: 'xxx'}</code>.
+ */
+ protected CharSequence getClientSideUploadErrorCallBack()
+ {
+ return "function (res) {}";
+ }
+
+ /**
+ * @return The IUploadsFileManager
+ */
+ protected IUploadsFileManager fileManager()
+ {
+ return getFileUploadResourceReference().getUploadFileManager();
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js
new file mode 100644
index 0000000000..05a5349167
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.js
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+;(function (undefined) {
+
+ 'use strict';
+
+ if (typeof(Wicket.FileUploadToResourceField) === 'object') {
+ return;
+ }
+
+ Wicket.FileUploadToResourceField = function (settings, clientBeforeSendCallBack, clientSideSuccessCallBack, clientSideCancelCallBack, uploadErrorCallBack)
+ {
+ this.inputName = settings.inputName;
+ this.input = document.getElementById(this.inputName);
+ this.resourceUrl = settings.resourceUrl + "?uploadId=" + this.inputName;
+ this.ajaxCallBackUrl = settings.ajaxCallBackUrl;
+ this.clientBeforeSendCallBack = clientBeforeSendCallBack;
+ this.clientSideSuccessCallBack = clientSideSuccessCallBack;
+ this.clientSideCancelCallBack = clientSideCancelCallBack;
+ this.uploadErrorCallBack = uploadErrorCallBack;
+ }
+
+ Wicket.FileUploadToResourceField.prototype.upload = function()
+ {
+ // get a fresh reference to input
+ this.input = document.getElementById(this.inputName);
+ // we add the files to a FormData object.
+ var formData = new FormData();
+ var totalfiles = this.input.files.length;
+ for (var index = 0; index < totalfiles; index++) {
+ formData.append("WICKET-FILE-UPLOAD",this.input.files[index]);
+ }
+ // pass the input name to know where to store files at server side.
+ formData.append("uploadId", this.inputName);
+ var self = this;
+ // we use jQuery to post the files to the resource (this.resourceUrl)
+ // and we keep a reference to the request in order to be able
+ // to cancel the upload
+ this.xhr = $.ajax({
+ url: this.resourceUrl,
+ type: "POST",
+ data: formData,
+ processData: false,
+ contentType: false,
+ success: function (res) {
+ // do clean up on success
+ if (res.error) {
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": res});
+ self.uploadErrorCallBack(res);
+ } else {
+ self.clientSideSuccessCallBack();
+ var ep = {'error': false, 'filesInfo': JSON.stringify(res)};
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ }
+ },
+ beforeSend: function (xhr) {
+ self.clientBeforeSendCallBack(xhr);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ if (textStatus === "abort") {
+ // user aborted the upload.
+ var ep = {'error': true, 'errorMessage': 'upload.canceled'};
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ } else if (textStatus === "error"){
+ var ep = {'error': true, "errorMessage": errorThrown};
+ self.uploadErrorCallBack(ep);
+ Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep});
+ } else if (textStatus === "parsererror"){
+ // this error will only happen is generated JSON at server side is faulty
+ var data = jqXHR.responseText;
+ Wicket.Log.log(data);
+ }
+ }
+ });
+ }
+
+ // cancel the upload
+ Wicket.FileUploadToResourceField.prototype.cancel = function () {
+ // we have a reference to the request we can cancel it.
+ if (this.xhr) {
+ this.xhr.abort();
+ this.clientSideCancelCallBack();
+ Wicket.Log.log("The upload associated with field '" + this.inputName + "' has been canceled!");
+ delete (this.xhr);
+ } else {
+ Wicket.Log.log("Too late to cancel upload for field '" + this.inputName + "': the upload has already finished.");
+ }
+ }
+})();
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties
new file mode 100644
index 0000000000..4399cf5805
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadToResourceField.properties
@@ -0,0 +1,16 @@
+# 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.
+wicket.no.files.selected = Nothing to upload: no files were selected.
+wicket.multipart.size.exceeded = The total size of the selected files exceeds configured limits.
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java
new file mode 100644
index 0000000000..0146b82099
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FolderUploadsFileManager.java
@@ -0,0 +1,83 @@
+/*
+ * 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.resource;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Implementation of {@link IUploadsFileManager} that stores files in sub-folders of a given folder.
+ * Sub-folders are named using the input field ID (and those are unique per application). Thus, there
+ * is no possibility that uploads get mixed up.
+ */
+public class FolderUploadsFileManager implements IUploadsFileManager
+{
+
+ private final File folder;
+
+ public FolderUploadsFileManager(File folder)
+ {
+ Args.notNull(folder, "folder");
+ if (!folder.exists())
+ {
+ try
+ {
+ Files.createDirectories(folder.toPath());
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException(e);
+ }
+ }
+ else if (folder.exists() && !folder.isDirectory())
+ {
+ throw new IllegalArgumentException("Not a folder : " + folder.getAbsolutePath());
+ }
+ this.folder = folder;
+ }
+
+ public File getFolder() {
+ return folder;
+ }
+
+ @Override
+ public void save(FileUpload fileItem, String uploadFieldId)
+ {
+ File uploadFieldFolder = new File(getFolder(), uploadFieldId);
+ uploadFieldFolder.mkdirs();
+ try
+ {
+ IOUtils.copy(fileItem.getInputStream(), new FileOutputStream(new File(uploadFieldFolder, fileItem.getClientFileName())));
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException(e);
+ }
+ }
+
+ @Override
+ public File getFile(String uploadFieldId, String clientFileName)
+ {
+ return new File(new File(getFolder(), uploadFieldId), clientFileName);
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java
new file mode 100644
index 0000000000..78a1208503
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java
@@ -0,0 +1,52 @@
+/*
+ * 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.resource;
+
+import org.apache.wicket.markup.html.form.upload.FileUpload;
+import org.apache.wicket.util.file.File;
+
+/**
+ * This interface defines the bridge between a file uploaded to a resource and the wicket component.
+ * Wicket component uses some identifier (passed as a request parameter) to instruct the resource
+ * how to store file. Same identifier is used to retrieve the file, once uploaded, from component in page.
+ * Mind that uploader resource is a singleton => identifier needs to be unique among different sessions
+ * (and pages in a session).
+ */
+public interface IUploadsFileManager
+{
+
+ /**
+ * Saves an uploaded files into some persistent storage (e,g, disk).
+ *
+ * @param fileItem
+ * The {@link FileUpload}
+ * @param uploadFieldId
+ * The unique ID of the upload field.
+ */
+ void save(FileUpload fileItem, String uploadFieldId);
+
+ /**
+ * Retrieves the file based on an uploadFieldId and clientFileName.
+ *
+ * @param uploadFieldId
+ * The unique ID of the upload field.
+ * @param clientFileName
+ * The client file name
+ * @return File the file
+ */
+ File getFile(String uploadFieldId, String clientFileName);
+}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
index 0826393842..f6c5054c28 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadApplication.java
@@ -18,7 +18,11 @@ package org.apache.wicket.examples.upload;
import org.apache.wicket.Page;
import org.apache.wicket.examples.WicketExampleApplication;
+import org.apache.wicket.markup.html.form.upload.resource.FileUploadResourceReference;
+import org.apache.wicket.markup.html.form.upload.resource.FolderUploadsFileManager;
+import org.apache.wicket.markup.html.form.upload.resource.IUploadsFileManager;
import org.apache.wicket.util.file.Folder;
+import org.apache.wicket.util.lang.Bytes;
/**
@@ -30,6 +34,8 @@ public class UploadApplication extends WicketExampleApplication
{
private Folder uploadFolder = null;
+ private IUploadsFileManager uploadsFileManager;
+
@Override
public Class<? extends Page> getHomePage()
{
@@ -55,9 +61,22 @@ public class UploadApplication extends WicketExampleApplication
// Ensure folder exists
uploadFolder.mkdirs();
+ uploadsFileManager = new FolderUploadsFileManager(uploadFolder);
+
+ mountResource("/uploads", FileUploadResourceReference.createNewInstance(uploadsFileManager, ()-> Bytes.kilobytes(100)));
+
mountPage("/multi", MultiUploadPage.class);
mountPage("/single", UploadPage.class);
+ mountPage("/uploadToResource", UploadToResourcePage.class);
getApplicationSettings().setUploadProgressUpdatesEnabled(true);
}
+
+ public IUploadsFileManager getUploadsFileManager() {
+ return uploadsFileManager;
+ }
+
+ public static UploadApplication getInstance() {
+ return (UploadApplication)get();
+ }
}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
index 3fe09b7eef..2ad1498e6b 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadPage.java
@@ -20,7 +20,6 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
-import org.apache.wicket.Application;
import org.apache.wicket.examples.WicketExamplePage;
import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadProgressBar;
import org.apache.wicket.markup.html.basic.Label;
@@ -173,7 +172,7 @@ public class UploadPage extends WicketExamplePage
// Add folder view
add(new Label("dir", uploadFolder.getAbsolutePath()));
- fileListView = new FileListView("fileList", new LoadableDetachableModel<List<File>>()
+ fileListView = new FileListView("fileList", new LoadableDetachableModel<>()
{
@Override
protected List<File> load()
@@ -216,6 +215,6 @@ public class UploadPage extends WicketExamplePage
private Folder getUploadFolder()
{
- return ((UploadApplication)Application.get()).getUploadFolder();
+ return UploadApplication.getInstance().getUploadFolder();
}
}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html
new file mode 100644
index 0000000000..ba2097cddf
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.html
@@ -0,0 +1,44 @@
+<html xmlns:wicket="http://wicket.apache.org">
+<head>
+ <title>Wicket Examples - upload</title>
+ <style>
+ legend { border: 1px solid #e9601a; background-color: #bbb; color: #fff; padding: 4px;}
+ fieldset { border: 1px solid #e9601a; padding: 10px; margin-top: 10px;}
+ </style>
+</head>
+<body>
+ <wicket:extend>
+ <p>Wicket can upload to a <em>mounted resource.</em></p>
+ <p>This upload is done via jQuery AJAX and <em>does not</em> block Wicket AJAX</p>
+ <p><em>Upload progressbar</em> has been updated to work with this component</p>
+ <br/>
+
+ <div>
+ <fieldset>
+ <legend>Upload a file(s) to a <em>mounted resource</em></legend>
+ <p>
+ <label wicket:for="fileInput">File</label>
+ <input wicket:id="fileInput" type="file" multiple="multiple"/>
+ </p>
+ <form wicket:id="form">
+ <p>
+ <input wicket:id="allowToLeavePageWhileUploading" type="checkbox"/>
+ <label wicket:for="allowToLeavePageWhileUploading">Block leaving page while upload is happening?</label>
+ </p>
+ </form>
+ <div>
+ <span wicket:id="progress">[[upload progressbar]]</span>
+ </div>
+ <input wicket:id="upload" type="button" value="Upload to a resource"/>
+ <input wicket:id="cancelUpload" type="button" value="Cancel upload via AJAX request"/>
+ <input wicket:id="cancelUploadClientSide" type="button" value="Cancel upload at client side"/>
+ <button wicket:id="counter" type="button">Click me</button>
+ </fieldset>
+ </div>
+ <br/>
+ <div>
+ <span wicket:id="uploadFeedback"/>
+ </div>
+ </wicket:extend>
+</body>
+</html>
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java
new file mode 100644
index 0000000000..e9e43e9872
--- /dev/null
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/upload/UploadToResourcePage.java
@@ -0,0 +1,263 @@
+/*
+ * 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.examples.upload;
+
+import java.util.List;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.examples.WicketExamplePage;
+import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadProgressBar;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.head.OnEventHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.upload.resource.IUploadsFileManager;
+import org.apache.wicket.markup.html.form.upload.resource.FileUploadToResourceField;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+/**
+ * Upload example.
+ *
+ * @author reiern70
+ */
+public class UploadToResourcePage extends WicketExamplePage
+{
+ private static class UpdateInEachAjaxRequestBehavior extends Behavior
+ {
+
+ @Override
+ public void bind(Component component)
+ {
+ component.setOutputMarkupPlaceholderTag(true);
+ }
+
+ @Override
+ public void onEvent(Component component, IEvent<?> event)
+ {
+ if (event.getPayload() instanceof AjaxRequestTarget)
+ {
+ ((AjaxRequestTarget)event.getPayload()).add(component);
+ }
+ }
+ }
+
+ private final FeedbackPanel uploadFeedback;
+
+ private final FileUploadToResourceField fileUploadToResourceField;
+
+ private boolean uploading;
+
+ private int counter;
+
+ private final IModel<Boolean> allowToLeavePageWhileUploading = Model.of(true);
+
+ /**
+ * Constructor.
+ *
+ * @param parameters
+ * Page parameters
+ */
+ public UploadToResourcePage(final PageParameters parameters)
+ {
+ fileUploadToResourceField = new FileUploadToResourceField("fileInput")
+ {
+
+ private void updateFeedback(AjaxRequestTarget target)
+ {
+ target.add(uploadFeedback);
+ uploading = false;
+ }
+
+ @Override
+ protected void onUploadSuccess(AjaxRequestTarget target, List<UploadInfo> infos)
+ {
+ for (UploadInfo uploadInfo : infos) {
+ info("File " + uploadInfo.getClientFileName() + " successfully uploaded. Size: " + uploadInfo.getSize() + "bytes. ContentType: "
+ + uploadInfo.getContentType() +". Stored at " + uploadInfo.getFile().getAbsolutePath());
+ }
+ updateFeedback(target);
+ }
+
+ @Override
+ protected void onUploadFailure(AjaxRequestTarget target, String errorInfo)
+ {
+ error("Upload failed with error: " + errorInfo);
+ updateFeedback(target);
+ }
+
+ @Override
+ public void startUpload(IPartialPageRequestHandler target) {
+ if (allowToLeavePageWhileUploading.getObject())
+ {
+ target.prependJavaScript("Wicket.CurrentUpload = {};");
+ }
+ super.startUpload(target);
+ }
+
+ @Override
+ protected void onUploadCanceled(AjaxRequestTarget target)
+ {
+ info("You have canceled the upload!");
+ updateFeedback(target);
+ }
+
+ @Override
+ protected CharSequence getClientSideSuccessCallBack() {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected CharSequence getClientSideUploadErrorCallBack()
+ {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected String getClientSideCancelCallBack()
+ {
+ return "function() {Wicket.CurrentUpload = null}";
+ }
+
+ @Override
+ protected IUploadsFileManager fileManager()
+ {
+ return getUploadsFileManager();
+ }
+ };
+ add(fileUploadToResourceField);
+
+ Form<Void> form = new Form<>("form");
+ add(form);
+ form.add(new AjaxCheckBox("allowToLeavePageWhileUploading", allowToLeavePageWhileUploading) {
+ @Override
+ protected void onUpdate(AjaxRequestTarget target) {
+
+ }
+ });
+ final UploadProgressBar uploadProgressBar = new UploadProgressBar("progress", fileUploadToResourceField)
+ {
+
+ @Override
+ protected String getOnProgressUpdatedCallBack()
+ {
+ return "function(percent) { console.log(percent);}";
+ }
+ };
+ add(uploadProgressBar);
+ add(new AjaxLink<Void>("upload")
+ {
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ uploading = true;
+ fileUploadToResourceField.startUpload(target);
+ uploadProgressBar.start(target);
+ target.add(uploadFeedback);
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(!uploading);
+ }
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+ add(new AjaxLink<Void>("cancelUpload")
+ {
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ fileUploadToResourceField.cancelUpload(target);
+ target.add(uploadFeedback);
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(uploading);
+ }
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+
+ add(new WebMarkupContainer("cancelUploadClientSide")
+ {
+
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ response.render(OnEventHeaderItem.forComponent(this, "click", fileUploadToResourceField.getTriggerCancelUploadScript()));
+ }
+
+ @Override
+ protected void onConfigure()
+ {
+ super.onConfigure();
+ setVisible(uploading);
+ }
+
+ }.add(new UpdateInEachAjaxRequestBehavior()));
+
+ add(new AjaxLink<Void>("counter")
+ {
+
+ @Override
+ public void onClick(AjaxRequestTarget target)
+ {
+ counter ++;
+ target.add(this);
+ }
+
+ }.setBody(()-> "Click me: upload does not blocks normal wicket AJAX. I was clicked " + counter).setOutputMarkupId(true));
+ // Create feedback panels
+ uploadFeedback = new FeedbackPanel("uploadFeedback");
+ uploadFeedback.setOutputMarkupId(true);
+
+ // Add uploadFeedback to the page itself
+ add(uploadFeedback);
+
+ }
+
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ response.render(
+ OnDomReadyHeaderItem.forScript("$(window).bind('beforeunload', function(){ if (Wicket.CurrentUpload != null) { return 'leave?'; }});"));
+ }
+
+ @Override
+ protected void onBeforeRender()
+ {
+ super.onBeforeRender();
+ uploading = false;
+ }
+
+ private IUploadsFileManager getUploadsFileManager()
+ {
+ return UploadApplication.getInstance().getUploadsFileManager();
+ }
+}
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html b/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
index 46631905f3..4c339f81c5 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/homepage/HomePage.html
@@ -23,6 +23,7 @@
<tr><td align="right"><a href="authorization">authorization</a></td><td> - Demonstrates authorization for pages and components.</td></tr>
<tr><td align="right"><a href="upload/single">upload</a></td><td> - Single file upload.</td></tr>
<tr><td align="right"><a href="upload/multi">upload</a></td><td> - Multiple file upload.</td></tr>
+ <tr><td align="right"><a href="upload/singleUploadToResource">upload</a></td><td> - Single AJAX file upload to a resource.</td></tr>
<tr><td align="right"><a href="template">template</a></td><td> - Templating example.</td></tr>
<tr><td align="right"><a href="mailtemplate">mail template</a></td><td> - Generate mail templates out of Page, Panel or TextTemplate.</td></tr>
<tr><td align="right"><a href="stateless">stateless</a></td><td> - Demonstrates stateless pages/sessions.</td></tr>
diff --git a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
index f27f2c2dac..81fcf9216f 100644
--- a/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
+++ b/wicket-examples/src/main/resources/org/apache/wicket/examples/style.css
@@ -305,7 +305,7 @@ button.button--alert {
input[type=submit],
input[type=reset],
input[type=button],
-button {
+button, a.button {
background: #FF9925;
color: #fff; }
.button:hover,
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
index d938e370e3..2b7e4aeca2 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
@@ -21,6 +21,7 @@ import java.util.Formatter;
import org.apache.wicket.Application;
import org.apache.wicket.IInitializer;
import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -29,7 +30,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
@@ -109,7 +109,7 @@ public class UploadProgressBar extends Panel
private static final long serialVersionUID = 1L;
- private final Form<?> form;
+ private Form<?> form;
private MarkupContainer statusDiv;
@@ -117,6 +117,30 @@ public class UploadProgressBar extends Panel
private final FileUploadField uploadField;
+ /**
+ * Constructor that will display the upload progress bar for every submit of the given form.
+ *
+ * @param id
+ * component id (not null)
+ * @param uploadField
+ * the file upload field to check for a file upload, or null to display the upload
+ * field for every submit of the given form
+ */
+ public UploadProgressBar(final String id, final FileUploadField uploadField)
+ {
+ super(id);
+
+ this.uploadField = uploadField;
+ if (uploadField != null)
+ {
+ uploadField.setOutputMarkupId(true);
+ }
+
+ setRenderBodyOnly(true);
+ }
+
+
+
/**
* Constructor that will display the upload progress bar for every submit of the given form.
*
@@ -163,7 +187,11 @@ public class UploadProgressBar extends Panel
protected void onInitialize()
{
super.onInitialize();
- getCallbackForm().setOutputMarkupId(true);
+ Form<?> form = getCallbackForm();
+ if (form != null)
+ {
+ form.setOutputMarkupId(true);
+ }
barDiv = newBarComponent("bar");
add(barDiv);
@@ -228,29 +256,53 @@ public class UploadProgressBar extends Panel
ResourceReference ref = new SharedResourceReference(RESOURCE_NAME);
- final String uploadFieldId = (uploadField == null) ? "" : uploadField.getMarkupId();
+ final String uploadFieldId = (uploadField == null) ? "null" : ("'" + uploadField.getMarkupId() + "'");
- final String status = new StringResourceModel(RESOURCE_STARTING, this, (IModel<?>)null).getString();
+ final String status = new StringResourceModel(RESOURCE_STARTING, this, null).getString();
- CharSequence url = urlFor(ref, UploadStatusResource.newParameter(getPage().getId()));
+ CharSequence url = form != null ? urlFor(ref, UploadStatusResource.newParameter(getPage().getId())) :
+ urlFor(ref, UploadStatusResource.newParameter(uploadField.getMarkupId()));
StringBuilder builder = new StringBuilder(128);
Formatter formatter = new Formatter(builder);
- formatter.format(
- "new Wicket.WUPB('%s', '%s', '%s', '%s', '%s', '%s');",
- getCallbackForm().getMarkupId(), statusDiv.getMarkupId(), barDiv.getMarkupId(), url, uploadFieldId,
- status);
+ Form<?> form = getCallbackForm();
+
+ formatter.format(getVarName() + " = new Wicket.WUPB(%s, '%s', '%s', '%s', %s, '%s', %s);",
+ form != null ? "'" + form.getMarkupId() + "'" : "null", statusDiv.getMarkupId(), barDiv.getMarkupId(), url, uploadFieldId,
+ status, getOnProgressUpdatedCallBack());
response.render(OnDomReadyHeaderItem.forScript(builder.toString()));
}
+ /**
+ * Allows to pass a JavaScript function that is called when progress in updated.
+ *
+ * @return A JavaScript function.
+ */
+ protected String getOnProgressUpdatedCallBack()
+ {
+ return "function(percent) {}";
+ }
+
+ private String getVarName() {
+ return "window.upb_" + barDiv.getMarkupId();
+ }
+
+
+ public void start(IPartialPageRequestHandler handler)
+ {
+ handler.appendJavaScript(getVarName() + ".start();");
+ }
+
/**
* Form on where will be installed the JavaScript callback to present the progress bar.
*
* @return form
*/
- private Form<?> getCallbackForm()
- {
- return form.getRootForm();
+ private Form<?> getCallbackForm() {
+ if (form != null) {
+ return form.getRootForm();
+ }
+ return null;
}
}
diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
index 6cd0ad930b..f540347786 100644
--- a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/progressbar.js
@@ -25,16 +25,19 @@
Wicket.WUPB = Wicket.Class.create();
Wicket.WUPB.prototype = {
- initialize : function(formid, statusid, barid, url, fileid, initialStatus) {
+ initialize : function(formid, statusid, barid, url, fileid, initialStatus, onProgressUpdated) {
this.statusid = statusid;
this.barid = barid;
this.url = url;
this.fileid = fileid;
this.initialStatus = initialStatus;
+ this.onProgressUpdated = onProgressUpdated;
- var formElement = Wicket.$(formid);
- this.originalCallback = formElement.onsubmit;
- formElement.onsubmit = Wicket.bind(this.submitCallback, this);
+ if (formid) {
+ var formElement = Wicket.$(formid);
+ this.originalCallback = formElement.onsubmit;
+ formElement.onsubmit = Wicket.bind(this.submitCallback, this);
+ }
},
submitCallback : function() {
@@ -55,8 +58,14 @@
if (displayprogress) {
this.setPercent(0);
this.setStatus(this.initialStatus);
- Wicket.$(this.statusid).removeAttribute('hidden');
- Wicket.$(this.barid).removeAttribute('hidden');
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ Wicket.DOM.show($statusId);
+ }
+ var $barid = Wicket.$(this.barid);
+ if ($barid != null) {
+ Wicket.DOM.show($barid);
+ }
this.scheduleUpdate();
}
},
@@ -64,15 +73,24 @@
setStatus : function(status) {
var label = document.createElement("label");
label.innerHTML = status;
- var oldLabel = Wicket.$(this.statusid).firstChild;
- if( oldLabel != null){
- Wicket.$(this.statusid).removeChild(oldLabel);
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ var oldLabel = $statusId.firstChild;
+ if (oldLabel != null){
+ $statusId.removeChild(oldLabel);
+ }
+ $statusId.appendChild(label);
}
- Wicket.$(this.statusid).appendChild(label);
},
setPercent : function(progressPercent) {
- Wicket.$(this.barid).firstChild.firstChild.style.width = progressPercent + '%';
+ var barId = Wicket.$(this.barid);
+ if (barId != null && barId.firstChild != null && barId.firstChild.firstChild != null) {
+ barId.firstChild.firstChild.style.width = progressPercent + '%';
+ }
+ if (this.onProgressUpdated) {
+ this.onProgressUpdated(progressPercent);
+ }
},
scheduleUpdate : function(){
@@ -117,8 +135,14 @@
this.iframe = null;
if (progressPercent === '100') {
- Wicket.$(this.statusid).setAttribute('hidden', '');
- Wicket.$(this.barid).setAttribute('hidden', '');
+ var $statusId = Wicket.$(this.statusid);
+ if ($statusId != null) {
+ Wicket.DOM.hide($statusId);
+ }
+ var $barid = Wicket.$(this.barid);
+ if ($barid != null) {
+ Wicket.DOM.hide($barid);
+ }
} else {
this.scheduleUpdate();
}