Skip to content

Commit

Permalink
NXP-15638: handle Part conversion to serializable Blob for state mana…
Browse files Browse the repository at this point in the history
…gement in case of validation error, or within lists, and avoid crash on file submit inside an ajax request (display a specific validation error instead) + add non regression test for validation error use case
  • Loading branch information
atchertchian authored and nuxeojenkins committed Dec 19, 2014
1 parent c76165d commit a6490f2
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
*/
package org.nuxeo.ftest.cap;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import java.util.Date;

import org.junit.Test;
import org.nuxeo.functionaltests.AbstractTest;
import org.nuxeo.functionaltests.pages.DocumentBasePage;
import org.nuxeo.functionaltests.pages.FileDocumentBasePage;
import org.nuxeo.functionaltests.pages.forms.FileCreationFormPage;

/**
* Test file upload in Nuxeo DM.
Expand All @@ -38,7 +42,8 @@ public void testFileUpload() throws Exception {
DocumentBasePage defaultDomainPage = login();

// Init repository with a test Workspace
DocumentBasePage testWorkspacePage = initRepository(defaultDomainPage);
String wsTitle = "WorkspaceTitle_" + new Date().getTime();
DocumentBasePage testWorkspacePage = createWorkspace(defaultDomainPage, wsTitle, "");

// Create a File with an uploaded blob
String filePrefix = "NX-Webdriver-test-";
Expand All @@ -51,9 +56,58 @@ public void testFileUpload() throws Exception {
uploadedFileName.contains(filePrefix));

// Clean up repository
cleanRepository(fileDocumentBasePage);
deleteWorkspace(fileDocumentBasePage, wsTitle);

// Logout
logout();
}

/**
* Non-regression test for NXP-15638
*
* @since 7.1
*/
@Test
public void testFileUploadOnValidationError() throws Exception {

// Login as Administrator
DocumentBasePage defaultDomainPage = login();

// Init repository with a test Workspace
String wsTitle = "WorkspaceTitle_" + new Date().getTime();
DocumentBasePage testWorkspacePage = createWorkspace(defaultDomainPage, wsTitle, "");

// Create a File with an uploaded blob
String filePrefix = "NX-Webdriver-test-";
// do not fill the title: expect a validation error to occur
FileCreationFormPage fileCreationFormPage = testWorkspacePage.getContentTab().getDocumentCreatePage("File",
FileCreationFormPage.class);
FileCreationFormPage creationPageAfterError = fileCreationFormPage.createFileDocumentWithoutTitle(filePrefix,
".txt", "Webdriver test file content.");

// Check validation error
assertEquals("Value is required", creationPageAfterError.getTitleMessage());

// Check file is still there and filename is present
assertEquals("tempKeep", creationPageAfterError.getSelectedOption());
String filename = creationPageAfterError.getSelectedFilename();
assertNotNull(filename);
assertTrue("Wrong uploaded file name '" + filename + "', expected it to contain '" + filePrefix + "'",
filename.contains(filePrefix));

creationPageAfterError.titleTextInput.sendKeys("File title");
creationPageAfterError.create();
FileDocumentBasePage fileDocumentBasePage = asPage(FileDocumentBasePage.class);
// Check uploaded file name
String uploadedFileName = fileDocumentBasePage.getFileSummaryTab().getMainContentFileText();
assertTrue("Wrong uploaded file name '" + uploadedFileName + "', expected it to contain '" + filePrefix + "'",
uploadedFileName.contains(filePrefix));

// Clean up repository
deleteWorkspace(fileDocumentBasePage, wsTitle);

// Logout
logout();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ public FileWidgetElement(WebDriver driver, String id) {
super(driver, id);
}

enum InputFileChoice {
none, keep, upload, delete, tempKeep,
}

/**
* @since 7.1
*/
public String getEditChoice() {
for (InputFileChoice choice : InputFileChoice.values()) {
String subid = "choice" + choice.name();
if (hasSubElement(subid) && getSubElement(subid).isSelected()) {
return choice.name();
}
}
return null;
}

public String getFilename(boolean isEdit) {
WebElement link;
if (isEdit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class DublinCoreCreationDocumentFormPage extends AbstractPage {
@FindBy(id = "document_create:nxl_heading:nxw_title")
public WebElement titleTextInput;

@FindBy(id = "document_create:nxl_heading:nxw_title_message")
public WebElement titleTextInputMessage;

@Required
@FindBy(id = "document_create:nxl_heading:nxw_description")
public WebElement descriptionTextInput;
Expand Down Expand Up @@ -59,4 +62,11 @@ public DocumentBasePage createDocument(String title, String description) {
return asPage(DocumentBasePage.class);
}

/**
* @since 7.1
*/
public String getTitleMessage() {
return titleTextInputMessage == null ? null : titleTextInputMessage.getText();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
*/
public class FileCreationFormPage extends DublinCoreCreationDocumentFormPage {

/**
* @param driver
*/
public FileCreationFormPage(WebDriver driver) {
super(driver);
}
Expand All @@ -42,14 +39,49 @@ public FileDocumentBasePage createFileDocument(String title, String description,
descriptionTextInput.sendKeys(description);

if (uploadBlob) {
LayoutElement layout = new LayoutElement(driver, "document_create:nxl_file");
// on file document, a widget template is used => standard file
// widget is wrapped, hence the duplicate nxw_file id
FileWidgetElement fileWidget = layout.getWidget("nxw_file:nxw_file_file", FileWidgetElement.class);
fileWidget.uploadTestFile(filePrefix, fileSuffix, fileContent);
uploadBlob(filePrefix, fileSuffix, fileContent);
}

create();
return asPage(FileDocumentBasePage.class);
}

protected FileWidgetElement getFileWidgetElement() {
LayoutElement layout = new LayoutElement(driver, "document_create:nxl_file");
// on file document, a widget template is used => standard file
// widget is wrapped, hence the duplicate nxw_file id
return layout.getWidget("nxw_file:nxw_file_file", FileWidgetElement.class);
}

protected void uploadBlob(String filePrefix, String fileSuffix, String fileContent) throws IOException {
FileWidgetElement fileWidget = getFileWidgetElement();
fileWidget.uploadTestFile(filePrefix, fileSuffix, fileContent);
}

/**
* @since 7.1
*/
public FileCreationFormPage createFileDocumentWithoutTitle(String filePrefix, String fileSuffix, String fileContent)
throws IOException {
uploadBlob(filePrefix, fileSuffix, fileContent);
create();
return asPage(FileCreationFormPage.class);
}

/**
* @since 7.1
*/
public String getSelectedOption() {
FileWidgetElement fileWidget = getFileWidgetElement();
return fileWidget.getEditChoice();
}

/**
* @since 7.1
*/
public String getSelectedFilename() {
FileWidgetElement fileWidget = getFileWidgetElement();
return fileWidget.getFilename(true);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Anahide Tchertchian
*/
package org.nuxeo.ecm.platform.ui.web.component.file;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException;
import org.nuxeo.ecm.core.api.impl.blob.StringBlob;
import org.nuxeo.ecm.platform.ui.web.util.files.FileUtils;

import com.sun.faces.renderkit.html_basic.FileRenderer;

/**
* Renderer for file input.
* <p>
* Overrides the base JSF renderer for files to ignore error when submitting file inside a non multipart form.
* <p>
* Component {@link UIInputFile} will handle error message management in UI, if validation phase occurs.
*
* @since 7.1
*/
public class NXFileRenderer extends FileRenderer {

public static final String RENDERER_TYPE = "javax.faces.NXFile";

@Override
public void decode(FacesContext context, UIComponent component) {

rendererParamsNotNull(context, component);

if (!shouldDecode(component)) {
return;
}

String clientId = decodeBehaviors(context, component);

if (clientId == null) {
clientId = component.getClientId(context);
}

assert (clientId != null);
ExternalContext externalContext = context.getExternalContext();
Map<String, String> requestMap = externalContext.getRequestParameterMap();

if (requestMap.containsKey(clientId)) {
setSubmittedValue(component, requestMap.get(clientId));
}

HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
try {
Collection<Part> parts = request.getParts();
for (Part cur : parts) {
if (clientId.equals(cur.getName())) {
// Nuxeo patch: transform into serializable blob right away, and do not set component transient
// component.setTransient(true);
// setSubmittedValue(component, cur);
String filename = FileUtils.retrieveFilename(cur);
String mimetype = cur.getContentType();
setSubmittedValue(component,
FileUtils.createSerializableBlob(cur.getInputStream(), filename, mimetype));
}
}
} catch (IOException ioe) {
throw new FacesException(ioe);
} catch (ServletException se) {
Throwable cause = se.getCause();
// Nuxeo specific error management
if ((cause instanceof InvalidContentTypeException)
|| (cause != null && cause.getClass().getName().contains("InvalidContentTypeException"))) {
setSubmittedValue(component, new StringBlob(""));
} else {
throw new FacesException(se);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.impl.blob.StringBlob;
import org.nuxeo.ecm.platform.ui.web.application.NuxeoResponseStateManagerImpl;
import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
import org.nuxeo.ecm.platform.ui.web.util.files.FileUtils;
import org.nuxeo.runtime.api.Framework;

import com.sun.faces.util.MessageFactory;
Expand Down Expand Up @@ -97,7 +99,8 @@ public UIInputFile() {
app.createComponent(UIOutputFile.COMPONENT_TYPE));
ComponentUtils.initiateSubComponent(this, EDIT_FILENAME_FACET_NAME,
app.createComponent(HtmlInputText.COMPONENT_TYPE));
ComponentUtils.initiateSubComponent(this, UPLOAD_FACET_NAME, app.createComponent(HtmlInputFile.COMPONENT_TYPE));
ComponentUtils.initiateSubComponent(this, UPLOAD_FACET_NAME,
app.createComponent(faces, HtmlInputFile.COMPONENT_TYPE, NXFileRenderer.RENDERER_TYPE));
}

// component will render itself
Expand Down Expand Up @@ -381,57 +384,20 @@ public void validateBlob(FacesContext context, InputFileInfo submitted) throws V
if (uploadFacet instanceof HtmlInputFile) {
HtmlInputFile uploadComp = (HtmlInputFile) uploadFacet;
Object submittedFile = uploadComp.getSubmittedValue();
if (submittedFile instanceof Part) {
Part file = (Part) submittedFile;
try {
submitted.setBlob(file.getInputStream());
} catch (IOException e) {
ComponentUtils.addErrorMessage(context, this, e.getMessage());
if (submittedFile instanceof Blob) {
Blob sblob = (Blob) submittedFile;
if (sblob.getLength() == 0) {
String message = context.getPartialViewContext().isAjaxRequest() ? InputFileInfo.INVALID_WITH_AJAX_MESSAGE
: InputFileInfo.INVALID_FILE_MESSAGE;
ComponentUtils.addErrorMessage(context, this, message);
setValid(false);
return;
}
submitted.setFilename(retrieveFilename(file));
submitted.setMimeType(file.getContentType());
}
Blob blob = null;
try {
blob = submitted.getConvertedBlob();
} catch (ConverterException ce) {
ComponentUtils.addErrorMessage(context, this, ce.getMessage());
setValid(false);
return;
}
if (blob == null) {
Map<String, String> requestParameters = context.getExternalContext().getRequestParameterMap();
String message = requestParameters.containsKey("AJAXREQUEST") ? InputFileInfo.INVALID_WITH_AJAX_MESSAGE
: InputFileInfo.INVALID_FILE_MESSAGE;
ComponentUtils.addErrorMessage(context, this, message);
setValid(false);
return;
}
// get new filename
String filename;
try {
filename = submitted.getConvertedFilename();
} catch (ConverterException ce) {
ComponentUtils.addErrorMessage(context, this, ce.getMessage());
setValid(false);
return;
}
submitted.setBlob(blob);
submitted.setFilename(filename);
}
}

// protected method waiting for servlet-api improvements
protected String retrieveFilename(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return filename;
submitted.setBlob(sblob);
submitted.setFilename(sblob.getFilename());
submitted.setMimeType(sblob.getMimeType());
}
}
return null;
}

public void updateFilename(FacesContext context, String newFilename) {
Expand Down
Loading

0 comments on commit a6490f2

Please sign in to comment.