blob: a4fb78735e93a806c72a1534d2070508ca362367 [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 static org.eclipse.osee.framework.core.enums.DeletionFlag.INCLUDE_DELETED;
import static org.eclipse.osee.framework.core.enums.ModificationType.ARTIFACT_DELETED;
import static org.eclipse.osee.framework.core.enums.ModificationType.DELETED;
import static org.eclipse.osee.framework.core.enums.ModificationType.INTRODUCED;
import static org.eclipse.osee.framework.core.enums.ModificationType.MODIFIED;
import static org.eclipse.osee.framework.core.enums.ModificationType.NEW;
import static org.eclipse.osee.framework.core.enums.ModificationType.REPLACED_WITH_VERSION;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osee.framework.core.data.ArtifactToken;
import org.eclipse.osee.framework.core.data.AttributeId;
import org.eclipse.osee.framework.core.data.BranchId;
import org.eclipse.osee.framework.core.data.RelationTypeSide;
import org.eclipse.osee.framework.core.data.TransactionId;
import org.eclipse.osee.framework.core.enums.ModificationType;
import org.eclipse.osee.framework.core.enums.PermissionEnum;
import org.eclipse.osee.framework.core.enums.RelationSide;
import org.eclipse.osee.framework.core.enums.RelationTypeMultiplicity;
import org.eclipse.osee.framework.core.enums.TransactionDetailsType;
import org.eclipse.osee.framework.core.model.TransactionRecord;
import org.eclipse.osee.framework.core.model.access.PermissionStatus;
import org.eclipse.osee.framework.core.model.type.RelationType;
import org.eclipse.osee.framework.core.operation.IOperation;
import org.eclipse.osee.framework.core.operation.Operations;
import org.eclipse.osee.framework.jdk.core.type.CompositeKeyHashMap;
import org.eclipse.osee.framework.jdk.core.type.Id;
import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
import org.eclipse.osee.framework.jdk.core.type.OseeStateException;
import org.eclipse.osee.framework.jdk.core.util.time.GlobalTime;
import org.eclipse.osee.framework.skynet.core.AccessPolicy;
import org.eclipse.osee.framework.skynet.core.User;
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.ArtifactTransactionData;
import org.eclipse.osee.framework.skynet.core.artifact.Attribute;
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.AttributeTransactionData;
import org.eclipse.osee.framework.skynet.core.internal.ServiceUtil;
import org.eclipse.osee.framework.skynet.core.relation.RelationEventType;
import org.eclipse.osee.framework.skynet.core.relation.RelationLink;
import org.eclipse.osee.framework.skynet.core.relation.RelationTransactionData;
import org.eclipse.osee.framework.skynet.core.relation.RelationTypeManager;
import org.eclipse.osee.framework.skynet.core.transaction.TxMonitorImpl.TxState;
import org.eclipse.osee.framework.skynet.core.utility.ConnectionHandler;
/**
* @author Robert A. Fisher
* @author Roberto E. Escobar
* @author Ryan D. Brooks
* @author Jeff C. Phillips
*/
public final class SkynetTransaction extends TransactionOperation<BranchId> {
private static final String ATTR_ID_SEQ = "SKYNET_ATTR_ID_SEQ";
private static final String REL_LINK_ID_SEQ = "SKYNET_REL_LINK_ID_SEQ";
private final CompositeKeyHashMap<Class<? extends BaseTransactionData>, Id, BaseTransactionData> transactionDataItems =
new CompositeKeyHashMap<>();
// Used to avoid garbage collection of artifacts until the transaction has been committed and determine attribute events;
private final Set<Artifact> modifiedArtifacts = new HashSet<>();
private final Set<Artifact> alreadyProcessedArtifacts = new HashSet<>();
private String comment;
private User user;
private AccessPolicy access;
private TransactionRecord transaction;
protected SkynetTransaction(TxMonitor<BranchId> txMonitor, BranchId branch, String comment) {
super(txMonitor, branch, comment);
this.comment = comment;
}
public void setComment(String comment) {
this.comment = comment;
}
private AttributeId getNewAttributeId(Artifact artifact, Attribute<?> attribute) throws OseeCoreException {
return AttributeId.valueOf(ConnectionHandler.getNextSequence(ATTR_ID_SEQ, true));
}
private int getNewRelationId() throws OseeCoreException {
return (int) ConnectionHandler.getNextSequence(REL_LINK_ID_SEQ, true);
}
private User getAuthor() throws OseeCoreException {
if (user == null) {
user = UserManager.getUser();
}
return user;
}
private void checkAccess(Artifact artifact) throws OseeCoreException {
if (UserManager.duringMainUserCreation()) {
return;
}
BranchId txBranch = getBranch();
if (!artifact.isOnBranch(txBranch)) {
String msg = String.format("The artifact [%s] is on branch [%s] but this transaction is for branch [%s]",
artifact.getGuid(), artifact.getBranch(), txBranch);
throw new OseeStateException(msg);
}
checkBranch(artifact);
checkNotHistorical(artifact);
getAccess().hasArtifactPermission(Collections.singleton(artifact), PermissionEnum.WRITE, Level.FINE);
}
private void checkBranch(ArtifactToken artifact) throws OseeCoreException {
if (!isBranchWritable(artifact.getBranch())) {
throw new OseeStateException("The artifact [%s] is on a non-editable branch [%s] ", artifact,
artifact.getBranch());
}
}
private void checkBranch(RelationLink link) throws OseeCoreException {
if (!isBranchWritable(link.getBranch())) {
throw new OseeStateException("The relation link [%s] is on a non-editable branch [%s] ", link,
link.getBranch());
}
}
private void checkNotHistorical(Artifact artifact) throws OseeCoreException {
if (artifact.isHistorical()) {
throw new OseeStateException("The artifact [%s] must be at the head of the branch to be edited.",
artifact.getGuid());
}
}
private boolean isBranchWritable(BranchId branch) throws OseeCoreException {
boolean toReturn = true;
if (!UserManager.duringMainUserCreation()) {
toReturn = getAccess().hasBranchPermission(branch, PermissionEnum.WRITE,
Level.FINE).matched() && BranchManager.isEditable(branch);
}
return toReturn;
}
private void checkAccess(Artifact artifact, RelationLink link) throws OseeCoreException {
if (UserManager.duringMainUserCreation()) {
return;
}
checkBranch(link);
BranchId txBranch = getBranch();
if (!link.isOnBranch(txBranch)) {
String msg = String.format("The relation link [%s] is on branch [%s] but this transaction is for branch [%s]",
link.getId(), link.getBranch(), txBranch);
throw new OseeStateException(msg);
}
RelationSide sideToCheck = link.getSide(artifact).oppositeSide();
PermissionStatus status = getAccess().canRelationBeModified(artifact, null,
new RelationTypeSide(link.getRelationType(), sideToCheck), Level.FINE);
if (!status.matched()) {
throw new OseeCoreException(
"Access Denied - [%s] does not have valid permission to edit this relation\n itemsToPersist:[%s]\n reason:[%s]",
getAuthor(), link, status.getReason());
}
}
private AccessPolicy getAccess() throws OseeCoreException {
if (access == null) {
access = ServiceUtil.getAccessPolicy();
}
return access;
}
private Collection<BaseTransactionData> getTransactionData() {
return transactionDataItems.values();
}
public Collection<Artifact> getArtifactReferences() {
return modifiedArtifacts;
}
/**
* Reset state so transaction object can be re-used
*/
@Override
protected void clear() {
transactionDataItems.clear();
modifiedArtifacts.clear();
alreadyProcessedArtifacts.clear();
}
public BranchId getBranch() {
return getKey();
}
public void addArtifact(Artifact artifact) throws OseeCoreException {
synchronized (getTxMonitor()) {
addArtifact(artifact, true);
}
}
private void addArtifact(Artifact artifact, boolean force) throws OseeCoreException {
if (artifact != null) {
ensureCanBeAdded(artifact);
boolean wasAdded = alreadyProcessedArtifacts.add(artifact);
if (wasAdded || force) {
addArtifactAndAttributes(artifact);
addRelations(artifact);
}
}
}
private void addArtifactAndAttributes(Artifact artifact) throws OseeCoreException {
if (artifact.hasDirtyAttributes() || artifact.hasDirtyArtifactType() || artifact.getModType() == REPLACED_WITH_VERSION || artifact.isUseBackingdata()) {
if (artifact.isDeleted() && !artifact.isInDb()) {
for (Attribute<?> attribute : artifact.internalGetAttributes()) {
if (attribute.isDirty()) {
attribute.setNotDirty();
}
}
return;
}
checkAccess(artifact);
setTxState(TxState.MODIFIED);
if (!artifact.isInDb() || artifact.hasDirtyArtifactType() || artifact.getModType().isDeleted() || artifact.getModType() == REPLACED_WITH_VERSION || artifact.isUseBackingdata()) {
BaseTransactionData txItem = transactionDataItems.get(ArtifactTransactionData.class, artifact);
if (txItem == null) {
modifiedArtifacts.add(artifact);
txItem = new ArtifactTransactionData(artifact);
transactionDataItems.put(ArtifactTransactionData.class, artifact, txItem);
} else {
updateTxItem(txItem, artifact.getModType());
}
}
for (Attribute<?> attribute : artifact.internalGetAttributes()) {
if (attribute.isDirty()) {
modifiedArtifacts.add(artifact);
addAttribute(artifact, attribute);
}
}
}
}
private void checkMultiplicity(Artifact artifact, Attribute<?> attr) {
if (attr.getAttributeType().getMaxOccurrences() == 1 && artifact.getAttributeCount(attr.getAttributeType()) > 1) {
throw new OseeStateException("Artifact %s can only have 1 [%s] attribute but has %d",
artifact.toStringWithId(), attr.getAttributeType().getName(),
artifact.getAttributeCount(attr.getAttributeType()));
}
}
private void checkMultiplicity(Artifact art, RelationLink link) {
RelationType relationType = RelationTypeManager.getType(link.getRelationType());
RelationTypeMultiplicity multiplicity = relationType.getMultiplicity();
RelationSide sideToCheck = link.getOppositeSide(art);
int limitToCheck = sideToCheck.isSideA() ? multiplicity.getSideALimit() : multiplicity.getSideBLimit();
if (limitToCheck == 1) {
int count = art.getRelatedArtifactsCount(RelationTypeSide.create(relationType, sideToCheck));
if (count > 1) {
throw new OseeStateException("Artifact %s can only have 1 [%s] on [%s] but has %d", art.toStringWithId(),
relationType.getSideName(sideToCheck), sideToCheck.name(), count);
}
}
}
private void addAttribute(Artifact artifact, Attribute<?> attribute) throws OseeCoreException {
if (attribute.isDeleted() && !attribute.isInDb()) {
return;
}
checkMultiplicity(artifact, attribute);
if (attribute.isInvalid()) {
attribute.internalSetAttributeId(getNewAttributeId(artifact, attribute));
}
BaseTransactionData txItem = transactionDataItems.get(AttributeTransactionData.class, attribute);
if (txItem == null) {
txItem = new AttributeTransactionData(attribute);
transactionDataItems.put(AttributeTransactionData.class, attribute, txItem);
} else {
updateTxItem(txItem, attribute.getModificationType());
}
}
private void addRelations(Artifact artifact) throws OseeCoreException {
List<RelationLink> links = artifact.getRelationsAll(INCLUDE_DELETED);
for (RelationLink relation : links) {
if (relation.isDirty()) {
addRelation(artifact, relation);
}
}
}
private void addRelation(Artifact artifact, RelationLink link) throws OseeCoreException {
synchronized (getTxMonitor()) {
checkAccess(artifact, link);
setTxState(TxState.MODIFIED);
link.setNotDirty();
ModificationType modificationType;
RelationEventType relationEventType; // needed until persist undeleted modtypes and modified == rational only change
BranchId branch = link.getBranch();
Artifact aArtifact = ArtifactQuery.checkArtifactFromId(link.getAArtifactId(), branch, INCLUDE_DELETED);
Artifact bArtifact = ArtifactQuery.checkArtifactFromId(link.getBArtifactId(), branch, INCLUDE_DELETED);
if (link.isInDb()) {
if (link.isUnDeleted()) {
modificationType = MODIFIED; // Temporary until UNDELETED persisted to DB
relationEventType = RelationEventType.Undeleted;
} else if (link.isDeleted()) {
if (aArtifact != null && aArtifact.isDeleted() || bArtifact != null && bArtifact.isDeleted()) {
modificationType = ARTIFACT_DELETED;
relationEventType = RelationEventType.Deleted;
} else {
modificationType = DELETED;
relationEventType = RelationEventType.Deleted;
}
} else {
if (link.isUseBackingData() || link.getModificationType().matches(REPLACED_WITH_VERSION, INTRODUCED)) {
modificationType = link.getModificationType();
} else {
modificationType = MODIFIED;
}
relationEventType = RelationEventType.ModifiedRationale;
}
} else {
if (link.isDeleted()) {
return;
}
checkMultiplicity(artifact, link);
link.internalSetRelationId(getNewRelationId());
modificationType = NEW;
relationEventType = RelationEventType.Added;
}
/**
* Always want to persist artifacts on other side of dirty relation. This is necessary for ordering attribute
* to be persisted and desired for other cases.
*/
addArtifact(aArtifact, false);
addArtifact(bArtifact, false);
Id relId = Id.valueOf(link.getId());
BaseTransactionData txItem = transactionDataItems.get(RelationTransactionData.class, relId);
if (txItem == null) {
txItem = new RelationTransactionData(link, modificationType, relationEventType);
transactionDataItems.put(RelationTransactionData.class, relId, txItem);
if (aArtifact != null) {
modifiedArtifacts.add(aArtifact);
}
if (bArtifact != null) {
modifiedArtifacts.add(bArtifact);
}
} else {
updateTxItem(txItem, modificationType);
}
}
}
private void updateTxItem(BaseTransactionData itemToCheck, ModificationType currentModType) {
if (itemToCheck.getModificationType() == NEW && currentModType.isDeleted()) {
transactionDataItems.removeAndGet(itemToCheck.getClass(), itemToCheck.getItemId());
} else {
itemToCheck.setModificationType(currentModType);
}
}
private IOperation createStorageOp() throws OseeCoreException {
transaction = internalCreateTransaction(getBranch(), getAuthor(), comment);
return new StoreSkynetTransactionOperation(getName(), getBranch(), transaction, getTransactionData(),
getArtifactReferences());
}
public static synchronized TransactionRecord internalCreateTransaction(BranchId branch, User userToBlame, String comment) throws OseeCoreException {
if (comment == null) {
comment = "";
}
Integer authorArtId = userToBlame.getArtId();
TransactionDetailsType txType = TransactionDetailsType.NonBaselined;
Date timestamp = GlobalTime.GreenwichMeanTimestamp();
//keep transaction id's sequential in the face of concurrent transaction by multiple users
Long txId = ConnectionHandler.getNextSequence("SKYNET_TRANSACTION_ID_SEQ", false);
return new TransactionRecord(txId, branch, comment, timestamp, authorArtId, 0, txType);
}
@Override
public boolean containsItem(Object object) {
synchronized (getTxMonitor()) {
return modifiedArtifacts.contains(object);
}
}
@Override
protected void txWork(IProgressMonitor monitor) throws Exception {
IOperation subOp = createStorageOp();
doSubWork(subOp, monitor, 1.00);
}
@Override
public String toString() {
return String.format("uuid:[%s] branch[%s] comment[%s]", getUuid(), getBranch(), comment);
}
//TODO this method needs to be removed
public TransactionId execute() throws OseeCoreException {
Operations.executeWorkAndCheckStatus(this);
return transaction;
}
public void cancel() {
getTxMonitor().cancel(getBranch(), this);
}
}