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> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>javax.mvc</groupId> <groupId>javax.mvc</groupId>
<artifactId>javax.mvc-api</artifactId> <artifactId>javax.mvc-api</artifactId>

View File

@ -18,10 +18,16 @@
*/package org.librecms.assets; */package org.librecms.assets;
import com.arsdigita.cms.ui.assets.forms.FileAssetForm; import com.arsdigita.cms.ui.assets.forms.FileAssetForm;
import java.io.Serializable; import java.io.Serializable;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import org.hibernate.envers.Audited; 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.CmsConstants.*;
import static org.librecms.assets.AssetConstants.*; import static org.librecms.assets.AssetConstants.*;
@ -39,6 +45,10 @@ import static org.librecms.assets.AssetConstants.*;
labelBundle = ASSETS_BUNDLE, labelBundle = ASSETS_BUNDLE,
descriptionKey = "fileasset.description", descriptionKey = "fileasset.description",
descriptionBundle = ASSETS_BUNDLE) descriptionBundle = ASSETS_BUNDLE)
@MvcAssetEditKit(
createStep = FileAssetCreateStep.class,
editStep = FileAssetEditStep.class
)
public class FileAsset extends BinaryAsset implements Serializable { public class FileAsset extends BinaryAsset implements Serializable {
private static final long serialVersionUID = -8195062456502964401L; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -274,15 +275,21 @@ public class AssetFolderController {
assetFolderTree.buildFolderTree(section, folder) assetFolderTree.buildFolderTree(section, folder)
); );
contentSectionModel.setAvailableAssetTypes( contentSectionModel.setAvailableAssetTypes(assetCreateSteps
assetCreateSteps .stream()
.stream() .sorted(
.collect( (step1, step2) -> step1.getLabel().compareTo(
Collectors.toMap( step2.getLabel()
step -> step.getAssetType(),
step -> step.getLabel()
)
) )
)
.collect(
Collectors.toMap(
step -> step.getAssetType(),
step -> step.getLabel(),
(value1, value2) -> value1,
() -> new LinkedHashMap<>()
)
)
); );
assetFolderModel.setRows( assetFolderModel.setRows(
@ -665,11 +672,11 @@ public class AssetFolderController {
row.setFolderPath( row.setFolderPath(
folderManager folderManager
.getFolderPath(folder) .getFolderPath(folder)
// .substring( // .substring(
// folderManager // folderManager
// .getFolderPath(section.getRootAssetsFolder()) // .getFolderPath(section.getRootAssetsFolder())
// .length() // .length()
// ) // )
); );
row.setName(entry.getDisplayName()); row.setName(entry.getDisplayName());
row.setTitle( row.setTitle(

View File

@ -66,7 +66,7 @@ public class AssetUi {
) { ) {
models.put("section", section.getLabel()); models.put("section", section.getLabel());
models.put("assetPath", assetPath); 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<>(); final Set<Class<?>> classes = new HashSet<>();
classes.add(BookmarkEditStep.class); classes.add(BookmarkEditStep.class);
classes.add(FileAssetEditStep.class);
classes.add(LegalMetadataEditStep.class); classes.add(LegalMetadataEditStep.class);
classes.add(PostalAddressEditStep.class); classes.add(PostalAddressEditStep.class);
classes.add(SideNoteEditStep.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.LogManager;
import org.apache.logging.log4j.Logger; 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.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.FileAsset; import org.librecms.assets.FileAsset;
@ -33,9 +35,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.Arrays; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.activation.MimeType; import javax.activation.MimeType;
@ -44,8 +49,6 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.mvc.Controller; import javax.mvc.Controller;
import javax.mvc.Models; import javax.mvc.Models;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part; import javax.servlet.http.Part;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -54,8 +57,8 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; 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.setFileName(getFileAsset().getFileName());
editStepModel.setMimeType(getFileAsset().getMimeType().toString()); editStepModel.setMimeType(
Optional
.ofNullable(getFileAsset().getMimeType())
.map(MimeType::toString)
.orElse("")
);
editStepModel.setSize(getFileAsset().getSize()); editStepModel.setSize(getFileAsset().getSize());
final long size = getFileAsset().getSize(); final long size = getFileAsset().getSize();
if (size < 2048) { if (size < 2048) {
editStepModel.setSizeLabel(String.format("%d Bytes", size)); editStepModel.setSizeLabel(String.format("%d Bytes", size));
} else if(size < 1024 * 1024) { } else if (size < 1024 * 1024) {
editStepModel.setSizeLabel( editStepModel.setSizeLabel(
String.format("%d kB", size / 1024) String.format("%d kB", size / 1024)
); );
} else if (size < 1024 * 1024 * 1024){ } else if (size < 1024 * 1024 * 1024) {
editStepModel.setSizeLabel( editStepModel.setSizeLabel(
String.format("%d MB", size / (1024 * 1024)) String.format("%d MB", size / (1024 * 1024))
); );
@ -313,12 +321,12 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
@Consumes(MediaType.MULTIPART_FORM_DATA) @Consumes(MediaType.MULTIPART_FORM_DATA)
@AuthorizationRequired @AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String removeDescription( public String uploadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM) @PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier, final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME) @PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath, final String assetPath,
@Context final HttpServletRequest request final MultipartFormDataInput input
) { ) {
try { try {
init(); init();
@ -331,61 +339,54 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
if (assetPermissionsChecker.canEditAsset(getAsset())) { if (assetPermissionsChecker.canEditAsset(getAsset())) {
final FileAsset fileAsset = getFileAsset(); final FileAsset fileAsset = getFileAsset();
final Map<String, List<InputPart>> uploadForm = input
.getFormDataMap();
final List<InputPart> inputParts = uploadForm.get("fileData");
final Part part; final Part part;
final String fileName; String fileName = "";
try { String contentType = "";
part = request.getPart("file"); for (final InputPart inputPart : inputParts) {
final String contentDisposition = part.getHeader( try {
"content-disposition" final MultivaluedMap<String, String> headers = inputPart
); .getHeaders();
fileName = Arrays fileName = getFileName(headers);
.stream(contentDisposition.split(";")) contentType = getContentType(headers);
.filter(field -> field.startsWith("filename")) final byte[] bytes = new byte[1024];
.findAny() try (InputStream inputStream = inputPart.getBody(
.map( InputStream.class, null
field -> field );
.substring(field.indexOf('=') + 1) ByteArrayOutputStream fileDataOutputStream
.trim().replace("\"", "") = new ByteArrayOutputStream()) {
).orElse(""); while (inputStream.read(bytes) != -1) {
} catch (IOException | ServletException ex) { fileDataOutputStream.writeBytes(bytes);
LOGGER.error( }
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
final byte[] bytes = new byte[1024]; fileAsset.setData(fileDataOutputStream.toByteArray());
try (InputStream fileInputStream = part.getInputStream(); }
ByteArrayOutputStream fileDataOutputStream
= new ByteArrayOutputStream()) { } catch (IOException ex) {
while (fileInputStream.read(bytes) != -1) { LOGGER.error(
fileDataOutputStream.writeBytes(bytes); "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.setFileName(fileName);
fileAsset.setSize(fileAsset.getData().length); fileAsset.setSize(fileAsset.getData().length);
try (BufferedInputStream stream = new BufferedInputStream( try {
new ByteArrayInputStream(fileAsset.getData()))) { fileAsset.setMimeType(new MimeType(contentType));
fileAsset.setMimeType( } catch (MimeTypeParseException ex) {
new MimeType(URLConnection LOGGER.error(
.guessContentTypeFromStream(stream)) "Failed to upload file for FileAsset {}:", assetPath
); );
} catch (IOException | MimeTypeParseException ex) { LOGGER.error(ex);
LOGGER.error("Failed to get file type.", ex);
models.put("failedToGetType", true); models.put("uploadFailed", true);
return buildRedirectPathForStep(); 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 Map<String, String> descriptionValues;
private List<String> descriptionLocales; private List<String> unusedDescriptionLocales;
private String fileName; private String fileName;
@ -57,14 +57,14 @@ public class FileAssetEditStepModel {
this.descriptionValues = new HashMap<>(descriptionValues); this.descriptionValues = new HashMap<>(descriptionValues);
} }
public List<String> getDescriptionLocales() { public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(descriptionLocales); return Collections.unmodifiableList(unusedDescriptionLocales);
} }
protected void setUnusedDescriptionLocales( protected void setUnusedDescriptionLocales(
final List<String> descriptionLocales final List<String> descriptionLocales
) { ) {
this.descriptionLocales = new ArrayList<>(descriptionLocales); this.unusedDescriptionLocales = new ArrayList<>(descriptionLocales);
} }
public String getFileName() { public String getFileName() {

View File

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

View File

@ -207,3 +207,4 @@ fileasset.editstep.file.name=File name
fileasset.editstep.file.type=File Type fileasset.editstep.file.type=File Type
fileasset.editstep.file.size=File size fileasset.editstep.file.size=File size
editstep.legalmetadata.rights.remove.submit=Remove 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.type=Dateityp
fileasset.editstep.file.size=Gr\u00f6\u00dfe der Datei fileasset.editstep.file.size=Gr\u00f6\u00dfe der Datei
editstep.legalmetadata.rights.remove.submit=Entfernen 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> <version>2.4.0-b180830.0438</version>
</dependency> </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 Jakarta MVC is a thin layer ontop of JAX-RS providing a
MVC framework for Jakarta EE: https://www.mvc-spec.org MVC framework for Jakarta EE: https://www.mvc-spec.org