Package hirondelle.web4j.model

Source Code of hirondelle.web4j.model.ConvertParamImpl

package hirondelle.web4j.model;

import java.math.BigDecimal;
import java.util.*;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import hirondelle.web4j.util.Util;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.readconfig.InitParam;
import hirondelle.web4j.request.DateConverter;
import hirondelle.web4j.request.Formats;
import hirondelle.web4j.security.SafeText;
import sun.util.calendar.ZoneInfo;

/** Default implementation of {@link ConvertParam}.*/
public class ConvertParamImpl implements ConvertParam {

  /** Called by the framework upon startup.  */
  public static void init(ServletConfig aConfig){
    fIgnorableParamValue = fIGNORABLE_PARAM_VALUE.fetch(aConfig).getValue();
    fAllowString = Util.parseBoolean(fALLOW_STRING.fetch(aConfig).getValue());
   
    fSUPPORTED_CLASSES = new ArrayList<Class<?>>();
    fSUPPORTED_CLASSES.add(Integer.class);
    fSUPPORTED_CLASSES.add(int.class);
    fSUPPORTED_CLASSES.add(Boolean.class);
    fSUPPORTED_CLASSES.add(boolean.class);
    fSUPPORTED_CLASSES.add(BigDecimal.class);
    fSUPPORTED_CLASSES.add(java.util.Date.class);
    fSUPPORTED_CLASSES.add(Long.class);
    fSUPPORTED_CLASSES.add(long.class);
    fSUPPORTED_CLASSES.add(Id.class);
    fSUPPORTED_CLASSES.add(SafeText.class);
    fSUPPORTED_CLASSES.add(Locale.class);
    fSUPPORTED_CLASSES.add(TimeZone.class);
    fSUPPORTED_CLASSES.add(ZoneInfo.class); //Cheating - used to id TimeZones
    fSUPPORTED_CLASSES.add(Decimal.class);
    fSUPPORTED_CLASSES.add(DateTime.class);
    if(fAllowString){
      fSUPPORTED_CLASSES.add(String.class);
    }
   
    fLogger.fine("Supported Classes : " + Util.logOnePerLine(fSUPPORTED_CLASSES));
  }

  /**
   Return <tt>true</tt> only if <tt>aTargetClass</tt> is supported by this implementation.
   <P>
   The following classes are supported by this implementation as building block classes :
   <ul>
   <li><tt>{@link SafeText}</tt>
   <li><tt>String</tt> (conditionally, see below)
   <li><tt>Integer</tt>
   <li><tt>Long</tt>
   <li><tt>Boolean</tt>
   <li><tt>BigDecimal</tt>
   <li><tt>{@link Decimal}</tt>
   <li><tt>{@link Id}</tt>
   <li><tt>{@link DateTime}</tt>
   <li><tt>java.util.Date</tt>
   <li><tt>Locale</tt>
   <li><tt>TimeZone</tt> and <tt>sun.util.calendar.ZoneInfo</tt> (which is <i>cheating</i> - see below).
   </ul>
  
   <P><i>You are not obliged to use this class to model Locale and TimeZone.
   Many will choose to implement them as just another
   <a href='http://www.web4j.com/UserGuide.jsp#StartupTasksAndCodeTables'>code table</a>
    instead.</i> In this case, your model object constructors would usually take an {@link Id} parameter for these
    items, and translate them into a {@link Code}. See the example apps for a demonstration of this technique.
  
   <P>The <tt>TimeZone</tt> class is a problem here, since it's abstract. In fact, <b>this demonstrates a defect in the {@link ConvertParam}
   interface itself</b> - it should take objects themselves, instead of classes. That would allow more flexible checks on <i>type</i> as
   opposed to concrete <i>class</i>. In this implementation, {@link ZoneInfo} is hard-coded as the representative of all {@link TimeZone}
   objects. This is poor style, since <tt>ZoneInfo</tt> is public, but "unpublished" by Sun - they may change to some other
   class in the future. 
   
   <P><b>String is supported only when explicitly allowed.</b>
   The <tt>AllowStringAsBuildingBlock</tt> setting in <tt>web.xml</tt>
   controls whether or not this class allows <tt>String</tt> as a supported class.
   By default, its value is <tt>FALSE</tt>, since {@link SafeText} is the recommended
   replacement for <tt>String</tt>
  */
  public final boolean isSupported(Class<?> aTargetClass){
    return fSUPPORTED_CLASSES.contains(aTargetClass);
  }

  /**
   Coerce all parameters with no visible content to <tt>null</tt>.
  
   <P>In addition, any raw input value that matches <tt>IgnorableParamValue</tt> in <tt>web.xml</tt> is
   also coerced to <tt>null</tt>. See <tt>web.xml</tt> for more information.
  
   <P>Any non-<tt>null</tt> result is trimmed.
   This method can be overridden, if desired.
  */
  public String filter(String aRawInputValue){
    String result = aRawInputValue;
    if ( ! Util.textHasContent(aRawInputValue) || aRawInputValue.equals(getIgnorableParamValue()) ){
      result = null;
    }
    return Util.trimPossiblyNull(result); //some apps may elect to trim elsewhere
  }

