blob: 4df8ebd021e8d31f10a3f216a9e5f5ef845b388e [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.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.eclipse.osee.framework.core.client.ClientSessionManager;
import org.eclipse.osee.framework.core.enums.BranchState;
import org.eclipse.osee.framework.core.enums.ModificationType;
import org.eclipse.osee.framework.core.enums.TxChange;
import org.eclipse.osee.framework.db.connection.ConnectionHandler;
import org.eclipse.osee.framework.db.connection.ConnectionHandlerStatement;
import org.eclipse.osee.framework.db.connection.DbTransaction;
import org.eclipse.osee.framework.db.connection.OseeConnection;
import org.eclipse.osee.framework.db.connection.OseeDbConnection;
import org.eclipse.osee.framework.db.connection.core.SequenceManager;
import org.eclipse.osee.framework.db.connection.exception.OseeCoreException;
import org.eclipse.osee.framework.db.connection.exception.OseeDataStoreException;
import org.eclipse.osee.framework.db.connection.exception.OseeStateException;
import org.eclipse.osee.framework.jdk.core.type.CompositeKeyHashMap;
import org.eclipse.osee.framework.jdk.core.type.HashCollection;
import org.eclipse.osee.framework.logging.OseeLog;
import org.eclipse.osee.framework.skynet.core.UserManager;
import org.eclipse.osee.framework.skynet.core.artifact.Artifact;
import org.eclipse.osee.framework.skynet.core.artifact.ArtifactCache;
import org.eclipse.osee.framework.skynet.core.artifact.ArtifactTransactionData;
import org.eclipse.osee.framework.skynet.core.artifact.Branch;
import org.eclipse.osee.framework.skynet.core.artifact.BranchManager;
import org.eclipse.osee.framework.skynet.core.attribute.Attribute;
import org.eclipse.osee.framework.skynet.core.attribute.AttributeTransactionData;
import org.eclipse.osee.framework.skynet.core.attribute.AttributeType;
import org.eclipse.osee.framework.skynet.core.event.ArtifactTransactionModifiedEvent;
import org.eclipse.osee.framework.skynet.core.event.OseeEventManager;
import org.eclipse.osee.framework.skynet.core.internal.Activator;
import org.eclipse.osee.framework.skynet.core.relation.RelationLink;
import org.eclipse.osee.framework.skynet.core.relation.RelationSide;
import org.eclipse.osee.framework.skynet.core.relation.RelationTransactionData;
/**
* @author Robert A. Fisher
*/
public class SkynetTransaction extends DbTransaction {
private static final String UPDATE_TXS_NOT_CURRENT =
"UPDATE osee_txs txs1 SET tx_current = " + TxChange.NOT_CURRENT.getValue() + " WHERE txs1.transaction_id = ? AND txs1.gamma_id = ?";
private static final String GET_EXISTING_ATTRIBUTE_IDS =
"SELECT att1.attr_id FROM osee_attribute att1, osee_artifact_version arv1, osee_txs txs1, osee_tx_details txd1 WHERE att1.attr_type_id = ? AND att1.art_id = ? AND att1.art_id = arv1.art_id AND arv1.gamma_id = txs1.gamma_id AND txs1.transaction_id = txd1.transaction_id AND txd1.branch_id <> ?";
private TransactionId transactionId;
private final CompositeKeyHashMap<Class<? extends BaseTransactionData>, Integer, BaseTransactionData> transactionDataItems =
new CompositeKeyHashMap<Class<? extends BaseTransactionData>, Integer, BaseTransactionData>();
private final HashCollection<String, Object[]> dataItemInserts = new HashCollection<String, Object[]>();
private final Map<Integer, String> dataInsertOrder = new HashMap<Integer, String>();
private final Branch branch;
private boolean madeChanges = false;
private boolean executedWithException = false;
private final String comment;
public SkynetTransaction(Branch branch) throws OseeCoreException {
this(branch, "");
}
public SkynetTransaction(Branch branch, String comment) throws OseeCoreException {
this.branch = branch;
this.comment = comment;
}
/**
* Reset state so transaction object can be re-used
*/
private void reset() {
madeChanges = false;
executedWithException = false;
dataInsertOrder.clear();
transactionDataItems.clear();
dataItemInserts.clear();
transactionId = null;
}
/**
* @return the branch
*/
public Branch getBranch() {
return branch;
}
/**
* Performs branch validation checks
*/
private void checkBranch(Artifact artifact) throws OseeStateException {
ensureCorrectBranch(artifact);
ensureBranchIsEditable(artifact);
}
/**
* Performs branch validation checks
*/
private void checkBranch(RelationLink link) throws OseeStateException {
ensureCorrectBranch(link);
ensureBranchIsEditable(link);
}
private void ensureCorrectBranch(RelationLink link) throws OseeStateException {
if (!link.getBranch().equals(branch)) {
String msg =
String.format("The relation link [%s] is on branch [%s] but this transaction is for branch [%s]",
link.getRelationId(), link.getBranch(), branch);
throw new OseeStateException(msg);
}
}
private void ensureCorrectBranch(Artifact artifact) throws OseeStateException {
if (!artifact.getBranch().equals(branch)) {
String msg =
String.format("The artifact [%s] is on branch [%s] but this transaction is for branch [%s]",
artifact.getHumanReadableId(), artifact.getBranch(), branch);
throw new OseeStateException(msg);
}
}
private void ensureBranchIsEditable(RelationLink link) throws OseeStateException {
if (!link.getBranch().isEditable()) {
String msg =
String.format("The relation link [%s] is on a non-editable branch [%s]", link.getRelationId(),
link.getBranch());
throw new OseeStateException(msg);
}
}
private void ensureBranchIsEditable(Artifact artifact) throws OseeStateException {
if (!artifact.getBranch().isEditable()) {
String msg =
String.format("The artifact [%s] is on a non-editable branch [%s]", artifact.getHumanReadableId(),
artifact.getBranch());
throw new OseeStateException(msg);
}
}
private void fetchTxNotCurrent(OseeConnection connection, BaseTransactionData transactionData, List<Object[]> results) throws OseeCoreException {
ConnectionHandlerStatement chStmt = new ConnectionHandlerStatement(connection);
try {
String query = ClientSessionManager.getSQL(transactionData.getSelectTxNotCurrentSql());
chStmt.runPreparedQuery(query, transactionData.getItemId(), this.branch.getBranchId());
while (chStmt.next()) {
results.add(new Object[] {chStmt.getInt("transaction_id"), chStmt.getLong("gamma_id")});
}
} finally {
chStmt.close();
}
}
private void executeTransactionDataItems(OseeConnection connection) throws OseeCoreException {
if (transactionDataItems.isEmpty()) {
return;
}
List<Object[]> txNotCurrentData = new ArrayList<Object[]>();
for (BaseTransactionData transactionData : transactionDataItems.values()) {
// Collect inserts for attribute, relation, artifact, and artifact version tables
transactionData.addInsertToBatch(this);
// Collect stale tx currents for batch update
fetchTxNotCurrent(connection, transactionData, txNotCurrentData);
}
// Insert into data tables - i.e. attribute, relation and artifact version tables
List<Integer> keys = new ArrayList<Integer>(dataInsertOrder.keySet());
Collections.sort(keys);
for (int priority : keys) {
String sqlKey = dataInsertOrder.get(priority);
ConnectionHandler.runBatchUpdate(connection, sqlKey, (List<Object[]>) dataItemInserts.getValues(sqlKey));
}
// Set stale tx currents in txs table
ConnectionHandler.runBatchUpdate(connection, UPDATE_TXS_NOT_CURRENT, txNotCurrentData);
}
void internalAddInsertToBatch(int insertPriority, String insertSql, Object... data) {
dataItemInserts.put(insertSql, data);
dataInsertOrder.put(insertPriority, insertSql);
}
@Override
public void execute() throws OseeCoreException {
if (madeChanges) {
super.execute();
} else {
OseeDbConnection.reportTxStart(this);
OseeDbConnection.reportTxEnd(this);
}
}
/**
* @return the transaction number.
* @throws OseeDataStoreException
*/
public int getTransactionNumber() throws OseeCoreException {
return internalGetTransactionId().getTransactionNumber();
}
/**
* @return Returns the transactionId.
* @throws OseeDataStoreException
*/
TransactionId internalGetTransactionId() throws OseeCoreException {
if (transactionId == null) {
transactionId = TransactionIdManager.createNextTransactionId(branch, UserManager.getUser(), comment);
}
return transactionId;
}
public void addArtifact(Artifact artifact) throws OseeCoreException {
checkBranch(artifact);
ModificationType modificationType = artifact.getModType();
if (artifact.isDeleted()) {
if (!artifact.isInDb()) {
return;
}
} else {
if (modificationType != ModificationType.INTRODUCED) {
if (artifact.isInDb()) {
modificationType = ModificationType.MODIFIED;
} else {
modificationType = ModificationType.NEW;
}
}
}
madeChanges = true;
addArtifactHelper(artifact, modificationType);
if (artifact.anAttributeIsDirty()) {
// Add Attributes to Transaction
for (Attribute<?> attribute : artifact.internalGetAttributes()) {
if (attribute != null) { // TODO: is it really possible to get a null in the attribute list and if so WHY!!!
if (attribute.isDirty()) {
addAttribute(artifact, attribute);
}
}
}
}
}
private void addArtifactHelper(Artifact artifact, ModificationType modificationType) throws OseeCoreException {
BaseTransactionData txItem = transactionDataItems.get(ArtifactTransactionData.class, artifact.getArtId());
if (txItem == null) {
txItem = new ArtifactTransactionData(artifact, modificationType);
transactionDataItems.put(ArtifactTransactionData.class, artifact.getArtId(), txItem);
} else {
updateTxItem(txItem, modificationType);
}
}
private void addAttribute(Artifact artifact, Attribute<?> attribute) throws OseeCoreException {
ModificationType modificationType = attribute.getModificationType();
if (attribute.isDeleted()) {
if (!attribute.isInDb()) {
return;
}
} else {
if (artifact.getModType() == ModificationType.INTRODUCED) {
modificationType = ModificationType.INTRODUCED;
} else {
if (attribute.isInDb()) {
modificationType = ModificationType.MODIFIED;
} else {
modificationType = ModificationType.NEW;
}
}
}
addAttributeHelper(artifact, attribute, modificationType);
}
/**
* @param artifact
* @param attribute
* @param modificationType
* @throws OseeDataStoreException
*/
private void addAttributeHelper(Artifact artifact, Attribute<?> attribute, ModificationType modificationType) throws OseeDataStoreException {
if (attribute.getAttrId() == 0) {
attribute.internalSetAttributeId(getNewAttributeId(artifact, attribute));
}
BaseTransactionData txItem = transactionDataItems.get(AttributeTransactionData.class, attribute.getAttrId());
if (txItem == null) {
txItem = new AttributeTransactionData(attribute, modificationType);
transactionDataItems.put(AttributeTransactionData.class, attribute.getAttrId(), txItem);
} else {
updateTxItem(txItem, modificationType);
}
}
private int getNewAttributeId(Artifact artifact, Attribute<?> attribute) throws OseeDataStoreException {
ConnectionHandlerStatement chStmt = new ConnectionHandlerStatement();
AttributeType attributeType = attribute.getAttributeType();
int attrId = -1;
// reuse an existing attribute id when there should only be a max of one and it has already been created on another branch
if (attributeType.getMaxOccurrences() == 1) {
try {
chStmt.runPreparedQuery(GET_EXISTING_ATTRIBUTE_IDS, attributeType.getAttrTypeId(), artifact.getArtId(),
artifact.getBranch().getBranchId());
if (chStmt.next()) {
attrId = chStmt.getInt("attr_id");
}
} finally {
chStmt.close();
}
}
if (attrId < 1) {
attrId = SequenceManager.getNextAttributeId();
}
return attrId;
}
public void addRelation(RelationLink link) throws OseeCoreException {
checkBranch(link);
madeChanges = true;
link.setNotDirty();
ModificationType modificationType;
if (link.isInDb()) {
if (link.isDeleted()) {
Artifact aArtifact = ArtifactCache.getActive(link.getAArtifactId(), link.getABranch());
Artifact bArtifact = ArtifactCache.getActive(link.getBArtifactId(), link.getBBranch());
if (aArtifact != null && aArtifact.isDeleted() || bArtifact != null && bArtifact.isDeleted()) {
modificationType = ModificationType.ARTIFACT_DELETED;
} else {
modificationType = ModificationType.DELETED;
}
} else {
modificationType = ModificationType.MODIFIED;
}
} else {
if (link.isDeleted()) {
return;
}
Artifact aArtifact = link.getArtifact(RelationSide.SIDE_A);
if (!aArtifact.isInDb()) {
aArtifact.persistAttributesAndRelations(this);
}
Artifact bArtifact = link.getArtifact(RelationSide.SIDE_B);
if (!bArtifact.isInDb()) {
bArtifact.persistAttributesAndRelations(this);
}
link.internalSetRelationId(SequenceManager.getNextRelationId());
modificationType = ModificationType.NEW;
}
BaseTransactionData txItem = transactionDataItems.get(RelationTransactionData.class, link.getRelationId());
if (txItem == null) {
txItem = new RelationTransactionData(link, modificationType);
transactionDataItems.put(RelationTransactionData.class, link.getRelationId(), txItem);
} else {
updateTxItem(txItem, modificationType);
}
}
boolean isInTransaction(Class<? extends BaseTransactionData> clazz, int id) {
return transactionDataItems.containsKey(clazz, id);
}
private void updateTxItem(BaseTransactionData itemToCheck, ModificationType currentModType) {
if (itemToCheck.getModificationType() == ModificationType.NEW && currentModType.isDeleted()) {
transactionDataItems.remove(itemToCheck.getClass(), itemToCheck.getItemId());
} else {
itemToCheck.setModificationType(currentModType);
}
}
/* (non-Javadoc)
* @see org.eclipse.osee.framework.db.connection.core.transaction.DbTransaction#handleTxWork(java.sql.OseeConnection)
*/
@Override
protected void handleTxWork(OseeConnection connection) throws OseeCoreException {
executeTransactionDataItems(connection);
BranchManager.setBranchState(connection, branch, BranchState.MODIFIED);
}
/* (non-Javadoc)
* @see org.eclipse.osee.framework.db.connection.core.transaction.DbTransaction#handleTxException(java.lang.Exception)
*/
@Override
protected void handleTxException(Exception ex) {
executedWithException = true;
for (BaseTransactionData transactionData : transactionDataItems.values()) {
try {
transactionData.internalOnRollBack();
} catch (OseeCoreException ex1) {
OseeLog.log(Activator.class, Level.SEVERE, ex1);
}
}
}
private void updateModifiedCachedObject() throws OseeCoreException {
Collection<ArtifactTransactionModifiedEvent> xModifiedEvents = new ArrayList<ArtifactTransactionModifiedEvent>();
// Update all transaction items before collecting events
for (BaseTransactionData transactionData : transactionDataItems.values()) {
transactionData.internalUpdate(internalGetTransactionId());
}
// Collect events before clearing any dirty flags
for (BaseTransactionData transactionData : transactionDataItems.values()) {
transactionData.internalAddToEvents(xModifiedEvents);
}
// Clear all dirty flags
for (BaseTransactionData transactionData : transactionDataItems.values()) {
transactionData.internalClearDirtyState();
}
if (xModifiedEvents.size() > 0) {
OseeEventManager.kickTransactionEvent(this, xModifiedEvents);
xModifiedEvents.clear();
}
}
/* (non-Javadoc)
* @see org.eclipse.osee.framework.db.connection.DbTransaction#handleTxFinally()
*/
@Override
protected void handleTxFinally() throws OseeCoreException {
if (!executedWithException) {
updateModifiedCachedObject();
}
reset();
}
}