Package hirondelle.web4j.request

Source Code of hirondelle.web4j.request.RequestParser

package hirondelle.web4j.request;

import java.util.*;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.request.Formats;
import hirondelle.web4j.request.RequestParameter;
import hirondelle.web4j.security.ApplicationFirewallImpl;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.action.Action;
import hirondelle.web4j.model.AppException;
import hirondelle.web4j.model.BadRequestException;
import hirondelle.web4j.model.ConvertParamError;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.Id;
import hirondelle.web4j.model.ConvertParam;
import hirondelle.web4j.security.SafeText;

/**
Abstract Base Class (ABC) for mapping a request to an {@link Action}.

<P>See the {@link hirondelle.web4j.BuildImpl} for important information on how this item is configured.

<P><span class="highlight">Almost all concrete implementations of this Abstract Base Class will need to
implement only a single method</span> - {@link #getWebAction()}. WEB4J provides a default implementation
{@link RequestParserImpl}.
<P>The role of this class is to view the request at a higher level than the underlying
Servlet API. In particular, its services include :
<ul>
<li>mapping a request to an {@link Action}
<li>parsing request parameters into common "building block" objects (and Collections thereof),
such as {@link Date}, {@link BigDecimal} and so on, using the configured implementation of
{@link hirondelle.web4j.model.ConvertParam}. (The application programmer will usually use
{@link hirondelle.web4j.model.ModelFromRequest} to build Model Objects.)
</ul>
<P><a href="RequestParameter.html#FileUpload">File upload</a> parameters are not returned
by this class. Such parameters must be examined in an {@link Action}. The Servlet API has poor
support for file upload parameters, and use of a third party tool is recommended.
<P>The various <tt>toXXX</tt> methods are offered as a convenience for accessing <tt>String</tt>
and <tt>String</tt>-like data. All such <tt>toXXX</tt> methods apply the filtering (and possible
preprocessing) performed by {@link hirondelle.web4j.model.ConvertParam}.  
*/
public abstract class RequestParser {
 
  /**
   Called by the framework upon startup.
  
   <P>Initialize both this class and this package, using settings in <tt>web.xml</tt>.
  
   <P>This method will always call {@link ApplicationFirewallImpl#init(ServletConfig)}) and
   {@link RequestParserImpl#initWebActionMappings(ServletConfig)}, regardless of whether or not they are
   actually the configured implementations. This lets the application programmer forget about
   calling these methods in their application's {@link hirondelle.web4j.StartupTasks}.
  */
  public static void initUiLayer(ServletConfig aConfig){
    Formats.init(aConfig);
    RequestParameter.init(aConfig);
  }
 
  /**
   Return the configured concrete instance of this Abstract Base Class.
   <P>See the {@link hirondelle.web4j.BuildImpl} for important information on how
   this item is configured.
  */
  public static RequestParser getInstance(HttpServletRequest aRequest, HttpServletResponse aResponse){
    List<Object> args = new ArrayList<Object>();
    args.add(aRequest);
    args.add(aResponse);
    RequestParser result = (RequestParser)BuildImpl.forAbstractionPassCtorArgs(
      RequestParser.class.getName(),
      args
    );
    return result;
  }
 
  /** Constructor called by subclasses.  */
  public RequestParser(HttpServletRequest aRequest, HttpServletResponse aResponse){
    fRequest = aRequest;
    fResponse = aResponse;
    fLocale = BuildImpl.forLocaleSource().get(aRequest);
    fTimeZone = BuildImpl.forTimeZoneSource().get(aRequest);
    fConvertUserInput = BuildImpl.forConvertParam();
    fConversionError = BuildImpl.forConvertParamError();
  }

  /**
   Map a given request to a corresponding {@link Action}.
 
   <P>The mapping is determined entirely by concrete subclasses, and must 
   be implemented by the application programmer. {@link RequestParserImpl} is
   provided as a default implementation, and is very likely adequate for most
   applications.
  
   <P>If the incoming request does not map to a known {@link Action}, then throw
   a {@link BadRequestException}. <span class="highlight">Such requests
   are expected only for bugs and for malicious attacks, and never as part of the normal operation
   of the program.</span>
  */
  abstract public Action getWebAction() throws BadRequestException;
 
  /**
   Return the parameter value exactly as it appears in the request.
   
   <P>Can return <tt>null</tt> values, empty values, values containing
   only whitespace, and values equal to the <tt>IgnorableParamValue</tt> configured in <tt>web.xml</tt>.
  */
  public final String getRawParamValue(RequestParameter aReqParam){
    String result = fRequest.getParameter(aReqParam.getName());
    return result;
  }
 
  /**
   Return a multi-valued parameter's values exactly as they appear in the request.
  
   <P>Can return <tt>null</tt> values, empty values, values containing
   only whitespace, and values equal to the <tt>IgnorableParamValue</tt> configured in <tt>web.xml</tt>.
  */
  public final String[] getRawParamValues(RequestParameter aReqParam){
    String[] result = fRequest.getParameterValues(aReqParam.getName());
    return result;
  }

