This is an automated email from the ASF dual-hosted git repository. reiern70 pushed a commit to branch reiern70/WICKET-7033 in repository https://gitbox.apache.org/repos/asf/wicket.git
commit aab23d5752cc69839918be28c7e4330132a6538f Author: reiern70 <[email protected]> AuthorDate: Thu Apr 6 10:20:25 2023 +0300 [WICKET-7033] WIP --- .../resource/AbstractFileUploadResource.java | 194 +++++++++++++++++++++ .../resource/FileUploadResourceReference.java | 107 ++++++++++++ .../form/upload/resource/IUploadsFileManager.java | 57 ++++++ .../upload/resource/ResourceFileUploadField.java | 62 +++++++ .../upload/resource/ResourceFileUploadField.js | 91 ++++++++++ 5 files changed, 511 insertions(+) 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..6b9d35a356 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/AbstractFileUploadResource.java @@ -0,0 +1,194 @@ +/* + * 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.fileupload.FileItem; +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; + +/** + * 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 = "FILE-UPLOAD"; + + 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(); + + String identifier = webRequest.getRequestParameters().getParameterValue("uploadId").toString("resource"); + + try + { + MultipartServletWebRequest multiPartRequest = webRequest.newMultipartWebRequest(getMaxSize(), identifier); + multiPartRequest.parseFileParts(); + + RequestCycle.get().setRequest(multiPartRequest); + + 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, identifier); + 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 files selected!"); + String error = json.toString(); + attributes.getResponse().write(error); + } + }); + } + + } + catch (Exception fux) + { + LOG.error("An error occurred while uploading a file", fux); + resourceResponse.setContentType("application/json"); + JSONObject json = new JSONObject(); + json.put("error", true); + json.put("errorMessage", fux.getMessage()); + 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 identifier) { + for (FileUpload fileItem : fileItems) { + fileManager.save(fileItem, identifier + "_" + fileItem.getClientFileName()); + } + } + + /** + * 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..a62e49125e --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/FileUploadResourceReference.java @@ -0,0 +1,107 @@ +/* + * 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.Bytes; +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 page for things like getting the client file name and file size). + */ +public class FileUploadResourceReference extends ResourceReference +{ + + private final IUploadsFileManager uploadFileManager; + + private static FileUploadResourceReference i; + + public static FileUploadResourceReference getInstance() { + return i; + } + + public static FileUploadResourceReference createNewInstance(IUploadsFileManager fileManager, Bytes maxSize) + { + if (i == null) + { + i = new FileUploadResourceReference(fileManager, maxSize); + } + return i; + } + + private final Bytes maxSize; + + private FileUploadResourceReference(IUploadsFileManager uploadFileManager, Bytes maxSize) + { + super(FileUploadResourceReference.class, "file-uploads"); + + this.uploadFileManager = uploadFileManager; + this.maxSize = maxSize; + } + + @Override + public IResource getResource() + { + return new AbstractFileUploadResource(uploadFileManager) + { + @Override + protected Bytes getMaxSize() + { + return maxSize; + } + + @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(); + } + + }; + } + + 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/IUploadsFileManager.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java new file mode 100644 index 0000000000..9527460cfd --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/IUploadsFileManager.java @@ -0,0 +1,57 @@ +/* + * 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 org.apache.wicket.markup.html.form.upload.FileUpload; + +/** + * 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 identifier + * The identifier used for this file. + */ + void save(FileUpload fileItem, String identifier); + + /** + * Retrieves the file based on an identifier. + * + * @param identifier + * The identifier used for this file. + * @return File the file + */ + File getFile(String identifier); + + /** + * Deletes a file based on identifier. + * + * @param identifier + * The identifier + */ + void deleteFile(String identifier); +} diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.java new file mode 100644 index 0000000000..105766700b --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.java @@ -0,0 +1,62 @@ +/* + * 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 org.apache.wicket.Session; +import org.apache.wicket.markup.html.form.upload.FileUploadField; +import org.apache.wicket.model.IModel; + +public abstract class ResourceFileUploadField extends FileUploadField { + + private static abstract class FileModel implements IModel<File> { + + @Override + public File getObject() { + return fileManager().getFile(getIdentifier()); + } + + @Override + public void setObject(File object) { + // do thing + } + + protected abstract IUploadsFileManager fileManager(); + + protected abstract String getIdentifier(); + } + + public ResourceFileUploadField(String id) { + super(id); + setOutputMarkupId(true); + setMarkupId("WRFUF" + Session.get().getId() + System.currentTimeMillis()); + setDefaultModel(new FileModel() { + @Override + protected IUploadsFileManager fileManager() { + return ResourceFileUploadField.this.fileManager(); + } + + @Override + protected String getIdentifier() + { + return ResourceFileUploadField.this.getMarkupId(); + } + }); + } + + protected abstract IUploadsFileManager fileManager(); +} diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.js b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.js new file mode 100644 index 0000000000..861352eef5 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/resource/ResourceFileUploadField.js @@ -0,0 +1,91 @@ +/* + * 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.ResourceFileUploadField) === 'object') { + return; + } + + Wicket.ResourceFileUploadField = function (settings) + { + this.link = '#' + settings.link; + this.inputName = settings.inputName; + this.input = document.getElementById(this.inputName) + this.resourceUrl = settings.resourceUrl + "?uploadId=" + this.inputName; + this.ajaxCallBackUrl = settings.ajaxCallBackUrl; + this.onErrorCallBack = settings.onErrorCallBack; + this.connectionErrorCallBack = settings.connectionErrorCallBack; + this.uploadFinished = settings.uploadFinished; + } + + Wicket.ResourceFileUploadField.prototype.upload = function() + { + // we add the file (yes only one) to a FormData object. + let formData = new FormData(); + let file = this.input.files[0]; + formData.append("FILE-UPLOAD", file); + // we need to pass to mounted resource the ID of the file + // where the uploaded file will be stored. Later on JAVA code will + // use IFileManager#getFile(inputName) to retrieve the uploaded file. + // Java side makes sure this inputName is UNIQUE + // this is needed as the mounted resource is a singleton and have no state + formData.append("uploadId", this.inputName); + const self = this; + // we use jQuery to post the file to the resource (this.url) + // and we keep a reference to the request in order to be + // 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 + self.uploadFinished(); + if (res.error) { + const ep = {'upload-success': false}; + Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep}); + self.onErrorCallBack(res); + return; + } + // resource sent a JSON object with file client name + // we need this in page this we schedule a wicket (AJAX) request to the component + // UploadPanel passing the clientFileName and success = true + const ep = {'upload-success': true, 'clientFileName': res[0]['clientFileName']}; + Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep}); + }, + error: function (jqXHR, textStatus, errorThrown) { + // the thing failed. Tell UploadPanel about this. + self.uploadFinished(); + if (textStatus === "abort") { + const ep = {'upload-success': false, 'info':''}; + Wicket.Ajax.get({"u": self.ajaxCallBackUrl, "ep": ep}); + } else if (textStatus === "error"){ + // this happens if there is some connection error + self.connectionErrorCallBack(); + } else if (textStatus === "parsererror"){ + var data = jqXHR.responseText; + Wicket.Log.log(data); + } + } + }); + } +})(); \ No newline at end of file
