Skip to content

Commit

Permalink
WW-3177 adds better way to handle error messages when an exception oc…
Browse files Browse the repository at this point in the history
…curs, adds support for I18N to translate exceptions and adds default error messages

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1379458 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
lukaszlenart committed Aug 31, 2012
1 parent ade5c99 commit 5d68b26
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 52 deletions.
Expand Up @@ -26,6 +26,7 @@
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.config.Configuration;
Expand Down Expand Up @@ -767,7 +768,8 @@ public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext
if (mpr == null ) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
} else {
request = new StrutsRequestWrapper(request);
}
Expand Down
Expand Up @@ -21,11 +21,13 @@

package org.apache.struts2.dispatcher.multipart;

import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItem;
Expand All @@ -51,42 +53,74 @@
* Multipart form data request adapter for Jakarta Commons Fileupload package.
*/
public class JakartaMultiPartRequest implements MultiPartRequest {

static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class);

// maps parameter name -> List of FileItem objects
protected Map<String,List<FileItem>> files = new HashMap<String,List<FileItem>>();
protected Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();

// maps parameter name -> List of param values
protected Map<String,List<String>> params = new HashMap<String,List<String>>();
protected Map<String, List<String>> params = new HashMap<String, List<String>>();

// any errors while processing this request
protected List<String> errors = new ArrayList<String>();

protected long maxSize;
private Locale defaultLocale = Locale.ENGLISH;

@Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE)
public void setMaxSize(String maxSize) {
this.maxSize = Long.parseLong(maxSize);
}

@Inject
public void setLocaleProvider(LocaleProvider provider) {
defaultLocale = provider.getLocale();
}

/**
* Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's
* multipart classes (see class description).
*
* @param saveDir the directory to save off the file
* @param saveDir the directory to save off the file
* @param request the request containing the multipart
* @throws java.io.IOException is thrown if encoding fails.
* @throws java.io.IOException is thrown if encoding fails.
*/
public void parse(HttpServletRequest request, String saveDir) throws IOException {
try {
setLocale(request);
processUpload(request, saveDir);
} catch (FileUploadException e) {
} catch (FileUploadBase.SizeLimitExceededException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Request exceeded size limit!", e);
}
String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Unable to parse request", e);
LOG.warn("Unable to parse request", e);
}
errors.add(e.getMessage());
String errorMessage = buildErrorMessage(e, new Object[]{});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
}
}

protected void setLocale(HttpServletRequest request) {
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
}

protected String buildErrorMessage(Throwable e, Object[] args) {
String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
if (LOG.isDebugEnabled()) {
LOG.debug("Preparing error message for key: [#0]", errorKey);
}
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
}