  /**
   Return a building block object.
  
   <P>Uses all methods of the configured implementation of {@link ConvertParam}.
   @param aReqParam underlying request parameter
   @param aSupportedTargetClass must be supported - see {@link ConvertParam#isSupported(Class)}
  */
  public <T> T toSupportedObject(RequestParameter aReqParam, Class<T> aSupportedTargetClass) throws ModelCtorException {
    T result = null;
    if( ! fConvertUserInput.isSupported(aSupportedTargetClass) ){
      throw new AssertionError("This class is not supported by ConvertParam: " + Util.quote(aSupportedTargetClass));
    }
    String filteredValue = fConvertUserInput.filter(getRawParamValue(aReqParam));
    if( Util.textHasContent(filteredValue) ){
      try {
        result  = fConvertUserInput.convert(filteredValue, aSupportedTargetClass, fLocale, fTimeZone);
      }
      catch (ModelCtorException ex){
        ModelCtorException conversionEx = fConversionError.get(aSupportedTargetClass, filteredValue, aReqParam);
        throw conversionEx;
      }
    }
    return result;
  }

  /**
   Return an ummodifiable <tt>List</tt> of building block objects.
  
   <P>Uses all methods of the configured implementation of {@link ConvertParam}.
   <P>
   <em>Design Note</em><br>
   <tt>List</tt> is returned here since HTML specs state that browsers submit param values
   in the order of appearance of the corresponding controls in the web page.
   @param aReqParam underlying request parameter
   @param aSupportedTargetClass must be supported - see {@link ConvertParam#isSupported(Class)}
  */
  public <T> List<T> toSupportedObjects(RequestParameter aReqParam, Class<T> aSupportedTargetClass) throws ModelCtorException {
    List<T> result = new ArrayList<T>();
    ModelCtorException conversionExceptions = new ModelCtorException();
    if( ! fConvertUserInput.isSupported(aSupportedTargetClass) ){
      throw new AssertionError("This class is not supported by ConvertParam: " + Util.quote(aSupportedTargetClass));
    }
    String[] rawValues = getRawParamValues(aReqParam);
    if(rawValues != null){
      for(String rawValue: rawValues){
        String filteredValue = fConvertUserInput.filter(rawValue); //possibly null
        //is it possible to have a multi-valued boolean param???       
        if ( Util.textHasContent(filteredValue) || Boolean.class == aSupportedTargetClass){
          try {
            T convertedItem  = fConvertUserInput.convert(filteredValue, aSupportedTargetClass, fLocale, fTimeZone);
            result.add(convertedItem);
          }
          catch (ModelCtorException ex){
            AppException conversionEx = fConversionError.get(aSupportedTargetClass, filteredValue, aReqParam);
            conversionExceptions.add(conversionEx);
          }
        }
        else {
          result.add(null);
        }
      }
      if (conversionExceptions.isNotEmpty()) throw conversionExceptions;
    }
    return Collections.unmodifiableList(result);
  }

  /** Return a single-valued request parameter as {@link SafeText}.  */
  public final SafeText toSafeText(RequestParameter aReqParam) {
    SafeText result = null;
    try {
      result = toSupportedObject(aReqParam, SafeText.class);
    }
    catch (ModelCtorException ex){
      changeToRuntimeException(ex);
    }
    return result;
  }
 
  /** Return a multi-valued request parameter as a {@code Collection<SafeText>}.  */
  public final Collection<SafeText> toSafeTexts(RequestParameter aReqParam) {
    Collection<SafeText> result = null;
    try {
      result = toSupportedObjects(aReqParam, SafeText.class);
    }
    catch (ModelCtorException ex){
      changeToRuntimeException(ex);
    }
    return result;
  }
   
  /** Return a single-valued request parameter as an {@link Id}.  */
  public final Id toId(RequestParameter aReqParam) {
    Id result = null;
    try {
      result = toSupportedObject(aReqParam, Id.class);
    }
    catch(ModelCtorException ex){
      changeToRuntimeException(ex);
    }
    return result;
  }
  
  /** Return a multi-valued request parameter as a {@code Collection<Id>}.  */
  public final Collection<Id> toIds(RequestParameter aReqParam) {
    Collection<Id> result = null;
    try {
      result = toSupportedObjects(aReqParam, Id.class);
    }
    catch (ModelCtorException ex){
      changeToRuntimeException(ex);
    }
    return result;
  }
 
  /** Return the underlying request.   */
  public final HttpServletRequest getRequest(){
    return fRequest;
  }
 
  /** Return the response associated with the underlying request.  */
  public final HttpServletResponse getResponse(){
    return fResponse;
  }
 
  /**
   Return <tt>true</tt> only if the request is a <tt>POST</tt>, and has 
   content type starting with <tt>multipart/form-data</tt>.
  */
  public final boolean isFileUploadRequest(){
    return
      fRequest.getMethod().equalsIgnoreCase("POST") &&
      fRequest.getContentType().startsWith("multipart/form-data")
    ;
  }
 
  // PRIVATE //
  private final HttpServletRequest fRequest;
  private final HttpServletResponse fResponse;
  private final Locale fLocale;
  private final TimeZone fTimeZone;
  private final ConvertParam fConvertUserInput;
  private final ConvertParamError fConversionError;
 
  /**
   Change from a checked to an unchecked ex.
  
   <P>This is unusual, and a bit ugly. For stringy data, there isn't any possibility of a
   parse error. Requiring Action constructors to catch or throw a ModelCtorEx is distasteful
   (this would happen for items that have an Operation built in the constructor.)
  */
  private void changeToRuntimeException(ModelCtorException ex){
    throw new IllegalArgumentException(ex);
  }
}
TOP

Related Classes of hirondelle.web4j.request.RequestParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.