All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wings.SFileChooser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings;

import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wings.event.SParentFrameEvent;
import org.wings.event.SParentFrameListener;
import org.wings.plaf.FileChooserCG;
import org.wings.util.LocaleCharSet;

/**
 * Shows a textfield with a browse-button to enter a filename. The file is uploaded via HTTP and made accessible to the WingS
 * application.
 * 

*

The uploaded file is stored temporarily in the filesystem of the server with a unique name, so that uploaded files with the * same filename do not clash. You can access this internal name with the {@link #getFileDir()} and {@link #getFileId()} methods. * The user provided filename can be queried with the {@link #getFileName()} method. *

* Since the file is stored temporarily in the filesystem, you should {@link File#delete()} it, when you are done with it. * However, if you don't delete the file yourself, it is eventually being removed by the Java garbage collector, if you haven't * renamed it (see {@link #getFile()}). *

*

The form, you add this SFileChooser to, needs to have the encoding type multipart/form-data set (form.{@link * SForm#setEncodingType(String) setEncodingType("multipart/form-data")}). This is handled by the form. You can explicitly set it * via the above method, though, in order to increase speed. *

*

You can limit the size of files to be uploaded, so it is hard to make a denial-of-service (harddisk, bandwidth) attack from * outside to your server. You can modify the maximum content length to be posted in {@link * org.wings.session.Session#setMaxContentLength(int)}. This is 64 kByte by default, so you might want to change this in your * application. *

*

* The SFileChooser notifies the form if something has gone wrong with uploading a file. *

* Szenario Files that are too big to be uploaded are blocked very early in the upload-process (if you are curious: this is * done in {@link org.wings.session.MultipartRequest}). At that time, only a partial input is read, the rest is discarded to * thwart denial of service attacks. Since we read only part of the input, we cannot make sure, that all parameters are * gathered from the input, thus we cannot just deliver the events contained, since they might be incomplete. However, the file * chooser needs to be informed, that something went wrong as to present an error message to the user. So in that case, only * one event is delivered to the enclosing form, that contains this SFileChooser. *

*

Note, that in this case, this will not trigger the action listener that you might have added to the submit-button. * This means, that you always should add your action listener to the {@link SForm} ({@link * SForm#addActionListener(java.awt.event.ActionListener)}), not the submit button. * * @author Holger Engels * @author Henner Zeller */ public class SFileChooser extends SComponent implements LowLevelEventListener, SParentFrameListener { private final transient static Logger log = LoggerFactory.getLogger(SFileChooser.class); /** maximum visible amount of characters in the file chooser. */ protected int columns = 16; protected String fileNameFilter = null; protected Class filter = null; protected String fileDir = null; protected String fileName = null; protected String fileId = null; protected String fileType = null; /** the temporary file created on upload. This file is automatically removed if and when it is not accessible anymore. */ protected TempFile currentFile = null; /** the temporary file created on upload. This file is automatically removed if and when it is not accessible anymore. */ protected IOException exception = null; private SForm parentForm; /** Creates a new FileChooser. */ public SFileChooser() { addParentFrameListener(this); } /** Find the form, this FileChooser is embedded in. */ protected final SForm getParentForm() { SComponent parent = getParent(); while (parent != null && !(parent instanceof SForm)) { parent = parent.getParent(); } return (SForm) parent; } /** * notifies the parent form, to fire action performed. This is necessary, if an exception in parsing a MultiPartRequest * occurs, e.g. upload file is too big. */ protected final void notifyParentForm() { SForm form = getParentForm(); if (form != null) { SForm.addArmedComponent(form); } } @Override public void parentFrameAdded(SParentFrameEvent e) { parentForm = getParentForm(); if (parentForm != null) { parentForm.registerFileChooser(this); } else { log.warn("file chooser not in a form"); } } @Override public void parentFrameRemoved(SParentFrameEvent e) { if (parentForm != null) { parentForm.unregisterFileChooser(this); } else { log.warn("file chooser not in a form"); } parentForm = null; } /** * Set the visible amount of columns in the textfield. * * @param c columns; '-1' sets the default that is browser dependent. */ public void setColumns(int c) { int oldColumns = columns; columns = c; if (columns != oldColumns) { reload(); } propertyChangeSupport.firePropertyChange("columns", oldColumns, this.columns); } /** * returns the number of visible columns. * * @return number of visible columns. */ public int getColumns() { return columns; } /** * Unlike the swing filechooser that allows to match certain file-suffices, this sets the mimetype to be accepted. * This filter may be fully qualified like text/html or can contain a wildcard in the subtype like text/ * *. Some browsers may as well accept a file-suffix wildcard as well. *

*

In any case, you hould check the result, since you cannot assume, that the browser actually does filter. Worse, browsers * may not guess the correct type so users cannot upload a file even if it has the correct type. So, bottomline, it is * generally a good idea to let the file name filter untouched, unless you know bugs of the browser at the other end of the * wire... * * @param mimeFilter the mime type to be filtered. */ public void setFileNameFilter(String mimeFilter) { String oldVal = this.fileNameFilter; fileNameFilter = mimeFilter; propertyChangeSupport.firePropertyChange("fileNameFilter", oldVal, this.fileNameFilter); } /** * returns the current filename filter. This is a mimetype-filter * * @return the current filename filter or 'null', if no filename filter is provided. * @see #setFileNameFilter(String) */ public String getFileNameFilter() { return fileNameFilter; } /** * Returns the filename, that has been given by the user in the upload text-field. * * @return the filename, given by the user. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) */ public String getFileName() throws IOException { if (exception != null) { throw exception; } return fileName; } /** * Returns the name of the system directory, the file has been stored temporarily in. You won't need this, unless you want to * access the file directly. Don't store the value you receive here for use later, since the SFileChooser does its own * garbage collecting of unused files. * * @return the pathname of the system directory, the file is stored in. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) */ public String getFileDir() throws IOException { if (exception != null) { throw exception; } return fileDir; } /** * Returns the internal ID of this file, that has been assigned at upload time. This ID is unique to prevent clashes with * other uploaded files. You won't need this, unless you want to access the file directly. Don't store the value you receive * here for later use, since the SFileChooser does its own garbage collecting of unused files. * * @return the internal, unique file id given to the uploaded file. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) */ public String getFileId() throws IOException { if (exception != null) { throw exception; } return fileId; } /** * Returns the mime type of this file, if known. * * @return the mime type of this file. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) */ public String getFileType() throws IOException { if (exception != null) { throw exception; } return fileType; } /** * returns the file, that has been uploaded. Use this, to open and read from the file uploaded by the user. Don't use this * method to query the actual filename given by the user, since this file wraps a system generated file with a different * (unique) name. Use {@link #getFileName()} instead. *

*

The file returned here will delete itself if you loose the reference to it and it is garbage collected to avoid filling * up the filesystem (This doesn't mean, that you shouldn't be a good programmer and delete the file yourself, if you don't * need it anymore :-). If you rename() the file to use it somewhere else, it is regarded not temporary anymore and thus will * not be removed from the filesystem. * * @return a File to access the content of the uploaded file. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) */ public File getSelectedFile() throws IOException { if (exception != null) { throw exception; } return currentFile; } protected void setSelectedFile(TempFile file) { currentFile = file; } /** * resets this FileChooser (no file selected). It does not remove an upload filter!. reset() will not remove a * previously selected file from the local tmp disk space, so as long as you have a reference to such a file, you can still * access it. If you don't have a reference to the file, it will automatically be removed when the file object is garbage * collected. */ public void reset() { currentFile = null; fileId = null; fileDir = null; fileType = null; fileName = null; exception = null; } /** * returns the file, that has been uploaded. Use this, to open and read from the file uploaded by the user. Don't use this * method to query the actual filename given by the user, since this file wraps a system generated file with a different * (unique) name. Use {@link #getFileName()} instead. *

*

The file returned here will delete itself if you loose the reference to it and it is garbage collected to avoid filling * up the filesystem (This doesn't mean, that you shouldn't be a good programmer and delete the file yourself, if you don't * need it anymore :-). If you rename() the file to use it somewhere else, it is regarded not temporary anymore and thus will * not be removed from the filesystem. * * @return a File to access the content of the uploaded file. * @throws IOException if something went wrong with the upload (most likely, the maximum allowed filesize is exceeded, see * {@link org.wings.session.Session#setMaxContentLength(int)}) * @deprecated use {@link org.wings.SFileChooser#getSelectedFile()} instead. */ public File getFile() throws IOException { return getSelectedFile(); } /** * An FilterOutputStream, that filters incoming files. You can use UploadFilters to inspect the stream or rewrite it to some * own format. * * @param filter the Class that is instanciated to filter incoming files. */ public void setUploadFilter(Class filter) { if (!FilterOutputStream.class.isAssignableFrom(filter)) { throw new IllegalArgumentException(filter.getName() + " is not a FilterOutputStream!"); } Class oldVal = this.filter; UploadFilterManager.registerFilter(getLowLevelEventId(), filter); this.filter = filter; propertyChangeSupport.firePropertyChange("uploadFilter", oldVal, this.filter); } /** Returns the upload filter set in {@link #setUploadFilter(Class)} */ public Class getUploadFilter() { return filter; } public FilterOutputStream getUploadFilterInstance() { return UploadFilterManager.getFilterInstance(getLowLevelEventId()); } public void setCG(FileChooserCG cg) { super.setCG(cg); } // -- Implementation of LowLevelEventListener @Override public void processLowLevelEvent(String action, String... values) { processKeyEvents(values); if (action.endsWith("_keystroke")) { return; } assert values != null && values[0] != null; exception = null; final String encoding = getSession().getCharacterEncoding() != null ? getSession().getCharacterEncoding() : LocaleCharSet.DEFAULT_ENCODING; try { String[] splittedValues = values[0].split("&"); Map params = new HashMap<>(); for (String splittedValue : splittedValues) { final int seperatorPos = splittedValue.indexOf('='); if (seperatorPos > 0 && seperatorPos < splittedValue.length()) { String key = splittedValue.substring(0, seperatorPos); String value = splittedValue.substring(seperatorPos + 1, splittedValue.length()); value = URLDecoder.decode(value, encoding); params.put(key, value); } } // parse Elements this.fileDir = params.get("dir"); this.fileName = params.get("name"); this.fileId = params.get("id"); this.fileType = params.get("type"); if (fileDir != null && fileId != null) { currentFile = new TempFile(fileDir, fileId); } } catch (UnsupportedEncodingException e) { log.warn("Failed to url-decode '" + values[0] + "'."); exception = e; } catch (Exception ex) { log.warn("Unknown Exception during URL decoding '" + values[0] + "'."); exception = new IOException(ex.getMessage()); } if (exception != null) { notifyParentForm(); } // clear the input field reload(); } @Override public void fireIntermediateEvents() { } /** @see LowLevelEventListener#isEpochCheckEnabled() */ private boolean epochCheckEnabled = true; /** @see LowLevelEventListener#isEpochCheckEnabled() */ @Override public boolean isEpochCheckEnabled() { return epochCheckEnabled; } /** @see LowLevelEventListener#isEpochCheckEnabled() */ public void setEpochCheckEnabled(boolean epochCheckEnabled) { boolean oldVal = this.epochCheckEnabled; this.epochCheckEnabled = epochCheckEnabled; propertyChangeSupport.firePropertyChange("epochCheckEnabled", oldVal, this.epochCheckEnabled); } /** * A temporary file. This file removes its representation in the filesysten, when there are no references to it (i.e. it is * garbage collected) */ protected static class TempFile extends File { private boolean isTemp; public TempFile(String parent, String child) { super(parent, child); deleteOnExit(); isTemp = true; } /** when this file is renamed, then it is not temporary anymore, thus will not be removed on cleanup. */ @Override public boolean renameTo(File newfile) { boolean success = super.renameTo(newfile); isTemp &= !success; // we are not temporary anymore on success. return success; } /** removes the file in the filesystem, if it is still temporary. */ private void cleanup() { if (isTemp) { delete(); } } /** do a cleanup, if this temporary file is garbage collected. */ @Override protected void finalize() throws Throwable { super.finalize(); if (isTemp) { log.debug("garbage collect file " + getName()); } cleanup(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy