Custom Liquibase Change error when integrating with Spring: Unknown Liquibase extension. Are you missing a jar from your classpath?

StackOverflow https://stackoverflow.com/questions/22635666

  •  20-06-2023
  •  | 
  •  

Question

As part of the database upgrade for an existing application, a custom Liquibase change which extends AbstractChange has been created:

@DatabaseChange(name = "springBeanChange", description = "Runs a spring bean custom change", priority = ChangeMetaData.PRIORITY_DEFAULT)
public class SpringBeanChange extends AbstractChange {
    private String beanName;
    private String changeClass;

    @DatabaseChangeProperty(description = "Spring bean name (optional)")
    public String getBeanName() {
        return this.beanName;
    }

    public void setBeanName(final String beanName) {
        this.beanName = beanName;
    }

    @DatabaseChangeProperty(description = "Spring bean class")
    public String getChangeClass() {
        return this.changeClass;
    }

    public void setChangeClass(final String changeClass) {
        this.changeClass = changeClass;
    }

    private CustomTaskChange bean;

    @Override
    public boolean generateStatementsVolatile(final Database database) {
        return true;
    }

    @Override
    public String getConfirmationMessage() {
        return findBean().getConfirmationMessage();
    }

    @Override
    public SqlStatement[] generateStatements(final Database database) {
        try {
            findBean().execute(database);
        } catch (CustomChangeException e) {
            throw new UnexpectedLiquibaseException(e);
        }
        return new SqlStatement[0];
    }

    @SuppressWarnings("unchecked")
    private CustomTaskChange findBean() {
        Class<CustomTaskChange> requiredType = CustomTaskChange.class;
        if (this.changeClass != null) {
            try {
                Class<?> requestedType = Class.forName(this.changeClass);
                if (CustomTaskChange.class.isAssignableFrom(requestedType)) {
                    requiredType = (Class<CustomTaskChange>) requestedType;
                } else {
                    throw new UnexpectedLiquibaseException(
                            "Specified changeClass " + this.changeClass
                                    + " was not an instance of "
                                    + CustomTaskChange.class);
                }
            } catch (ClassNotFoundException e) {
                throw new UnexpectedLiquibaseException(
                        "Could not create change class", e);
            }
        }
        if (this.bean == null) {
            if (getBeanName() == null) {
                this.bean = SpringContextHolder.getInstance().getContext()
                        .getBean(requiredType);
            } else {
                this.bean = SpringContextHolder.getInstance().getContext()
                        .getBean(getBeanName(), requiredType);
            }
        }
        return this.bean;
    }

    @Override
    public ValidationErrors validate(final Database database) {
        try {
            return findBean().validate(database);
        } catch (NullPointerException p_exception) {
            return new ValidationErrors()
                    .addError("Exception thrown calling validate():"
                            + p_exception.getMessage());
        } catch (UnexpectedLiquibaseException p_exception) {
            return new ValidationErrors()
                    .addError("Exception thrown calling validate():"
                            + p_exception.getMessage());
        } catch (NoSuchBeanDefinitionException p_exception) {
            return new ValidationErrors()
                    .addError("Exception thrown calling validate():"
                            + p_exception.getMessage());
        } catch (BeanNotOfRequiredTypeException p_exception) {
            return new ValidationErrors()
                    .addError("Exception thrown calling validate():"
                            + p_exception.getMessage());
        } catch (BeansException p_exception) {
            return new ValidationErrors()
                    .addError("Exception thrown calling validate():"
                            + p_exception.getMessage());
        }
    }
}

This is then configured in the database change log XML file as follows:

    <changeSet id="15" author="theauthor">
        <XXX:springBeanChange
            changeClass="XXX.XXX.XXX.upgrade.tasks.TaskUpgrade" />
    </changeSet>

Where TaskUpgrade implements CustomTaskChange and is the class that is returned get the call to getBean in SpringBeanChange.

The database change log file also contains many 'normal' liquibase commands such as addColumn.

A custom Java class has then been written which actually performs the upgrade (the following lines show the important lines of code which actually do the upgrade). This Java program is manually executed after the application has been deployed onto the server:

        Liquibase liquibase =
                new Liquibase(getChangeLogFile(), new ClassLoaderResourceAccessor(), new JdbcConnection(conn));
        if ("update".equalsIgnoreCase(command)) {
            liquibase.update(contexts);
        } else {
            throw new IllegalArgumentException("Unknown command " + command);
        }

This works fine and executes the database upgrade.

I'm looking to avoid the need to use the custom Java program to perform the upgrade and actually do it when the application starts up. The app already uses Spring so it makes sense to to the upgrade when the Spring context is initialising.

To do this, I've added the following to the applicationContext file:

    <bean id="myLiquibase" class="liquibase.integration.spring.SpringLiquibase">
        <property name="dataSource" ref="dataSource" />
        <property name="changeLog" value="classpath:databaseChangeLog.xml" />
        <property name="contexts" value="test, production" />
    </bean>

However, when the application starts up, I get the following exception:

2014-03-25 13:02:16,097 [main] ERROR context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myLiquibase' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is liquibase.exception.ChangeLogParseException: Invalid Migration File: Unknown Liquibase extension: springBeanChange.  Are you missing a jar from your classpath?
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1488)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:524)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:461)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:626)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:389)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:294)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
    at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:549)
    at org.mortbay.jetty.servlet.Context.startContext(Context.java:136)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1282)
    at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:518)
    at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:499)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.jetty.Server.doStart(Server.java:224)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at XXX.XXX.XXX.demo.YYYDemo.main(YYYDemo.java:47)
Caused by: liquibase.exception.ChangeLogParseException: Invalid Migration File: Unknown Liquibase extension: springBeanChange.  Are you missing a jar from your classpath?
    at liquibase.parser.core.xml.XMLChangeLogSAXParser.parse(XMLChangeLogSAXParser.java:133)
    at liquibase.Liquibase.update(Liquibase.java:129)
    at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:291)
    at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:258)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1547)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1485)
    ... 22 more
Caused by: org.xml.sax.SAXException: Unknown Liquibase extension: springBeanChange.  Are you missing a jar from your classpath?
    at liquibase.parser.core.xml.XMLChangeLogSAXHandler.startElement(XMLChangeLogSAXHandler.java:495)
    at org.apache.xerces.parsers.AbstractSAXParser.startElement(Unknown Source)
    at org.apache.xerces.parsers.AbstractXMLDocumentParser.emptyElement(Unknown Source)
    at org.apache.xerces.impl.xs.XMLSchemaValidator.emptyElement(Unknown Source)
    at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
    at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
    at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
    at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
    at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
    at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
    at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
    at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
    at liquibase.parser.core.xml.XMLChangeLogSAXParser.parse(XMLChangeLogSAXParser.java:99)
    ... 27 more
Caused by: org.xml.sax.SAXException: Unknown Liquibase extension: springBeanChange.  Are you missing a jar from your classpath?
    at liquibase.parser.core.xml.XMLChangeLogSAXHandler.startElement(XMLChangeLogSAXHandler.java:359)
    ... 39 more

I can't find anything on the Liquibase site or anywhere else which provides any help as to what JAR I'm missing (if indeed I am missing one) or if anything else is missing/not defined.

Does anyone have any ideas?

Thanks in advance.

Was it helpful?

Solution

The jar that contains the custom extension is what is missing from your classpath. I don't use Spring, so I don't know how it sets up the classpath, but you probably need to either put the jar in a standard location so Spring can find it or else configure the Spring classpath.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top