package com.gammastream.validity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.webobjects.appserver.xml.WOXMLDecoder;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSPathUtilities;
import com.webobjects.foundation.NSValidation;
/**
* This is Validity's validation engine. This class loads and
* applies the rules you have modeled in your Validity model file.
* This class is accessed via a singleton accessor.
*
* @author GammaStream Technologies, Inc.
*/
public final class GSVEngine {
/********************************** STATIC **********************************/
/**
* Key used to access the Validity exception dictionary from
* NSValidation.Exception's userinfo dictionary.
*/
public static String ERROR_DICTIONARY_KEY = "GSVExceptions";
//internal constants used to determine when a rule should be applied.
private static int ON_SAVE = 1;
private static int ON_INSERT = 2;
private static int ON_UPDATE = 3;
private static int ON_DELETE = 4;
private static int ON_DEMAND = 5;
//GSVEngine is a singlet class, this iVar stores the single instance.
private static GSVEngine _sharedValidationEngine = null;
/**
* Creates a new GSVEngine if one is not already created, and returns the shared instance.
*
* @return The shared validation engine.
*/
public static GSVEngine sharedValidationEngine(){
if( _sharedValidationEngine == null ){
//NSLog.debug.appendln("*** Validity: GSVEngine.sharedValidationEngine(): Creating Shared Validation Engine");
_sharedValidationEngine = new GSVEngine();
}
return _sharedValidationEngine;
}
public static NSValidation.ValidationException exceptionByAppendingErrorToException(String errorMessage, String key, NSValidation.ValidationException ex){
NSMutableDictionary userInfo = null;
NSMutableDictionary errorDict = null;
NSMutableArray errorList = null;
NSValidation.ValidationException returnException = ex;
//get or create the userInfo dictionary
if( returnException == null || returnException.userInfo() == null ){
userInfo = new NSMutableDictionary(new NSMutableDictionary(), GSVEngine.ERROR_DICTIONARY_KEY);
} else {
userInfo = new NSMutableDictionary(returnException.userInfo());
}
//get or create the GSV error dictionary
errorDict = (NSMutableDictionary)userInfo.objectForKey(GSVEngine.ERROR_DICTIONARY_KEY);
if( errorDict == null ){
errorDict = new NSMutableDictionary();
userInfo.setObjectForKey(errorDict, GSVEngine.ERROR_DICTIONARY_KEY);
}
//get or create the gsv error list
errorList = (NSMutableArray)errorDict.objectForKey(key);
if( errorList == null ){
errorList = new NSMutableArray();
errorDict.setObjectForKey(errorList, key);
}
//add the error
errorList.addObject(errorMessage);
//return the new aggregated exception
return new NSValidation.ValidationException("Validity Exception", userInfo);
}
/********************************** INSTANCE **********************************/
//caches
private NSMutableDictionary _modelCache = null;
private NSMutableDictionary _classCache = null;
private NSMutableDictionary _methodCache = null;
//stores the reusable method signature for all gsv rules
private Class[] _gsvRuleSignature = new Class[4];
//The default model group used of accessing the EOModel associated with the Validity model
private EOModelGroup _defaultModelGroup = null;
//store whether the beta/trial period has passed
private boolean _expired = false;
/**
* Private constructor called by the singlet accessor.
*/
private GSVEngine(){
super();
this.checkExpirationDate();
this.generateGSVRuleSignature();
_defaultModelGroup = EOModelGroup.defaultGroup();
//initialize caches
_modelCache = new NSMutableDictionary();
_classCache = new NSMutableDictionary();
_methodCache = new NSMutableDictionary();
}
/**
* Called from an object being validated (i.e. validateForSave());
*/
public boolean validateEOObjectOnSave(EOEnterpriseObject eoObject){
//NSLog.debug.appendln("*** Validity: GSVEngine.validateEOObjectOnSave(EOEnterpriseObject eoObject)");
return ( this.validateEOObject(eoObject, ON_SAVE) );
}
/**
* Called from an object being validated (i.e. validateForInsert());
*/
public boolean validateEOObjectOnInsert(EOEnterpriseObject eoObject){
//NSLog.debug.appendln("*** Validity: GSVEngine.validateEOObjectOnInsert(EOEnterpriseObject eoObject)");
return ( this.validateEOObject(eoObject, ON_INSERT) );
}
/**
* Called from an object being validated (i.e. validateForUpdate());
*/
public boolean validateEOObjectOnUpdate(EOEnterpriseObject eoObject){
//NSLog.debug.appendln("*** Validity: GSVEngine.validateEOObjectOnUpdate(EOEnterpriseObject eoObject)");
return ( this.validateEOObject(eoObject, ON_UPDATE) );
}
/**
* Called from an object being validated (i.e. validateForDelete());
*/
public boolean validateEOObjectOnDelete(EOEnterpriseObject eoObject){
//NSLog.debug.appendln("*** Validity: GSVEngine.validateEOObjectOnDelete(EOEnterpriseObject eoObject)");
return ( this.validateEOObject(eoObject, ON_DELETE) );
}
public NSDictionary validateKeyAndValueInEntity(String key, String value, String entity) {
GSVEntity gsvEntity = null;
GSVAttribute currentAttribute = null;
GSVRule currentRule = null;
NSMutableDictionary errorDict = new NSMutableDictionary();
boolean rulePassed = false;
gsvEntity = this.entityWithName(entity);
if( gsvEntity != null ){
//check each of the object's attributes
currentAttribute = gsvEntity.attributeNamed(key);
if (currentAttribute != null) {
NSArray rules = currentAttribute.rules();
if (rules != null) {
for(int r=0; r<currentAttribute.rules().count(); r++){
currentRule = (GSVRule)currentAttribute.rules().objectAtIndex(r);
rulePassed = this.checkRule(currentRule, key, errorDict, value, ON_SAVE);
if(currentRule.stopIfFails() && !rulePassed){
break;
}
}
}
}
}
//if the error dict contains values, rules failed, so throw an exception
if(errorDict.allKeys().count()>0) {
return errorDict;
} else {
return null;
}
}
/**
* May be called arbitrarily to validate and eo object
* Returns <code>true</code> if all validation succeeds.
* Throws an NSValidation.ValidationException if one or more of the rules fails.
*/
public boolean validateEOObject(EOEnterpriseObject eoObject, int when){
GSVEntity gsvEntity = null;
GSVAttribute currentAttribute = null;
GSVRule currentRule = null;
NSMutableDictionary errorDict = new NSMutableDictionary();
boolean rulePassed = false;
if( !_expired ){
gsvEntity = this.entityForObject(eoObject);
if( gsvEntity != null ){
//check each of the object's attributes
for(int i=0;i<gsvEntity.attributes().count();i++){
currentAttribute = (GSVAttribute)gsvEntity.attributes().objectAtIndex(i);
//check each or the attribute's rules
for(int r=0;r<currentAttribute.rules().count();r++){
currentRule = (GSVRule)currentAttribute.rules().objectAtIndex(r);
rulePassed = this.checkRule(currentRule, currentAttribute.name(), errorDict, eoObject, when);
if(currentRule.stopIfFails() && !rulePassed){
break;
}
}
}
}
//if the error dict contains values, rules failed, so throw an exception
if(errorDict.allKeys().count()>0){
NSDictionary userInfo = new NSDictionary(errorDict, GSVEngine.ERROR_DICTIONARY_KEY);
System.out.println("eo="+eoObject);
throw new NSValidation.ValidationException("Validity Exception", userInfo);
}
} else {
//System.out.println("Validity 1.0 has expired. Please visit http://www.gammastream.com");
//System.out.println("for the latest release.");
return false;
}
return true;
}
/**
* May be called arbitrarily to validate an object
* Returns <code>true</code> if all validation succeeds.
* Throws an NSValidation.ValidationException if one or more of the rules fails.
*/
public boolean validateAttribute(Object object, String attributeName, GSVRule rule){
NSMutableDictionary errorDict = new NSMutableDictionary();
if( !_expired ){
if( object != null ){
this.checkRule(rule, attributeName, errorDict, object, GSVEngine.ON_DEMAND);
}
//if the error dict contains values, rules failed, so throw an exception
if(errorDict.allKeys().count()>0){
NSDictionary userInfo = new NSDictionary(errorDict, GSVEngine.ERROR_DICTIONARY_KEY);
throw new NSValidation.ValidationException("Validity Exception", userInfo);
}
} else {
//System.out.println("Validity 1.0 b1 has expired. Please visit http://www.gammastream.com");
//System.out.println("for the latest release.");
return false;
}
return true;
}
private boolean checkRule(GSVRule rule, String attributeName, NSMutableDictionary errorDict, String value, Object eoObject, int when) {
//determine whether this rule applies to the provided 'when'
if( (when == GSVEngine.ON_SAVE && rule.onSave()) ||
(when == GSVEngine.ON_INSERT && rule.onInsert()) ||
(when == GSVEngine.ON_UPDATE && rule.onUpdate()) ||
(when == GSVEngine.ON_DELETE && rule.onDelete()) ||
(when == GSVEngine.ON_DEMAND)){
try{
Object attributeValue = (eoObject == null) ? value : NSKeyValueCoding.Utility.valueForKey(eoObject,attributeName);
//if the attributeValue is null, we can stop
if( attributeValue == null && rule.continueIfNULL()==false){
if( rule.failIfNULL() ){
NSMutableArray errorArray = (NSMutableArray)errorDict.objectForKey( attributeName );
//if this is the first error for this attribute, we need to create an error array
if(errorArray == null){
errorArray = new NSMutableArray();
//place the new error array into the error dictionary
errorDict.setObjectForKey(errorArray, attributeName);
}
//now append the error message to error array
if(rule.errorMessage()!=null){
errorArray.addObject(rule.errorMessage());
}else{
errorArray.addObject(attributeName+": Error - No message provided.");
}
return false;
}
return true; //rule passed
}
//continue
String classKey = rule.cName();
String ruleKey = rule.cName() + "." + rule.mName();
Class clss = (Class)_classCache.objectForKey(classKey);
Method method = (Method)_methodCache.objectForKey(ruleKey);
Class methodReturnType = null;
boolean rulePassed = false;
if(method == null){
//first lets get the class, if we don't have it already
if( clss == null ){
clss = Class.forName(classKey);
//the class was found, cache it.
if( clss != null ){
_classCache.setObjectForKey(clss, classKey);
}
}
//now get the method from the class, if the class was found
if( clss != null ){
method = clss.getMethod(rule.mName(), _gsvRuleSignature);
//the method was found, cache it.
if(method != null){
_methodCache.setObjectForKey(method, ruleKey);
}
}
}
//we have the method, lets continue;
if( method != null ){
methodReturnType = method.getReturnType();
//this return type must be a <code>boolean</code>
if(methodReturnType.toString().equals("boolean")){
Object[] params = {eoObject, attributeValue , attributeName, rule.parameters()};
rulePassed = ((Boolean)method.invoke(null, params)).booleanValue();
//did the rule fail, if so, populate the error dictionary
if( (!rule.negate() && !rulePassed) || (rule.negate() && rulePassed) ){
NSMutableArray errorArray = (NSMutableArray)errorDict.objectForKey( attributeName );
//if this is the first error for this attribute, we need to create an error array
if(errorArray == null){
errorArray = new NSMutableArray();
//place the new error array into the error dictionary
errorDict.setObjectForKey(errorArray, attributeName);
}
//now append the error message to error array
if(rule.errorMessage()!=null){
errorArray.addObject(rule.errorMessage());
}else{
errorArray.addObject(attributeName+": Error - No message provided.");
}
}
}
}
}catch(InvocationTargetException e){
System.out.println(e);
return false;
}catch(IllegalAccessException e){
System.out.println(e);
return false;
}catch(ClassNotFoundException e){
System.out.println(e);
return false;
}catch(NoSuchMethodException e){
System.out.println(e);
return false;
}
}
return true;
}
public boolean checkRule(GSVRule rule, String attributeName, NSMutableDictionary errorDict, String value, int when){
return checkRule(rule, attributeName, errorDict, value, null, when);
}
/**
* May be called arbitrarily to validate an EO Object, though
* it is explicitly called by <code>validateObject</code>
* Returns <code>true</code> if all validation succeeds.
* Returns <code>false</code> if the rule fails, and populates the error
* dictionary with the error.
*/
public boolean checkRule(GSVRule rule, String attributeName, NSMutableDictionary errorDict, Object eoObject, int when){
return checkRule(rule, attributeName, errorDict, null, eoObject, when);
}
private GSVEntity entityWithName(String name) {
EOEntity eoentity = _defaultModelGroup.entityNamed(name);
return entityForEntity(eoentity);
}
private GSVEntity entityForEntity(EOEntity eoentity) {
//if the eoentity, indeed exists, find out the name of it's model
if( eoentity != null ){
String modelName = eoentity.model().name();
//check the cache to see if this Valididty model has been loaded
GSVModel model = (GSVModel)_modelCache.objectForKey(modelName);
//if the model hasn't been cache, load then cache it
if( model == null ){
EOModel eomodel = eoentity.model();
String eoModelPath = NSPathUtilities.stringByDeletingLastPathComponent(eomodel.path());
eoModelPath = NSPathUtilities.stringByAppendingPathComponent(eoModelPath, eomodel.name());
eoModelPath = NSPathUtilities.stringByAppendingPathExtension(eoModelPath, "eomodeld");
String gsvModelPath = NSPathUtilities.stringByAppendingPathComponent(eoModelPath, GSVModel.MODEL_NAME);
gsvModelPath = NSPathUtilities.stringByAppendingPathExtension(gsvModelPath, GSVModel.MODEL_EXTENSION);
WOXMLDecoder decoder = WOXMLDecoder.decoder();
decoder.setEncoding("UTF-8");
model = (GSVModel)decoder.decodeRootObject(gsvModelPath);
model.setEomodelPath(eoModelPath); //not sure why we need to do this?
model.init(eomodel);
model.saveModel(); //not sure why we need to do this?
//now cache
_modelCache.setObjectForKey(model, modelName);
}
return model.entityNamed(eoentity.name());
}
//the eo entity wasn't found
return null;
}
/**
* Loads and returns the GSVEntity associated with the enterprise object.
*/
private GSVEntity entityForObject(EOEnterpriseObject eoObject){
EOEntity eoentity = _defaultModelGroup.entityNamed(eoObject.entityName());
return entityForEntity(eoentity);
}
/**
* Checks the expiration date for the trial/beta versions of Validity.
*/
private void checkExpirationDate(){
/*
NSTimestamp now = new NSTimestamp();
if( now.yearOfCommonEra() >= 2002 && now.monthOfYear() >= 2 && now.dayOfMonth() >= 1){
System.out.println("Validity 1.0 b1 has expired. Please visit http://www.gammastream.com");
System.out.println("for the latest release.");
_expired = true;
}
*/
_expired = false;
}
/**
* Simply generates the method signature for a Validity Rule. A Validity Rule
* must use this signature.
*/
private void generateGSVRuleSignature(){
try{
Class[] temp = {
Class.forName("java.lang.Object"),
Class.forName("java.lang.Object"),
Class.forName("java.lang.String"),
Class.forName("com.webobjects.foundation.NSDictionary")
};
_gsvRuleSignature = temp;
} catch(Exception e){
System.out.println(e);
}
}
}