/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.gui.components.form.flexible.impl.elements;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.Normalizer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.services.image.Crop;
import org.olat.core.commons.services.image.ImageService;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemCollection;
import org.olat.core.gui.components.form.flexible.elements.FileElement;
import org.olat.core.gui.components.form.flexible.impl.Form;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormItemImpl;
import org.olat.core.gui.components.image.ImageFormItem;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.ControllerEventListener;
import org.olat.core.gui.control.Disposable;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.FileUtils;
import org.olat.core.util.UserSession;
import org.olat.core.util.Util;
import org.olat.core.util.ValidationStatus;
import org.olat.core.util.ValidationStatusImpl;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
/**
* <h3>Description:</h3>
* <p>
* Implementation of the file element. See the interface for more documentation.
* <p>
* The class implements the disposable interface to cleanup temporary files on
* form disposal.
* <p>
* Initial Date: 08.12.2008 <br>
*
* @author Florian Gnaegi, frentix GmbH, http://www.frentix.com
*/
public class FileElementImpl extends FormItemImpl
implements FileElement, FormItemCollection, ControllerEventListener, Disposable {
private static final OLog log = Tracing.createLoggerFor(FileElementImpl.class);
private final FileElementComponent component;
private ImageFormItem previewEl;
private File initialFile, tempUploadFile;
private Set<String> mimeTypes;
private long maxUploadSizeKB = UPLOAD_UNLIMITED;
private String uploadFilename;
private String uploadMimeType;
private boolean deleteEnabled;
private boolean confirmDelete;
private boolean checkForMaxFileSize = false;
private boolean checkForMimeTypes = false;
private boolean cropSelectionEnabled = false;
// error keys
private String i18nErrMandatory;
private String i18nErrMaxSize;
private String i18nErrMimeType;
private String[] i18nErrMaxSizeArgs;
private String[] i18nErrMimeTypeArgs;
private String fileExampleKey;
private String[] fileExampleParams;
private WindowControl wControl;
private DialogBoxController dialogCtr;
/**
* Constructor for a file element. Use the limitToMimeType and setter
* methods to configure the element
*
* @param name
*/
public FileElementImpl(WindowControl wControl, String name) {
super(name);
this.wControl = wControl;
component = new FileElementComponent(this);
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#evalFormRequest(org.olat.core.gui.UserRequest)
*/
@Override
public void evalFormRequest(UserRequest ureq) {
Form form = getRootForm();
String dispatchuri = form.getRequestParameter("dispatchuri");
if (dispatchuri != null && dispatchuri.equals(component.getFormDispatchId())) {
if ("delete".equals(form.getRequestParameter("delete"))) {
if (isConfirmDelete()) {
doConfirmDelete(ureq);
} else {
getRootForm().fireFormEvent(ureq,
new FileElementEvent(FileElementEvent.DELETE, this, FormEvent.ONCLICK));
}
}
}
Set<String> keys = form.getRequestMultipartFilesSet();
if (keys.size() > 0 && keys.contains(component.getFormDispatchId())) {
// Remove old files first
if (tempUploadFile != null && tempUploadFile.exists()) {
tempUploadFile.delete();
}
// Move file from a temporary request scope location to a location
// with a
// temporary form item scope. The file must be moved later using the
// moveUploadFileTo() method to the final destination.
tempUploadFile = new File(WebappHelper.getTmpDir(), CodeHelper.getUniqueID());
File tmpRequestFile = form.getRequestMultipartFile(component.getFormDispatchId());
// Move file to internal temp location
boolean success = tmpRequestFile.renameTo(tempUploadFile);
if (!success) {
// try to move file by copying it, command above might fail
// when source and target are on different volumes
FileUtils.copyFileToFile(tmpRequestFile, tempUploadFile, true);
}
uploadFilename = form.getRequestMultipartFileName(component.getFormDispatchId());
// prevent an issue with Firefox
uploadFilename = Normalizer.normalize(uploadFilename, Normalizer.Form.NFKC);
// use mime-type from file name to have deterministic mime types
uploadMimeType = WebappHelper.getMimeType(uploadFilename);
if (uploadMimeType == null) {
// use browser mime type as fallback if unknown
uploadMimeType = form.getRequestMultipartFileMimeType(component.getFormDispatchId());
}
if (uploadMimeType == null) {
// use application fallback for worst case
uploadMimeType = "application/octet-stream";
}
if (previewEl != null && uploadMimeType != null
&& (uploadMimeType.startsWith("image/") || uploadMimeType.startsWith("video/"))) {
VFSLeaf media = new LocalFileImpl(tempUploadFile);
previewEl.setMedia(media, uploadMimeType);
previewEl.setCropSelectionEnabled(cropSelectionEnabled);
previewEl.setMaxWithAndHeightToFitWithin(300, 200);
previewEl.setVisible(true);
}
// Mark associated component dirty, that it gets rerendered
component.setDirty(true);
}
}
@Override
public void dispatchEvent(UserRequest ureq, Controller source, Event event) {
if (DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
getRootForm().fireFormEvent(ureq, new FileElementEvent(FileElementEvent.DELETE, this, FormEvent.ONCLICK));
}
}
private void doConfirmDelete(UserRequest ureq) {
Translator fileTranslator = Util.createPackageTranslator(FileElementImpl.class, ureq.getLocale(), getTranslator());
String title = fileTranslator.translate("confirm.delete.file.title");
String text = fileTranslator.translate("confirm.delete.file");
dialogCtr = DialogBoxUIFactory.createOkCancelDialog(ureq, wControl, title, text);
dialogCtr.addControllerListener(this);
dialogCtr.activate();
}
@Override
public Iterable<FormItem> getFormItems() {
if (previewEl != null) {
return Collections.<FormItem> singletonList(previewEl);
}
return Collections.emptyList();
}
@Override
public FormItem getFormComponent(String name) {
if (previewEl != null && previewEl.getName().equals(name)) {
return previewEl;
}
return null;
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#getFormItemComponent()
*/
@Override
protected Component getFormItemComponent() {
return component;
}
protected ImageFormItem getPreviewFormItem() {
return previewEl;
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#reset()
*/
@Override
public void reset() {
if (tempUploadFile != null && tempUploadFile.exists()) {
tempUploadFile.delete();
}
tempUploadFile = null;
if (previewEl != null) {
if (initialFile != null) {
VFSLeaf media = new LocalFileImpl(initialFile);
previewEl.setMedia(media);
previewEl.setMaxWithAndHeightToFitWithin(300, 200);
previewEl.setVisible(true);
} else if (previewEl != null) {
previewEl.setVisible(false);
}
}
uploadFilename = null;
uploadMimeType = null;
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#rootFormAvailable()
*/
@Override
protected void rootFormAvailable() {
if (previewEl != null && previewEl.getRootForm() != getRootForm()) {
previewEl.setRootForm(getRootForm());
}
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#setMandatory(boolean,
* java.lang.String)
*/
@Override
public void setMandatory(boolean mandatory, String i18nErrKey) {
super.setMandatory(mandatory);
this.i18nErrMandatory = i18nErrKey;
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormItemImpl#validate(java.util.List)
*/
@Override
public void validate(List<ValidationStatus> validationResults) {
int lastFormError = getRootForm().getLastRequestError();
if (lastFormError == Form.REQUEST_ERROR_UPLOAD_LIMIT_EXCEEDED) {
// check if total upload limit is exceeded (e.g. sum of files)
setErrorKey(i18nErrMaxSize, i18nErrMaxSizeArgs);
validationResults.add(new ValidationStatusImpl(ValidationStatus.ERROR));
return;
// check for a general error
} else if (lastFormError == Form.REQUEST_ERROR_GENERAL) {
setErrorKey("file.element.error.general", null);
validationResults.add(new ValidationStatusImpl(ValidationStatus.ERROR));
return;
// check if uploaded at all
} else if (isMandatory() && ((initialFile == null && (tempUploadFile == null || !tempUploadFile.exists()))
|| (initialFile != null && tempUploadFile != null && !tempUploadFile.exists()))) {
setErrorKey(i18nErrMandatory, null);
validationResults.add(new ValidationStatusImpl(ValidationStatus.ERROR));
return;
// check for file size of current file
} else if (checkForMaxFileSize && tempUploadFile != null && tempUploadFile.exists()
&& tempUploadFile.length() > maxUploadSizeKB * 1024l) {
setErrorKey(i18nErrMaxSize, i18nErrMaxSizeArgs);
validationResults.add(new ValidationStatusImpl(ValidationStatus.ERROR));
return;
// check for mime types
} else if (checkForMimeTypes && tempUploadFile != null && tempUploadFile.exists()) {
boolean found = false;
if (uploadMimeType != null) {
for (String validType : mimeTypes) {
if (validType.equals(uploadMimeType)) {
// exact match: image/jpg
found = true;
break;
} else if (validType.endsWith("/*")) {
// wildcard match: image/*
if (uploadMimeType != null
&& uploadMimeType.startsWith(validType.substring(0, validType.length() - 2))) {
found = true;
break;
}
}
}
}
if (!found) {
setErrorKey(i18nErrMimeType, i18nErrMimeTypeArgs);
validationResults.add(new ValidationStatusImpl(ValidationStatus.ERROR));
return;
}
}
// No error, clear errors from previous attempts
clearError();
}
@Override
public String getExampleText() {
if(fileExampleKey != null) {
if(fileExampleParams != null) {
return translator.translate(fileExampleKey, fileExampleParams);
}
return translator.translate(fileExampleKey);
}
return null;
}
@Override
public void setExampleKey(String exampleKey, String[] params) {
this.fileExampleKey = exampleKey;
this.fileExampleParams = params;
}
@Override
public void setPreview(UserSession usess, boolean enable) {
if (enable) {
previewEl = new ImageFormItem(usess, this.getName() + "_PREVIEW");
previewEl.setRootForm(getRootForm());
} else {
previewEl = null;
}
}
@Override
public void setCropSelectionEnabled(boolean enable) {
this.cropSelectionEnabled = enable;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#setInitialFile(java.io.File)
*/
@Override
public void setInitialFile(File initialFile) {
this.initialFile = initialFile;
if (initialFile != null && previewEl != null) {
VFSLeaf media = new LocalFileImpl(initialFile);
previewEl.setMedia(media);
previewEl.setMaxWithAndHeightToFitWithin(300, 200);
previewEl.setVisible(true);
} else if (previewEl != null) {
previewEl.setVisible(false);
}
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getInitialFile()
*/
public File getInitialFile() {
return initialFile;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#limitToMimeType(java.util.Set,
* java.lang.String, java.lang.String[])
*/
public void limitToMimeType(Set<String> mimeTypes, String i18nErrKey, String[] i18nArgs) {
this.mimeTypes = mimeTypes;
this.checkForMimeTypes = true;
this.i18nErrMimeType = i18nErrKey;
this.i18nErrMimeTypeArgs = i18nArgs;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getMimeTypeLimitations()
*/
public Set<String> getMimeTypeLimitations() {
if (mimeTypes == null)
mimeTypes = new HashSet<String>();
return mimeTypes;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#setMaxUploadSizeKB(int,
* java.lang.String, java.lang.String[])
*/
public void setMaxUploadSizeKB(long maxUploadSizeKB, String i18nErrKey, String[] i18nArgs) {
this.maxUploadSizeKB = maxUploadSizeKB;
this.checkForMaxFileSize = (maxUploadSizeKB == UPLOAD_UNLIMITED ? false : true);
this.i18nErrMaxSize = i18nErrKey;
this.i18nErrMaxSizeArgs = i18nArgs;
}
/**
* @see org.olat.core.gui.components.form.flexible.FormMultipartItem#getMaxUploadSizeKB()
*/
public long getMaxUploadSizeKB() {
return maxUploadSizeKB;
}
@Override
public boolean isDeleteEnabled() {
return deleteEnabled;
}
@Override
public void setDeleteEnabled(boolean deleteEnabled) {
this.deleteEnabled = deleteEnabled;
}
public boolean isConfirmDelete() {
return confirmDelete;
}
public void setConfirmDelete(boolean confirmDelete) {
this.confirmDelete = confirmDelete;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#isUploadSuccess()
*/
public boolean isUploadSuccess() {
if (tempUploadFile != null && tempUploadFile.exists()) {
return true;
}
return false;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getUploadFileName()
*/
public String getUploadFileName() {
return uploadFilename;
}
@Override
public void setUploadFileName(String uploadFileName) {
this.uploadFilename = uploadFileName;
this.uploadMimeType = WebappHelper.getMimeType(uploadFilename);
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getUploadMimeType()
*/
public String getUploadMimeType() {
return uploadMimeType;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getUploadFile()
*/
public File getUploadFile() {
return tempUploadFile;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getUploadInputStream()
*/
public InputStream getUploadInputStream() {
if (tempUploadFile == null)
return null;
try {
return new FileInputStream(tempUploadFile);
} catch (FileNotFoundException e) {
log.error("Could not open stream for file element::" + getName(), e);
}
return null;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#getUploadSize()
*/
public long getUploadSize() {
if (tempUploadFile != null && tempUploadFile.exists()) {
return tempUploadFile.length();
} else if (initialFile != null && initialFile.exists()) {
return initialFile.length();
} else {
return 0;
}
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#moveUploadFileTo(java.io.File)
*/
public File moveUploadFileTo(File destinationDir) {
if (tempUploadFile != null && tempUploadFile.exists()) {
destinationDir.mkdirs();
// Check if such a file does already exist, if yes rename new file
File existsFile = new File(destinationDir, uploadFilename);
if (existsFile.exists()) {
// Use standard rename policy
File tmpF = new File(uploadFilename);
uploadFilename = FileUtils.rename(tmpF);
}
// Move file now
File targetFile = new File(destinationDir, uploadFilename);
if (FileUtils.copyFileToFile(tempUploadFile, targetFile, true)) {
return targetFile;
}
}
return null;
}
/**
* @see org.olat.core.gui.components.form.flexible.elements.FileElement#moveUploadFileTo(org.olat.core.util.vfs.VFSContainer)
*/
@Override
public VFSLeaf moveUploadFileTo(VFSContainer destinationContainer) {
return moveUploadFileTo(destinationContainer, false);
}
@Override
public VFSLeaf moveUploadFileTo(VFSContainer destinationContainer, boolean crop) {
VFSLeaf targetLeaf = null;
if (tempUploadFile != null && tempUploadFile.exists()) {
// Check if such a file does already exist, if yes rename new file
VFSItem existsChild = destinationContainer.resolve(uploadFilename);
if (existsChild != null) {
// Use standard rename policy
uploadFilename = VFSManager.rename(destinationContainer, uploadFilename);
}
// Create target leaf file now and delete original temp file
if (destinationContainer instanceof LocalFolderImpl) {
// Optimize for local files (don't copy, move instead)
LocalFolderImpl folderContainer = (LocalFolderImpl) destinationContainer;
File destinationDir = folderContainer.getBasefile();
File targetFile = new File(destinationDir, uploadFilename);
Crop cropSelection = previewEl == null ? null : previewEl.getCropSelection();
if (crop && cropSelection != null) {
CoreSpringFactory.getImpl(ImageService.class).cropImage(tempUploadFile, targetFile, cropSelection);
targetLeaf = (VFSLeaf) destinationContainer.resolve(targetFile.getName());
} else if (FileUtils.copyFileToFile(tempUploadFile, targetFile, true)) {
targetLeaf = (VFSLeaf) destinationContainer.resolve(targetFile.getName());
} else {
log.error("Error after copying content from temp file, cannot copy file::"
+ (tempUploadFile == null ? "NULL" : tempUploadFile) + " - "
+ (targetFile == null ? "NULL" : targetFile), null);
}
if (targetLeaf == null) {
log.error("Error after copying content from temp file, cannot resolve copied file::"
+ (tempUploadFile == null ? "NULL" : tempUploadFile) + " - "
+ (targetFile == null ? "NULL" : targetFile), null);
}
} else {
// Copy stream in case the destination is a non-local container
VFSLeaf leaf = destinationContainer.createChildLeaf(uploadFilename);
boolean success = false;
try {
success = VFSManager.copyContent(new FileInputStream(tempUploadFile), leaf);
} catch (FileNotFoundException e) {
log.error("Error while copying content from temp file::"
+ (tempUploadFile == null ? "NULL" : tempUploadFile.getAbsolutePath()), e);
}
if (success) {
// Delete original temp file after copy to simulate move
// behavior
tempUploadFile.delete();
targetLeaf = leaf;
}
}
} else if (log.isDebug()) {
log.debug("Error while copying content from temp file, no temp file::"
+ (tempUploadFile == null ? "NULL" : tempUploadFile.getAbsolutePath()));
}
return targetLeaf;
}
/**
* @see org.olat.core.gui.control.Disposable#dispose()
*/
public void dispose() {
if (tempUploadFile != null && tempUploadFile.exists()) {
tempUploadFile.delete();
}
}
}