package pt.ist.fenixframework.consistencyPredicates;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import jvstm.Transaction;
import jvstm.cps.ConsistencyCheckTransaction;
import jvstm.cps.Depended;
import jvstm.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pt.ist.fenixframework.DomainFenixFrameworkRoot;
import pt.ist.fenixframework.DomainMetaClass;
import pt.ist.fenixframework.DomainMetaObject;
import pt.ist.fenixframework.DomainObject;
import pt.ist.fenixframework.FenixFramework;
import pt.ist.fenixframework.NoDomainMetaObjects;
import pt.ist.fenixframework.adt.bplustree.BPlusTree;
/**
* A <code>DomainConsistencyPredicate</code> is the persistent domain entity
* that represents a consistency predicate method inside a domain class. The {@link DomainFenixFrameworkRoot} uses this
* entity after each initialization, to determine which predicates were already known, and which are new.
*
* @author João Neves - JoaoRoxoNeves@ist.utl.pt
**/
@NoDomainMetaObjects
public abstract class DomainConsistencyPredicate extends DomainConsistencyPredicate_Base {
private static final Logger logger = LoggerFactory.getLogger(DomainConsistencyPredicate.class);
public DomainConsistencyPredicate() {
super();
checkFrameworkNotInitialized();
setInitialized(false);
}
/**
* Checks that the {@link FenixFramework} is not initialized, throws an exception otherwise.
* Should be called before any changes are made to {@link DomainMetaClass}es or to {@link DomainConsistencyPredicate}s.
*
* @throws RuntimeException
* if the framework was already initialized
**/
protected void checkFrameworkNotInitialized() {
if (FenixFramework.isInitialized()) {
throw new RuntimeException("Instances of " + getClass().getSimpleName()
+ " cannot be edited after the FenixFramework has been initialized.");
}
}
public abstract boolean isPublic();
public abstract boolean isPrivate();
public abstract boolean isFinal();
public boolean isInitialized() {
return getInitialized();
}
@Override
public void setInitialized(Boolean initialized) {
checkFrameworkNotInitialized();
super.setInitialized(initialized);
}
@Override
public Method getPredicate() {
Method predicateMethod = super.getPredicate();
if (predicateMethod != null) {
predicateMethod.setAccessible(true);
}
return predicateMethod;
}
@Override
public void setPredicate(Method predicate) {
checkFrameworkNotInitialized();
super.setPredicate(predicate);
}
@Override
public void setDomainMetaClass(DomainMetaClass domainMetaClass) {
checkFrameworkNotInitialized();
super.setDomainMetaClass(domainMetaClass);
}
/**
* Finds and initializes the {@link PublicConsistencyPredicate} that is
* being overridden by this predicate (if any).
*/
public abstract void initConsistencyPredicateOverridden();
/**
* Finds and updates the {@link PublicConsistencyPredicate} that is being
* overridden by this predicate (if any). Only performs changes if the
* current information about the overridden predicate is outdated.
*/
public abstract void updateConsistencyPredicateOverridden();
/**
* Executes this consistency predicate for all objects of the given {@link DomainMetaClass},
* and objects of subclasses. The predicate will NOT be executed for objects of any subclass
* that overrides this consistency predicate.
*
* @param metaClass
* the {@link DomainMetaClass} for which to execute this
* predicate.
*/
public abstract void executeConsistencyPredicateForMetaClassAndSubclasses(DomainMetaClass metaClass);
/**
* Checks all the subclasses of this consistency predicate for any methods that override it.
* For each method found, checks that it has the {@link ConsistencyPredicate} annotation.
*
* @throws Error
* if this predicate is being overridden by a non-predicate
* method
*/
public abstract void checkOverridingMethods(DomainMetaClass metaClass);
/**
* Executes this consistency predicate for all objects in the given <code>List</code>.
* For each object, after the execution, this method creates a {@link DomainDependenceRecord} based on
* the dependencies of that object.
*
* @param metaClass
* the {@link DomainMetaClass} of {@link DomainObject} for which to execute this predicate.
*/
protected void executeConsistencyPredicateForExistingDomainObjects(DomainMetaClass metaClass) {
checkFrameworkNotInitialized();
BPlusTree<DomainMetaObject> metaObjects = metaClass.getExistingDomainMetaObjects();
if (metaObjects.isEmpty() || getPredicate() == null) {
return;
}
logger.info("Executing startup consistency predicate: " + getPredicate().getName() + " for the class: "
+ metaClass.getDomainClass().getSimpleName());
int count = 0;
for (DomainMetaObject existingMetaObject : metaObjects) {
count++;
if ((count % (ConsistencyPredicateSupport.getInstance().getBatchSize() / 2)) == 0) {
// Commits the current, and starts a new write transaction.
// This is necessary to split the load of the mass creation of DomainDependenceRecords among several transactions.
// Each transaction processes a maximum of objects in order to avoid OutOfMemoryExceptions.
// This limit is half the batch size defined in the ConsistencyPredicate Support class.
// Because this method checks for repeated OwnDependenceRecords of each meta object being checked, there is no problem with
// processing only an incomplete part of the objects of the given class.
logger.info("Transaction finished. Number of processed " + metaClass.getDomainClass().getSimpleName()
+ " objects: " + count);
DomainFenixFrameworkRoot.checkpointTransaction();
}
// The predicate was already checked during a previous incomplete initialization of this DomainConsistencyPredicate
if (existingMetaObject.hasOwnDependenceRecord(this)) {
continue;
}
Pair pair = executePredicateForOneObject(existingMetaObject.getDomainObject(), getPredicate());
// If an object is consistent and only depends on itself, the DomainDependenceRecord is not necessary.
if (!isConsistent(pair) || !dependsOnlyOnItself(pair)) {
new DomainDependenceRecord(existingMetaObject.getDomainObject(), this, (Set<Depended>) pair.first,
(Boolean) pair.second);
}
}
logger.info("Transaction finished. Number of processed " + metaClass.getDomainClass().getSimpleName() + " objects: "
+ count);
DomainFenixFrameworkRoot.checkpointTransaction();
}
public static boolean isConsistent(Pair pair) {
return (Boolean) pair.second;
}
public static boolean dependsOnlyOnItself(Pair pair) {
return ((Set<Depended>) pair.first).isEmpty();
}
/**
* Executes a certain consistency predicate method for a single object, with
* the only goal of returning information about the consistency of the
* object, and its dependencies. Even if the predicate returns false, no
* exception is thrown, and no transaction is aborted.<br>
* This method should only be invoked during the initialization of the {@link FenixFramework}.
*
* @param obj
* the <code>Object</code> for which to execute the predicate
* @param predicate
* the <code>Method</code> of the predicate to execute
* @return a {@link Pair} that contains a <code>Set</code> of {@link Depended} with the dependencies of the object's
* consistency, and a boolean that indicates if the object is
* consistent or not
*/
private Pair executePredicateForOneObject(DomainObject obj, Method predicate) {
checkFrameworkNotInitialized();
// starts a new transaction where the readSet used by the predicate will be collected.
ConsistencyCheckTransaction tx =
ConsistencyPredicateSupport.getInstance().createNewConsistencyCheckTransactionForObject(obj);
tx.start();
try {
// returns a pair with the depended of the transaction (readSet)
// and the result of the predicate (consistent or not)
Boolean predicateResult = (Boolean) predicate.invoke(obj);
Transaction.commit();
// Do not register the own object as a depended
Set<Depended> depended = tx.getDepended();
if (!depended.isEmpty()) {
depended.remove(DomainMetaObject.getDomainMetaObjectFor(obj));
}
return new Pair(depended, predicateResult);
} catch (InvocationTargetException ite) {
// if an exception is thrown during the execution of the predicate,
// the object is NOT consistent
Transaction.commit();
// Do not register the own object as a depended
Set<Depended> depended = tx.getDepended();
if (!depended.isEmpty()) {
depended.remove(DomainMetaObject.getDomainMetaObjectFor(obj));
}
return new Pair(depended, false);
} catch (Throwable t) {
// any other kind of throwable is an Error in the framework that should be fixed
throw new Error(t);
}
}
/**
* Deletes this <code>DomainConsistencyPredicate</code>.<br>
* A <code>DomainConsistencyPredicate</code> should be deleted when the
* consistency predicate method is removed from the code, or the containing
* class is removed. In either case, the framework can delete all the
* associated {@link DomainDependenceRecords}.<br>
*
* This method is called when the predicate is being removed, and not the class.
*
* @see DomainConsistencyPredicate#classDelete()
**/
public void delete() {
classDelete();
}
/**
* Deletes this <code>DomainConsistencyPredicate</code>.<br>
* A <code>DomainConsistencyPredicate</code> should be deleted when the
* consistency predicate method is removed from the code, or the containing
* class is removed. In either case, the framework can delete all the
* associated {@link DomainDependenceRecords}.<br>
*
* This method is called when the predicate's class is being removed.
*
* @see DomainConsistencyPredicate#delete()
**/
public void classDelete() {
checkFrameworkNotInitialized();
logger.info("Deleting predicate " + getPredicate()
+ ((getPredicate() == null) ? " of " + getDomainMetaClass().getDomainClass() : ""));
setInitialized(false);
int count = 0;
for (DomainDependenceRecord dependenceRecord : getDomainDependenceRecordSet()) {
count++;
if ((count % (ConsistencyPredicateSupport.getInstance().getBatchSize() / 2)) == 0) {
// Commits the current, and starts a new write transaction.
// This is necessary to split the load of the mass deletion of DomainDependenceRecords among several transactions.
// Each transaction processes a maximum of objects in order to avoid OutOfMemoryExceptions.
// This limit is half the batch size defined in the ConsistencyPredicate Support class.
// Because this method sets the current predicate as not finalized, there is no problem with processing only an incomplete part
// of the DomainDependenceRecords.
logger.info("Transaction finished. Number of deleted DomainDependenceRecords: " + count);
DomainFenixFrameworkRoot.checkpointTransaction();
}
dependenceRecord.delete();
}
setDomainMetaClass(null);
deleteDomainObject();
}
public static <PredicateT extends DomainConsistencyPredicate> PredicateT readDomainConsistencyPredicate(
Class<? extends DomainObject> domainClass, String predicateName) {
return (PredicateT) DomainMetaClass.readDomainMetaClass(domainClass).getDeclaredConsistencyPredicate(predicateName);
}
public static <PredicateT extends DomainConsistencyPredicate> PredicateT readDomainConsistencyPredicate(Method predicateMethod) {
return (PredicateT) DomainMetaClass.readDomainMetaClass(
(Class<? extends DomainObject>) predicateMethod.getDeclaringClass()).getDeclaredConsistencyPredicate(
predicateMethod);
}
/**
* Creates a new <code>DomainConsistencyPredicate</code> of the correct type,
* to represent the given predicate method, inside the given meta class.<br>
* The <code>DomainConsistencyPredicate</code> returned is not yet initialized.
*/
public static <PredicateT extends DomainConsistencyPredicate> PredicateT createNewDomainConsistencyPredicate(
Method predicateMethod, DomainMetaClass metaClass) {
DomainConsistencyPredicate newDomainConsistencyPredicate;
int methodModifiers = predicateMethod.getModifiers();
if (Modifier.isPrivate(methodModifiers)) {
newDomainConsistencyPredicate = new PrivateConsistencyPredicate(predicateMethod, metaClass);
} else if (Modifier.isFinal(methodModifiers)) {
newDomainConsistencyPredicate = new FinalConsistencyPredicate(predicateMethod, metaClass);
} else {
newDomainConsistencyPredicate = new PublicConsistencyPredicate(predicateMethod, metaClass);
}
return (PredicateT) newDomainConsistencyPredicate;
}
}