de.codesourcery.spring.contextrewrite.ContextRewritingBootStrapper.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.spring.contextrewrite.ContextRewritingBootStrapper.java

Source

/**
 * Copyright 2015 Tobias Gierke <tobias.gierke@code-sourcery.de>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.codesourcery.spring.contextrewrite;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.support.AbstractGenericContextLoader;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import org.xml.sax.InputSource;

/**
 * Spring {@link TestContextBootstrapper} that provides annotation support for rewriting the Spring XML using XPath expressions.
 *
 * Example Usage:
 * {@code
 {@literal @}BootstrapWith(value=ContextRewritingBootStrapper.class)
 {@literal @}ContextRewritingBootStrapper.ContextConfiguration(value="/spring-auth-test.xml",dumpRewrittenXML=true)
 {@literal @}ReplaceRule( xpath="/beans/bean[{@literal @}id='settingsResource']/constructor-arg/{@literal @}value" , replacement ="/some.properties" )
 {@literal @}RemoveRule( xpath="/beans/bean[{@literal @}id='settingsResource']/constructor-arg" )
 {@literal @}InsertRule( xpath="/beans/bean[{@literal @}id='settingsResource']" , insert ="<constructor-arg value="other.properties" )
 public class SpringIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests
 {
 {@literal @}Test
 public void testDoNothing() {
 }
 }
 * }
 * Note that rules get executed in order they are listed.
 * 
 * @author tobias.gierke@code-sourcery.de
 */
public class ContextRewritingBootStrapper extends DefaultTestContextBootstrapper {
    /**
     * Hack used to fake 'null' / 'not-set' default values for annotation attributes. 
     */
    public static final String NULL_STRING = "THIS STRING IS ABSENT";

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface ContextConfiguration {
        public String value();

        public boolean dumpRewrittenXML() default false;

        public boolean debug() default false;
    }

    /*
     * Replace
     */

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface ReplaceRules {
        public ReplaceRule[] value();
    }

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(ReplaceRules.class)
    public static @interface ReplaceRule {
        public static final Class<?> NULL_CLASS = Void.class;

        public String xpath();

        public String id() default NULL_STRING; // annotations cannot have NULL as default value ... god knows why...

        public String replacement() default NULL_STRING; // annotations cannot have NULL as default value ... god knows why...

        public Class<?> replacementClassName() default Void.class;// annotations cannot have NULL as default value ... god knows why...
    }

    /*
     * Remove
     */

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface RemoveRules {
        public RemoveRule[] value();
    }

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(RemoveRules.class)
    public static @interface RemoveRule {
        public String xpath();

        public String id() default NULL_STRING; // annotations cannot have NULL as default value ... god knows why...        
    }

    /*
     * INSERT ELEMENT
     */
    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface InsertElementRules {
        public InsertElementRule[] value();
    }

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(InsertElementRules.class)
    public static @interface InsertElementRule {
        public String xpath();

        public String id() default NULL_STRING; // annotations cannot have NULL as default value ... god knows why...        

        public String insert();
    }

    /*
     * INSERT ATTRIBUTE
     */
    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface InsertAttributeRules {
        public InsertAttributeRule[] value();
    }

    @Target(value = { ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(InsertAttributeRules.class)
    public static @interface InsertAttributeRule {
        public String xpath();

        public String id() default NULL_STRING; // annotations cannot have NULL as default value ... god knows why...        

        public String name();

        public String value();
    }

    @Override
    public void setBootstrapContext(final BootstrapContext ctx) {
        final RewriteConfig config = new AnnotationParser().parse(ctx.getTestClass());

        final XMLRewrite rewrite = new XMLRewrite();

        super.setBootstrapContext(new BootstrapContext() {

            @Override
            public Class<?> getTestClass() {
                return ctx.getTestClass();
            }

            @Override
            public CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
                return new CacheAwareContextLoaderDelegate() {

                    @Override
                    public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
                        final MergedContextConfiguration wrapper = new MergedContextConfiguration(
                                mergedContextConfiguration) {
                            @Override
                            public ContextLoader getContextLoader() {
                                return new AbstractGenericContextLoader() {

                                    @Override
                                    protected BeanDefinitionReader createBeanDefinitionReader(
                                            GenericApplicationContext context) {
                                        final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(
                                                context) {
                                            private int loadBeanDefinitions() {
                                                try {
                                                    return super.loadBeanDefinitions(new EncodedResource(
                                                            rewrite.filterResource(config.getResource(), config)));
                                                } catch (Exception e) {
                                                    throw new BeanDefinitionStoreException(
                                                            "Failed to load from classpath:"
                                                                    + config.getContextPath(),
                                                            e);
                                                }
                                            }

                                            @Override
                                            public int loadBeanDefinitions(String location)
                                                    throws BeanDefinitionStoreException {
                                                return loadBeanDefinitions((EncodedResource) null);
                                            }

                                            @Override
                                            public int loadBeanDefinitions(String location,
                                                    Set<Resource> actualResources)
                                                    throws BeanDefinitionStoreException {
                                                if (StringUtils.isBlank(location)) {
                                                    return loadBeanDefinitions();
                                                }
                                                return super.loadBeanDefinitions(location, actualResources);
                                            }

                                            @Override
                                            public int loadBeanDefinitions(String... locations)
                                                    throws BeanDefinitionStoreException {
                                                return loadBeanDefinitions();
                                            }

                                            public int loadBeanDefinitions(InputSource inputSource)
                                                    throws BeanDefinitionStoreException {
                                                return loadBeanDefinitions();
                                            }

                                            public int loadBeanDefinitions(InputSource inputSource,
                                                    String resourceDescription)
                                                    throws BeanDefinitionStoreException {
                                                return loadBeanDefinitions();
                                            }
                                        };
                                        return reader;
                                    }

                                    @Override
                                    protected String getResourceSuffix() {
                                        return ".xml";
                                    }
                                };
                            }
                        };
                        return ctx.getCacheAwareContextLoaderDelegate().loadContext(wrapper);
                    }

                    @Override
                    public void closeContext(MergedContextConfiguration mergedContextConfiguration,
                            HierarchyMode hierarchyMode) {
                        ctx.getCacheAwareContextLoaderDelegate().closeContext(mergedContextConfiguration,
                                hierarchyMode);
                    }
                };
            }
        });
    }
}