All Downloads are FREE. Search and download functionalities are using the official Maven repository.

liquibase.sdk.watch.WatchCommand Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase.sdk.watch;

import liquibase.change.ColumnConfig;
import liquibase.command.AbstractCommand;
import liquibase.command.CommandValidationErrors;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
import liquibase.integration.commandline.CommandLineResourceAccessor;
import liquibase.lockservice.LockService;
import liquibase.lockservice.LockServiceFactory;
import liquibase.resource.CompositeResourceAccessor;
import liquibase.sdk.Main;
import liquibase.sdk.TemplateService;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.statement.core.SelectFromDatabaseChangeLogStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Schema;
import liquibase.util.ISODateFormat;
import liquibase.util.StringUtils;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

public class WatchCommand extends AbstractCommand {

    private String url;
    private String username;
    private String password;
    private int port = 8080;

    private Main mainApp;

    public WatchCommand(Main mainApp) {
        this.mainApp = mainApp;
    }

    @Override
    public String getName() {
        return "watch";
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public CommandValidationErrors validate() {
        return new CommandValidationErrors(this);
    }

    @Override
    protected Object run() throws Exception {
        Server server = new Server(port);

        List jarUrls = new ArrayList();
        File libDir = new File("../../lib/");
        for (File file : libDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith("jar");
            }
        })) {
            jarUrls.add(file.toURL());
        }
        CompositeResourceAccessor resourceAccessor = new CompositeResourceAccessor(
                new CommandLineResourceAccessor(new URLClassLoader(jarUrls.toArray(new URL[jarUrls.size()]), this.getClass().getClassLoader()))
        );
        Database database = DatabaseFactory.getInstance().openDatabase(url, username, password, null, resourceAccessor);

        ResourceHandler staticHandler = new ResourceHandler();
        staticHandler.setDirectoriesListed(false);
        staticHandler.setWelcomeFiles(new String[]{"index.html"});
        staticHandler.setResourceBase(getClass().getClassLoader().getResource("liquibase/sdk/watch/index.html.vm").toExternalForm().replaceFirst("index.html.vm$", ""));

        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[]{new DynamicContentHandler(database), staticHandler, new DefaultHandler()});

        server.setHandler(handlers);
        server.start();

        mainApp.out("Liquibase Watch running on http://localhost:"+getPort()+"/");
        server.join();


        return "Started";
    }

    private static class DynamicContentHandler extends AbstractHandler {
        private final Database database;
        private final Executor executor;

        public DynamicContentHandler(Database database) {
            this.database = database;
            executor = ExecutorService.getInstance().getExecutor(database);
        }

        @Override
        public void handle(String url, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
            try {

                if (url.equals("/favicon.ico")) {
                    httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
                    request.setHandled(true);
                } else if (url.equals("/index.html") || url.equals("/") || url.equals("")) {
                    Map context = new HashMap();
                    this.loadIndexData(context);

                    httpServletResponse.setContentType("text/html");
                    httpServletResponse.setStatus(HttpServletResponse.SC_OK);

                    TemplateService.getInstance().write("liquibase/sdk/watch/index.html.vm", httpServletResponse.getWriter(), context);
                    request.setHandled(true);
                } else if (url.equals("/liquibase-status.json")) {
                    if (SnapshotGeneratorFactory.getInstance().hasDatabaseChangeLogTable(database)) {
                        LockService lockService = LockServiceFactory.getInstance().getLockService(database);
                        lockService.waitForLock();
                        List> rows;
                        try {
                            SelectFromDatabaseChangeLogStatement select = new SelectFromDatabaseChangeLogStatement(new ColumnConfig().setName("COUNT(*) AS ROW_COUNT", true), new ColumnConfig().setName("MAX(DATEEXECUTED) AS LAST_EXEC", true));
                            rows = executor.queryForList(select);
                        } finally {
                            lockService.releaseLock();
                        }
                        PrintWriter writer = httpServletResponse.getWriter();

                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
                        if (rows.size() == 0) {
                            writer.print("{\"count\": 0}");
                        } else {
                            Map row = rows.iterator().next();
                            writer.print("{\"count\":" + row.get("ROW_COUNT") + ", \"lastExec\": \"" + new ISODateFormat().format((Date) row.get("LAST_EXEC")) + "\"}");
                        }
                        request.setHandled(true);
                    } else {
                        PrintWriter writer = httpServletResponse.getWriter();

                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
                        writer.print("{\"count\": -1}");
                        request.setHandled(true);
                    }
                }
            } catch (Throwable e) {
                throw new ServletException(e);
            }
        }

        protected void writeDatabaseChangeLogTab(Map context) throws DatabaseException {
            String outString;
            String changeLogDetails = "";
            if (SnapshotGeneratorFactory.getInstance().hasDatabaseChangeLogTable(database)) {

                outString = "";
                outString += "";

                SelectFromDatabaseChangeLogStatement select = new SelectFromDatabaseChangeLogStatement("FILENAME", "AUTHOR", "ID", "MD5SUM", "DATEEXECUTED", "ORDEREXECUTED", "EXECTYPE", "DESCRIPTION", "COMMENTS", "TAG", "LIQUIBASE").setOrderBy("DATEEXECUTED DESC", "ORDEREXECUTED DESC"); //going in opposite order for easier reading
                List ranChangeSets = (List) ExecutorService.getInstance().getExecutor(database).queryForList(select);

                for (Map row : ranChangeSets) {
                    String id = cleanHtmlId(row.get("ID") + ":" + row.get("AUTHOR") + ":" + row.get("FILENAME"));
                    outString += "" +
                            "" +
                            "" +
                            "" +
                            "" +
                            "" +
                            "";

                    changeLogDetails += wrapDetails(id, row.get("ID") + " :: " + row.get("AUTHOR") + " :: " + row.get("FILENAME"), writeDatabaseChangeLogDetails(row));
                }
                outString += "
IdAuthorPathExecTypeTag
" + StringUtils.escapeHtml((String) row.get("ID")) + "" + StringUtils.escapeHtml((String) row.get("AUTHOR")) + "" + StringUtils.escapeHtml((String) row.get("FILENAME")) + "" + row.get("EXECTYPE") + "" + StringUtils.escapeHtml((String) StringUtils.trimToEmpty((String) row.get("TAG"))) + "
"; } else { outString = "

No DatabaseChangeLog Table

"; } context.put("changeLog", outString); context.put("changeLogDetails", changeLogDetails); } private String cleanHtmlId(String id) { return id.replaceAll("[^a-zA-Z0-9_\\-]", "_"); } protected String writeDatabaseChangeLogDetails(Map row) throws DatabaseException { String outString = ""; outString += "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; outString += "
Id" + row.get("ID") + "
Author" + row.get("AUTHOR") + "
Filename" + row.get("FILENAME") + "
DateExecuted" + new ISODateFormat().format((Date) row.get("DATEEXECUTED")) + "
OrderExecuted" + row.get("ORDEREXECUTED") + "
ExecType" + row.get("EXECTYPE") + "
MD5Sum" + row.get("MD5SUM") + "
Description" + row.get("DESCRIPTION") + "
Comments" + row.get("COMMENTS") + "
Tag" + StringUtils.trimToNull((String) row.get("TAG")) + "
Liquibase" + row.get("LIQUIBASE") + "
"; return outString; } public void loadIndexData(Map context) { try { DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(database.getDefaultSchema(), database, new SnapshotControl(database)); StringBuilder buffer = new StringBuilder(); Database database = snapshot.getDatabase(); buffer.append("
"); buffer.append("

").append(StringUtils.escapeHtml(database.getConnection().getURL())).append("

\n"); buffer.append("
"); buffer.append("Database type: ").append(StringUtils.escapeHtml(database.getDatabaseProductName())).append("
\n"); buffer.append("Database version: ").append(StringUtils.escapeHtml(database.getDatabaseProductVersion())).append("
\n"); buffer.append("Database user: ").append(StringUtils.escapeHtml(database.getConnection().getConnectionUserName())).append("
\n"); Set schemas = snapshot.get(Schema.class); if (schemas.size() > 1) { throw new UnexpectedLiquibaseException("Can only display one schema"); } Schema schema = schemas.iterator().next(); if (database.supportsSchemas()) { buffer.append("Catalog & Schema: ").append(schema.getCatalogName()).append(" / ").append(schema.getName()).append("
\n"); } else { buffer.append("Catalog: ").append(schema.getCatalogName()).append("
\n"); } buffer.append("
\n"); buffer.append("
\n"); SnapshotControl snapshotControl = snapshot.getSnapshotControl(); List includedTypes = sort(snapshotControl.getTypesToInclude()); StringBuilder catalogBuffer = new StringBuilder(); StringBuilder detailsBuilder = new StringBuilder(); catalogBuffer.append("\n"); catalogBuffer.append("
\n"); catalogBuffer.append("
\n"); writeDatabaseChangeLogTab(context); detailsBuilder.append(context.get("changeLogDetails")); catalogBuffer.append(context.get("changeLog")); catalogBuffer.append("
"); for (Class type : includedTypes) { List databaseObjects = sort(schema.getDatabaseObjects(type)); if (databaseObjects.size() > 0) { catalogBuffer.append("
\n"); catalogBuffer.append("
    \n"); StringBuilder typeBuffer = new StringBuilder(); for (DatabaseObject databaseObject : databaseObjects) { String id = databaseObject.getClass().getName() + "-" + databaseObject.getName(); id = cleanHtmlId(id); typeBuffer.append("
  1. ").append(StringUtils.escapeHtml(databaseObject.getName())).append("
  2. \n"); detailsBuilder.append(wrapDetails(id, type.getSimpleName()+" "+databaseObject.getName(), writeDatabaseObject(databaseObject, new HashSet(), databaseObject.getName()))).append("\n"); } catalogBuffer.append(StringUtils.indent(typeBuffer.toString(), 4)).append("\n"); catalogBuffer.append("
\n"); catalogBuffer.append("
\n"); } } catalogBuffer.append("
\n"); buffer.append(StringUtils.indent(catalogBuffer.toString(), 4)); context.put("snapshot", buffer.toString()); //standardize all newline chars context.put("details", detailsBuilder.toString()); //standardize all newline chars } catch (Exception e) { throw new UnexpectedLiquibaseException(e); } } protected String wrapDetails(String id, String title, String details) { StringBuilder buffer = new StringBuilder(); buffer.append(""); return buffer.toString(); } protected String writeDatabaseObject(final DatabaseObject databaseObject, Set oldParentNames, String newParentName) { final Set parentNames = new HashSet(oldParentNames); parentNames.add(newParentName); StringBuilder singleValueOut = new StringBuilder(); StringBuilder multiValueOut = new StringBuilder(); final List attributes = sort(databaseObject.getAttributes()); for (String attribute : attributes) { if (attribute.equals("name")) { continue; } if (attribute.equals("schema")) { continue; } Object value = databaseObject.getAttribute(attribute, Object.class); if (value instanceof Schema) { continue; } boolean multiValue = false; if (value instanceof DatabaseObject) { if (parentNames.contains(((DatabaseObject) value).getName())) { value = null; } else { value = databaseObject.getSerializableFieldValue(attribute); } } else if (value instanceof Collection) { if (((Collection) value).size() == 0) { value = null; } else { multiValue = true; Object firstValue = ((Collection) value).iterator().next(); if (firstValue instanceof DatabaseObject) { final List rowAttributes = new ArrayList(); rowAttributes.add("name"); for (DatabaseObject obj : ((Collection) value)) { for (String rowAttribute : obj.getAttributes()) { if (!rowAttributes.contains(rowAttribute)) { Object cellValue = obj.getAttribute(rowAttribute, Object.class); if (cellValue instanceof DatabaseObject && parentNames.contains(((DatabaseObject) cellValue).getName())) { continue; } else { if (cellValue == null || (cellValue instanceof Collection && ((Collection) cellValue).size() == 0)) { continue; } else { rowAttributes.add(rowAttribute); } } } } } value = StringUtils.join((Collection) value, "\n", new StringUtils.StringUtilsFormatter() { @Override public String toString(Object obj) { if (obj instanceof DatabaseObject) { String row = ""; for (String attribute : rowAttributes) { if (((DatabaseObject) obj).getAttributes().contains(attribute)) { row += "" + StringUtils.escapeHtml(((DatabaseObject) obj).getSerializableFieldValue(attribute).toString()); } else { row += ""; } } row += ""; return row; } else { return obj.toString(); } } }); String header = ""; for (String rowAttribute : rowAttributes) { header += "" + rowAttribute + ""; } value = "
" + header + "\n" + StringUtils.indent((String) value, 4) + "
"; } else { value = databaseObject.getSerializableFieldValue(attribute); } } } else { value = databaseObject.getSerializableFieldValue(attribute); } if (value != null) { if (multiValue) { multiValueOut.append("

").append(attribute).append(":

"); multiValueOut.append(StringUtils.escapeHtml(value.toString())); multiValueOut.append("
"); } else { singleValueOut.append("").append(attribute).append(""); singleValueOut.append(value); singleValueOut.append(""); } } } String finalOut = singleValueOut.toString(); if (finalOut.length() > 0) { finalOut = "

attributes:

" + finalOut + "

"; } finalOut = finalOut + multiValueOut.toString(); return finalOut; } private List sort(Collection objects) { return sort(objects, new Comparator() { @Override public int compare(Object o1, Object o2) { if (o1 instanceof Comparable) { return ((Comparable) o1).compareTo(o2); } else if (o1 instanceof Class) { return ((Class) o1).getName().compareTo(((Class) o2).getName()); } else { throw new ClassCastException(o1.getClass().getName() + " cannot be cast to java.lang.Comparable or java.lang.Class"); } } }); } private List sort(Collection objects, Comparator comparator) { List returnList = new ArrayList(objects); Collections.sort(returnList, comparator); return returnList; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy