/*******************************************************************************
* Copyright (c) 2014 EURA NOVA.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v2.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Aldemar Reynaga - initial API and implementation
* Salim Jouili - initial API and implementation
******************************************************************************/
package com.steffi.storage;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import com.steffi.common.Configuration;
import com.steffi.common.Configuration.Key;
import com.steffi.index.ImgIndex;
import com.steffi.model.Cell;
import com.steffi.model.CellType;
import com.steffi.model.SteffiEdge;
import com.steffi.model.SteffiGraph;
/**
* @author Aldemar Reynaga
* Main class for Imgraph transactions, contains the pending operations and the logic of commit and rollbacks
*/
public class CellTransaction {
private Map<Long, CellOperations> transactionCells;
private Set<Long> removedCellIds;
private Map<String, IndexOperation<Cell>> indexOperations;
public static enum TransactionConclusion {
COMMIT,
ROLLBACK
}
public CellTransaction() {
transactionCells = new HashMap<Long, CellOperations>();
}
private Set<Long> getRemovedCellIds() {
if (removedCellIds == null)
removedCellIds = new HashSet<Long>();
return removedCellIds;
}
private Map<String, IndexOperation<Cell>> getIndexOperations() {
if (indexOperations == null)
indexOperations = new HashMap<String, IndexOperation<Cell>>();
return indexOperations;
}
private <T extends Cell> String makeIndexOperationKey(String key, T cell) {
return key + "::" + cell.getClass().getSimpleName();
}
public <T extends Cell> void putKeyValueIndex(String indexName, String key, Object value, T cell) {
getIndexOperation(indexName, key, cell).addKeyValue(key, value, cell);
}
public <T extends Cell> void removeKeyValueIndex(String indexName, String key, Object value, T cell) {
getIndexOperation(indexName, key, cell).removeKeyValue(key, value, cell);
}
private <T extends Cell> IndexOperation<Cell> getIndexOperation(String indexName, String key, T cell) {
String indexOpKey = makeIndexOperationKey(key, cell);
IndexOperation<Cell> indexOperation = getIndexOperations().get(indexOpKey);
if (indexOperation == null) {
indexOperation = new IndexOperation<Cell>(indexName, (Class<Cell>) cell.getClass());
getIndexOperations().put(indexOpKey, indexOperation);
}
return indexOperation;
}
public void addCell(Cell cell) {
if (transactionCells.containsKey(cell.getId()))
throw new RuntimeException("The cell is already in the transaction");
transactionCells.put(cell.getId(), new CellOperations(cell));
}
public Cell getCell(long cellId) {
CellOperations cellOperations = transactionCells.get(cellId);
if (cellOperations != null)
return cellOperations.getCell();
return null;
}
public void removeCell(Cell cell) {
getRemovedCellIds().add(cell.getId());
transactionCells.get(cell.getId()).setRemovedCell(true);
}
private CellOperations getCellOperations(Cell cell) {
CellOperations cellOperations = transactionCells.get(cell.getId());
if (cellOperations == null) {
if (!cell.getCellType().equals(CellType.EDGE)) {
throw new RuntimeException("The " + cell.getCellType().toString() + " with Id " +
cell.getId() + " is not included in the transaction");
} else {
cellOperations = new CellOperations(cell);
transactionCells.put(cell.getId(), cellOperations);
}
}
return cellOperations;
}
public void createCell(Cell cell) {
CellOperations cellOperations = new CellOperations(cell);
cellOperations.addOperationType(CellOperationType.CREATE_CELL);
transactionCells.put(cell.getId(), cellOperations);
}
public void addEdge(Cell cell, SteffiEdge edge) {
CellOperations cellOperations = getCellOperations(cell);
if (!getRemovedCellIds().contains(cell.getId()) && !cellOperations.getTypes().contains(CellOperationType.CREATE_CELL)) {
cellOperations.addOperationType(CellOperationType.ADD_EDGE);
cellOperations.getNewEdges().add(edge);
}
}
public void removeEdge(Cell cell, SteffiEdge edge) {
CellOperations cellOperations = getCellOperations(cell);
if (!getRemovedCellIds().contains(cell.getId())) {
getRemovedCellIds().add(edge.getId());
cellOperations.addOperationType(CellOperationType.REMOVE_EDGE);
cellOperations.getRemovedEdges().add(edge);
}
}
public void setCellProperty(Cell cell, int keyIndex, Object newValue, Object oldValue) {
CellOperations cellOperations = getCellOperations(cell);
if (!getRemovedCellIds().contains(cell.getId())) {
cellOperations.addOperationType(CellOperationType.SET_CELL_PROPERTY);
cellOperations.getSetProperties().put(keyIndex, new CellOperations.ModifiedValue(oldValue, newValue));
}
}
public void removeCellProperty(Cell cell, int keyIndex) {
CellOperations cellOperations = getCellOperations(cell);
if (!getRemovedCellIds().contains(cell.getId())) {
cellOperations.addOperationType(CellOperationType.REMOVE_CELL_PROPERTY);
cellOperations.getRemovedProperties().add(keyIndex);
}
}
private void executeCellOperation(Cache<Long, Object> cache,
CellOperations cellOp) {
if (!cellOp.getCell().getCellType().equals(CellType.EDGE) &&
!getRemovedCellIds().contains(cellOp.getCellId()) ) {
SteffiGraph graph = SteffiGraph.getInstance();
cellOp.getCell().trimToSize();
graph.storeCell(cellOp.getCellId(), cellOp.getCell());
}
}
private void updateIndexes () {
for (IndexOperation<Cell> indexOperation : getIndexOperations().values()) {
ImgIndex<Cell> index = SteffiGraph.getInstance().getIndex(indexOperation.getIndexName(), indexOperation.getClassName());
index.commitChanges(indexOperation);
}
}
public void commit() {
Cache<Long, Object> cache = CacheContainer.getCellCache();
TransactionManager tm = null;
Local2HopNeighborUpdater local2HNUpdater = null;
if (Configuration.getProperty(Key.USE_JTA_TRANSACTIONS).equals("true"))
tm = cache.getAdvancedCache().getTransactionManager();
try {
if (tm != null)
tm.begin();
if (!transactionCells.isEmpty()) {
for (CellOperations cellOp: transactionCells.values())
if (!cellOp.getTypes().isEmpty())
executeCellOperation(cache,cellOp);
}
for (Long cellId : getRemovedCellIds())
cache.remove(cellId);
local2HNUpdater = new Local2HopNeighborUpdater();
local2HNUpdater.update2HNList(transactionCells);
updateIndexes();
if (tm != null)
tm.commit();
} catch (Exception e) {
if (tm != null)
try {
tm.rollback();
} catch (Exception tranException) {
throw new RuntimeException(tranException);
}
throw new RuntimeException(e);
}
closeTransaction();
}
public void rollback() {
closeTransaction();
}
private void closeTransaction() {
for (CellOperations cop : transactionCells.values())
cop.clear();
if (transactionCells != null)
transactionCells.clear();
if (removedCellIds != null)
removedCellIds.clear();
if (indexOperations != null)
indexOperations.clear();
CellTransactionThread.unset();
}
}