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

  • Custom PropertyEditor for Resource

    Hi all:

    I'm trying to use a new type of resource for configuration files, that are environment-dependant. The idea is to use something like this:

    <property name="location" value="config:application.properties"/>

    where location would be a property of type Resource. The idea is to use these kind of resources in PropertyPlaceHolderConfigurers:

    Code:
        <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location" value="config:application.properties"/>
        </bean>
    I'm trying to use the CustomEditorConfigurer so I can register a custom property editor that allows for a custom ResourceLoader to resolve resources, instead of using the Application Context's ResourceLoader:

    Code:
    	<bean id="configurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    		<property name="customEditors">
    			<map>
    				<entry key="org.springframework.core.io.Resource">
    					<bean class="org.springframework.core.io.ResourceEditor">
    					   <constructor-arg>
    					       <bean class="es.map.sgtic.ext.ove.core.config.ConfigResourceLoader"/>
    					   </constructor-arg>
    					</bean>
    				</entry>
    			</map>
    		</property>
    	</bean>
    The problem is that the custom PropertyEditor gets registered AFTER the Resource property is injected into the PropertyPlaceHolderConfigurer, so the Resource gets loaded using the Application Context instead of using the PropertyEditor that would allow me to override this behaviour.

    So my questions are:

    1) Is it possible to register a custom PropertyEditor before the Resource gets injected? If so, how?
    2) Am I missing some other way to achieve what I'm looking for? Is there another way to override the Application Context resource loading mechanism?

    Thanks in advance!

  • #2
    You can set the order property on both BeanFactoryPostProcessors (CustomEditorConfigurer and PropertyPlaceholderConfigurer) and make sure yours gets executed first.

    Not sure if that will work because they are both BeanFactoryPostProcessors.

    Comment


    • #3
      Hello Marten, thanks for your response:

      Originally posted by Marten Deinum View Post
      You can set the order property on both BeanFactoryPostProcessors (CustomEditorConfigurer and PropertyPlaceholderConfigurer) and make sure yours gets executed first.

      Not sure if that will work because they are both BeanFactoryPostProcessors.
      I'm afraid it doesn't work...

      The PropertyPlaceHolderConfigurer has its resource(s) injected before the BeanFactoryPostProcessors have a chance to execute their postProcessBeanFactory() method. AbstractApplicationContext's invokeBeanFactoryPostProcessors() calls the beanFactory.getBean() method, that injects the properties for the beans:

      Code:
      			if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
      				priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i]));
      			}
      ...so the resource is injected before the custom PropertyEditor may be registered, independently of the postprocessor priority order.

      It seems that priority order in a postprocessor has no effect over a bean dependency injection, as dependency injection happens before postprocessor beans have its chance.

      Comment


      • #4
        Spring uses the following algorithm:
        1. Create all bean factory post processors;
        2. Invoke target methods postProcessBeanFactory() on every post processor;

        So, the main principle here is that bean definition changes performed by one bean factory post processor doesn't affect another bean factory post processors (see org.springframework.context.support.AbstractApplic ationContext.invokeBeanFactoryPostProcessors())

        I can offer the following workaround:
        1. Create custom bean factory post processor class that serves as a wrapper for the PropertyPlaceholderConfigurer;
        2. When postProcessBeanFactory() method of that class is called it creates necessary Resource object on the basis of configured resource type/name;
        3. Custom post processor creates standard PropertyPlaceholderConfigurer and injects created resource to it;
        4. Custom post processor calls postProcessBeanFactory() method on the created PropertyPlaceholderConfigurer instance;

        You can implement a custom spring xml extension for that, i.e. you can use something like <resourceroperty-placeholder location="config:application.properties"/> instead of standard <contextroperty-placeholder location="config:application.properties"/>

        Comment


        • #5
          That is what I figured would happen (although the flow is a bit different then you are explaining here ). But the general idea is the same.

          Dependency injections happens AFTER the BeanFactoryPostProcessor have fired. BeanFactoryPostProcessors operate on BeanDefinitions as they fire there are no instances yet only configuration meta data.

          The simplest solution here would be to create a custom extension to the CustomEditorConfigurer which adds the PriorityOrdered interface, that way it is taken into account.

          You also might want to register a JIRA issue for this because IMHO this specific class was missed when they introduced the PriorityOrdered interface.
          Last edited by Marten Deinum; Jul 8th, 2009, 06:34 AM.

          Comment


          • #6
            Hello Marten, thank you very much for your response:

            Originally posted by Marten Deinum View Post
            That is what I figured would happen (although the flow is a bit different then you are explaining here ). But the general idea is the same.

            Dependency injections happens AFTER the BeanFactoryPostProcessor have fired. BeanFactoryPostProcessors operate on BeanDefinitions as they fire there are no instances yet only configuration meta data.
            I don't understand this... I've been debugging the ApplicationContext initilization process and the Resource gets created using the (previously registered) ResourceEditor, and injects it in the PropertyPlaceHolderConfigurer...

            I'm sure there's something I don't grasp here, but the call stack for the resource creation is this (sorry for dumping the stack trace, but I don't figure a better way of explaining :-) )

            Code:
            GenericApplicationContext(DefaultResourceLoader).getResource(String) line: 90	
            GenericApplicationContext.getResource(String) line: 179	
            ResourceEditor.setAsText(String) line: 74	
            TypeConverterDelegate.doConvertTextValue(Object, String, PropertyEditor) line: 382	
            TypeConverterDelegate.doConvertValue(Object, Object, Class, PropertyEditor) line: 358	
            TypeConverterDelegate.convertIfNecessary(String, Object, Object, Class, PropertyDescriptor, MethodParameter) line: 173	
            TypeConverterDelegate.convertIfNecessary(Object, Object, PropertyDescriptor) line: 138	
            BeanWrapperImpl.convertForProperty(Object, String) line: 386	
            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).convertForProperty(Object, String, BeanWrapper, TypeConverter) line: 1289	
            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyPropertyValues(String, BeanDefinition, BeanWrapper, PropertyValues) line: 1250	
            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).populateBean(String, AbstractBeanDefinition, BeanWrapper) line: 1010	
            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 472	
            AbstractAutowireCapableBeanFactory$1.run() line: 409	
            AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]	
            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 380	
            AbstractBeanFactory$1.getObject() line: 264	
            DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 222	
            DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class, Object[], boolean) line: 261	
            DefaultListableBeanFactory(AbstractBeanFactory).getBean(String, Class, Object[]) line: 185	
            DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 164	
            GenericApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 515
            So the property gets injected and it doesn't get affected by the CustomEditorConfigurer afterwards (i'm afraid I get a bit lost in Spring core internals).

            Am I missing something here? Could you tell me to some resources where I can learn more about Spring Application Context initialization process?

            Originally posted by Marten Deinum View Post
            The simplest solution here would be to create a custom extension to the CustomEditorConfigurer which adds the PriorityOrdered interface, that way it is taken into account.
            I've tried this solution (creating a subclass that implements PriorirtyOrdered), and the result is the same... The problem is that the PropertyPlaceHolderConfigurer has its location resource already initialized in the beanFactory.getBean(), and the property doesn't get injected after invokeBeanFactoryPostProcessors

            Comment


            • #7
              The real problem here is that you want to post process a BeanFactoryPostProcessor with another BeanFactoryPostProcessor. I was hoping that making the CustomEditorConfigurer a PriorityOrdered instance would fix it (especially if it would run before the PPHC), but this is obviously not the case. It is quite difficult actually to make a BeanFactoryPostProcessor that will process other BeanFactoryPostProcessors.

              Also the problem runs even deeper than you might think the ResourceEditorRegistrar (used to register resource loaders etc.) is invoked and registered programmatically before everything else (check the prepareBeanFactoryMethod). Which is then used to set the properties on the PPHC (due to the getBean) before it is being post processed (as I stated this is quite difficult).

              The basic lifecylce is quite easy to understand

              1) Load and parse bean definition metadata (can be xml, classes, properties files)
              2) Register and execute BeanFactoryPostProcessors
              3) Create instances of beans and inject dependencies
              4) Apply BeanPostProcessors

              That is basically it (Chapter 3 of the reference guide explains the BeanFactory/ApplicationContext stuff in more detail).

              Comment


              • #8
                Thank you very much for the explanation, Marten.

                Originally posted by Marten Deinum View Post
                The real problem here is that you want to post process a BeanFactoryPostProcessor with another BeanFactoryPostProcessor. I was hoping that making the CustomEditorConfigurer a PriorityOrdered instance would fix it (especially if it would run before the PPHC), but this is obviously not the case. It is quite difficult actually to make a BeanFactoryPostProcessor that will process other BeanFactoryPostProcessors.

                Also the problem runs even deeper than you might think the ResourceEditorRegistrar (used to register resource loaders etc.) is invoked and registered programmatically before everything else (check the prepareBeanFactoryMethod). Which is then used to set the properties on the PPHC (due to the getBean) before it is being post processed (as I stated this is quite difficult).

                The basic lifecylce is quite easy to understand

                1) Load and parse bean definition metadata (can be xml, classes, properties files)
                2) Register and execute BeanFactoryPostProcessors
                3) Create instances of beans and inject dependencies
                4) Apply BeanPostProcessors

                That is basically it (Chapter 3 of the reference guide explains the BeanFactory/ApplicationContext stuff in more detail).

                Comment


                • #9
                  Hello Denis:

                  I've tried your solution and it works perfectly.

                  I've seen quite a few posts that ask for using different configuration files according to which environment (development, production, etc) is being used. I thought of this solution for that problem (being able to load properties files using a new ResourceLoader, that loads resources from an environment-specified path).

                  Do you think there is a better approach for this?

                  Thank you very much to you (and Marten) for your responses


                  Originally posted by denis.zhdanov View Post
                  Spring uses the following algorithm:
                  1. Create all bean factory post processors;
                  2. Invoke target methods postProcessBeanFactory() on every post processor;

                  So, the main principle here is that bean definition changes performed by one bean factory post processor doesn't affect another bean factory post processors (see org.springframework.context.support.AbstractApplic ationContext.invokeBeanFactoryPostProcessors())

                  I can offer the following workaround:
                  1. Create custom bean factory post processor class that serves as a wrapper for the PropertyPlaceholderConfigurer;
                  2. When postProcessBeanFactory() method of that class is called it creates necessary Resource object on the basis of configured resource type/name;
                  3. Custom post processor creates standard PropertyPlaceholderConfigurer and injects created resource to it;
                  4. Custom post processor calls postProcessBeanFactory() method on the created PropertyPlaceholderConfigurer instance;

                  You can implement a custom spring xml extension for that, i.e. you can use something like <resourceroperty-placeholder location="config:application.properties"/> instead of standard <contextroperty-placeholder location="config:application.properties"/>

                  Comment


                  • #10
                    Originally posted by bones1976 View Post
                    Hello Denis:

                    I've tried your solution and it works perfectly.

                    I've seen quite a few posts that ask for using different configuration files according to which environment (development, production, etc) is being used. I thought of this solution for that problem (being able to load properties files using a new ResourceLoader, that loads resources from an environment-specified path).

                    Do you think there is a better approach for this?

                    Thank you very much to you (and Marten) for your responses
                    Welcome

                    I see two possible ways to get environment-specific behavior:
                    1. Define all settings at the environment-level, i.e. set necessary environment variables/jvm properties before the application start and have a delivery unit that is common for all environments;
                    2. Define necessary settings at the VCS-managed files and parametrize build procedure, i.e. when you build delivery unit you define the environment it's going to be deployed at and the build script automatically applies necessary settings;

                    As for me, I prefer the second way because I can observe all settings from the IDE then.

                    Comment


                    • #11
                      The problem with the second approach is that you aren't deploying the same to your Test, Acceptance and Production environment. You have to recreate the artifacts and this can be troublesome in some businesses.

                      There is actually a third option, which is part option 1.

                      3) Specify a system variable which points to the configuration directory (i.e. config_path) and reference this from your PPHC.

                      Code:
                      <property name="location" value="${config_path}/jdbc.properties" />
                      That way you can deploy the same war/ear/.... to the server and only have different properties files, stored in a path configured at the server.

                      Comment


                      • #12
                        Originally posted by Marten Deinum View Post
                        The problem with the second approach is that you aren't deploying the same to your Test, Acceptance and Production environment. You have to recreate the artifacts and this can be troublesome in some businesses.
                        The main point here is that all delivery units have the same codebase but different settings. Agreed that that may be inappropriate for particular situations.



                        Originally posted by Marten Deinum View Post
                        There is actually a third option, which is part option 1.

                        3) Specify a system variable which points to the configuration directory (i.e. config_path) and reference this from your PPHC.

                        Code:
                        <property name="location" value="${config_path}/jdbc.properties" />
                        That way you can deploy the same war/ear/.... to the server and only have different properties files, stored in a path configured at the server.
                        Good point!

                        Comment


                        • #13
                          Well I worked for a lot of banks and insurance companies and they just don't like it if you need to build a new artifact when you move to a new server (Test/Accept/Production). They just want to copy the war/ear to the new server, maybe edit a properties file for different datasource/jms settings but that is it. The codebase has to be the same because that is the one that has been tested by the test department. Next to that it also saves you 2 build cycles and if you have CI in place they can just grab a nightly build and test it if they want.. .

                          Comment


                          • #14
                            Originally posted by Marten Deinum View Post
                            Well I worked for a lot of banks and insurance companies and they just don't like it if you need to build a new artifact when you move to a new server (Test/Accept/Production). They just want to copy the war/ear to the new server, maybe edit a properties file for different datasource/jms settings but that is it. The codebase has to be the same because that is the one that has been tested by the test department. Next to that it also saves you 2 build cycles and if you have CI in place they can just grab a nightly build and test it if they want.. .
                            I can say that Deutsche Bank is ok to the different deliveries per platform

                            I don't say that your approach is worse, I just say that every approach has pros and cons.

                            Comment

                            Working...
                            X