Skip to content

Add a Resource Accessor

Prerequisites

Implementing Liquibase extensions requires an understanding of Java. You will be creating classes, overriding methods, and working with inheritance hierarchies.

Project Setup

If you have not already created a repository to hold your code, see Your First Extension in the Getting Started guide.

Overview

When adding support for a new resource accessor, the interface you are going to implement is liquibase.resource.ResourceAccessor.

ResourceAccessors contain and hide the implementation details of how the file references in changelog files are actually found and read. They convert the path names into liquibase.resource.Resources which can then be used like files normally are.

For example, the path my/file.sql can be read from a directory, from a zip file, or from the network depending on the configured ResourceAccessors.

ResourceAccessor should be thread-safe.

Tip

There is a liquibase.resource.AbstractResourceAccessor base class you can use which limits the number of methods you must implement.

You can also extend existing classes like liquibase.resource.DirectoryResourceAccessor or liquibase.resource.ClassLoaderResourceAccessor or liquibase.resource.ZipResourceAccessor as needed.

Custom Resources

Depending on the new ResourceAccessor, you may need to define a new liquibase.resource.Resource implementation.

Tip

AbstractResource defines resolvePath() and resolveSiblingPath() methods that compute the other paths based purely on the path strings.

If your underlying system has a better way to resolve files, those should be used. But those methods exist as a fallback when needed.

Example Code

package com.example.resource;


import liquibase.resource.AbstractResourceAccessor;
import liquibase.resource.Resource;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ExampleResourceAccessor extends AbstractResourceAccessor {

    private final String root;

    public ExampleResourceAccessor() {
        root = null;
    }

    public ExampleResourceAccessor(String root) {
        this.root = root;
    }

    @Override
    public List<Resource> search(String path, boolean recursive) throws IOException {
        List<Resource> returnList = new ArrayList<>();
        returnList.add(get(path + "/1.sql"));
        returnList.add(get(path + "/2.sql"));
        if (recursive) {
            returnList.add(get(path + "/a/3.sql"));
            returnList.add(get(path + "/b/4.sql"));
        }

        return returnList;
    }

    @Override
    public List<Resource> getAll(String path) throws IOException {
        return Collections.singletonList(new ExampleResource(path, this));
    }

    @Override
    public List<String> describeLocations() {
        return Collections.singletonList("Random Resource Value");
    }

    @Override
    public void close() throws Exception {

    }

}

ExampleResource Code

package com.example.resource;

import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.resource.AbstractResource;
import liquibase.resource.Resource;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

class ExampleResource extends AbstractResource {

    private final ExampleResourceAccessor resourceAccessor;

    public ExampleResource(String path, ExampleResourceAccessor resourceAccessor) {
        super(path, URI.create("example:" + path));
        this.resourceAccessor = resourceAccessor;
    }

    @Override
    public InputStream openInputStream() throws IOException {
        return new ByteArrayInputStream(("Example content from " + getPath()).getBytes());
    }

    @Override
    public boolean exists() {
        return true;
    }

    @Override
    public Resource resolve(String other) {
        try {
            return resourceAccessor.get(resolvePath(other));
        } catch (IOException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }

    @Override
    public Resource resolveSibling(String other) {
        try {
            return resourceAccessor.get(resolvePath(other));
        } catch (IOException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }
}