Tutorial: Write your own extension

I started writing some extensions for LiquiBase and want to share my knowledge on this, because the documentation is …
This example shows how to write an extension for LiquiBase that introduces the new “helloWorld” tag for changelogs.

  1. At first you have to extend the changelog schema. Create a new schema changelog-ext.xsd in the package liquibase.parser.core.xml. The LiquiBase schema resolver is watching for schemas in this package.
    <?xml version="1.0" encoding="UTF-8"?>

    <xsd:schema xmlns:xsd=“http://www.w3.org/2001/XMLSchema”

               targetNamespace=“http://www.liquibase.org/xml/ns/changelog-ext”
               xmlns=“http://www.liquibase.org/xml/ns/changelog-ext”
               elementFormDefault=“qualified”>

     <xsd:element name=“helloWorld”>
       xsd:complexType
         <xsd:attribute name=“message” type=“xsd:string”/>
       </xsd:complexType>
     </xsd:element>

    </xsd:schema>

  1. Implement the Change interface that takes the data from the changelog file when it is processed.
    public class HelloWorldChange extends AbstractChange {

     private String message;
     
     public HelloWorldChange() {
       super(“helloWorld”, “Hello World”, ChangeMetaData.PRIORITY_DEFAULT);
     }
     
     @Override
     public SqlStatement[] generateStatements(Database database) {
       return new SqlStatement[] {
           new HelloWorldStatement(getMessage())
       };
     }

     @Override
     public String getConfirmationMessage() {
       return "Hello World " + getMessage();
     }

     public String getMessage() {
       return message;
     }

     public void setMessage(String message) {
       this.message = message;
     }

    }


The first argument when calling the super constructor defines the tag name to which this change should be connected.

  1. The change returns an array of statements which should be executed. In our case we only have the HelloWorldStatement.
    public class HelloWorldStatement implements SqlStatement {

     private String message;
     
     public HelloWorldStatement(String message) {
       setMessage(message);
     }

     public String getMessage() {
       return message;
     }

     public void setMessage(String message) {
       this.message = message;
     }
     
    }

  1. You need a generator that converts the abstract statement into SQL.
    public class HelloWorldGenerator implements SqlGenerator {

     @Override
     public Sql[] generateSql(HelloWorldStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
       return new Sql[] {
           new UnparsedSql("SELECT * FROM " + statement.getMessage())
       };
     }

     @Override
     public int getPriority() {
       return PRIORITY_DEFAULT;
     }

     @Override
     public boolean supports(HelloWorldStatement statement, Database database) {
       return true;
     }

     @Override
     public ValidationErrors validate(HelloWorldStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
       ValidationErrors validationErrors = new ValidationErrors();
       return validationErrors;
     }

    }

Put all the classes into the package liquibase.ext.helloworld. LiquiBase is looking in liquibase.ext for extensions (among others).

  1. Finally you can use the new helloWorld tag in a changelog.
    <?xml version="1.0" encoding="UTF-8"?>

    <databaseChangeLog
     xmlns=“http://www.liquibase.org/xml/ns/dbchangelog”
    xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
    xmlns:ext=“http://www.liquibase.org/xml/ns/changelog-ext”
    xsi:schemaLocation=“http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd
                       http://www.liquibase.org/xml/ns/changelog-ext changelog-ext.xsd”>
     
     
       
       

Thanks for the write-up.  I am planning on improving the documentation, just working on finalizing the code first… 

Whenever you find areas that could be improved, feel free to update the site as well.  It is running as a wiki to allow anyone to add to the docs as needed.

Nathan

so I did all the above documented steps… but I keep getting the following exception:

    org.xml.sax.SAXException: Unknown Liquibase extension: dropAllProcedures.  Are you missing a jar from your classpath?        at liquibase.parser.core.xml.XMLChangeLogSAXHandler.startElement(XMLChangeLogSAXHandler.java:332)        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:533)        at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:220)        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:739)        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:322)        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(XMLDocumentFragmentScannerImpl.java:1693)        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:368)        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:834)        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:148)        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1242)        at liquibase.parser.core.xml.XMLChangeLogSAXParser.parse(XMLChangeLogSAXParser.java:98)        at liquibase.Liquibase.update(Liquibase.java:106)        at liquibase.Liquibase.update(Liquibase.java:144)        at liquibase.integration.commandline.Main.doMigration(Main.java:700)        at liquibase.integration.commandline.Main.main(Main.java:115)

That makes it sound like either your custom class is not being found by the classloader running liquibase, or else your custom class is not passing “dropAllProcedures” as the first parameter to the superclass’s constructor.  It will not be found by the classloader if it is not in one of the packages (or it’s subpackages) listed in the MANIFEST.MF file:

liquibase.change,
liquibase.database,
liquibase.parser,
liquibase.precondition,
liquibase.serializer,
liquibase.sqlgenerator,
liquibase.executor,
liquibase.snapshot,
liquibase.logging,
liquibase.ext

Nathan

That is strange… Coz, I have passed the argument as well as conforming to the package structure… BTW, I am using the 2.0.0-bin distribution…

Interestingly, it works when I add the files to the liquibase.jar manually. May be a classloader issue ?

I tried the following and it did not work with any:

  1. Add the extension jar to the lib folder under the liquibase dir.
  2. Add the extension jar to the liquibase dir
  3. Have the extension jar outside the liquibase folders and add it to the classpath variable in the liquibase.properties file

That is odd.  Could you attach the extension jar file you have and how you are tryign to run it?  If you’d prefer, you can email it to me at nathan@liquibase.org

Nathan

Thank you for the post!

I tried the steps to create my own mssql extension, but got error “Invalid content was found…” for elements I defined in dbchangelog-ext.xsd.

The version of Liquibase I downloaded is 3.0.0-beta2-snapshot.  I have replaced dbchangelog-ext.xsd in liquibase-core\src\main\resources\liquibase\parser\core\xml and placed a copy of dbchangelog-ext.xsd in the folder where the extension jar file is located and rebuilt liquibase and extension jars, but I still got the error.

Any idea what i did wrong?

Thanks very much,