org.raistlic.spring.test.flyway.FlywayTestExecutionListener.java Source code

Java tutorial

Introduction

Here is the source code for org.raistlic.spring.test.flyway.FlywayTestExecutionListener.java

Source

package org.raistlic.spring.test.flyway;

/*
 * Copyright 2015 Lei CHEN (raistlic@gmail.com)
 *
 * 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.
 */

import org.flywaydb.core.Flyway;
import org.raistlic.common.precondition.Precondition;
import org.raistlic.common.precondition.PreconditionException;
import org.raistlic.spring.test.DataReset;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Lei CHEN (2015-08-30)
 * @since 1.2
 */
public class FlywayTestExecutionListener implements TestExecutionListener {

    private static final Set<String> FIRST_RESET_DONE_SET = Collections.synchronizedSet(new HashSet<String>());

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        // do nothing
    }

    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {
        // do nothing
    }

    /**
     * The method checks whether a database reset needs to be done for the test method, based on the
     * annotations on the test method and test class, and calls flyway to do the database reset if
     * needed.
     *
     * @param testContext the test context, cannot be {@code null}.
     * @throws PreconditionException when the {@code testContext} is {@code null} or not valid.
     * @throws FlywayTestExecutionException when anything goes wrong when trying to do the check or
     *         database reset.
     */
    @Override
    public void beforeTestMethod(TestContext testContext)
            throws PreconditionException, FlywayTestExecutionException {

        Precondition.param(testContext, "testContext").notNull();
        Precondition.param(testContext.getApplicationContext(), "testContext.applicationContext").notNull();
        Precondition.param(testContext.getTestClass(), "testContext.testClass").notNull();
        Precondition.param(testContext.getTestMethod(), "testContext.testMethod").notNull();

        DataReset annotation = getDataResetAnnotation(testContext);
        if (annotation == null) {
            return;
        }
        String name = annotation.value();

        synchronized (FIRST_RESET_DONE_SET) {

            if (FIRST_RESET_DONE_SET.contains(name)) {
                return;
            }
            ApplicationContext applicationContext = testContext.getApplicationContext();
            Flyway flyway = getFlywayBean(applicationContext, name);
            doReset(flyway, name);
            FIRST_RESET_DONE_SET.add(name);
        }
    }

    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {

        DataReset annotation = getDataResetAnnotation(testContext);
        if (annotation == null) {
            return;
        }
        if (annotation.readOnly()) {
            return;
        }
        String name = annotation.value();
        ApplicationContext applicationContext = testContext.getApplicationContext();
        Flyway flyway = getFlywayBean(applicationContext, name);
        doReset(flyway, name);
    }

    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        // do nothing
    }

    /**
     * The method gets and returns the {@link DataReset} annotation from the {@code testContext}, or
     * {@code null} if none found.
     *
     * @param testContext the test context to get {@link DataReset} annotation from, cannot be
     *                    {@code null} and must be valid.
     * @return the {@link DataReset} found, or {@code null} if none found.
     *
     * @throws PreconditionException when {@code testContext} is {@code null} or invalid.
     */
    static DataReset getDataResetAnnotation(TestContext testContext) {

        Precondition.param(testContext, "testContext").notNull();
        Precondition.param(testContext.getTestMethod(), "testContext.testMethod").notNull();
        Precondition.param(testContext.getTestClass(), "testContext.testClass").notNull();

        DataReset annotation = AnnotationUtils.getAnnotation(testContext.getTestMethod(), DataReset.class);
        if (annotation == null) {
            annotation = AnnotationUtils.getAnnotation(testContext.getTestClass(), DataReset.class);
        }
        return annotation;
    }

    /**
     *
     *
     * @param flyway
     * @param name
     */
    static void doReset(Flyway flyway, String name) {

        try {
            flyway.clean();
            flyway.migrate();
        } catch (Exception ex) {
            if (ex instanceof FlywayTestExecutionException) {
                throw (FlywayTestExecutionException) ex;
            } else {
                throw new FlywayTestExecutionException(ex);
            }
        }
    }

    /**
     * The method gets the {@link Flyway} bean from the {@code applicationContext}, using the given
     * {@code name} if its not empty and there are multiple instances of beans of type {@link Flyway}.
     *
     * @param applicationContext the application context to find the bean from, cannot be {@code null}.
     * @param name the name to help identify the bean if multiple instances exist, {@code null} or
     *             empty if not specified.
     * @return the required {@link Flyway} bean.
     *
     * @throws PreconditionException when {@code applicationContext} is {@code null}.
     * @throws FlywayTestExecutionException when any other error occur in the process of finding the
     *         {@link Flyway} bean.
     */
    static Flyway getFlywayBean(ApplicationContext applicationContext, String name) {

        Precondition.param(applicationContext, "applicationContext").notNull();

        try {

            String[] names = applicationContext.getBeanNamesForType(Flyway.class);
            if (names.length == 0) {
                throw new FlywayTestExecutionException("Flyway bean definition not found in applicationContext.");
            }
            if (names.length == 1) {
                return applicationContext.getBean(Flyway.class);
            }
            if (name == null || name.isEmpty()) {
                throw new FlywayTestExecutionException(
                        "Name not specified while multiple Flyway beans found in applicationContext: "
                                + Arrays.asList(names));
            }
            return applicationContext.getBean(name, Flyway.class);
        } catch (Exception ex) {

            if (ex instanceof FlywayTestExecutionException) {
                throw (FlywayTestExecutionException) ex;
            } else {
                throw new FlywayTestExecutionException(ex);
            }
        }
    }
}