Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
liquibase.sdk.watch.WatchCommand Maven / Gradle / Ivy
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 += "Id Author Path ExecType Tag ";
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 += "" +
"" + 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"))) + " " +
" ";
changeLogDetails += wrapDetails(id, row.get("ID") + " :: " + row.get("AUTHOR") + " :: " + row.get("FILENAME"), writeDatabaseChangeLogDetails(row));
}
outString += "
";
} 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 += "Id " + row.get("ID") + " \n" +
"Author " + row.get("AUTHOR") + " \n" +
"Filename " + row.get("FILENAME") + " \n" +
"DateExecuted " + new ISODateFormat().format((Date) row.get("DATEEXECUTED")) + " \n" +
"OrderExecuted " + row.get("ORDEREXECUTED") + " \n" +
"ExecType " + row.get("EXECTYPE") + " \n" +
"MD5Sum " + row.get("MD5SUM") + " \n" +
"Description " + row.get("DESCRIPTION") + " \n" +
"Comments " + row.get("COMMENTS") + " \n" +
"Tag " + StringUtils.trimToNull((String) row.get("TAG")) + " \n" +
"Liquibase " + row.get("LIQUIBASE") + " \n";
outString += "
";
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 extends DatabaseObject> 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("").append(StringUtils.escapeHtml(databaseObject.getName())).append(" \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("");
buffer.append("\n");
buffer.append("
");
buffer.append(StringUtils.indent(details, 4));
buffer.append("
");
buffer.append("");
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 + 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;
}
}
}