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.hub.core.StandardHubService Maven / Gradle / Ivy
Go to download
Liquibase is a tool for managing and executing database changes.
package liquibase.hub.core;
import liquibase.Scope;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.RanChangeSet;
import liquibase.exception.LiquibaseException;
import liquibase.hub.*;
import liquibase.hub.model.*;
import liquibase.integration.IntegrationDetails;
import liquibase.logging.Logger;
import liquibase.plugin.Plugin;
import liquibase.util.ISODateFormat;
import liquibase.util.LiquibaseUtil;
import liquibase.util.StringUtil;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
public class StandardHubService implements HubService {
private static final String DATE_TIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private Boolean available;
private UUID organizationId;
private String organizationName;
private UUID userId;
private Map hubChangeLogCache = new HashMap<>();
private HttpClient http;
public StandardHubService() {
this.http = createHttpClient();
}
public HttpClient createHttpClient() {
return new HttpClient();
}
@Override
public int getPriority() {
return Plugin.PRIORITY_DEFAULT + 100;
}
@Override
public boolean isOnline() {
return HubConfiguration.LIQUIBASE_HUB_MODE.getCurrentValue() != HubConfiguration.HubMode.OFF;
}
public boolean isHubAvailable() {
if (this.available == null) {
final Logger log = Scope.getCurrentScope().getLog(getClass());
final HubServiceFactory hubServiceFactory = Scope.getCurrentScope().getSingleton(HubServiceFactory.class);
if (HubConfiguration.LIQUIBASE_HUB_MODE.getCurrentValue() == HubConfiguration.HubMode.OFF) {
hubServiceFactory.setOfflineReason("property liquibase.hub.mode is 'OFF'. To send data to Liquibase Hub, please set it to \"all\"");
this.available = false;
} else if (getApiKey() == null) {
hubServiceFactory.setOfflineReason("liquibase.hub.apiKey was not specified");
this.available = false;
} else {
try {
if (userId == null) {
HubUser me = this.getMe();
this.userId = me.getId();
}
if (organizationId == null) {
Organization organization = this.getOrganization();
this.organizationId = organization.getId();
}
log.info("Connected to Liquibase Hub with an API Key '" + HubConfiguration.LIQUIBASE_HUB_API_KEY.getCurrentValueObfuscated() + "'");
this.available = true;
} catch (LiquibaseHubException e) {
if (e.getCause() instanceof ConnectException) {
hubServiceFactory.setOfflineReason("Cannot connect to Liquibase Hub");
} else {
hubServiceFactory.setOfflineReason(e.getMessage());
}
log.info(e.getMessage(), e);
this.available = false;
}
}
String apiKey = getApiKey();
if (!this.available && apiKey != null) {
String message = "Hub communication failure: " + hubServiceFactory.getOfflineReason() + ".\n" +
"The data for your operations will not be recorded in your Liquibase Hub project";
Scope.getCurrentScope().getUI().sendMessage(message);
log.info(message);
}
}
return this.available;
}
public String getApiKey() {
return StringUtil.trimToNull(HubConfiguration.LIQUIBASE_HUB_API_KEY.getCurrentValue());
}
@Override
public HubUser getMe() throws LiquibaseHubException {
final Map response;
try {
response = http.doGet("/api/v1/users/me", Map.class);
} catch (LiquibaseHubSecurityException e) {
throw new LiquibaseHubSecurityException("Invalid Liquibase Hub api key", e);
}
HubUser user = new HubUser();
user.setId(UUID.fromString((String) response.get("id")));
user.setUsername((String) response.get("userName"));
return user;
}
@Override
public Organization getOrganization() throws LiquibaseHubException {
if (organizationId == null) {
final Map> response = http.doGet("/api/v1/organizations", Map.class);
List contentList = response.get("content");
String id = (String) contentList.get(0).get("id");
if (id != null) {
organizationId = UUID.fromString(id);
}
organizationName = (String) contentList.get(0).get("name");
}
Organization org = new Organization();
org.setId(organizationId);
org.setName(organizationName);
return org;
}
@Override
public Project getProject(UUID projectId) throws LiquibaseHubException {
final UUID organizationId = getOrganization().getId();
try {
return http.doGet("/api/v1/organizations/" + organizationId.toString() + "/projects/" + projectId, Project.class);
} catch (LiquibaseHubObjectNotFoundException lbe) {
Scope.getCurrentScope().getLog(getClass()).severe(lbe.getMessage(), lbe);
return null;
}
}
@Override
public Project findProjectByConnectionIdOrJdbcUrl(UUID connectionId, String jdbcUrl) throws LiquibaseHubException {
final AtomicReference organizationId = new AtomicReference<>(getOrganization().getId());
String searchParam = null;
if (connectionId != null) {
searchParam = "connections.id:\"" + connectionId.toString() + "\"";
} else if (jdbcUrl != null) {
searchParam = "connections.jdbcUrl:\"" + jdbcUrl + "\"";
} else {
throw new LiquibaseHubException("connectionId or jdbcUrl should be specified");
}
Map parameters = new LinkedHashMap<>();
parameters.put("search", searchParam);
final Map> response = http.doGet("/api/v1/organizations/" + organizationId.toString() + "/projects", parameters, Map.class);
List returnList = transformProjectResponseToList(response);
if (returnList.size() > 1) {
Scope.getCurrentScope().getLog(getClass()).warning(String.format("JDBC URL: %s was associated with multiple projects.", jdbcUrl));
return null;
}
if (returnList.isEmpty()) {
return null;
}
return returnList.get(0);
}
private List transformProjectResponseToList(Map> response) {
List contentList = response.get("content");
List returnList = new ArrayList<>();
for (int i = 0; i < contentList.size(); i++) {
String id = (String) contentList.get(i).get("id");
String name = (String) contentList.get(i).get("name");
String dateString = (String) contentList.get(i).get("createDate");
Date date = null;
try {
date = parseDate(dateString);
} catch (ParseException dpe) {
Scope.getCurrentScope().getLog(getClass()).warning("Project '" + name + "' has an invalid create date of '" + dateString + "'");
}
Project project = new Project();
project.setId(UUID.fromString(id));
project.setName(name);
project.setCreateDate(date);
returnList.add(project);
}
return returnList;
}
private String encodeValue(String value) throws LiquibaseHubException {
try {
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
throw new LiquibaseHubException(e);
}
}
@Override
public List getProjects() throws LiquibaseHubException {
final AtomicReference organizationId = new AtomicReference<>(getOrganization().getId());
final Map> response = http.doGet("/api/v1/organizations/" + organizationId.toString() + "/projects", Map.class);
return transformProjectResponseToList(response);
}
@Override
public HubRegisterResponse register(String email) throws LiquibaseException {
HubRegister hubRegister = new HubRegister();
hubRegister.setEmail(email);
try {
HubRegisterResponse response = http.doPost("/api/v1/register", hubRegister, HubRegisterResponse.class);
if (response.getApiKey() != null) {
return response;
}
} catch (LiquibaseHubException e) {
throw new LiquibaseException(e.getMessage(), e);
}
return null;
}
@Override
public Project createProject(Project project) throws LiquibaseException {
final UUID organizationId = getOrganization().getId();
return http.doPost("/api/v1/organizations/" + organizationId.toString() + "/projects", project, Project.class);
}
@Override
public HubChangeLog createChangeLog(HubChangeLog hubChangeLog) throws LiquibaseException {
final UUID organizationId = getOrganization().getId();
return http.doPost("/api/v1/organizations/" + organizationId.toString() + "/projects/" + hubChangeLog.getProject().getId() + "/changelogs", hubChangeLog, HubChangeLog.class);
}
@Override
public HubChangeLog deactivateChangeLog(HubChangeLog hubChangeLog) throws LiquibaseHubException {
return http.doPut("/api/v1/organizations/" + getOrganization().getId() +
"/projects/" + hubChangeLog.getProject().getId().toString() +
"/changelogs/" + hubChangeLog.getId().toString(), hubChangeLog, HubChangeLog.class);
}
@Override
public void setRanChangeSets(Connection connection, List ranChangeSets) throws LiquibaseHubException {
List hubChangeList = new ArrayList<>();
for (RanChangeSet ranChangeSet : ranChangeSets) {
hubChangeList.add(new HubChange(ranChangeSet));
}
http.doPut("/api/v1/organizations/" + getOrganization().getId() + "/connections/" + connection.getId() + "/changes", hubChangeList, ArrayList.class);
}
@Override
public Connection getConnection(Connection exampleConnection, boolean createIfNotExists) throws LiquibaseHubException {
if (exampleConnection.getId() != null) {
//do not auto-create if specifying the exact id
return http.doGet("/api/v1/connections/" + exampleConnection.getId().toString(), null, Connection.class);
}
final List connections;
try {
connections = getConnections(exampleConnection);
} catch (LiquibaseHubObjectNotFoundException e) {
//the API should not throw this exception, but it does
if (createIfNotExists) {
return createConnection(exampleConnection);
} else {
throw new LiquibaseHubObjectNotFoundException("Connection not found");
}
}
if (connections.size() == 0) {
if (createIfNotExists) {
return createConnection(exampleConnection);
} else {
throw new LiquibaseHubObjectNotFoundException("Connection not found");
}
} else if (connections.size() == 1) {
return connections.get(0);
} else {
throw new LiquibaseHubException("The url " + exampleConnection.getJdbcUrl() + " is used by more than one connection. Please specify 'hubConnectionId=' or 'changeLogFile=' in liquibase.properties or the command line");
}
}
@Override
public List getConnections(Connection exampleConnection) throws LiquibaseHubException {
final Organization organization = getOrganization();
final ListResponse response;
try {
response = http.doGet("/api/v1/organizations/" + organization.getId() + "/connections", Collections.singletonMap("search", toSearchString(exampleConnection)), ListResponse.class, Connection.class);
} catch (LiquibaseHubObjectNotFoundException e) {
//Hub should not be returning this, but does
return new ArrayList<>();
}
List returnList = new ArrayList<>();
try {
for (Map object : (List) response.getContent()) {
returnList.add(new Connection()
.setId(UUID.fromString((String) object.get("id")))
.setJdbcUrl((String) object.get("jdbcUrl"))
.setName((String) object.get("name"))
.setDescription((String) object.get("description"))
.setCreateDate(parseDate((String) object.get("createDate")))
.setUpdateDate(parseDate((String) object.get("updateDate")))
.setRemoveDate(parseDate((String) object.get("removeDate")))
.setProject(exampleConnection != null ? exampleConnection.getProject() : null)
);
}
} catch (ParseException e) {
throw new LiquibaseHubException(e);
}
return returnList;
}
protected Date parseDate(String stringDate) throws ParseException {
if (stringDate == null) {
return null;
}
return new ISODateFormat().parse(stringDate);
}
@Override
public Connection createConnection(Connection connection) throws LiquibaseHubException {
if (connection.getProject() == null || connection.getProject().getId() == null) {
throw new LiquibaseHubUserException("projectId is required to create a connection");
}
//cannot send project information
Connection sendConnection = new Connection()
.setName(connection.getName())
.setJdbcUrl(connection.getJdbcUrl())
.setDescription(connection.getDescription());
if (sendConnection.getName() == null) {
sendConnection.setName(sendConnection.getJdbcUrl());
}
return http.doPost("/api/v1/organizations/" + getOrganization().getId() + "/projects/" + connection.getProject().getId() + "/connections", sendConnection, Connection.class);
}
@Override
public String shortenLink(String url) throws LiquibaseException {
HubLinkRequest reportHubLink = new HubLinkRequest();
reportHubLink.url = url;
return http.getHubUrl() + http.doPut("/api/v1/links", reportHubLink, HubLink.class).getShortUrl();
}
/**
* Query for a changelog ID. If no result we return null
* We cache this result and a map
*
* @param changeLogId Changelog ID for query
* @return HubChangeLog Object container for result
* @throws LiquibaseHubException
*/
@Override
public HubChangeLog getHubChangeLog(UUID changeLogId) throws LiquibaseHubException {
return getHubChangeLog(changeLogId, null);
}
/**
* Query for a changelog ID. If no result we return null
* We cache this result and a map
*
* @param changeLogId Changelog ID for query
* @param includeStatus Allowable status for returned changelog
* @return HubChangeLog Object container for result
* @throws LiquibaseHubException
*/
@Override
public HubChangeLog getHubChangeLog(UUID changeLogId, String includeStatus) {
if (includeStatus == null && hubChangeLogCache.containsKey(changeLogId)) {
return hubChangeLogCache.get(changeLogId);
}
try {
Map parameters = new HashMap<>();
if (includeStatus != null) {
parameters.put("includeStatus", includeStatus);
}
HubChangeLog hubChangeLog = http.doGet("/api/v1/changelogs/" + changeLogId, parameters, HubChangeLog.class);
hubChangeLogCache.put(changeLogId, hubChangeLog);
return hubChangeLog;
} catch (LiquibaseHubException lbe) {
final String message = lbe.getMessage();
String uiMessage = "Retrieving Hub Change Log failed for Changelog ID " + changeLogId.toString();
Scope.getCurrentScope().getUI().sendMessage(uiMessage + ": " + message);
Scope.getCurrentScope().getLog(getClass()).warning(message, lbe);
return null;
}
}
@Override
public Operation createOperation(String operationType, String operationCommand, HubChangeLog changeLog, Connection connection) throws LiquibaseHubException {
final IntegrationDetails integrationDetails = Scope.getCurrentScope().get("integrationDetails", IntegrationDetails.class);
Map requestBody = new HashMap<>();
requestBody.put("connectionId", connection.getId());
requestBody.put("connectionJdbcUrl", connection.getJdbcUrl());
requestBody.put("projectId", connection.getProject() == null ? null : connection.getProject().getId());
requestBody.put("changelogId", changeLog == null ? null : changeLog.getId());
requestBody.put("operationType", operationType);
requestBody.put("operationCommand", operationCommand);
requestBody.put("operationStatusType", "PASS");
requestBody.put("statusMessage", operationType);
requestBody.put("clientMetadata", getClientMetadata(integrationDetails));
if (integrationDetails!=null) {
requestBody.put("operationParameters", getCleanOperationParameters(integrationDetails.getParameters()));
}
final Operation operation = http.doPost("/api/v1/operations", requestBody, Operation.class);
operation.setConnection(connection);
return operation;
}
private Map getClientMetadata(IntegrationDetails integrationDetails) {
String hostName;
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (Throwable e) {
Scope.getCurrentScope().getLog(getClass()).severe("Cannot determine hostname to send to hub", e);
hostName = null;
}
Map clientMetadata = new HashMap<>();
clientMetadata.put("liquibaseVersion", LiquibaseUtil.getBuildVersion());
clientMetadata.put("hostName", hostName);
clientMetadata.put("systemUser", System.getProperty("user.name"));
if (integrationDetails != null) {
clientMetadata.put("clientInterface", integrationDetails.getName());
}
return clientMetadata;
}
@Override
public Operation createOperationInOrganization(String operationType, String operationCommand, UUID organizationId) throws LiquibaseHubException {
final IntegrationDetails integrationDetails = Scope.getCurrentScope().get("integrationDetails", IntegrationDetails.class);
Map requestBody = new HashMap<>();
requestBody.put("operationType", operationType);
requestBody.put("operationCommand", operationCommand);
requestBody.put("operationStatusType", "PASS");
requestBody.put("statusMessage", operationType);
requestBody.put("clientMetadata", getClientMetadata(integrationDetails));
if (integrationDetails!=null) {
requestBody.put("operationParameters", getCleanOperationParameters(integrationDetails.getParameters()));
}
return http.doPost("/api/v1/organizations/" + organizationId.toString() + "/operations", requestBody, Operation.class);
}
protected Map getCleanOperationParameters(Map originalParams) {
if (originalParams == null) {
return null;
}
Set paramsToRemove = new HashSet<>(Arrays.asList(
"url",
"username",
"password",
"apiKey",
"classpath"
));
final Map returnMap = new HashMap<>();
for (Map.Entry param : originalParams.entrySet()) {
boolean allowed = true;
for (String skipKey : paramsToRemove) {
if (param.getKey().toLowerCase().contains(skipKey.toLowerCase())) {
allowed = false;
break;
}
}
if (allowed) {
String value = param.getValue();
if (param.getKey().toLowerCase().contains("liquibaseProLicenseKey".toLowerCase()) ||
param.getKey().toLowerCase().contains("liquibaseLicenseKey".toLowerCase())) {
if (value != null && value.length() > 8) {
value = value.substring(0, 8) + "************";
}
}
returnMap.put(param.getKey(), value);
}
}
return returnMap;
}
@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent) throws LiquibaseException {
final Organization organization = getOrganization();
return sendOperationEvent(operation, operationEvent, organization.getId());
}
@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent, UUID organizationId) throws LiquibaseException {
Map requestParams = new HashMap<>();
requestParams.put("eventType", operationEvent.getEventType());
requestParams.put("startDate", operationEvent.getStartDate());
requestParams.put("endDate", operationEvent.getEndDate());
if (operationEvent.getOperationEventStatus() != null) {
requestParams.put("statusType", operationEvent.getOperationEventStatus().getOperationEventStatusType());
requestParams.put("statusMessage", operationEvent.getOperationEventStatus().getStatusMessage());
requestParams.put("operationEventStatusType", operationEvent.getOperationEventStatus().getOperationEventStatusType());
}
if (operationEvent.getOperationEventLog() != null) {
if (HubConfiguration.LIQUIBASE_HUB_MODE.getCurrentValue() != HubConfiguration.HubMode.META) {
requestParams.put("logs", operationEvent.getOperationEventLog().getLogMessage());
requestParams.put("logsTimestamp", operationEvent.getOperationEventLog().getTimestampLog());
}
}
return http.doPost("/api/v1/organizations/" + organizationId + ((operation.getConnection() == null || operation.getConnection().getProject() == null) ? "" : "/projects/" + operation.getConnection().getProject().getId()) + "/operations/" + operation.getId() + "/operation-events", requestParams, OperationEvent.class);
}
private Date convertDateToUTC(Date dateInString) {
//LocalDateTime ldt = LocalDateTime.parse(dateInString, DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_STRING));
return null;
}
@Override
public void sendOperationChangeEvent(OperationChangeEvent operationChangeEvent) throws LiquibaseException {
String changesetBody = null;
String[] generatedSql = null;
String logs = null;
Date logsTimestamp = operationChangeEvent.getLogsTimestamp();
if (HubConfiguration.LIQUIBASE_HUB_MODE.getCurrentValue() != HubConfiguration.HubMode.META) {
changesetBody = operationChangeEvent.getChangesetBody();
generatedSql = operationChangeEvent.getGeneratedSql();
logs = operationChangeEvent.getLogs();
}
OperationChangeEvent sendOperationChangeEvent =
new OperationChangeEvent()
.setEventType(operationChangeEvent.getEventType())
.setChangesetId(operationChangeEvent.getChangesetId())
.setChangesetAuthor(operationChangeEvent.getChangesetAuthor())
.setChangesetFilename(operationChangeEvent.getChangesetFilename())
.setStartDate(operationChangeEvent.getStartDate())
.setEndDate(operationChangeEvent.getEndDate())
.setDateExecuted(operationChangeEvent.getDateExecuted())
.setOperationStatusType(operationChangeEvent.getOperationStatusType())
.setChangesetBody(changesetBody)
.setGeneratedSql(generatedSql)
.setLogs(logs)
.setStatusMessage(operationChangeEvent.getStatusMessage())
.setLogsTimestamp(logsTimestamp);
http.doPost("/api/v1" +
"/organizations/" + getOrganization().getId().toString() +
"/projects/" + operationChangeEvent.getProject().getId().toString() +
"/operations/" + operationChangeEvent.getOperation().getId().toString() +
"/change-events",
sendOperationChangeEvent, OperationChangeEvent.class);
}
@Override
public void sendOperationChanges(OperationChange operationChange) throws LiquibaseHubException {
List hubChangeList = new ArrayList<>();
for (ChangeSet changeSet : operationChange.getChangeSets()) {
hubChangeList.add(new HubChange(changeSet));
}
http.doPost("/api/v1" +
"/organizations/" + getOrganization().getId().toString() +
"/projects/" + operationChange.getProject().getId().toString() +
"/operations/" + operationChange.getOperation().getId().toString() +
"/changes",
hubChangeList, ArrayList.class);
}
@Override
public CoreInitOnboardingResponse validateOnboardingToken(String token) throws LiquibaseHubException {
// This call does not use authentication, so we purposefully override any existing hub API key with empty string.
Map hubApiKeyScopeValues = Collections.singletonMap(HubConfiguration.LIQUIBASE_HUB_API_KEY.getKey(), "");
try {
return Scope.child(hubApiKeyScopeValues, () -> http.doPost("/api/v1/init", Collections.singletonMap("onboardingToken", token), CoreInitOnboardingResponse.class));
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).severe("Failed to call Hub to validate onboarding token", e);
throw new LiquibaseHubException(e);
}
}
/**
* Converts an object to a search string.
* Any properties with non-null values are used as search arguments.
* If a HubModel has an id specified, only that value is used in the search.
*/
protected String toSearchString(HubModel object) {
if (object == null) {
return "";
}
SortedSet clauses = new TreeSet<>();
toSearchString(object, "", clauses);
return StringUtil.join(clauses, " AND ");
}
private void toSearchString(HubModel object, String clausePrefix, SortedSet clauses) {
final Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
if (field.isSynthetic()) {
continue;
}
Object value = field.get(object);
if (value != null) {
if (value instanceof HubModel) {
final UUID modelId = ((HubModel) value).getId();
if (modelId == null) {
String newPrefix = clausePrefix + field.getName() + ".";
newPrefix = newPrefix.replaceFirst("^\\.", "");
toSearchString((HubModel) value, newPrefix, clauses);
} else {
clauses.add(clausePrefix + field.getName() + ".id:\"" + modelId + "\"");
}
} else {
value = value.toString().replace("\"", "\\\"");
clauses.add(clausePrefix + field.getName() + ":\"" + value + "\"");
}
}
} catch (IllegalAccessException ignored) {
//don't use it as a param
}
}
}
protected static class HubLinkRequest {
protected String url;
}
}