Package liquibase.change.custom

Source Code of liquibase.change.custom.CustomChangeWrapper

package liquibase.change.custom;

import liquibase.change.AbstractChange;
import liquibase.change.DatabaseChange;
import liquibase.change.ChangeMetaData;
import liquibase.change.DatabaseChangeProperty;
import liquibase.database.Database;
import liquibase.exception.*;
import liquibase.parser.core.ParsedNode;
import liquibase.parser.core.ParsedNodeException;
import liquibase.resource.ResourceAccessor;
import liquibase.statement.SqlStatement;
import liquibase.util.ObjectUtil;

import java.util.*;

/**
* Adapts CustomChange implementations to the standard change system used by Liquibase.
* Custom change implementations should implement CustomSqlChange or CustomTaskChange
*
* @see liquibase.change.custom.CustomSqlChange
* @see liquibase.change.custom.CustomTaskChange
*/
@DatabaseChange(name="customChange",
        description = "Although Liquibase tries to provide a wide range of database refactorings, there are times you may want to create your own custom refactoring class.\n" +
                "\n" +
                "To create your own custom refactoring, simply create a class that implements the liquibase.change.custom.CustomSqlChange or liquibase.change.custom.CustomTaskChange interface and use the <custom> tag in your change set.\n" +
                "\n" +
                "If your change can be rolled back, implement the liquibase.change.custom.CustomSqlRollback interface as well.\n" +
                "\n" +
                "For a sample custom change class, see liquibase.change.custom.ExampleCustomSqlChange",
        priority = ChangeMetaData.PRIORITY_DEFAULT)
public class CustomChangeWrapper extends AbstractChange {

    /**
     * Non-private access only for testing.
     */
    CustomChange customChange;
   
    private String className;

    private SortedSet<String> params = new TreeSet<String>();

    private Map<String, String> paramValues = new HashMap<String, String>();

    private ClassLoader classLoader;

    private boolean configured = false;

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

    /**
     * Return the CustomChange instance created by the call to {@link #setClass(String)}.
     */
    @DatabaseChangeProperty(isChangeProperty = false)
    public CustomChange getCustomChange() {
        return customChange;
    }

