blob: 2c94ac42baaaba117d27db413f2ad5f4d82cb26a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 Boeing.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.osee.framework.skynet.core.revision;
import static org.eclipse.osee.framework.core.enums.DeletionFlag.INCLUDE_DELETED;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osee.framework.core.data.IAttributeType;
import org.eclipse.osee.framework.core.enums.ConflictStatus;
import org.eclipse.osee.framework.core.enums.ModificationType;
import org.eclipse.osee.framework.core.exception.BranchMergeException;
import org.eclipse.osee.framework.core.model.Branch;
import org.eclipse.osee.framework.core.model.TransactionRecord;
import org.eclipse.osee.framework.core.sql.OseeSql;
import org.eclipse.osee.framework.jdk.core.type.OseeArgumentException;
import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
import org.eclipse.osee.framework.jdk.core.util.Strings;
import org.eclipse.osee.framework.skynet.core.artifact.Artifact;
import org.eclipse.osee.framework.skynet.core.artifact.BranchManager;
import org.eclipse.osee.framework.skynet.core.artifact.search.ArtifactQuery;
import org.eclipse.osee.framework.skynet.core.attribute.AttributeTypeManager;
import org.eclipse.osee.framework.skynet.core.conflict.ArtifactConflictBuilder;
import org.eclipse.osee.framework.skynet.core.conflict.AttributeConflict;
import org.eclipse.osee.framework.skynet.core.conflict.AttributeConflictBuilder;
import org.eclipse.osee.framework.skynet.core.conflict.Conflict;
import org.eclipse.osee.framework.skynet.core.conflict.ConflictBuilder;
import org.eclipse.osee.framework.skynet.core.internal.ServiceUtil;
import org.eclipse.osee.framework.skynet.core.transaction.TransactionManager;
import org.eclipse.osee.framework.skynet.core.utility.ArtifactJoinQuery;
import org.eclipse.osee.framework.skynet.core.utility.ConnectionHandler;
import org.eclipse.osee.framework.skynet.core.utility.IdJoinQuery;
import org.eclipse.osee.framework.skynet.core.utility.JoinUtility;
import org.eclipse.osee.framework.skynet.core.utility.OseeInfo;
import org.eclipse.osee.jdbc.JdbcStatement;
/**
* @author Theron Virgin
* @author Jeff C. Phillips
*/
public class ConflictManagerInternal {
private static final String MULTIPLICITY_DETECTION =
"select attr_s.art_id, attr_s.attr_id as source_attr_id, attr_d.attr_id as dest_attr_id " + //
"from " + //
"osee_attribute attr_s, " + //
"osee_txs txs_s, " + //
"osee_txs txs_d, " + //
"osee_attribute attr_d, " + //
"osee_join_id jid " + //
"where " + //
"txs_s.tx_current = 1 AND " + //
"txs_s.branch_id = ? AND " + //
"txs_s.transaction_id > ? AND " + //
"txs_s.gamma_id = attr_s.gamma_id AND " + //
"attr_s.attr_type_id = jid.id AND " + //
"jid.query_id = ? AND " + //
"txs_d.tx_current = 1 AND " + //
"txs_d.branch_id = ? AND " + //
"txs_d.gamma_id = attr_d.gamma_id AND " + //
"attr_d.attr_type_id = attr_s.attr_type_id AND " + //
"attr_d.art_id = attr_s.art_id AND " + //
"attr_d.attr_id <> attr_s.attr_id";
private static final String CONFLICT_CLEANUP =
"DELETE FROM osee_conflict t1 WHERE merge_branch_id = ? and NOT EXISTS (SELECT 'X' FROM osee_join_artifact WHERE query_id = ? and t1.conflict_id = art_id and (t1.conflict_type = transaction_id or transaction_id is NULL))";
private static final String GET_DESTINATION_BRANCHES =
"SELECT dest_branch_id FROM osee_merge WHERE source_branch_id = ?";
private static final String GET_MERGE_DATA =
"SELECT commit_transaction_id, merge_branch_id FROM osee_merge WHERE source_branch_id = ? AND dest_branch_id = ?";
private static final String GET_COMMIT_TRANSACTION_COMMENT =
"SELECT transaction_id FROM osee_tx_details WHERE osee_comment = ? AND branch_id = ?";
public static List<Conflict> getConflictsPerBranch(TransactionRecord commitTransaction, IProgressMonitor monitor) throws OseeCoreException {
monitor.beginTask(String.format("Loading Merge Manager for Transaction %d", commitTransaction.getId()), 100);
monitor.subTask("Finding Database stored conflicts");
ArrayList<Conflict> conflicts = new ArrayList<>();
JdbcStatement chStmt = ConnectionHandler.getStatement();
try {
chStmt.runPreparedQuery(ServiceUtil.getSql(OseeSql.CONFLICT_GET_HISTORICAL_ATTRIBUTES),
commitTransaction.getId());
while (chStmt.next()) {
Branch sourceBranch = BranchManager.getBranch(chStmt.getLong("source_branch_id"));
if (sourceBranch.getArchiveState().isArchived()) {
sourceBranch = null;
}
AttributeConflict attributeConflict = new AttributeConflict(chStmt.getInt("source_gamma_id"),
chStmt.getInt("dest_gamma_id"), chStmt.getInt("art_id"), null, commitTransaction,
chStmt.getString("source_value"), chStmt.getInt("attr_id"), chStmt.getLong("attr_type_id"),
BranchManager.getBranch(chStmt.getLong("merge_branch_id")), sourceBranch,
BranchManager.getBranch(chStmt.getLong("dest_branch_id")));
attributeConflict.setStatus(ConflictStatus.valueOf(chStmt.getInt("status")));
conflicts.add(attributeConflict);
}
} finally {
chStmt.close();
monitor.done();
}
return conflicts;
}
public static List<Conflict> getConflictsPerBranch(Branch sourceBranch, Branch destinationBranch, TransactionRecord baselineTransaction, IProgressMonitor monitor) throws OseeCoreException {
@SuppressWarnings("unused")
// This is for bulk loading so we do not lose our references
Collection<Artifact> bulkLoadedArtifacts;
List<ConflictBuilder> conflictBuilders = new ArrayList<>();
List<Conflict> conflicts = new ArrayList<>();
Set<Integer> artIdSet = new HashSet<>();
Set<Integer> artIdSetDontShow = new HashSet<>();
Set<Integer> artIdSetDontAdd = new HashSet<>();
// Check to see if the branch has already been committed, then use the
// transaction version
int commitTransactionId = getCommitTransaction(sourceBranch, destinationBranch);
if (commitTransactionId > 0) {
return getConflictsPerBranch(TransactionManager.getTransactionId(commitTransactionId), monitor);
}
if (sourceBranch == null || destinationBranch == null) {
throw new OseeArgumentException("Source Branch = %s Destination Branch = %s",
sourceBranch == null ? "NULL" : sourceBranch.getUuid(),
destinationBranch == null ? "NULL" : destinationBranch.getUuid());
}
monitor.beginTask(String.format("Loading Merge Manager for Branch %d into Branch %d", sourceBranch.getUuid(),
destinationBranch.getUuid()), 100);
monitor.subTask("Finding Database stored conflicts");
TransactionRecord commonTransaction = findCommonTransaction(sourceBranch, destinationBranch);
String disableChecks = OseeInfo.getValue("osee.disable.multiplicity.conflicts");
if (!Strings.isValid(disableChecks)) {
// check for multiplicity conflicts
Collection<IAttributeType> singleMultiplicityTypes = AttributeTypeManager.getSingleMultiplicityTypes();
loadMultiplicityConflicts(singleMultiplicityTypes, sourceBranch, destinationBranch, baselineTransaction,
conflictBuilders, artIdSet);
}
loadArtifactVersionConflicts(ServiceUtil.getSql(OseeSql.CONFLICT_GET_ARTIFACTS_DEST), sourceBranch,
destinationBranch, baselineTransaction, conflictBuilders, artIdSet, artIdSetDontShow, artIdSetDontAdd, monitor,
commonTransaction);
loadArtifactVersionConflicts(ServiceUtil.getSql(OseeSql.CONFLICT_GET_ARTIFACTS_SRC), sourceBranch,
destinationBranch, baselineTransaction, conflictBuilders, artIdSet, artIdSetDontShow, artIdSetDontAdd, monitor,
commonTransaction);
loadAttributeConflictsNew(sourceBranch, destinationBranch, baselineTransaction, conflictBuilders, artIdSet,
monitor, commonTransaction);
artIdSet.removeAll(artIdSetDontAdd);
if (artIdSet.isEmpty()) {
return conflicts;
}
monitor.subTask("Creating and/or maintaining the Merge Branch");
Branch mergeBranch =
BranchManager.getOrCreateMergeBranch(sourceBranch, destinationBranch, new ArrayList<Integer>(artIdSet));
if (mergeBranch == null) {
throw new BranchMergeException("Could not create the Merge Branch.");
}
monitor.worked(15);
bulkLoadedArtifacts = preloadConflictArtifacts(sourceBranch, destinationBranch, mergeBranch, artIdSet, monitor);
// Don't create the conflicts for attributes on an artifact that is
// deleted etc.
for (ConflictBuilder conflictBuilder : conflictBuilders) {
Conflict conflict = getConflict(conflictBuilder, mergeBranch, artIdSetDontShow);
if (conflict != null) {
conflicts.add(conflict);
}
}
cleanUpConflictDB(conflicts, mergeBranch.getUuid(), monitor);
return conflicts;
}
private static Conflict getConflict(ConflictBuilder conflictBuilder, Branch mergeBranch, Set<Integer> artIdSetDontShow) throws OseeCoreException {
Conflict conflict = conflictBuilder.getConflict(mergeBranch, artIdSetDontShow);
if (conflict != null) {
conflict.computeStatus();
}
return conflict;
}
private static Collection<Artifact> preloadConflictArtifacts(Branch sourceBranch, Branch destBranch, Branch mergeBranch, Collection<Integer> artIdSet, IProgressMonitor monitor) throws OseeCoreException {
monitor.subTask("Preloading Artifacts Associated with the Conflicts");
Collection<Artifact> artifacts = ArtifactQuery.getArtifactListFromIds(artIdSet, sourceBranch, INCLUDE_DELETED);
artifacts.addAll(ArtifactQuery.getArtifactListFromIds(artIdSet, destBranch, INCLUDE_DELETED));
artifacts.addAll(ArtifactQuery.getArtifactListFromIds(artIdSet, mergeBranch, INCLUDE_DELETED));
monitor.worked(25);
return artifacts;
}
private static void loadMultiplicityConflicts(Collection<IAttributeType> types, Branch source, Branch dest, TransactionRecord baselineTransaction, List<ConflictBuilder> conflictBuilders, Set<Integer> artIdSet) {
IdJoinQuery joinQuery = JoinUtility.createIdJoinQuery();
JdbcStatement chStmt = ConnectionHandler.getStatement();
List<Object[]> batchParams = new LinkedList<>();
try {
for (IAttributeType type : types) {
joinQuery.add(type.getGuid());
}
joinQuery.store();
chStmt.runPreparedQuery(MULTIPLICITY_DETECTION, source.getUuid(), source.getBaseTransaction().getId(),
joinQuery.getQueryId(), dest.getUuid());
while (chStmt.next()) {
int artId = chStmt.getInt("art_id");
int sAttrId = chStmt.getInt("source_attr_id");
int dAttrId = chStmt.getInt("dest_attr_id");
artIdSet.add(artId);
batchParams.add(new Object[] {dAttrId, sAttrId, artId});
}
} finally {
joinQuery.delete();
chStmt.close();
}
if (!batchParams.isEmpty()) {
String updateSql = "update osee_attribute set attr_id = ? where attr_id = ? and art_id = ?";
ConnectionHandler.runBatchUpdate(updateSql, batchParams);
// update cached source artifacts
for (Object[] params : batchParams) {
ArtifactQuery.reloadArtifactFromId((int) params[2], source);
}
}
}
private static void loadArtifactVersionConflicts(String sql, Branch sourceBranch, Branch destinationBranch, TransactionRecord baselineTransaction, Collection<ConflictBuilder> conflictBuilders, Set<Integer> artIdSet, Set<Integer> artIdSetDontShow, Set<Integer> artIdSetDontAdd, IProgressMonitor monitor, TransactionRecord transactionId) throws OseeCoreException {
boolean hadEntries = false;
monitor.subTask("Finding Artifact Version Conflicts");
JdbcStatement chStmt = ConnectionHandler.getStatement();
try {
int commonTransactionNumber = transactionId != null ? transactionId.getId() : 0;
long commonBranchId = transactionId != null ? transactionId.getBranchId() : 0;
chStmt.runPreparedQuery(sql, sourceBranch.getUuid(), sourceBranch.getBaseTransaction().getId(),
destinationBranch.getUuid(), commonBranchId, commonTransactionNumber, commonTransactionNumber);
ArtifactConflictBuilder artifactConflictBuilder;
int artId = 0;
while (chStmt.next()) {
hadEntries = true;
int nextArtId = chStmt.getInt("art_id");
int sourceGamma = chStmt.getInt("source_gamma");
int destGamma = chStmt.getInt("dest_gamma");
ModificationType sourceModType = ModificationType.getMod(chStmt.getInt("source_mod_type"));
ModificationType destModType = ModificationType.getMod(chStmt.getInt("dest_mod_type"));
long artTypeId = chStmt.getLong("art_type_id");
if (artId != nextArtId) {
artId = nextArtId;
if (destModType == ModificationType.DELETED && sourceModType == ModificationType.MODIFIED || //
destModType == ModificationType.MODIFIED && sourceModType == ModificationType.DELETED) {
artifactConflictBuilder = new ArtifactConflictBuilder(sourceGamma, destGamma, artId,
baselineTransaction, sourceBranch, destinationBranch, sourceModType, destModType, artTypeId);
conflictBuilders.add(artifactConflictBuilder);
artIdSet.add(artId);
} else if (destModType == ModificationType.DELETED && sourceModType == ModificationType.DELETED) {
artIdSetDontShow.add(artId);
artIdSetDontAdd.add(artId);
}
}
}
} finally {
chStmt.close();
}
if (hadEntries) {
monitor.worked(20);
for (Integer integer : artIdSet) {
artIdSetDontShow.add(integer);
}
}
}
private static void loadAttributeConflictsNew(Branch sourceBranch, Branch destinationBranch, TransactionRecord baselineTransaction, Collection<ConflictBuilder> conflictBuilders, Set<Integer> artIdSet, IProgressMonitor monitor, TransactionRecord transactionId) throws OseeCoreException {
monitor.subTask("Finding the Attribute Conflicts");
JdbcStatement chStmt = ConnectionHandler.getStatement();
AttributeConflictBuilder attributeConflictBuilder;
try {
int commonTransactionNumber = transactionId != null ? transactionId.getId() : 0;
long commonBranchId = transactionId != null ? transactionId.getBranchId() : 0;
chStmt.runPreparedQuery(ServiceUtil.getSql(OseeSql.CONFLICT_GET_ATTRIBUTES), sourceBranch.getUuid(),
sourceBranch.getBaseTransaction().getId(), destinationBranch.getUuid(), commonBranchId,
commonTransactionNumber);
int attrId = 0;
if (chStmt.next()) {
do {
int nextAttrId = chStmt.getInt("attr_id");
int artId = chStmt.getInt("art_id");
int sourceGamma = chStmt.getInt("source_gamma");
int destGamma = chStmt.getInt("dest_gamma");
long attrTypeId = chStmt.getLong("attr_type_id");
String sourceValue = chStmt.getString("source_value") != null ? chStmt.getString(
"source_value") : chStmt.getString("dest_value");
if (attrId != nextAttrId && isAttributeConflictValid(destGamma, sourceBranch)) {
attrId = nextAttrId;
attributeConflictBuilder = new AttributeConflictBuilder(sourceGamma, destGamma, artId,
baselineTransaction, sourceBranch, destinationBranch, sourceValue, attrId, attrTypeId);
conflictBuilders.add(attributeConflictBuilder);
artIdSet.add(artId);
}
} while (chStmt.next());
}
} finally {
chStmt.close();
}
monitor.worked(30);
}
/**
* Checks source branch hierarchy to see if the conflict gamma exists. If it does, its not a real conflict because
* the source branch has already seen this change.
*
* @return Returns True if the AttributeConflict candidate is really a conflict.
*/
private static boolean isAttributeConflictValid(int destinationGammaId, Branch sourceBranch) throws OseeCoreException {
boolean isValidConflict = true;
// We just need the largest value at first so the complete source branch
// will be searched
int parentTransactionNumber = Integer.MAX_VALUE;
for (Branch branch : sourceBranch.getAncestors()) {
if (!branch.getBranchType().isSystemRootBranch() && !branch.getParentBranch().getBranchType().isSystemRootBranch()) {
isValidConflict &= isAttributeConflictValidOnBranch(destinationGammaId, branch, parentTransactionNumber);
if (branch.getSourceTransaction() != null) {
parentTransactionNumber = branch.getSourceTransaction().getId();
}
}
if (!isValidConflict) {
break;
}
}
return isValidConflict;
}
/**
* @return Returns True if the destination gamma does not exist on a branch else false if it does.
*/
private static boolean isAttributeConflictValidOnBranch(int destinationGammaId, Branch branch, int endTransactionNumber) throws OseeCoreException {
String sql =
"SELECT count(1) FROM osee_txs txs WHERE txs.gamma_id = ? AND txs.branch_id = ? AND txs.transaction_id <= ?";
return ConnectionHandler.runPreparedQueryFetchInt(0, sql, destinationGammaId, branch.getUuid(),
endTransactionNumber) == 0;
}
private static void cleanUpConflictDB(Collection<Conflict> conflicts, long branchUuid, IProgressMonitor monitor) throws OseeCoreException {
monitor.subTask("Cleaning up old conflict data");
if (conflicts != null && conflicts.size() != 0 && branchUuid != 0) {
ArtifactJoinQuery joinQuery = JoinUtility.createArtifactJoinQuery();
try {
for (Conflict conflict : conflicts) {
joinQuery.add(conflict.getObjectId(), branchUuid, conflict.getConflictType().getValue());
}
joinQuery.store();
ConnectionHandler.runPreparedUpdate(CONFLICT_CLEANUP, branchUuid, joinQuery.getQueryId());
} finally {
joinQuery.delete();
}
}
monitor.worked(10);
}
public static Collection<Long> getDestinationBranchesMerged(long sourceBranchId) throws OseeCoreException {
List<Long> destinationBranches = new LinkedList<>();
JdbcStatement chStmt = ConnectionHandler.getStatement();
try {
chStmt.runPreparedQuery(GET_DESTINATION_BRANCHES, sourceBranchId);
while (chStmt.next()) {
destinationBranches.add(chStmt.getLong("dest_branch_id"));
}
} finally {
chStmt.close();
}
if (destinationBranches.size() > 1) {
Collections.sort(destinationBranches);
}
return destinationBranches;
}
private static int getCommitTransaction(Branch sourceBranch, Branch destBranch) throws OseeCoreException {
int transactionId = 0;
JdbcStatement chStmt = ConnectionHandler.getStatement();
try {
if (sourceBranch != null && destBranch != null) {
chStmt.runPreparedQuery(GET_MERGE_DATA, sourceBranch.getUuid(), destBranch.getUuid());
if (chStmt.next()) {
transactionId = chStmt.getInt("commit_transaction_id");
}
if (transactionId == 0) {
chStmt.runPreparedQuery(GET_COMMIT_TRANSACTION_COMMENT,
BranchManager.COMMIT_COMMENT + sourceBranch.getName(), destBranch.getUuid());
if (chStmt.next()) {
transactionId = chStmt.getInt("transaction_id");
}
}
}
} finally {
chStmt.close();
}
return transactionId;
}
/**
* The purpose of this function is find the transaction (Branch Create) that holds the last common values for two
* branches that share a common history. If two branches share the same history than the point at which they diverged
* should provide the reference for detecting conflicts based on the gamma at that point.
*/
public static TransactionRecord findCommonTransaction(Branch sourceBranch, Branch destBranch) throws OseeCoreException {
Collection<Branch> sourceBranches = sourceBranch.getAncestors();
Collection<Branch> destBranches = destBranch.getAncestors();
Branch commonBranch = null;
for (Branch branch : sourceBranches) {
if (destBranches.contains(branch)) {
commonBranch = branch;
break;
}
}
if (commonBranch == null) {
throw new OseeCoreException("Cannot find a common ancestor for Branch %s and Branch %s",
sourceBranch.getShortName(), destBranch.getShortName());
}
TransactionRecord sourceTransaction = null;
TransactionRecord destTransaction = null;
if (commonBranch.equals(destBranch)) {
destTransaction = TransactionManager.getHeadTransaction(commonBranch);
} else {
for (Branch branch : destBranches) {
if (branch.getParentBranch().equals(commonBranch)) {
destTransaction = branch.getBaseTransaction();
break;
}
}
}
for (Branch branch : sourceBranches) {
if (commonBranch.equals(branch.getParentBranch())) {
sourceTransaction = branch.getBaseTransaction();
break;
}
}
TransactionRecord toReturn = null;
if (sourceTransaction == null && destTransaction != null) {
toReturn = destTransaction;
} else if (sourceTransaction != null && destTransaction == null) {
toReturn = sourceTransaction;
} else {
toReturn = sourceTransaction.getId() <= destTransaction.getId() ? sourceTransaction : destTransaction;
}
return toReturn;
}
}