Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.conversion.impl » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2006 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.conversion.impl;
    6   
    7   import java.io.IOException;
    8   import java.io.InputStream;
    9   import java.lang.annotation.Annotation;
   10   import java.lang.reflect.Member;
   11   import java.lang.reflect.Method;
   12   import java.net.URL;
   13   import java.util.HashMap;
   14   import java.util.HashSet;
   15   import java.util.Iterator;
   16   import java.util.Map;
   17   import java.util.Properties;
   18   
   19   import com.opensymphony.xwork2.ActionContext;
   20   import com.opensymphony.xwork2.ObjectFactory;
   21   import com.opensymphony.xwork2.XWorkMessages;
   22   import com.opensymphony.xwork2.conversion.TypeConverter;
   23   import com.opensymphony.xwork2.conversion.annotations.Conversion;
   24   import com.opensymphony.xwork2.conversion.annotations.ConversionRule;
   25   import com.opensymphony.xwork2.conversion.annotations.ConversionType;
   26   import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
   27   import com.opensymphony.xwork2.inject.Inject;
   28   import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper;
   29   import com.opensymphony.xwork2.util.AnnotationUtils;
   30   import com.opensymphony.xwork2.util.ClassLoaderUtil;
   31   import com.opensymphony.xwork2.util.CompoundRoot;
   32   import com.opensymphony.xwork2.util.FileManager;
   33   import com.opensymphony.xwork2.util.LocalizedTextUtil;
   34   import com.opensymphony.xwork2.util.ValueStack;
   35   import com.opensymphony.xwork2.util.logging.Logger;
   36   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   37   import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
   38   
   39   
   40   /**
   41    * XWorkConverter is a singleton used by many of the Struts 2's Ognl extention points,
   42    * such as InstantiatingNullHandler, XWorkListPropertyAccessor etc to do object 
   43    * conversion.
   44    * 
   45    * <!-- START SNIPPET: javadoc -->
   46    *
   47    * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web
   48    * is type-agnostic (everything is a string in HTTP), Struts 2's type conversion features are very useful. For instance,
   49    * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have
   50    * Struts 2 do the conversion both from String to Point and from Point to String.
   51    *
   52    * <p/> Using this "point" example, if your action (or another compound object in which you are setting properties on)
   53    * has a corresponding ClassName-conversion.properties file, Struts 2 will use the configured type converters for
   54    * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following
   55    * entry to <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter
   56    * interface):
   57    *
   58    * <p/><b>point = com.acme.PointConverter</b>
   59    *
   60    * <p/> Your type converter should be sure to check what class type it is being requested to convert. Because it is used
   61    * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in
   62    * to Points, and one that turns Points in to Strings.
   63    *
   64    * <p/> After this is done, you can now reference your point (using &lt;s:property value="point"/&gt; in JSP or ${point}
   65    * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be
   66    * converted back to a Point once again.
   67    *
   68    * <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file
   69    * <b>xwork-conversion.properties</b> in the root of your class path (typically WEB-INF/classes) and providing a
   70    * property in the form of the class name of the object you wish to convert on the left hand side and the class name of
   71    * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean
   72    * adding the following entry:
   73    *
   74    * <p/><b>com.acme.Point = com.acme.PointConverter</b>
   75    *
   76    * <!-- END SNIPPET: javadoc -->
   77    *
   78    * <p/>
   79    *
   80    * <!-- START SNIPPET: i18n-note -->
   81    *
   82    * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out
   83    * properly formatted dates. Rather, you should use the i18n features of Struts 2 (and consult the JavaDocs for JDK's
   84    * MessageFormat object) to see how a properly formatted date should be displayed.
   85    *
   86    * <!-- END SNIPPET: i18n-note -->
   87    *
   88    * <p/>
   89    *
   90    * <!-- START SNIPPET: error-reporting -->
   91    *
   92    * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the
   93    * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string,
   94    * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to
   95    * distinguish between a user not entering a value vs. entering a blank value.
   96    *
   97    * <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>,
   98    * which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name)
   99    * in your global i18n resource bundle.
  100    *
  101    * <p/>However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n
  102    * key associated with just your action (Action.properties) using the pattern <b>invalid.fieldvalue.xxx</b>, where xxx
  103    * is the field name.
  104    *
  105    * <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
  106    * called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the
  107    * errors can be reported accordingly.
  108    *
  109    * <!-- END SNIPPET: error-reporting -->
  110    *
  111    * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
  112    * @author Rainer Hermanns
  113    * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
  114    * @author tm_jee
  115    * 
  116    * @version $Date: 2008-03-28 19:48:23 +0100 (Fri, 28 Mar 2008) $ $Id: XWorkConverter.java 1770 2008-03-28 18:48:23Z rainerh $
  117    * 
  118    * @see XWorkBasicConverter
  119    */
  120   public class XWorkConverter extends DefaultTypeConverter {
  121   
  122       protected static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class);
  123       public static final String REPORT_CONVERSION_ERRORS = "report.conversion.errors";
  124       public static final String CONVERSION_PROPERTY_FULLNAME = "conversion.property.fullName";
  125       public static final String CONVERSION_ERROR_PROPERTY_PREFIX = "invalid.fieldvalue.";
  126       public static final String CONVERSION_COLLECTION_PREFIX = "Collection_";
  127   
  128       public static final String LAST_BEAN_CLASS_ACCESSED = "last.bean.accessed";
  129       public static final String LAST_BEAN_PROPERTY_ACCESSED = "last.property.accessed";
  130   
  131       /**
  132        * Target class conversion Mappings. 
  133        * <pre>
  134        * Map<Class, Map<String, Object>>
  135        *  - Class -> convert to class
  136        *  - Map<String, Object>
  137        *    - String -> property name 
  138        *                eg. Element_property, property etc.
  139        *    - Object -> String to represent properties 
  140        *                eg. value part of 
  141        *                    KeyProperty_property=id
  142        *             -> TypeConverter to represent an Ognl TypeConverter
  143        *                eg. value part of 
  144        *                    property=foo.bar.MyConverter
  145        *             -> Class to represent a class
  146        *                eg. value part of 
  147        *                    Element_property=foo.bar.MyObject
  148        * </pre>               
  149        */
  150       protected HashMap<Class,Map<String,Object>> mappings = new HashMap<Class,Map<String,Object>>(); // action 			
  151       
  152       /**
  153        * Unavailable target class conversion mappings, serves as a simple cache.
  154        */
  155       protected HashSet<Class> noMapping = new HashSet<Class>(); // action
  156       
  157       /**
  158        * Record class and its type converter mapping.
  159        * <pre>
  160        * - String - classname as String
  161        * - TypeConverter - instance of TypeConverter
  162        * </pre>
  163        */
  164       protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>();  // non-action (eg. returned value)
  165       
  166       /**
  167        * Record classes that doesn't have conversion mapping defined.
  168        * <pre>
  169        * - String -> classname as String
  170        * </pre>
  171        */
  172       protected HashSet<String> unknownMappings = new HashSet<String>(); 	// non-action (eg. returned value)
  173       
  174       private TypeConverter defaultTypeConverter;
  175       private ObjectFactory objectFactory;
  176   
  177   
  178       protected XWorkConverter() {
  179       }
  180   
  181       @Inject
  182       public void setObjectFactory(ObjectFactory factory) {
  183           this.objectFactory = factory;
  184           try {
  185               // note: this file is deprecated
  186               loadConversionProperties("xwork-default-conversion.properties");
  187           } catch (Exception e) {
  188           }
  189   
  190           try {
  191               loadConversionProperties("xwork-conversion.properties");
  192           } catch (Exception e) {
  193           }
  194       }
  195   
  196       @Inject
  197       public void setDefaultTypeConverter(XWorkBasicConverter conv) {
  198           this.defaultTypeConverter = conv;
  199       }
  200   
  201       public static String getConversionErrorMessage(String propertyName, ValueStack stack) {
  202           String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE,
  203                   ActionContext.getContext().getLocale(),
  204                   new Object[]{
  205                           propertyName
  206                   });
  207           String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')";
  208           String message = (String) stack.findValue(getTextExpression);
  209   
  210           if (message == null) {
  211               message = defaultMessage;
  212           }
  213   
  214           return message;
  215       }
  216   
  217   
  218       public static String buildConverterFilename(Class clazz) {
  219           String className = clazz.getName();
  220           String resource = className.replace('.', '/') + "-conversion.properties";
  221   
  222           return resource;
  223       }
  224   
  225       public Object convertValue(Map map, Object o, Class aClass) {
  226           return convertValue(map, null, null, null, o, aClass);
  227       }
  228   
  229       /**
  230        * Convert value from one form to another.
  231        * Minimum requirement of arguments:
  232        * <ul>
  233        * 		<li>supplying context, toClass and value</li>
  234        * 		<li>supplying context, target and value.</li>
  235        * </ul>
  236        * 
  237        * @see TypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.reflect.Member, java.lang.String, java.lang.Object, java.lang.Class)
  238        */
  239       public Object convertValue(Map context, Object target, Member member, String property, Object value, Class toClass) {
  240           //
  241           // Process the conversion using the default mappings, if one exists
  242           //
  243           TypeConverter tc = null;
  244   
  245           if ((value != null) && (toClass == value.getClass())) {
  246               return value;
  247           }
  248   
  249           // allow this method to be called without any context
  250           // i.e. it can be called with as little as "Object value" and "Class toClass"
  251           if (target != null) {
  252               Class clazz = target.getClass();
  253   
  254               Object[] classProp = null;
  255   
  256               // this is to handle weird issues with setValue with a different type
  257               if ((target instanceof CompoundRoot) && (context != null)) {
  258                   classProp = getClassProperty(context);
  259               }
  260   
  261               if (classProp != null) {
  262                   clazz = (Class) classProp[0];
  263                   property = (String) classProp[1];
  264               }
  265   
  266               tc = (TypeConverter) getConverter(clazz, property);
  267               
  268               if (LOG.isDebugEnabled())
  269                   LOG.debug("field-level type converter for property ["+property+"] = "+(tc==null?"none found":tc));
  270           }
  271           
  272           if (tc == null && context != null) {
  273               // ok, let's see if we can look it up by path as requested in XW-297
  274               Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH);
  275               Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
  276               if (lastPropertyPath != null && clazz != null) {
  277                   String path = lastPropertyPath + "." + property;
  278                   tc = (TypeConverter) getConverter(clazz, path);
  279               }
  280           }
  281   
  282           if (tc == null) {
  283               if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class)))
  284               {
  285                   // when converting to a string, use the source target's class's converter
  286                   tc = lookup(value.getClass());
  287               } else {
  288                   // when converting from a string, use the toClass's converter
  289                   tc = lookup(toClass);
  290               }
  291               
  292               if (LOG.isDebugEnabled())
  293                   LOG.debug("global-level type converter for property ["+property+"] = "+(tc==null?"none found":tc));
  294           }
  295           
  296           
  297   
  298           if (tc != null) {
  299               try {
  300                   return tc.convertValue(context, target, member, property, value, toClass);
  301               } catch (Exception e) {
  302                   e.printStackTrace();
  303                   handleConversionException(context, property, value, target);
  304   
  305                   return TypeConverter.NO_CONVERSION_POSSIBLE;
  306               }
  307           }
  308   
  309           if (defaultTypeConverter != null) {
  310               try {
  311                   if (LOG.isDebugEnabled())
  312                       LOG.debug("falling back to default type converter ["+defaultTypeConverter+"]");
  313                   return defaultTypeConverter.convertValue(context, target, member, property, value, toClass);
  314               } catch (Exception e) {
  315                   e.printStackTrace();
  316                   handleConversionException(context, property, value, target);
  317   
  318                   return TypeConverter.NO_CONVERSION_POSSIBLE;
  319               }
  320           } else {
  321               try {
  322                   if (LOG.isDebugEnabled())
  323                       LOG.debug("falling back to Ognl's default type conversion");
  324                   return super.convertValue(value, toClass);
  325               } catch (Exception e) {
  326                   e.printStackTrace();
  327                   handleConversionException(context, property, value, target);
  328   
  329                   return TypeConverter.NO_CONVERSION_POSSIBLE;
  330               }
  331           }
  332       }
  333   
  334       /**
  335        * Looks for a TypeConverter in the default mappings.
  336        *
  337        * @param className name of the class the TypeConverter must handle
  338        * @return a TypeConverter to handle the specified class or null if none can be found
  339        */
  340       public TypeConverter lookup(String className) {
  341           if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) {
  342               return null;
  343           }
  344   
  345           TypeConverter result = (TypeConverter) defaultMappings.get(className);
  346   
  347           //Looks for super classes
  348           if (result == null) {
  349               Class clazz = null;
  350   
  351               try {
  352                   clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
  353               } catch (ClassNotFoundException cnfe) {
  354               }
  355   
  356               result = lookupSuper(clazz);
  357   
  358               if (result != null) {
  359                   //Register now, the next lookup will be faster
  360                   registerConverter(className, result);
  361               } else {
  362                   // if it isn't found, never look again (also faster)
  363                   registerConverterNotFound(className);
  364               }
  365           }
  366   
  367           return result;
  368       }
  369   
  370       /**
  371        * Looks for a TypeConverter in the default mappings.
  372        *
  373        * @param clazz the class the TypeConverter must handle
  374        * @return a TypeConverter to handle the specified class or null if none can be found
  375        */
  376       public TypeConverter lookup(Class clazz) {
  377           return lookup(clazz.getName());
  378       }
  379   
  380       protected Object getConverter(Class clazz, String property) {
  381           if (LOG.isDebugEnabled()) {
  382               LOG.debug("Property: " + property);
  383               LOG.debug("Class: " + clazz.getName());
  384           }
  385           synchronized (clazz) {
  386               if ((property != null) && !noMapping.contains(clazz)) {
  387                   try {
  388                       Map<String, Object> mapping = mappings.get(clazz);
  389   
  390                       if (mapping == null) {
  391                           mapping = buildConverterMapping(clazz);
  392                       } else {
  393                           mapping = conditionalReload(clazz, mapping);
  394                       }
  395   
  396                       Object converter = mapping.get(property);
  397                       if (LOG.isDebugEnabled() && converter == null) {
  398                           LOG.debug("converter is null for property " + property + ". Mapping size: " + mapping.size());
  399                           Iterator<String> iter = mapping.keySet().iterator();
  400                           while (iter.hasNext()) {
  401                               String next = iter.next();
  402                               LOG.debug(next + ":" + mapping.get(next));
  403                           }
  404                       }
  405                       return converter;
  406                   } catch (Throwable t) {
  407                       noMapping.add(clazz);
  408                   }
  409               }
  410           }
  411   
  412           return null;
  413       }
  414   
  415       protected void handleConversionException(Map context, String property, Object value, Object object) {
  416           if ((Boolean.TRUE.equals(context.get(REPORT_CONVERSION_ERRORS)))) {
  417               String realProperty = property;
  418               String fullName = (String) context.get(CONVERSION_PROPERTY_FULLNAME);
  419   
  420               if (fullName != null) {
  421                   realProperty = fullName;
  422               }
  423   
  424               Map conversionErrors = (Map) context.get(ActionContext.CONVERSION_ERRORS);
  425   
  426               if (conversionErrors == null) {
  427                   conversionErrors = new HashMap();
  428                   context.put(ActionContext.CONVERSION_ERRORS, conversionErrors);
  429               }
  430   
  431               conversionErrors.put(realProperty, value);
  432           }
  433       }
  434   
  435       public synchronized void registerConverter(String className, TypeConverter converter) {
  436           defaultMappings.put(className, converter);
  437           if ( unknownMappings.contains(className)) {
  438               unknownMappings.remove(className);
  439           }
  440       }
  441   
  442       public synchronized void registerConverterNotFound(String className) {
  443           unknownMappings.add(className);
  444       }
  445   
  446       private Object[] getClassProperty(Map context) {
  447           return (Object[]) context.get("__link");
  448       }
  449   
  450       /**
  451        * Looks for converter mappings for the specified class and adds it to an existing map.  Only new converters are
  452        * added.  If a converter is defined on a key that already exists, the converter is ignored.
  453        *
  454        * @param mapping an existing map to add new converter mappings to
  455        * @param clazz   class to look for converter mappings for
  456        */
  457       void addConverterMapping(Map<String, Object> mapping, Class clazz) {
  458           try {
  459               String converterFilename = buildConverterFilename(clazz);
  460               InputStream is = FileManager.loadFile(converterFilename, clazz);
  461   
  462               if (is != null) {
  463                   if (LOG.isDebugEnabled()) {
  464                       LOG.debug("processing conversion file ["+converterFilename+"] [class="+clazz+"]");
  465                   }
  466                   
  467                   Properties prop = new Properties();
  468                   prop.load(is);
  469   
  470                   Iterator it = prop.entrySet().iterator();
  471   
  472                   while (it.hasNext()) {
  473                       Map.Entry entry = (Map.Entry) it.next();
  474                       String key = (String) entry.getKey();
  475   
  476                       if (mapping.containsKey(key)) {
  477                           break;
  478                       }
  479                       // for keyProperty of Set
  480                       if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX)
  481                               || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) {
  482                           if (LOG.isDebugEnabled()) {
  483                               LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as String]");
  484                           }
  485                           mapping.put(key, entry.getValue());
  486                       }
  487                       //for properties of classes
  488                       else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) ||
  489                               key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) ||
  490                               key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX))
  491                               ) {
  492                           TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
  493                           if (LOG.isDebugEnabled()) {
  494                               LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as TypeConverter "+_typeConverter+"]");
  495                           }
  496                           mapping.put(key, _typeConverter);
  497                       }
  498                       //for keys of Maps
  499                       else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) {
  500   
  501                           Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
  502                           
  503                           //check if the converter is a type converter if it is one
  504                           //then just put it in the map as is. Otherwise
  505                           //put a value in for the type converter of the class
  506                           if (converterClass.isAssignableFrom(TypeConverter.class)) {
  507                               TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
  508                               if (LOG.isDebugEnabled()) {
  509                                   LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as TypeConverter "+_typeConverter+"]");
  510                               }
  511                               mapping.put(key, _typeConverter);
  512                           } else {
  513                               if (LOG.isDebugEnabled()) {
  514                                   LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as Class "+converterClass+"]");
  515                               }
  516                               mapping.put(key, converterClass);
  517                           }
  518                       }
  519                       //elements(values) of maps / lists
  520                       else {
  521                           Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
  522                           if (LOG.isDebugEnabled()) {
  523                               LOG.debug("\t"+key + ":" + entry.getValue()+"[treated as Class "+_c+"]");
  524                           }
  525                           mapping.put(key, _c);
  526                       }
  527                   }
  528               }
  529           } catch (Exception ex) {
  530               LOG.error("Problem loading properties for " + clazz.getName(), ex);
  531           }
  532   
  533           // Process annotations
  534           Annotation[] annotations = clazz.getAnnotations();
  535   
  536           for (Annotation annotation : annotations) {
  537               if (annotation instanceof Conversion) {
  538                   Conversion conversion = (Conversion) annotation;
  539   
  540                   for (TypeConversion tc : conversion.conversions()) {
  541   
  542                       String key = tc.key();
  543   
  544                       if (mapping.containsKey(key)) {
  545                           break;
  546                       }
  547                       if (LOG.isDebugEnabled()) {
  548                           LOG.debug(key + ":" + key);
  549                       }
  550   
  551                       if (key != null) {
  552                           try {
  553                           	if (tc.type()  == ConversionType.APPLICATION) {
  554                                   defaultMappings.put(key, createTypeConverter(tc.converter()));
  555                               } else {
  556                                   if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) {
  557                                       mapping.put(key, tc.value());
  558                                   }
  559                                   //for properties of classes
  560                                   else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
  561                                           tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
  562                                           tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
  563                                           ) {
  564                                       mapping.put(key, createTypeConverter(tc.converter()));
  565                                  
  566   
  567   
  568                                   }
  569                                   //for keys of Maps
  570                                   else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
  571                                       Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
  572                                       if (LOG.isDebugEnabled()) {
  573                                           LOG.debug("Converter class: " + converterClass);
  574                                       }
  575                                       //check if the converter is a type converter if it is one
  576                                       //then just put it in the map as is. Otherwise
  577                                       //put a value in for the type converter of the class
  578                                       if (converterClass.isAssignableFrom(TypeConverter.class)) {
  579                                           mapping.put(key, createTypeConverter(tc.converter()));
  580                                       } else {
  581                                           mapping.put(key, converterClass);
  582                                           if (LOG.isDebugEnabled()) {
  583                                               LOG.debug("Object placed in mapping for key "
  584                                                       + key
  585                                                       + " is "
  586                                                       + mapping.get(key));
  587                                           }
  588   
  589                                       }
  590   
  591                                   }
  592                                   //elements(values) of maps / lists
  593                                   else {
  594                                       mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
  595                                   }
  596                               }
  597                           } catch (Exception e) {
  598                           }
  599                       }
  600                   }
  601               }
  602           }
  603   
  604           Method[] methods = clazz.getMethods();
  605   
  606           for (Method method : methods) {
  607   
  608               annotations = method.getAnnotations();
  609   
  610               for (Annotation annotation : annotations) {
  611                   if (annotation instanceof TypeConversion) {
  612                       TypeConversion tc = (TypeConversion) annotation;
  613   
  614                       String key = tc.key();
  615                       if (mapping.containsKey(key)) {
  616                           break;
  617                       }
  618                       // Default to the property name
  619                       if ( key != null && key.length() == 0) {
  620                           key = AnnotationUtils.resolvePropertyName(method);
  621                           LOG.debug("key from method name... " + key + " - " + method.getName());
  622                       }
  623   
  624   
  625                       if (LOG.isDebugEnabled()) {
  626                           LOG.debug(key + ":" + key);
  627                       }
  628   
  629                       if (key != null) {
  630                           try {
  631                           	if (tc.type() == ConversionType.APPLICATION) {
  632                                   defaultMappings.put(key, createTypeConverter(tc.converter()));
  633                               } else {
  634                                   if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) {
  635                                       mapping.put(key, tc.value());
  636                                   }
  637                                   //for properties of classes
  638                                   else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
  639                                           tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
  640                                           tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
  641                                           ) {
  642                                       mapping.put(key, createTypeConverter(tc.converter()));
  643                                   }
  644                                   //for keys of Maps
  645                                   else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
  646                                       Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
  647                                       if (LOG.isDebugEnabled()) {
  648                                           LOG.debug("Converter class: " + converterClass);
  649                                       }
  650                                       //check if the converter is a type converter if it is one
  651                                       //then just put it in the map as is. Otherwise
  652                                       //put a value in for the type converter of the class
  653                                       if (converterClass.isAssignableFrom(TypeConverter.class)) {
  654                                           mapping.put(key, createTypeConverter(tc.converter()));
  655                                       } else {
  656                                           mapping.put(key, converterClass);
  657                                           if (LOG.isDebugEnabled()) {
  658                                               LOG.debug("Object placed in mapping for key "
  659                                                       + key
  660                                                       + " is "
  661                                                       + mapping.get(key));
  662                                           }
  663   
  664                                       }
  665   
  666                                   }
  667                                   //elements(values) of maps / lists
  668                                   else {
  669                                       mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
  670                                   }
  671                               }
  672                           } catch (Exception e) {
  673                           }
  674                       }
  675                   }
  676               }
  677           }
  678       }
  679   
  680       /**
  681        * Looks for converter mappings for the specified class, traversing up its class hierarchy and interfaces and adding
  682        * any additional mappings it may find.  Mappings lower in the hierarchy have priority over those higher in the
  683        * hierarcy.
  684        *
  685        * @param clazz the class to look for converter mappings for
  686        * @return the converter mappings
  687        */
  688       private Map<String, Object> buildConverterMapping(Class clazz) throws Exception {
  689           Map<String, Object> mapping = new HashMap<String, Object>();
  690   
  691           // check for conversion mapping associated with super classes and any implemented interfaces
  692           Class curClazz = clazz;
  693   
  694           while (!curClazz.equals(Object.class)) {
  695               // add current class' mappings
  696               addConverterMapping(mapping, curClazz);
  697   
  698               // check interfaces' mappings
  699               Class[] interfaces = curClazz.getInterfaces();
  700   
  701               for (int x = 0; x < interfaces.length; x++) {
  702                   addConverterMapping(mapping, interfaces[x]);
  703               }
  704   
  705               curClazz = curClazz.getSuperclass();
  706           }
  707   
  708           if (mapping.size() > 0) {
  709               mappings.put(clazz, mapping);
  710           } else {
  711               noMapping.add(clazz);
  712           }
  713   
  714           return mapping;
  715       }
  716   
  717       private Map<String, Object> conditionalReload(Class clazz, Map<String, Object> oldValues) throws Exception {
  718           Map<String, Object> mapping = oldValues;
  719   
  720           if (FileManager.isReloadingConfigs()) {
  721               if (FileManager.fileNeedsReloading(buildConverterFilename(clazz))) {
  722                   mapping = buildConverterMapping(clazz);
  723               }
  724           }
  725   
  726           return mapping;
  727       }
  728   
  729       TypeConverter createTypeConverter(String className) throws Exception {
  730           // type converters are used across users
  731           Object obj = objectFactory.buildBean(className, null);
  732           if (obj instanceof TypeConverter) {
  733               return (TypeConverter) obj;
  734               
  735           // For backwards compatibility
  736           } else if (obj instanceof ognl.TypeConverter) {
  737               return new XWorkTypeConverterWrapper((ognl.TypeConverter)obj);
  738           } else {
  739               throw new IllegalArgumentException("Type converter class "+obj.getClass()+" doesn't implement com.opensymphony.xwork2.conversion.TypeConverter");
  740           }
  741       }
  742   
  743       public void loadConversionProperties(String propsName) throws IOException {
  744           Iterator<URL> resources = ClassLoaderUtil.getResources(propsName, getClass(), true);
  745           while (resources.hasNext()) {
  746               URL url = resources.next();
  747           Properties props = new Properties();
  748               props.load(url.openStream());
  749   
  750           if (LOG.isDebugEnabled()) {
  751               LOG.debug("processing conversion file ["+propsName+"]");
  752           }
  753           
  754               for (Iterator iterator = props.entrySet().iterator(); iterator.hasNext();) {
  755               Map.Entry entry = (Map.Entry) iterator.next();
  756               String key = (String) entry.getKey();
  757   
  758               try {
  759                   TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
  760                   if (LOG.isDebugEnabled()) {
  761                       LOG.debug("\t"+key + ":" + entry.getValue()+" [treated as TypeConverter "+_typeConverter+"]");
  762                   }
  763                   defaultMappings.put(key, _typeConverter);
  764               } catch (Exception e) {
  765                   LOG.error("Conversion registration error", e);
  766               }
  767           }
  768       }
  769       }
  770   
  771       /**
  772        * Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that
  773        * can handle the specified class.
  774        *
  775        * @param clazz the class the TypeConverter must handle
  776        * @return a TypeConverter to handle the specified class or null if none can be found
  777        */
  778       TypeConverter lookupSuper(Class clazz) {
  779           TypeConverter result = null;
  780   
  781           if (clazz != null) {
  782               result = (TypeConverter) defaultMappings.get(clazz.getName());
  783   
  784               if (result == null) {
  785                   // Looks for direct interfaces (depth = 1 )
  786                   Class[] interfaces = clazz.getInterfaces();
  787   
  788                   for (int i = 0; i < interfaces.length; i++) {
  789                       if (defaultMappings.containsKey(interfaces[i].getName())) {
  790                           result = (TypeConverter) defaultMappings.get(interfaces[i].getName());
  791                           break;
  792                       }
  793                   }
  794   
  795                   if (result == null) {
  796                       // Looks for the superclass
  797                       // If 'clazz' is the Object class, an interface, a primitive type or void then clazz.getSuperClass() returns null
  798                       result = lookupSuper(clazz.getSuperclass());
  799                   }
  800               }
  801           }
  802   
  803           return result;
  804       }
  805   
  806   
  807   }

Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.conversion.impl » [javadoc | source]