    /**
     * Returns the classloader to use when creating the CustomChange instance in {@link #setClass(String)}.
     */
    @DatabaseChangeProperty(isChangeProperty = false)
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * Specify the name of the class to use as the CustomChange. This method instantiates the class using {@link #getClassLoader()} or fallback methods
     * and assigns it to {@link #getCustomChange()}.
     * {@link #setClassLoader(ClassLoader)} must be called before this method. The passed class is constructed, but no parameters are set. They are set in {@link liquibase.change.Change#generateStatements(liquibase.database.Database)}
     */
    public CustomChangeWrapper setClass(String className) throws CustomChangeException {
        if (classLoader == null) {
            throw new CustomChangeException("CustomChangeWrapper classLoader not set");
        }
        this.className = className;
            try {
                try {
                    customChange = (CustomChange) Class.forName(className, true, classLoader).newInstance();
                } catch (ClassCastException e) { //fails in Ant in particular
                    try {
                        customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).newInstance();
                    } catch (ClassNotFoundException e1) {
                        customChange = (CustomChange) Class.forName(className).newInstance();
                    }
                }
        } catch (Exception e) {
            throw new CustomChangeException(e);
        }

        return this;
    }

    /**
     * Return the name of the custom class set in {@link #setClass(String)}
     * @return
     */
    @DatabaseChangeProperty(description = "Name class that implements the custom change.")
    public String getClassName() {
        return className;
    }

    /**
     * Specify a parameter on the CustomChange object to set before executing {@link liquibase.change.Change#generateStatements(liquibase.database.Database)}  or {@link #generateRollbackStatements(liquibase.database.Database)} on it.
     * The CustomChange class must have a set method for the given parameter. For example, to call setParam("lastName", "X") you must have a method setLastName(String val) on your class.
     */
    public void setParam(String name, String value) {
        this.params.add(name);
        this.paramValues.put(name, value);
    }

    /**
     * Returns the parameters set by {@link #setParam(String, String)}. If no parameters are set, an empty set will be returned
     */
    @DatabaseChangeProperty(isChangeProperty = false)
    public SortedSet<String> getParams() {
        return Collections.unmodifiableSortedSet(params);
    }

    /**
     * Get the value of a parameter set by {@link #setParam(String, String)}. If the parameter was not set, null will be returned.
     */
    public String getParamValue(String key) {
        return paramValues.get(key);
    }

    /**
     * Call the {@link CustomChange#validate(liquibase.database.Database)} method and return the result.
     */
    @Override
    public ValidationErrors validate(Database database) {
        if (!configured) {
            try {
                configureCustomChange();
            } catch (CustomChangeException e) {
                throw new UnexpectedLiquibaseException(e);
            }
        }

        try {
            return customChange.validate(database);
        } catch (Throwable e) {
            return new ValidationErrors().addError("Exception thrown calling "+getClassName()+".validate():"+ e.getMessage());
        }
    }

    /**
     * Required for the Change interface, but not supported by CustomChanges. Returns an empty Warnings object.
     */
    @Override
    public Warnings warn(Database database) {
        //does not support warns
        return new Warnings();
    }

    /**
     * Finishes configuring the CustomChange based on the values passed to {@link #setParam(String, String)} then calls {@link CustomSqlChange#generateStatements(liquibase.database.Database)}
     * or {@link CustomTaskChange#execute(liquibase.database.Database)} depending on the CustomChange implementation.
     * <p></p>
     * If the CustomChange returns a null SqlStatement array, this method returns an empty array. If a CustomTaskChange is being used, this method will return an empty array.
     */
    @Override
    public SqlStatement[] generateStatements(Database database) {
        SqlStatement[] statements = null;
        try {
            if (!configured) {
                configureCustomChange();
            }
            if (customChange instanceof CustomSqlChange) {
                statements = ((CustomSqlChange) customChange).generateStatements(database);
            } else if (customChange instanceof CustomTaskChange) {
                ((CustomTaskChange) customChange).execute(database);
            } else {
                throw new UnexpectedLiquibaseException(customChange.getClass().getName() + " does not implement " + CustomSqlChange.class.getName() + " or " + CustomTaskChange.class.getName());
            }
        } catch (CustomChangeException e) {
            throw new UnexpectedLiquibaseException(e);
        }

        if (statements == null) {
            statements = new SqlStatement[0];
        }
        return statements;
    }

    /**
     * Finishes configuring the CustomChange based on the values passed to {@link #setParam(String, String)} then calls {@link CustomSqlRollback#generateRollbackStatements(liquibase.database.Database)}
     * or {@link CustomTaskRollback#rollback(liquibase.database.Database)} depending on the CustomChange implementation.
     * <p></p>
     * If the CustomChange returns a null SqlStatement array, this method returns an empty array. If a CustomTaskChange is being used, this method will return an empty array.
     * Any {@link RollbackImpossibleException} exceptions thrown by the CustomChange will thrown by this method.
     */
    @Override
    public SqlStatement[] generateRollbackStatements(Database database) throws RollbackImpossibleException {
        SqlStatement[] statements = null;
        try {
            if (!configured) {
                configureCustomChange();
            }
            if (customChange instanceof CustomSqlRollback) {
                statements = ((CustomSqlRollback) customChange).generateRollbackStatements(database);
            } else if (customChange instanceof CustomTaskRollback) {
                ((CustomTaskRollback) customChange).rollback(database);
            } else {
                throw new RollbackImpossibleException("Unknown rollback type: "+customChange.getClass().getName());
            }
        } catch (CustomChangeException e) {
            throw new UnexpectedLiquibaseException(e);
        }

        if (statements == null) {
            statements = new SqlStatement[0];
        }
        return statements;

    }


    /**
     * Returns true if the customChange supports rolling back.
     * {@link #generateRollbackStatements} may still trow a {@link RollbackImpossibleException} when it is actually exectued, even if this method returns true.
     * Currently only checks if the customChange implements {@link CustomSqlRollback}
     */
    @Override
    public boolean supportsRollback(Database database) {
        return customChange instanceof CustomSqlRollback || customChange instanceof CustomTaskRollback;
    }

    /**
     * Return the customChange's {@link CustomChange#getConfirmationMessage} message as the Change's message.
     */
    @Override
    public String getConfirmationMessage() {
        return customChange.getConfirmationMessage();
    }

    private void configureCustomChange() throws CustomChangeException {
        try {
            for (String param : params) {
                ObjectUtil.setProperty(customChange, param, paramValues.get(param));
            }
            customChange.setFileOpener(getResourceAccessor());
            customChange.setUp();
        } catch (Exception e) {
            throw new CustomChangeException(e);
        }
    }

    @Override
    public SerializationType getSerializableFieldType(String field) {
        if (field.equals("class")) {
            return SerializationType.NAMED_FIELD;
        } else if (field.equals("param")) {
            return SerializationType.NESTED_OBJECT;
        } else {
            throw new UnexpectedLiquibaseException("Unexpected CustomChangeWrapper field "+field);
        }
    }

    @Override
    public Object getSerializableFieldValue(String field) {
        if (field.equals("class")) {
            return getClassName();
        } else if (field.equals("param")) {
            return this.paramValues;
        } else {
            throw new UnexpectedLiquibaseException("Unexpected CustomChangeWrapper field "+field);
        }
    }

    @Override
    public Set<String> getSerializableFields() {
        return new HashSet<String>(Arrays.asList("class", "param"));
    }

    @Override
    public String getSerializedObjectNamespace() {
        return STANDARD_CHANGELOG_NAMESPACE;
    }

    @Override
    public void load(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException {
        setClassLoader(resourceAccessor.toClassLoader());
        try {
            setClass(parsedNode.getChildValue(null, "class", String.class));
        } catch (CustomChangeException e) {
            throw new ParsedNodeException(e);
        }
        super.load(parsedNode, resourceAccessor);
    }

    @Override
    public void customLoadLogic(ParsedNode parsedNode, ResourceAccessor resourceAccessor) throws ParsedNodeException {
        ParsedNode paramsNode = parsedNode.getChild(null, "params");
        if (paramsNode == null) {
            paramsNode = parsedNode;
        }

        for (ParsedNode child : paramsNode.getChildren(null, "param")) {
            Object value = child.getValue();
            if (value == null) {
                value = child.getChildValue(null, "value");
            }
            if (value != null) {
                value = value.toString();
            }
            this.setParam(child.getChildValue(null, "name", String.class), (String) value);
        }

        CustomChange customChange = null;
        try {
            customChange = (CustomChange) Class.forName(className, false, resourceAccessor.toClassLoader()).newInstance();
        } catch (Exception e) {
            throw new UnexpectedLiquibaseException(e);
        }
        for (ParsedNode node : parsedNode.getChildren()) {
            Object value = node.getValue();
            if (value != null && ObjectUtil.hasProperty(customChange, node.getName())) {
                this.setParam(node.getName(), value.toString());
            }
        }
    }
}
TOP

Related Classes of liquibase.change.custom.CustomChangeWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.