  /**
   Apply reasonable parsing policies, suitable for most applications.
  
   <P>Roughly, the policies are :
   <ul>
   <li><tt>SafeText</tt> uses {@link SafeText#SafeText(String)}
   <li><tt>String</tt> just return the filtered value as is
   <li><tt>Integer</tt> uses {@link Integer#Integer(String)}
   <li><tt>BigDecimal</tt> uses {@link Formats#getDecimalInputFormat()}
   <li><tt>Decimal</tt> uses {@link Formats#getDecimalInputFormat()}
   <li><tt>Boolean</tt> uses {@link Util#parseBoolean(String)}
   <li><tt>DateTime</tt> uses {@link DateConverter#parseEyeFriendlyDateTime(String, Locale)}
   and {@link DateConverter#parseHandFriendlyDateTime(String, Locale)}
   <li><tt>Date</tt> uses {@link DateConverter#parseEyeFriendly(String, Locale, TimeZone)}
   and {@link DateConverter#parseHandFriendly(String, Locale, TimeZone)}
   <li><tt>Long</tt> uses {@link Long#Long(String)}
   <li><tt>Id</tt> uses {@link Id#Id(String)}
   <li><tt>Locale</tt> uses {@link Locale#getAvailableLocales()} and {@link Locale#toString()}, case sensitive.
   <li><tt>TimeZone</tt> uses {@link TimeZone#getAvailableIDs()}, case sensitive.
  </ul>
  */
  public final <T> T convert(String aFilteredInputValue, Class<T> aSupportedTargetClass, Locale aLocale, TimeZone aTimeZone) throws ModelCtorException {
    // Defensive : this check should have already been performed by the calling framework class.
    if( ! isSupported(aSupportedTargetClass) ) {
      throw new AssertionError("Unsupported type cannot be translated to an object: " + aSupportedTargetClass + ". If you're trying to use String, consider using SafeText instead. Otherwise, change the AllowStringAsBuildingBlock setting in web.xml.");
    }
   
    Object result = null;
    if (aSupportedTargetClass == SafeText.class){
      //no translation needed; some impl's might trim here, or force CAPS
      result = parseSafeText(aFilteredInputValue);
    }
    else if (aSupportedTargetClass == String.class) {
      result = aFilteredInputValue; //no translation needed; some impl's might trim here, or force CAPS
    }
    else if (aSupportedTargetClass == Integer.class || aSupportedTargetClass == int.class){
      result = parseInteger(aFilteredInputValue);
    }
    else if (aSupportedTargetClass == Boolean.class || aSupportedTargetClass == boolean.class){
      result = Util.parseBoolean(aFilteredInputValue);
    }
    else if (aSupportedTargetClass == BigDecimal.class){
      result = parseBigDecimal(aFilteredInputValue, aLocale, aTimeZone);
    }
    else if (aSupportedTargetClass == Decimal.class){
      result = parseDecimal(aFilteredInputValue, aLocale, aTimeZone);
    }
    else if (aSupportedTargetClass == java.util.Date.class){
      result = parseDate(aFilteredInputValue, aLocale, aTimeZone);
    }
    else if (aSupportedTargetClass == DateTime.class){
      result = parseDateTime(aFilteredInputValue, aLocale);
    }
    else if (aSupportedTargetClass == Long.class || aSupportedTargetClass == long.class){
      result = parseLong(aFilteredInputValue);
    }
    else if (aSupportedTargetClass == Id.class){
      result = new Id(aFilteredInputValue.trim());
    }
    else if (aSupportedTargetClass == Locale.class){
      result = parseLocale(aFilteredInputValue);
    }
    else if (aSupportedTargetClass == TimeZone.class){
      result = parseTimeZone(aFilteredInputValue);
    }
    else {
       throw new AssertionError("Failed to build object for ostensibly supported class: " + aSupportedTargetClass);
    }
    fLogger.finer("Converted request param into a " + aSupportedTargetClass.getName());
    return (T)result; //this cast is unavoidable, and safe.
  }
 
  /**
   Return the <tt>IgnorableParamValue</tt> configured in <tt>web.xml</tt>.
   See <tt>web.xml</tt> for more information.
  */
  public static final String getIgnorableParamValue(){
    return fIgnorableParamValue;
  }
 
  // PRIVATE
 
  private static List<Class<?>> fSUPPORTED_CLASSES;

  /**
   Possible values include
  <ul>
   <li> null, denoting that all param values are to be accepted
   <li> an empty String, corresponding to a blank OPTION
   <li> '---SELECT---', for example
  </ul>
  */
  private static String fIgnorableParamValue;
  private static final InitParam fIGNORABLE_PARAM_VALUE = new InitParam("IgnorableParamValue", "");
 
