Save This Page
Home » struts-2.0.11.2-src » org.apache » struts2 » interceptor » [javadoc | source]
    1   /*
    2    * $Id: FileUploadInterceptor.java 495094 2007-01-11 02:51:40Z mrdon $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
   10    * with the License.  You may obtain a copy of the License at
   11    *
   12    *  http://www.apache.org/licenses/LICENSE-2.0
   13    *
   14    * Unless required by applicable law or agreed to in writing,
   15    * software distributed under the License is distributed on an
   16    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   17    * KIND, either express or implied.  See the License for the
   18    * specific language governing permissions and limitations
   19    * under the License.
   20    */
   21   package org.apache.struts2.interceptor;
   22   
   23   import java.io.File;
   24   import java.util.Collection;
   25   import java.util.Collections;
   26   import java.util.Enumeration;
   27   import java.util.HashSet;
   28   import java.util.Iterator;
   29   import java.util.Locale;
   30   import java.util.Map;
   31   import java.util.Set;
   32   import java.util.StringTokenizer;
   33   
   34   import javax.servlet.http.HttpServletRequest;
   35   
   36   import org.apache.commons.logging.Log;
   37   import org.apache.commons.logging.LogFactory;
   38   import org.apache.struts2.ServletActionContext;
   39   import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
   40   
   41   import com.opensymphony.xwork2.ActionContext;
   42   import com.opensymphony.xwork2.ActionInvocation;
   43   import com.opensymphony.xwork2.ActionProxy;
   44   import com.opensymphony.xwork2.ValidationAware;
   45   import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
   46   import com.opensymphony.xwork2.util.LocalizedTextUtil;
   47   
   48   /**
   49    * <!-- START SNIPPET: description -->
   50    *
   51    * Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
   52    * includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
   53    * HTML form:
   54    *
   55    * <ul>
   56    *
   57    * <li>[File Name] : File - the actual File</li>
   58    *
   59    * <li>[File Name]ContentType : String - the content type of the file</li>
   60    *
   61    * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name)</li>
   62    *
   63    * </ul>
   64    *
   65    * <p/> You can get access to these files by merely providing setters in your action that correspond to any of the three
   66    * patterns above, such as setDocument(File document), setDocumentContentType(String contentType), etc.
   67    * <br/>See the example code section.
   68    *
   69    * <p/> This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
   70    * These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file
   71    * processed for all i18n requests. You can override the text of these messages by providing text for the following
   72    * keys:
   73    *
   74    * <ul>
   75    *
   76    * <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
   77    *
   78    * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
   79    *
   80    * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
   81    * content types specified</li>
   82    *
   83    * </ul>
   84    *
   85    * <!-- END SNIPPET: description -->
   86    *
   87    * <p/> <u>Interceptor parameters:</u>
   88    *
   89    * <!-- START SNIPPET: parameters -->
   90    *
   91    * <ul>
   92    *
   93    * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
   94    * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
   95    * Default to approximately 2MB.</li>
   96    *
   97    * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
   98    * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
   99    *
  100    * </ul>
  101    *
  102    * <!-- END SNIPPET: parameters -->
  103    *
  104    * <p/> <u>Extending the interceptor:</u>
  105    *
  106    * <p/>
  107    *
  108    * <!-- START SNIPPET: extending -->
  109    *
  110    * You can extend this interceptor and override the {@link #acceptFile} method to provide more control over which files
  111    * are supported and which are not.
  112    *
  113    * <!-- END SNIPPET: extending -->
  114    *
  115    * <p/> <u>Example code:</u>
  116    *
  117    * <pre>
  118    * <!-- START SNIPPET: example -->
  119    * &lt;action name="doUpload" class="com.examples.UploadAction"&gt;
  120    *     &lt;interceptor-ref name="fileUpload"/&gt;
  121    *     &lt;interceptor-ref name="basicStack"/&gt;
  122    *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
  123    * &lt;/action&gt;
  124    * </pre>
  125    *
  126    * And then you need to set encoding <code>multipart/form-data</code> in the form where the user selects the file to upload.
  127    * <pre>
  128    *   &lt;a:form action="doUpload" method="post" enctype="multipart/form-data"&gt;
  129    *       &lt;a:file name="upload" label="File"/&gt;
  130    *       &lt;a:submit/&gt;
  131    *   &lt;/a:form&gt;
  132    * </pre>
  133    *
  134    * And then in your action code you'll have access to the File object if you provide setters according to the
  135    * naming convention documented in the start.
  136    *
  137    * <pre>
  138    *    public com.examples.UploadAction implemements Action {
  139    *       private File file;
  140    *       private String contentType;
  141    *       private String filename;
  142    *
  143    *       public void setUpload(File file) {
  144    *          this.file = file;
  145    *       }
  146    *
  147    *       public void setUploadContentType(String contentType) {
  148    *          this.contentType = contentType;
  149    *       }
  150    *
  151    *       public void setUploadFileName(String filename) {
  152    *          this.filename = filename;
  153    *       }
  154    *
  155    *       ...
  156    *  }
  157    * </pre>
  158    * <!-- END SNIPPET: example -->
  159    *
  160    */
  161   public class FileUploadInterceptor extends AbstractInterceptor {
  162   
  163       private static final long serialVersionUID = -4764627478894962478L;
  164   
  165       protected static final Log log = LogFactory.getLog(FileUploadInterceptor.class);
  166       private static final String DEFAULT_DELIMITER = ",";
  167       private static final String DEFAULT_MESSAGE = "no.message.found";
  168   
  169       protected Long maximumSize;
  170       protected String allowedTypes;
  171       protected Set allowedTypesSet = Collections.EMPTY_SET;
  172   
  173       /**
  174        * Sets the allowed mimetypes
  175        *
  176        * @param allowedTypes A comma-delimited list of types
  177        */
  178       public void setAllowedTypes(String allowedTypes) {
  179           this.allowedTypes = allowedTypes;
  180   
  181           // set the allowedTypes as a collection for easier access later
  182           allowedTypesSet = getDelimitedValues(allowedTypes);
  183       }
  184   
  185       /**
  186        * Sets the maximum size of an uploaded file
  187        *
  188        * @param maximumSize The maximum size in bytes
  189        */
  190       public void setMaximumSize(Long maximumSize) {
  191           this.maximumSize = maximumSize;
  192       }
  193   
  194       /* (non-Javadoc)
  195        * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
  196        */
  197       public String intercept(ActionInvocation invocation) throws Exception {
  198           ActionContext ac = invocation.getInvocationContext();
  199           HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
  200   
  201           if (!(request instanceof MultiPartRequestWrapper)) {
  202               if (log.isDebugEnabled()) {
  203                   ActionProxy proxy = invocation.getProxy();
  204                   log.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ActionContext.getContext().getLocale()));
  205               }
  206   
  207               return invocation.invoke();
  208           }
  209   
  210           final Object action = invocation.getAction();
  211           ValidationAware validation = null;
  212   
  213           if (action instanceof ValidationAware) {
  214               validation = (ValidationAware) action;
  215           }
  216   
  217           MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
  218   
  219           if (multiWrapper.hasErrors()) {
  220               for (Iterator errorIter = multiWrapper.getErrors().iterator(); errorIter.hasNext();) {
  221                   String error = (String) errorIter.next();
  222   
  223                   if (validation != null) {
  224                       validation.addActionError(error);
  225                   }
  226   
  227                   log.error(error);
  228               }
  229           }
  230   
  231           Map parameters = ac.getParameters();
  232   
  233           // Bind allowed Files
  234           Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
  235           while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
  236               // get the value of this input tag
  237               String inputName = (String) fileParameterNames.nextElement();
  238   
  239               // get the content type
  240               String[] contentType = multiWrapper.getContentTypes(inputName);
  241   
  242               if (isNonEmpty(contentType)) {
  243                   // get the name of the file from the input tag
  244                   String[] fileName = multiWrapper.getFileNames(inputName);
  245   
  246                   if (isNonEmpty(fileName)) {
  247                       // Get a File object for the uploaded File
  248                       File[] files = multiWrapper.getFiles(inputName);
  249                       if (files != null) {
  250                           for (int index = 0; index < files.length; index++) {
  251   
  252                               if (acceptFile(files[index], contentType[index], inputName, validation, ac.getLocale())) {
  253                                   parameters.put(inputName, files);
  254                                   parameters.put(inputName + "ContentType", contentType);
  255                                   parameters.put(inputName + "FileName", fileName);
  256                               }
  257                           }
  258                       }
  259                   } else {
  260                       log.error(getTextMessage("struts.messages.invalid.file", new Object[]{inputName}, ActionContext.getContext().getLocale()));
  261                   }
  262               } else {
  263                   log.error(getTextMessage("struts.messages.invalid.content.type", new Object[]{inputName}, ActionContext.getContext().getLocale()));
  264               }
  265           }
  266   
  267           // invoke action
  268           String result = invocation.invoke();
  269   
  270           // cleanup
  271           fileParameterNames = multiWrapper.getFileParameterNames();
  272           while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
  273               String inputValue = (String) fileParameterNames.nextElement();
  274               File[] file = multiWrapper.getFiles(inputValue);
  275               for (int index = 0; index < file.length; index++) {
  276                   File currentFile = file[index];
  277                   log.info(getTextMessage("struts.messages.removing.file", new Object[]{inputValue, currentFile}, ActionContext.getContext().getLocale()));
  278   
  279                   if ((currentFile != null) && currentFile.isFile()) {
  280                       currentFile.delete();
  281                   }
  282               }
  283           }
  284   
  285           return result;
  286       }
  287   
  288       /**
  289        * Override for added functionality. Checks if the proposed file is acceptable based on contentType and size.
  290        *
  291        * @param file        - proposed upload file.
  292        * @param contentType - contentType of the file.
  293        * @param inputName   - inputName of the file.
  294        * @param validation  - Non-null ValidationAware if the action implements ValidationAware, allowing for better
  295        *                    logging.
  296        * @param locale
  297        * @return true if the proposed file is acceptable by contentType and size.
  298        */
  299       protected boolean acceptFile(File file, String contentType, String inputName, ValidationAware validation, Locale locale) {
  300           boolean fileIsAcceptable = false;
  301   
  302           // If it's null the upload failed
  303           if (file == null) {
  304               String errMsg = getTextMessage("struts.messages.error.uploading", new Object[]{inputName}, locale);
  305               if (validation != null) {
  306                   validation.addFieldError(inputName, errMsg);
  307               }
  308   
  309               log.error(errMsg);
  310           } else if (maximumSize != null && maximumSize.longValue() < file.length()) {
  311               String errMsg = getTextMessage("struts.messages.error.file.too.large", new Object[]{inputName, file.getName(), "" + file.length()}, locale);
  312               if (validation != null) {
  313                   validation.addFieldError(inputName, errMsg);
  314               }
  315   
  316               log.error(errMsg);
  317           } else if ((! allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
  318               String errMsg = getTextMessage("struts.messages.error.content.type.not.allowed", new Object[]{inputName, file.getName(), contentType}, locale);
  319               if (validation != null) {
  320                   validation.addFieldError(inputName, errMsg);
  321               }
  322   
  323               log.error(errMsg);
  324           } else {
  325               fileIsAcceptable = true;
  326           }
  327   
  328           return fileIsAcceptable;
  329       }
  330   
  331       /**
  332        * @param itemCollection - Collection of string items (all lowercase).
  333        * @param key            - Key to search for.
  334        * @return true if itemCollection contains the key, false otherwise.
  335        */
  336       private static boolean containsItem(Collection itemCollection, String key) {
  337           return itemCollection.contains(key.toLowerCase());
  338       }
  339   
  340       private static Set getDelimitedValues(String delimitedString) {
  341           Set<String> delimitedValues = new HashSet<String>();
  342           if (delimitedString != null) {
  343               StringTokenizer stringTokenizer = new StringTokenizer(delimitedString, DEFAULT_DELIMITER);
  344               while (stringTokenizer.hasMoreTokens()) {
  345                   String nextToken = stringTokenizer.nextToken().toLowerCase().trim();
  346                   if (nextToken.length() > 0) {
  347                       delimitedValues.add(nextToken);
  348                   }
  349               }
  350           }
  351           return delimitedValues;
  352       }
  353   
  354       private static boolean isNonEmpty(Object[] objArray) {
  355           boolean result = false;
  356           for (int index = 0; index < objArray.length && !result; index++) {
  357               if (objArray[index] != null) {
  358                   result = true;
  359               }
  360           }
  361           return result;
  362       }
  363   
  364       private String getTextMessage(String messageKey, Object[] args, Locale locale) {
  365           if (args == null || args.length == 0) {
  366               return LocalizedTextUtil.findText(this.getClass(), messageKey, locale);
  367           } else {
  368               return LocalizedTextUtil.findText(this.getClass(), messageKey, locale, DEFAULT_MESSAGE, args);
  369           }
  370       }
  371   }

Save This Page
Home » struts-2.0.11.2-src » org.apache » struts2 » interceptor » [javadoc | source]