private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException {
Expand Down Expand Up @@ -203,12 +237,12 @@ public File[] getFile(String fieldName) {
List<File> fileList = new ArrayList<File>(items.size());
for (FileItem fileItem : items) {
File storeLocation = ((DiskFileItem) fileItem).getStoreLocation();
if(fileItem.isInMemory() && storeLocation!=null && !storeLocation.exists()) {
if (fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) {
try {
storeLocation.createNewFile();
} catch (IOException e) {
if(LOG.isErrorEnabled()){
LOG.error("Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(),e);
if (LOG.isErrorEnabled()) {
LOG.error("Cannot write uploaded empty file to disk: " + storeLocation.getAbsolutePath(), e);
}
}
}
Expand Down Expand Up @@ -288,14 +322,14 @@ public String[] getParameterValues(String name) {
/* (non-Javadoc)
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
*/
public List getErrors() {
public List<String> getErrors() {
return errors;
}

/**
* Returns the canonical name of the given file.
*
* @param filename the given file
* @param filename the given file
* @return the canonical name of the given file
*/
private String getCanonicalName(String filename) {
Expand All @@ -313,7 +347,7 @@ private String getCanonicalName(String filename) {
/**
* Creates a RequestContext needed by Jakarta Commons Upload.
*
* @param req the request.
* @param req the request.
* @return a new request context.
*/
private RequestContext createRequestContext(final HttpServletRequest req) {
Expand Down
Expand Up @@ -113,7 +113,7 @@ public interface MultiPartRequest {
*
* @return a list of Strings that represent various errors during parsing
*/
public List getErrors();
public List<String> getErrors();

/**
* Cleans up all uploaded file, should be called at the end of request
Expand Down
Expand Up @@ -21,6 +21,8 @@

package org.apache.struts2.dispatcher.multipart;

import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
Expand All @@ -32,6 +34,7 @@
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

Expand All @@ -53,33 +56,54 @@
*
*/
public class MultiPartRequestWrapper extends StrutsRequestWrapper {

protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class);

Collection<String> errors;
MultiPartRequest multi;
private Collection<String> errors;
private MultiPartRequest multi;
private Locale defaultLocale = Locale.ENGLISH;

/**
* Process file downloads and log any errors.
*
* @param multiPartRequest Our MultiPartRequest object
* @param request Our HttpServletRequest object
* @param saveDir Target directory for any files that we save
* @param multiPartRequest Our MultiPartRequest object
* @param provider
*/
public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {
public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir, LocaleProvider provider) {
super(request);

errors = new ArrayList<String>();
multi = multiPartRequest;
defaultLocale = provider.getLocale();
setLocale(request);
try {
multi.parse(request, saveDir);
for (Object o : multi.getErrors()) {
String error = (String) o;
for (String error : multi.getErrors()) {
addError(error);
}
} catch (IOException e) {
addError("Cannot parse request: "+e.toString());
if (LOG.isWarnEnabled()) {
LOG.warn(e.getMessage(), e);
}
addError(buildErrorMessage(e, new Object[] {e.getMessage()}));
}
}

protected void setLocale(HttpServletRequest request) {
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
}

protected String buildErrorMessage(Throwable e, Object[] args) {
String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
if (LOG.isDebugEnabled()) {
LOG.debug("Preparing error message for key: [#0]", errorKey);
}
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
}

/**
* Get an enumeration of the parameter names for uploaded files
*
Expand Down Expand Up @@ -198,7 +222,7 @@ public String[] getParameterValues(String name) {
* @return <tt>true</tt> if any errors occured when parsing the HTTP multipart request, <tt>false</tt> otherwise.
*/
public boolean hasErrors() {
return !((errors == null) || errors.isEmpty());
return !errors.isEmpty();
}

/**
Expand All @@ -211,16 +235,14 @@ public Collection<String> getErrors() {
}

/**
* Adds an error message.
* Adds an error message when it isn't already added.
*
* @param anErrorMessage the error message to report.
*/
protected void addError(String anErrorMessage) {
if (errors == null) {
errors = new ArrayList<String>();
if (!errors.contains(anErrorMessage)) {
errors.add(anErrorMessage);
}

errors.add(anErrorMessage);
}

/**
Expand Down
Expand Up @@ -267,10 +267,6 @@ public String intercept(ActionInvocation invocation) throws Exception {
if (validation != null) {
validation.addActionError(error);
}

if (LOG.isWarnEnabled()) {
LOG.warn(error);
}
}
}

Expand Down
Expand Up @@ -31,6 +31,10 @@ struts.messages.error.file.too.large=The file is to large to be uploaded: {0} "{
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}

# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
struts.messages.upload.error.SizeLimitExceededException=Request exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!
struts.messages.upload.error.IOException=Error uploading: {0}!

devmode.notification=Developer Notification (set struts.devMode to false to disable this message):\n{0}

struts.exception.missing-package-action.with-context = There is no Action mapped for namespace [{0}] and action name [{1}] associated with context path [{2}].
Expand Up @@ -30,4 +30,8 @@ struts.messages.error.uploading=Fejl ved upload: {0}
struts.messages.error.file.too.large=Filen er for stor: {0} "{1}" {2}
struts.messages.error.content.type.not.allowed=Content-Type er ikke tilladt: {0} "{1}" {2}

# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
struts.messages.upload.error.SizeLimitExceededException=Request overskredet tilladte st\u00F8rrelse gr\u00E6nse! Max tilladte st\u00F8rrelse er: {0}, men anmodning var: {1}!
struts.messages.upload.error.IOException=Fejl ved upload: {0}!

devmode.notification=Note til udvikler (ret struts.devMode til 'false' for at deaktivere denne meddelse):\n{0}
Expand Up @@ -19,16 +19,20 @@
# under the License.
#
struts.messages.invalid.token=Das Formular wurde bereits verarbeitet oder es wurde kein Token angegeben, bitte versuchen Sie es erneut.
struts.internal.invalid.token=Das Formular Token {0} stimmt nicht mit dem Session Token {1} \u00fcberein.
struts.internal.invalid.token=Das Formular Token {0} stimmt nicht mit dem Session Token {1} \u00FCberein.

struts.messages.bypass.request=\u00dcberspringe {0}/{1}
struts.messages.bypass.request=\u00DCberspringe {0}/{1}
struts.messages.current.file=Datei {0} {1} {2} {3}
struts.messages.invalid.file=Es konnte kein Dateiname f\u00fcr {0} ermittelt werden. \u00dcberpr\u00fcfen Sie ob eine g\u00fcltige Datei \u00fcbermittelt wurde.
struts.messages.invalid.content.type=Es konnte kein Content-Type f\u00fcr {0} ermittelt werden. \u00dcberpr\u00fcfen Sie ob eine g\u00fcltige Datei \u00fcbermittelt wurde.
struts.messages.invalid.file=Es konnte kein Dateiname f\u00FCr {0} ermittelt werden. \u00DCberpr\u00FCfen Sie ob eine g\u00FCltige Datei \u00FCbermittelt wurde.
struts.messages.invalid.content.type=Es konnte kein Content-Type f\u00FCr {0} ermittelt werden. \u00DCberpr\u00FCfen Sie ob eine g\u00FCltige Datei \u00FCbermittelt wurde.
struts.messages.removing.file=Entferne Datei {0} {1}
struts.messages.error.uploading=Fehler beim Upload: {0}
struts.messages.error.file.too.large=Datei zu gross: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Content-Type nicht erlaubt: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension nicht erlaubt: {0} "{1}" "{2}" {3}

# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
struts.messages.upload.error.SizeLimitExceededException=Request \u00FCberschritten werden darf Gr\u00F6\u00DFenbeschr\u00E4nkung! Max zul\u00E4ssige Gr\u00F6\u00DFe ist: {0}, aber Anfrage war: {1}!
struts.messages.upload.error.IOException=Fehler beim Upload: {0}!

devmode.notification=Entwickler Hinweis (Setzen Sie struts.devMode auf false um diese Nachricht zu deaktivieren):\n{0}
Expand Up @@ -18,17 +18,21 @@
# specific language governing permissions and limitations
# under the License.
#
struts.messages.invalid.token=Formularz zosta\u0142 ju\u017c przetworzony lub nie za\u0142\u0105czono tokena, spr\u00f3buj ponownie.
struts.messages.invalid.token=Formularz zosta\u0142 ju\u017C przetworzony lub nie za\u0142\u0105czono tokena, spr\u00F3buj ponownie.
struts.internal.invalid.token=Token formularza {0} nie pasuje do tokena sesji {1}.

struts.messages.bypass.request=Omijanie {0}/{1}
struts.messages.current.file=Plik {0} {1} {2} {3}
struts.messages.invalid.file=Nie odnaleziono nazwy pliku dla {0}. Upewnij si\u0119 \u017ce wys\u0142ano w\u0142a\u015bciwy plik.
struts.messages.invalid.content.type=Nie odnaleziono Content-Type dla {0}. Upewnij si\u0119 \u017ce wys\u0142ano w\u0142a\u015bciwy plik.
struts.messages.invalid.file=Nie odnaleziono nazwy pliku dla {0}. Upewnij si\u0119 \u017Ce wys\u0142ano w\u0142a\u015Bciwy plik.
struts.messages.invalid.content.type=Nie odnaleziono Content-Type dla {0}. Upewnij si\u0119 \u017Ce wys\u0142ano w\u0142a\u015Bciwy plik.
struts.messages.removing.file=Usuwanie pliku {0} {1}
struts.messages.error.uploading=B\u0142\u0105d podczas wysy\u0142ania pliku: {0}
struts.messages.error.file.too.large=Plik jest za du\u017cy: {0} "{1}" "{2}" {3}
struts.messages.error.file.too.large=Plik jest za du\u017Cy: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Niedozwolony Content-Type: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=Niedozwolony File extension: {0} "{1}" "{2}" {3}

devmode.notification=Powiadmienie Developera (ustaw struts.devMode na false by wy\u0142\u0105czy\u0107 t\u0119 wiadomo\u015b\u0107):\n{0}
# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
struts.messages.upload.error.SizeLimitExceededException=Zapytanie przekroczy\u0142o dozwolony limit rozmiaru! Maksymalny rozmiar to: {0} ale \u017C\u0105danie mia\u0142o: {1}!
struts.messages.upload.error.IOException=B\u0142\u0105d podczas wysy\u0142ania pliku: {0}!

devmode.notification=Powiadmienie Developera (ustaw struts.devMode na false by wy\u0142\u0105czy\u0107 t\u0119 wiadomo\u015B\u0107):\n{0}
Expand Up @@ -30,4 +30,8 @@ struts.messages.error.uploading=Erro de uploading: {0}
struts.messages.error.file.too.large=Arquivo muito grande: {0} "{1}" {2}
struts.messages.error.content.type.not.allowed=Content-Type n\u00E3o permitido: {0} "{1}" {2}

# dedicated messages used to handle various problems with file upload - check {@link JakartaMultiPartRequest#parse(HttpServletRequest, String)}
struts.messages.upload.error.SizeLimitExceededException=Pedido excedeu o limite de tamanho permitido! Tamanho m\u00E1ximo permitido \u00E9: {0} mas foi pedido: {1}!
struts.messages.upload.error.IOException=Erro de uploading: {0}!

devmode.notification=Notifica\u00E7\u00E3o para o Desenvolvedor (altere o param\u00EAtro struts.devMode para false para desabilitar esta mensagem):\n{0}
Expand Up @@ -23,6 +23,7 @@

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.DefaultLocaleProvider;
import com.opensymphony.xwork2.ValidationAwareSupport;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
Expand Down Expand Up @@ -357,7 +358,7 @@ private String encodeTextFile(String bondary, String endline, String name, Strin
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize) throws IOException {
JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
jak.setMaxSize(String.valueOf(maxsize));
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath());
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
}

protected void setUp() throws Exception {
Expand Down

0 comments on commit 5d68b26

Please sign in to comment.