  private static boolean fAllowString;
  private static final InitParam fALLOW_STRING = new InitParam("AllowStringAsBuildingBlock", "NO");
 
  private static final ModelCtorException PROBLEM_FOUND = new ModelCtorException();
 
  private static final Logger fLogger = Util.getLogger(ConvertParamImpl.class);
 
  private Integer parseInteger(String aUserInputValue) throws ModelCtorException {
    try {
      return new Integer(aUserInputValue);
    }
    catch (NumberFormatException ex){
      throw PROBLEM_FOUND;
    }
  }
 
  private BigDecimal parseBigDecimal(String aUserInputValue, Locale aLocale, TimeZone aTimeZone) throws ModelCtorException {
    BigDecimal result = null;
    Formats formats = new Formats(aLocale, aTimeZone);
    Pattern pattern = formats.getDecimalInputFormat();
    if ( Util.matches(pattern, aUserInputValue)) {
      //BigDecimal ctor only takes '.' as decimal sign, never ','         
      result = new BigDecimal(aUserInputValue.replace(',', '.'));
    }
    else {
      throw PROBLEM_FOUND;
    }
    return result;
  }
 
  private Decimal parseDecimal(String aUserInputValue, Locale aLocale, TimeZone aTimeZone) throws ModelCtorException {
    Decimal result = null;
    BigDecimal amount = null;
    Formats formats = new Formats(aLocale, aTimeZone);
    Pattern pattern = formats.getDecimalInputFormat();
    if ( Util.matches(pattern, aUserInputValue)) {
      //BigDecimal ctor only takes '.' as decimal sign, never ','         
      amount = new BigDecimal(aUserInputValue.replace(',', '.'));
      try {
         result = new Decimal(amount);
      }
      catch(IllegalArgumentException ex){
        throw PROBLEM_FOUND;
      }
    }
    else {
      throw PROBLEM_FOUND;
    }
    return result;
  }
 
 
  private Date parseDate(String aUserInputValue, Locale aLocale, TimeZone aTimeZone) throws ModelCtorException {
    Date result = null;
    DateConverter dateConverter = BuildImpl.forDateConverter();
    result = dateConverter.parseHandFriendly(aUserInputValue, aLocale, aTimeZone);
    if ( result == null ){
      result = dateConverter.parseEyeFriendly(aUserInputValue, aLocale, aTimeZone);
    }
    if ( result == null ) {
      throw PROBLEM_FOUND;
    }
    return result;
  }
 
  private DateTime parseDateTime(String aUserInputValue, Locale aLocale) throws ModelCtorException {
    DateTime result = null;
    DateConverter dateConverter = BuildImpl.forDateConverter();
    result = dateConverter.parseHandFriendlyDateTime(aUserInputValue, aLocale);
    if ( result == null ){
      result = dateConverter.parseEyeFriendlyDateTime(aUserInputValue, aLocale);
    }
    if ( result == null ) {
      throw PROBLEM_FOUND;
    }
    return result;
  }
 
  private Long parseLong(String aUserInputValue) throws ModelCtorException {
    Long result = null;
    if ( Util.textHasContent(aUserInputValue) ){
      try {
        result = new Long(aUserInputValue);
      }
      catch (NumberFormatException ex){
        throw PROBLEM_FOUND;
      }
    }
    return result;
  }
 
  private SafeText parseSafeText(String aUserInputValue) throws ModelCtorException {
    SafeText result = null;
    if( Util.textHasContent(aUserInputValue) ) {
      try {
        result = new SafeText(aUserInputValue);
      }
      catch(IllegalArgumentException ex){
        throw PROBLEM_FOUND;
      }
    }
    return result;
  }

  /** Translate user input into a known time zone id. Case sensitive. */
  private TimeZone parseTimeZone(String aUserInputValue) throws ModelCtorException {
    TimeZone result = null;
    if ( Util.textHasContent(aUserInputValue) ){
      List<String> allTimeZoneIds = Arrays.asList(TimeZone.getAvailableIDs());
      for(String id : allTimeZoneIds){
        if (id.equals(aUserInputValue)){
          result = TimeZone.getTimeZone(id);
          break;
        }
      }
      if(result == null){ //has content, but no match found
        throw PROBLEM_FOUND;
      }
    }
    return result;
  }
 
  /** Translate user input into a known Locale id. Case sensitive. */
  private Locale parseLocale(String aUserInputValue) throws ModelCtorException {
    Locale result = null;
    if ( Util.textHasContent(aUserInputValue) ){
      List<Locale> allLocales = Arrays.asList(Locale.getAvailableLocales());
      for(Locale locale: allLocales){
        if (locale.toString().equals(aUserInputValue)){
          result = locale;
          break;
        }
      }
      if(result == null){ //has content, but no match found
        throw PROBLEM_FOUND;
      }
    }
    return result;
  }
 
 
}
TOP

Related Classes of hirondelle.web4j.model.ConvertParamImpl

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.