FileAsset file upload now works

pull/10/head
Jens Pelzetter 2021-05-27 20:31:57 +02:00
parent 0619a19ee2
commit 1bdddb5243
11 changed files with 215 additions and 147 deletions

View File

@ -71,6 +71,12 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.mvc</groupId>
<artifactId>javax.mvc-api</artifactId>

View File

@ -18,10 +18,16 @@
*/package org.librecms.assets;
import com.arsdigita.cms.ui.assets.forms.FileAssetForm;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.envers.Audited;
import org.librecms.ui.contentsections.assets.FileAssetCreateStep;
import org.librecms.ui.contentsections.assets.FileAssetEditStep;
import org.librecms.ui.contentsections.assets.MvcAssetEditKit;
import static org.librecms.CmsConstants.*;
import static org.librecms.assets.AssetConstants.*;
@ -39,6 +45,10 @@ import static org.librecms.assets.AssetConstants.*;
labelBundle = ASSETS_BUNDLE,
descriptionKey = "fileasset.description",
descriptionBundle = ASSETS_BUNDLE)
@MvcAssetEditKit(
createStep = FileAssetCreateStep.class,
editStep = FileAssetEditStep.class
)
public class FileAsset extends BinaryAsset implements Serializable {
private static final long serialVersionUID = -8195062456502964401L;

View File

@ -39,6 +39,7 @@ import org.librecms.ui.contentsections.assets.MvcAssetCreateStep;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -274,15 +275,21 @@ public class AssetFolderController {
assetFolderTree.buildFolderTree(section, folder)
);
contentSectionModel.setAvailableAssetTypes(
assetCreateSteps
.stream()
.collect(
Collectors.toMap(
step -> step.getAssetType(),
step -> step.getLabel()
)
contentSectionModel.setAvailableAssetTypes(assetCreateSteps
.stream()
.sorted(
(step1, step2) -> step1.getLabel().compareTo(
step2.getLabel()
)
)
.collect(
Collectors.toMap(
step -> step.getAssetType(),
step -> step.getLabel(),
(value1, value2) -> value1,
() -> new LinkedHashMap<>()
)
)
);
assetFolderModel.setRows(
@ -665,11 +672,11 @@ public class AssetFolderController {
row.setFolderPath(
folderManager
.getFolderPath(folder)
// .substring(
// folderManager
// .getFolderPath(section.getRootAssetsFolder())
// .length()
// )
// .substring(
// folderManager
// .getFolderPath(section.getRootAssetsFolder())
// .length()
// )
);
row.setName(entry.getDisplayName());
row.setTitle(

View File

@ -66,7 +66,7 @@ public class AssetUi {
) {
models.put("section", section.getLabel());
models.put("assetPath", assetPath);
return "/org/librecms/ui/contentsection/assets/asset-not-found.xhtml";
return "org/librecms/ui/contentsection/assets/asset-not-found.xhtml";
}
}

View File

@ -32,6 +32,7 @@ public class CmsAssetEditSteps implements MvcAssetEditSteps {
final Set<Class<?>> classes = new HashSet<>();
classes.add(BookmarkEditStep.class);
classes.add(FileAssetEditStep.class);
classes.add(LegalMetadataEditStep.class);
classes.add(PostalAddressEditStep.class);
classes.add(SideNoteEditStep.class);

View File

@ -20,6 +20,8 @@ package org.librecms.ui.contentsections.assets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.FileAsset;
@ -33,9 +35,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.activation.MimeType;
@ -44,8 +49,6 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
@ -54,8 +57,8 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
/**
*
@ -135,17 +138,22 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
);
editStepModel.setFileName(getFileAsset().getFileName());
editStepModel.setMimeType(getFileAsset().getMimeType().toString());
editStepModel.setMimeType(
Optional
.ofNullable(getFileAsset().getMimeType())
.map(MimeType::toString)
.orElse("")
);
editStepModel.setSize(getFileAsset().getSize());
final long size = getFileAsset().getSize();
if (size < 2048) {
editStepModel.setSizeLabel(String.format("%d Bytes", size));
} else if(size < 1024 * 1024) {
} else if (size < 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d kB", size / 1024)
);
} else if (size < 1024 * 1024 * 1024){
} else if (size < 1024 * 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d MB", size / (1024 * 1024))
);
@ -313,12 +321,12 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
@Consumes(MediaType.MULTIPART_FORM_DATA)
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeDescription(
public String uploadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@Context final HttpServletRequest request
final MultipartFormDataInput input
) {
try {
init();
@ -331,61 +339,54 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final FileAsset fileAsset = getFileAsset();
final Map<String, List<InputPart>> uploadForm = input
.getFormDataMap();
final List<InputPart> inputParts = uploadForm.get("fileData");
final Part part;
final String fileName;
try {
part = request.getPart("file");
final String contentDisposition = part.getHeader(
"content-disposition"
);
fileName = Arrays
.stream(contentDisposition.split(";"))
.filter(field -> field.startsWith("filename"))
.findAny()
.map(
field -> field
.substring(field.indexOf('=') + 1)
.trim().replace("\"", "")
).orElse("");
} catch (IOException | ServletException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
String fileName = "";
String contentType = "";
for (final InputPart inputPart : inputParts) {
try {
final MultivaluedMap<String, String> headers = inputPart
.getHeaders();
fileName = getFileName(headers);
contentType = getContentType(headers);
final byte[] bytes = new byte[1024];
try (InputStream inputStream = inputPart.getBody(
InputStream.class, null
);
ByteArrayOutputStream fileDataOutputStream
= new ByteArrayOutputStream()) {
while (inputStream.read(bytes) != -1) {
fileDataOutputStream.writeBytes(bytes);
}
final byte[] bytes = new byte[1024];
try (InputStream fileInputStream = part.getInputStream();
ByteArrayOutputStream fileDataOutputStream
= new ByteArrayOutputStream()) {
while (fileInputStream.read(bytes) != -1) {
fileDataOutputStream.writeBytes(bytes);
fileAsset.setData(fileDataOutputStream.toByteArray());
}
} catch (IOException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
fileAsset.setData(fileDataOutputStream.toByteArray());
} catch (IOException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
fileAsset.setFileName(fileName);
fileAsset.setSize(fileAsset.getData().length);
try (BufferedInputStream stream = new BufferedInputStream(
new ByteArrayInputStream(fileAsset.getData()))) {
fileAsset.setMimeType(
new MimeType(URLConnection
.guessContentTypeFromStream(stream))
try {
fileAsset.setMimeType(new MimeType(contentType));
} catch (MimeTypeParseException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
} catch (IOException | MimeTypeParseException ex) {
LOGGER.error("Failed to get file type.", ex);
models.put("failedToGetType", true);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
@ -400,4 +401,26 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
}
}
private String getFileName(final MultivaluedMap<String, String> headers) {
final String[] contentDisposition = headers
.getFirst("Content-Disposition")
.split(";");
for (final String fileName : contentDisposition) {
if (fileName.trim().startsWith("filename")) {
final String[] name = fileName.split("=");
return name[1].trim().replaceAll("\"", "");
}
}
return "";
}
private String getContentType(
final MultivaluedMap<String, String> headers
) {
return headers.getFirst("Content-Type");
}
}

View File

@ -37,7 +37,7 @@ public class FileAssetEditStepModel {
private Map<String, String> descriptionValues;
private List<String> descriptionLocales;
private List<String> unusedDescriptionLocales;
private String fileName;
@ -57,14 +57,14 @@ public class FileAssetEditStepModel {
this.descriptionValues = new HashMap<>(descriptionValues);
}
public List<String> getDescriptionLocales() {
return Collections.unmodifiableList(descriptionLocales);
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
protected void setUnusedDescriptionLocales(
final List<String> descriptionLocales
) {
this.descriptionLocales = new ArrayList<>(descriptionLocales);
this.unusedDescriptionLocales = new ArrayList<>(descriptionLocales);
}
public String getFileName() {

View File

@ -34,7 +34,7 @@
editDialogValueLabel="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.description.edit.value.label']}"
editMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/assets/#{CmsSelectedAssetModel.assetPath}/@fileasset-edit/description/edit"
editorId="description-editor"
hasUnusedLocales="#{!CmsFileAssetEditStep.unusedDescriptionLocales.isEmpty()}"
hasUnusedLocales="#{!CmsFileAssetEditStepModel.unusedDescriptionLocales.isEmpty()}"
headingLevel="3"
objectIdentifier="#{CmsSelectedAssetModel.assetPath}"
readOnly="#{!MvcAssetEditStepModel.canEdit}"
@ -45,83 +45,85 @@
removeDialogTitle="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.description.remove.title']}"
removeMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/assets/#{CmsSelectedAssetModel.assetPath}/@fileasset-edit/description/remove"
title="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.description.title']}"
unusedLocales="#{CmsFileAssetEditStep.unusedDescriptionLocales}"
unusedLocales="#{CmsFileAssetEditStepModel.unusedDescriptionLocales}"
useTextarea="true"
values="#{CmsFileAssetEditStep.descriptionValues}"
values="#{CmsFileAssetEditStepModel.descriptionValues}"
/>
</ui:define>
<h3>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.title']}</h3>
<c:if test="#{MvcAssetEditStepModel.canEdit}">
<div class="text-right">
<button class="btn btn-primary"
data-toggle="modal"
data-target="#file-upload-dialog"
type="button">
<bootstrap:svgIcon icon="upload" />
<span class="sr-only">#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.button.label']}</span>
</button>
</div>
<div aria-hidden="true"
aria-labelledby="file-upload-dialog-title"
class="modal fade"
id="file-upload-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/assets/#{CmsSelectedAssetModel.assetPath}/@fileasset-edit/upload"
enctype="multipart/formdata"
method="post">
<div class="modal-header">
<h4 class="modal-title"
id="file-upload-dialog-title">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.title']}
</h4>
<button
aria-label="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupFile
help="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.help']}"
inputId="fileData"
label="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.label']}"
name="fileData"
/>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
<h3>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.title']}</h3>
<c:if test="#{MvcAssetEditStepModel.canEdit}">
<div class="text-right">
<button class="btn btn-primary"
data-toggle="modal"
data-target="#file-upload-dialog"
type="button">
<bootstrap:svgIcon icon="upload" />
<span class="sr-only">#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.button.label']}</span>
</button>
</div>
<div aria-hidden="true"
aria-labelledby="file-upload-dialog-title"
class="modal fade"
id="file-upload-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/assets/#{CmsSelectedAssetModel.assetPath}/@fileasset-edit/upload"
class="modal-content"
enctype="multipart/form-data"
method="post">
<div class="modal-header">
<h4 class="modal-title"
id="file-upload-dialog-title">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.title']}
</h4>
<button
aria-label="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.close']}"
class="close"
data-dismiss="modal"
type="button">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.close']}
</button>
<button class="btn btn-success"
type="submit">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.submit']}
</button>
</div>
</form>
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupFile
help="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.help']}"
inputId="fileData"
label="#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.label']}"
name="fileData"
required="true"
/>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.close']}
</button>
<button class="btn btn-success"
type="submit">
#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.upload.submit']}
</button>
</div>
</form>
</div>
</div>
</div>
</c:if>
<dl>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.name']}</dt>
<dd>#{CmsFileAssetEditStepModel.fileName}</dd>
</div>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.type']}</dt>
<dd>#{CmsFileAssetEditStepModel.mimeType}</dd>
</div>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.size']}</dt>
<dd>#{CmsFileAssetEditStepModel.sizeLabel}</dd>
</div>
</dl>
</c:if>
<dl>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.name']}</dt>
<dd>#{CmsFileAssetEditStepModel.fileName}</dd>
</div>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.type']}</dt>
<dd>#{CmsFileAssetEditStepModel.mimeType}</dd>
</div>
<div>
<dt>#{CmsAssetsStepsDefaultMessagesBundle['fileasset.editstep.file.size']}</dt>
<dd>#{CmsFileAssetEditStepModel.sizeLabel}</dd>
</div>
</dl>
</ui:define>
</ui:composition>

View File

@ -207,3 +207,4 @@ fileasset.editstep.file.name=File name
fileasset.editstep.file.type=File Type
fileasset.editstep.file.size=File size
editstep.legalmetadata.rights.remove.submit=Remove
fileasset.editstep.file.upload.label=Upload file

View File

@ -207,3 +207,4 @@ fileasset.editstep.file.name=Dateiname
fileasset.editstep.file.type=Dateityp
fileasset.editstep.file.size=Gr\u00f6\u00dfe der Datei
editstep.legalmetadata.rights.remove.submit=Entfernen
fileasset.editstep.file.upload.label=Datei hochladen

17
pom.xml
View File

@ -534,6 +534,23 @@
<version>2.4.0-b180830.0438</version>
</dependency>
<!--
RESTeasy is used in Wildfly (primary target runtime) as
implementation of JAX-RS. Unfortunetly, multipart uploads are
not standardized in the JAX-RS, therefore we have to use
a vendor specific solution...
-->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-core</artifactId>
<version>4.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>3.14.0.Final</version>
</dependency>
<!--
Jakarta MVC is a thin layer ontop of JAX-RS providing a
MVC framework for Jakarta EE: https://www.mvc-spec.org