Announcement Announcement Module
Collapse
No announcement yet.
Custom Converter Example Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Custom Converter Example

    Does anyone have any resources on how to create a custom converter, define it in the configuration, and eventually use it?

    It seems there are some posts in this forum about it, but the information is scattered and there is never a "yes! got it working!" moment. Someone also pointed to the 2.0.3 changelog, but that turned up nothing.

    Much Thanks for your help.

  • #2
    The best example is in the booking-mvc sample application.

    Steps...

    1. Implement your custom converter, modeling after existing converters in org.springframework.binding.convert.converters (done once per custom converter). For example:

    Code:
    public class StringToMoney extends StringToObject {
    
       public StringToMoney() {
           super(Money.class);
       }
    
       @Override
       protected Object toObject(String string, Class targetClass) throws Exception {
           return Money.valueOf(string);
       }
    
       @Override
       protected String toString(Object object) throws Exception {
           Money amount = (Money) object;
           return amount.toString();
       }
    
    }
    2. Create a custom conversion service implementation that installs your custom converters (done once):

    Code:
    @Component("conversionService")
    public class ApplicationConversionService extends DefaultConversionService {
    
        @Override
        protected void addDefaultConverters() {
           super.addDefaultConverters();
    
           // registers a default converter for Money type
           addConverter(new StringToMoney());
    
           // registers a custom converter reference-able by id and applied when requested
           addConverter("shortDate", new StringToDate());
        }
    
    }
    3. Register the conversion service with Web Flow (done once):

    Code:
        <webflow:flow-builder-services id="flowBuilderServices" conversion-service="conversionService" .../>
    Now there is a bug in 2.0.3 where the default expression parser used during data binding does not apply your custom conversion service. This is fixed in 2.0.4 nightly, but to workaround it on 2.0.3 you must manually configure the expression parser implementation and configure its conversion service e.g.

    Code:
        <webflow:flow-builder-services id="flowBuilderServices" conversion-service="conversionService" expression-parser="expressionParser" .../>
    
         <bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
             <constructor-arg>
                 <bean class="org.jboss.el.ExpressionFactoryImpl"/>    
             </constructor-arg>
             <property name="conversionService" ref="conversionService"/>
         </bean>
    4. Default converters for your custom types should be used automatically anytime you bind to a property of that type. For custom converters to be applied for a specific type you reference the converter when you setup a view-binding:

    Code:
        <view-state id="enterBookingDetails" model="booking">
            <binder>
                <binding property="checkinDate" converter="shortDate" required="true" />
                <binding property="checkoutDate" converter="shortDate" required="true" />
                <binding property="beds" required="true" />
                <binding property="smoking" required="true" />
                <binding property="creditCard" required="true" />
                <binding property="creditCardName" required="true" />
                <binding property="creditCardExpiryMonth" required="true" />
                <binding property="creditCardExpiryYear" required="true" />
                <binding property="amenities" required="false" />
            </binder>
            <transition on="proceed" to="reviewBooking" />
            <transition on="cancel" to="cancel" bind="false" />
        </view-state>
    Last edited by Keith Donald; Aug 21st, 2008, 09:22 AM.

    Comment


    • #3
      Hi Keith,

      i try your workaround for 2.0.3 but my converter was not called. Modified your configuration by changing the id of expressionparser as following:
      Code:
       <webflow:flow-builder-services id="flowBuilderServices" conversion-service="conversionService" expression-parser="expressionParser" .../>
      
           <bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
               <constructor-arg>
                   <bean class="org.jboss.el.ExpressionFactoryImpl"/>    
               </constructor-arg>
               <property name="conversionService" ref="conversionService"/>
           </bean>

      I have downloaded the last nigthly build and my converter was not called too.
      Whats wrong?
      See my own Thread please:
      http://forum.springframework.org/showthread.php?t=59007

      I believe the converters are a very nice feature and the community will use it. Please check the following code and give me a suggestion:

      Environment: SWF 2.0.4.CI-573 with facelets

      webflow-config.xml
      Code:
      	<!-- Configures the Spring Web Flow JSF integration -->
      	<faces:flow-builder-services id="facesFlowBuilderServices" 
      		conversion-service="conversionService"/>
      
      	<bean id="conversionService" class="de.myApp.web.springframework.MyConversationService" />

      conversationService:
      Add my own converter and the shortDate from examples to test it too.
      Code:
      package de.myApp.web.springframework;
      
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      import org.springframework.binding.convert.converters.StringToDate;
      import org.springframework.binding.convert.service.DefaultConversionService;
      
      import de.myApp.web.springframework.converter.StringToUnterkunftTyp;
      
      public class MyConversationService extends DefaultConversionService {
      	private final static Log LOGGER = LogFactory
      			.getLog(MyConversationService.class);
      
      	public MyConversationService() {
      		super();
      	}
      	
      	protected void addDefaultConverters() {
      		  super.addDefaultConverters();
      		  addConverter("unterkunftTyp",new StringToUnterkunftTyp());
      		  // try the demo Converter from examples.
      		  addConverter("shortDate", new StringToDate());
      	}
      }
      my Flow
      Code:
      	<view-state id="add" model="backingBean">
      		<binder>
      			<binding property="unterkunftTyp" converter="unterkunftTyp" />
      			<binding property="jetzt" converter="shortDate" />
      		</binder>
      		
      		<transition on="save" to="commitAndEnd">
      			<evaluate expression="backingBean.save(flowRequestContext)" />
      		</transition>	
      		<transition on="chooseregion" to="regionchooser">
      		</transition>	
      		<transition on="reload" to="fewoadd">
      		</transition>	
      	</view-state>

      backingBean:
      Code:
      	// with getter and setter
      	private UnterkunftTyp unterkunftTyp;
      	private Date jetzt = new Date();

      my own converter:
      Code:
      package de.myApp.web.springframework.converter;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.binding.convert.converters.InvalidFormatException;
      import org.springframework.binding.convert.converters.StringToObject;
      import org.springframework.util.StringUtils;
      
      import de.myApp.bean.UnterkunftTyp;
      import de.myApp.bean.impl.UnterkunftTypImpl;
      import de.myApp.service.UnterkunftTypService;
      
      public class StringToUnterkunftTyp extends StringToObject {
      
      	@Autowired
      	private transient UnterkunftTypService unterkunftTypService;
      	
      	public StringToUnterkunftTyp() {
      		super(UnterkunftTypImpl.class);
      	}
      	
      	/* (non-Javadoc)
      	 * @see org.springframework.binding.convert.converters.StringToObject#toObject(java.lang.String, java.lang.Class)
      	 */
      	@Override
      	public Object toObject(String string, Class targetClass) throws Exception {
      		if (!StringUtils.hasText(string)) {
      			return null;
      		}
      		try {
      			return unterkunftTypService.getUnterkunftTypById(new Integer(string));
      		} catch (Exception e) {
      			throw new InvalidFormatException(string, "unterkunftTyp", e);
      		}
      	}
      
      	public String toString(Object target) throws Exception {
      		UnterkunftTyp unterkunftTyp = (UnterkunftTyp) target;
      		if (unterkunftTyp == null) {
      			return "";
      		}
      		return unterkunftTyp.getOid().toString();
      	}
      
      	/**
      	 * @param unterkunftTypService the unterkunftTypService to set
      	 */
      	public void setUnterkunftTypService(UnterkunftTypService unterkunftTypService) {
      		this.unterkunftTypService = unterkunftTypService;
      	}
      	
      }
      my facelet
      No converter was called and i saw the jetzt.toString() and unterkunftTyp.toString() implementation.
      Code:
      	<td><h:inputText id="jetzt" value="#{feWoController.jetzt}"/><br/>
      	<h:inputText id="unterunftTyp" value="#{feWoController.unterkunftTyp}"/><br/>
      Thanks,
      alex

      Comment


      • #4
        Alex,

        Thanks I corrected the typo in the post.

        I should have pointed out that converters do not yet get applied in a JSF environment, since JSF components use their own converters and handle the binding themselves, and the EL binding system is configured a bit differently. There is a JIRA open: http://jira.springframework.org/browse/SWF-799 to address this - I encourage you to vote / comment on it, and use native JSF converters for the meantime with Faces. If there are limitations that the JSF converters have our converters make considerably easier for you to address, it'd be good to know that as well!

        Keith

        Comment


        • #5
          I cannot get my Converter to be called as well. I'm using Spring Web, Webflow 2.0.3, applied your workaround, but when sourceClassConverters is checked, it is from the DefaultConversionService, not my ApplicationConversionService.

          ###EDIT
          Got it working, problem was somewhere else
          Last edited by icetbr; Aug 28th, 2008, 10:20 AM.

          Comment


          • #6
            I am using webflow 2.0.3 under WebSphere Portal 6.0.0.1 and OGNL parser.

            We have a bunch of Property Editors that essentially do some formatting of Strings. I am trying use webflow 2.0 converters to achieve this but unable to do so. This used to work with one of our previous projects that used Webflow 1.0 because we used the org.springframework.beans.PropertyEditorRegistrar to register our customer editors with FormAction classes of the flows.

            The problem lies in the fact that our property editors formats the source that is of type String to the target which is also a String (but formatted). As far as I know, webflow 2.0.3 is not calling the registered custom converters if the target class is assignable from source class i.e. both of of same type (see
            org.springframework.binding.convert.service.Generi cConversionService#getConversionExecutor(String id, Class sourceClass, Class targetClass) method and the executor that it returns has a 'execute' method which also assume same thing (see
            org.springframework.binding.convert.service.Static ConversionExecutor#execute(Object source) ).

            What is the solution to this? Below is the relevant snippet of my config file:

            Code:
            	<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
            	    <webflow:flow-location-pattern value="/WEB-INF/flows/**/**-flow.xml" />
            	    <webflow:flow-location-pattern value="/WEB-INF/common/**/**-flow.xml" />
            	</webflow:flow-registry>
                
            	<webflow:flow-builder-services id="flowBuilderServices" expression-parser="expressionParser"
            					conversion-service="conversionService" view-factory-creator="mvcViewFactoryCreator"/>
            
            	<bean id="conversionService" class="my.example.MyPropertyConversionService"/>
            
                <bean id="expressionParser" class="org.springframework.webflow.expression.WebFlowOgnlExpressionParser">
                    <property name="conversionService" ref="conversionService"/>
                </bean>
            	<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
            	    <property name="useSpringBeanBinding" value="true"/>
            	</bean>
            MyPropertyConversionService is extending DefaultConversionService and adds my property editors using webflow's PropertyEditorConverter class as shown below:
            Code:
            public class MyPropertyConversionService extends DefaultConversionService {
            	protected void addDefaultConverters() {
            		super.addDefaultConverters();
            		addConverter("myConverter", new PropertyEditorConverter(new MyStringPropertyEditor(), java.lang.String.class));
            	}
            ...
            The class MyStringPropertyEditor is extending java.beans.PropertyEditorSupport.

            And my flow has binding properties that use this converter as shown below:
            Code:
            	<view-state id="view1" model="view1Form">
            	    <binder>
            	        <binding property="email" converter="myConverter"/>
            ...
            In summary, is there any way to use a PropertyEditor that has the same source and target types in webflow 2?

            Comment


            • #7
              I'm having a similar problem to desank above where I cannot get my custom converter to work. What I'm trying to accomplish is provide a different pattern for the StringToDate converter that comes with webflow 2.0.3. I don't know if there is a way to do this other than what is mentioned above.

              What I've done is create a custom conversion service with an addDefaultConverters() method of:
              Code:
                  protected void addDefaultConverters() 
                  {
                      super.addDefaultConverters();
                      StringToDate myStringToDate = new StringToDate();
                      myStringToDate.setPattern("yyyyMMdd");
                      addConverter(myStringToDate);
                  }
              I register it in my config files like in this thread's earlier posts, but the DefaultConversionService is always run after my custom conversion service, so my "customized" string to date converter is overwritten.

              Can anyone tell me how to change the pattern that StringToDate uses, either following this path or another one? I really don't want to add a bunch of "<binder>" entries for my view-states because many of my pages have a lot of fields to bind and this wouldn't support maintenance really well.

              Comment


              • #8
                Converters only convert

                We too also had issues with the converters are previously mentioned. Tracing through spring-webflow code.

                We found out however that "under the hood" of spring webflow conversion only occurs if the types are different. That is

                - String=>Date - appropriate converter returned
                - String=>CustomObject - appropriate converter returned
                - String=>String - Webflow returns a NoOp converter which does nothing

                Therefore if you have spring Property-Editors that previously performed "string manipulation" i.e String=>String manipulation then regardless of registration or not with the ConverterService you will get a NoOp converter.

                Comment


                • #9
                  Is "string manipulation" a common requirement? Curious to learn more about your requirements.

                  Keith

                  Comment


                  • #10
                    Thanks for your post Keith.

                    Yes, String manipulation is very common requirement across all of our projects. Mainly we use String property editors for formatting of the data, however, there are certain use cases where we use them to do validation as well based on expected format of the data.

                    For example, we use String PropertyEditors for manipulating formatting a valid phone number according to a specific pattern. Also, we use them to format the bank account number in specific format as well. As for validation, we use them to validate correct date, email address, post codes etc.

                    These are just some examples of some of the editors we use. It works beautifully in projects using Spring MVC and Webflow 1.0 but not any more with Webflow 2.0 as pointed out earlier. I guess when I saw the adaptor class 'PropertyEditorConverter' in webflow 2.0 I was convinced that you would adapt any kind of property editors to webflow specific converters but I guess it is not the case. What is your suggestion to solve this problem in the meantime, Keith?

                    Comment


                    • #11
                      See http://jira.springframework.org/browse/SWF-896.

                      Will this meet your needs?

                      Comment


                      • #12
                        I would have to say that string manipulation is also part of our projects for property-editor/converters at present.

                        Having said this though, the converter approach has forced us to rethink and push the string manipulation logic into our domain classes and/or service layer - leaving the converter layer for pure type conversion.

                        IMHO removing the manipulation part from Converters is a better approach since now our domain classes perform manipulation which has lead to decoupling of dependencies.

                        Comment


                        • #13
                          Hello Keith,

                          Yes this jira looks to meet our needs. Thanks for that.

                          When is the webflow 2.0.4 planned to release?

                          Thanks.

                          Comment


                          • #14
                            But what about general conversion mechanism like:
                            Converting all "empty" strings into null values in binding phase without stating it explicitely in every flow using "<binder>" tag?
                            (something like StringTrimmerEditor in Spring MVC)
                            Last edited by jlubbis; Sep 24th, 2008, 01:58 AM.

                            Comment


                            • #15
                              Still not being called....

                              I've been hunting around all day to no avail. Using SWF 2.0.3, and am trying to register a custom converter so that select boxes in the view (string based) are converted to the pojo on the model when submitted. Unfortunately it does not seem to be calling my converter.

                              Relevant Code:

                              Conversion Service Registering
                              Code:
                              public class ApplicationConversionService extends DefaultConversionService {
                              
                                  @Override
                                  protected void addDefaultConverters() {
                                     super.addDefaultConverters();
                              
                                     // registers a default converter for a Report Document
                                     addConverter(new StringToReportDocument());
                                  }
                              
                              }
                              Setting it up in the context:

                              Code:
                              	 <webflow:flow-builder-services id="flowBuilderServices" conversion-service="conversionService" expression-parser="expressionParser" />
                              
                              
                                   <bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
                                       <constructor-arg>
                                           <bean class="org.apache.el.ExpressionFactoryImpl"/>    
                                       </constructor-arg>
                                       <property name="conversionService" ref="conversionService"/>
                                   </bean>
                                   
                                   <bean id="conversionService" class="com.mgl.epackage.converter.ApplicationConversionService" />
                              Webflow:

                              Code:
                              <view-state id="myState" model="myModel">
                              	    <binder>
                              	        <binding property="typeToConv1"/>
                              	        <binding property="typeToConv2" />    
                                  	    </binder>
                              ...
                              Converter:
                              Code:
                              public class StringToReportDocument implements TwoWayConverter {
                              
                              	public Class getSourceClass() {
                              		return ReportDocument.class;
                              	}
                              
                              	public Class getTargetClass() {
                              		return String.class;
                              	}
                              
                              	public java.lang.Object convertSourceToTargetClass(java.lang.Object source,
                              			Class targetClass) throws java.lang.Exception {
                              
                                              .....
                                              return myObj;
                              	}
                              
                              	public java.lang.Object convertTargetToSourceClass(java.lang.Object target,
                              			Class sourceClass) throws java.lang.Exception {
                              		 ...
                                               return myString;
                              	}
                              
                              }
                              When going to the view, I get an exception:

                              org.springframework.binding.convert.ConversionExec utorNotFoundException: No ConversionExecutor found for converting from sourceClass [com.ReportDocument] to target class [java.lang.String]

                              I tried changing the source/target around in the converter, but the converter code is simple never called when I debug it.

                              I also tried explicitly setting a converter name in both the addConverter and flow definitions, but that didn't help either.

                              Any ideas?

                              Comment

                              Working...
                              X