/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXEntityClassDescription.java
Java | 1063 lines | 605 code | 87 blank | 371 comment | 131 complexity | a80d3597a9878696bb3fd24466932ff9 MD5 | raw file
- /*
- * Copyright (C) NetStruxr, Inc. All rights reserved.
- *
- * This software is published under the terms of the NetStruxr
- * Public Software License version 0.5, a copy of which has been
- * included with this distribution in the LICENSE.NPL file. */
- package er.extensions.eof;
- import java.io.File;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.Enumeration;
- import org.apache.log4j.Logger;
- import com.webobjects.eoaccess.EOAttribute;
- import com.webobjects.eoaccess.EOEntity;
- import com.webobjects.eoaccess.EOEntityClassDescription;
- import com.webobjects.eoaccess.EOModel;
- import com.webobjects.eoaccess.EOModelGroup;
- import com.webobjects.eoaccess.EORelationship;
- import com.webobjects.eocontrol.EOClassDescription;
- import com.webobjects.eocontrol.EOEditingContext;
- import com.webobjects.eocontrol.EOEnterpriseObject;
- import com.webobjects.eocontrol.EOGenericRecord;
- import com.webobjects.eocontrol.EOGlobalID;
- import com.webobjects.eocontrol.EOKeyGlobalID;
- import com.webobjects.eocontrol.EOQualifier;
- import com.webobjects.eocontrol.EOQualifierEvaluation;
- import com.webobjects.foundation.NSArray;
- import com.webobjects.foundation.NSDictionary;
- import com.webobjects.foundation.NSForwardException;
- import com.webobjects.foundation.NSKeyValueCoding;
- import com.webobjects.foundation.NSKeyValueCodingAdditions;
- import com.webobjects.foundation.NSMutableArray;
- import com.webobjects.foundation.NSMutableDictionary;
- import com.webobjects.foundation.NSNotification;
- import com.webobjects.foundation.NSNotificationCenter;
- import com.webobjects.foundation.NSPropertyListSerialization;
- import com.webobjects.foundation.NSSelector;
- import com.webobjects.foundation.NSTimestamp;
- import com.webobjects.foundation.NSValidation;
- import er.extensions.foundation.ERXFileNotificationCenter;
- import er.extensions.foundation.ERXFileUtilities;
- import er.extensions.foundation.ERXMutableDictionary;
- import er.extensions.foundation.ERXPatcher;
- import er.extensions.foundation.ERXProperties;
- import er.extensions.foundation.ERXStringUtilities;
- import er.extensions.foundation.ERXThreadStorage;
- import er.extensions.foundation.ERXTimestampUtilities;
- import er.extensions.foundation.ERXValueUtilities;
- import er.extensions.localization.ERXLocalizer;
- import er.extensions.partials.ERXPartial;
- import er.extensions.validation.ERXValidationException;
- import er.extensions.validation.ERXValidationFactory;
- /**
- * The main purpose of the ERXClassDescription class is
- * to throw {@link ERXValidationException}s instead of the
- * usual {@link com.webobjects.foundation.NSValidation.ValidationException
- * NSValidation.ValidationException} objects. See the
- * ERXValidationException and ERXValidationFactory class
- * for more information about localized and templatized
- * validation exceptions. This class is configured to
- * register itself as the class description by calling
- * the method <code>registerDescription</code>. This method
- * is called when the principal class of this framework is
- * loaded. This happens really early so you shouldn't have
- * to worry about this at all.<br/>
- * <br />
- * Additionally, this class allows for model driven validations in a "poor-mans-Validity-way":
- * add a <code>ERXValidation</code> user info entry on your entity.
- * This is an example:<code><pre>
- * "ERXValidation" = {
- * // these keys are evaluated on validateForSave, they don't correspond to properties
- * additionalValidationKeys = ("validateEmailPassword");
- *
- * // This dictionary holds the keys to use for validating properties
- * validateForKey =
- * {
- *
- * // these keys are evaluated on validateForSave, they don't correspond to properties
- * email =
- * (
- * {
- * // this is the message code into ValidationStrings.plist
- * // User.email.wrongLength = "The mail does not have the right size (5 to 50)";
- * message = "wrongLength";
- *
- * // skip this rule if the value is null
- * ignoreIfNull = true;
- *
- * // if there is a qualifier key, then a dictionary containing "object" and "value" is evaluated
- * // and an exception is thrown if the evaluation returns false
- * qualifier = "(value.length >= 5) AND (value.length < 50)";
- * },
- * {
- * // again, this is the message code into ValidationStrings.plist
- * message = "sampleTest";
- *
- * // Given this key, an object of the corresponding EOQualifierEvaluation subclass is created
- * // and given this dictionary on creation. This object needs to be re-entrant.
- * className = "SampleTest";
- * // an example is:
- * // public class SampleTest implements EOQualifierEvaluation {
- * // int minLength, maxLength;
- * // public SampleTest(Object param) {
- * // NSDictionary dict = (NSDictionary)param;
- * // minLength = ERXValueUtilities.intValue(dict.objectForKey("minLength"));
- * // maxLength = ERXValueUtilities.intValue(dict.objectForKey("maxLength"));
- * // }
- * // public boolean evaluateObject(Object o) {
- * // ERXEntityClassDescription.ValidationObjectValue val
- * // = (ERXEntityClassDescription.ValidationObjectValue)o;
- * // EOEnterpriseObject eo = val.object();
- * // String value = (String)val.value();
- * // return value.length() >= minLength && value.length() <= maxLength;
- * // }
- * // }
- *
- * minLength = "5";
- * maxLength = "10";
- * }
- * );
- *
- * // This key does not correspond to any property, it get's evaluated in D2WApps where you have a
- * // multi-step page and need to do validation before validateForSave
- * "validateEmailPassword" =
- * (
- * {
- * message = "stupidTestWithEmailAndPassword";
- *
- * // means to get D2W to highlight the fields involved instead of only displaying the message
- * // For this to work, your corresponding localized String should be
- * // User.email,password.stupidTestWithEmailAndPassword = "Stupid test failed";
- * keyPaths = "email,password";
- *
- * qualifier = "(object.email.length >= object.password.length)";
- * }
- * );
- * };
- *
- * // These get checked when the object gets saved, additionally to "additionalValidations"
- * // The structure of "validateForInsert", "validateForUpdate" and "validateForDelete" is the same.
- * validateForSave =
- * (
- * {
- * message = "cantBeBoth";
- *
- * keyPaths = "isEditor,isAdmin";
- *
- * qualifier = "(object.isEditor = 'Y' and object.isAdmin = 'Y')";
- * }
- * );
- * }</pre></code>
- * This code is mainly a quick-and-dirty rewrite from PRValidation by Proteon.
- * <br/>
- * Additionally, this class adds a concept of "Default" values that get pushed into the object at creation time.
- * Simply add a "ERXDefaultValues" key into the entity's userInfo dictionary that contains key-value-pairs for every default you want to set. Alternately, you can set a "default" key on each of the relationship or attrbute's userInfo<br />
- * Example:<pre><code>
- * "ERXDefaultValues" = {
- *
- * // Example for simple values.
- * isAdmin = N;
- * isEditor = Y;
- *
- * // Example for a related object (->Languages(pk,displayName)). You need to enter the primary key value.
- * language = "de";
- *
- * // Example for an NSArray of related objects
- * recommendingUser = "@threadStorage.actor";
- *
- * // Example for an NSArray
- * articlesToRevisit = "@threadStorage.actor.articles";
- *
- * // Example for a NSTimestamp. All static methods from ERXTimestampUtilities are supported.
- * created = "@now";
- * updatePassword = "@tomorrow";
- *
- * }</pre></code>
- * <br/>
- * If you wish to provide your own class description subclass
- * see the documentation associated with the Factory inner class.
- */
- public class ERXEntityClassDescription extends EOEntityClassDescription {
- /**
- * Do I need to update serialVersionUID?
- * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
- * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
- */
- private static final long serialVersionUID = 1L;
- /** logging support */
- public static final Logger log = Logger.getLogger(ERXEntityClassDescription.class);
- /** validation logging support */
- public static final Logger validationLog = Logger.getLogger("er.validation.ERXEntityClassDescription");
- /** default logging support */
- public static final Logger defaultLog = Logger.getLogger("er.default.ERXEntityClassDescription");
- /** Holds validation info from the entities user info dictionary */
- protected NSDictionary _validationInfo;
- /** Holds validation qualifiers */
- protected NSMutableDictionary _validationQualiferCache;
- /** Holds default values */
- protected NSMutableDictionary _initialDefaultValues;
- /** Holds the default factory instance */
- private static Factory _factory;
- /** holds validity Methods */
- private static Method[] validityMethods = null;
- /** index of validity save method */
- private static int VALIDITY_SAVE = 0;
- /** index of validity delete method */
- private static int VALIDITY_DELETE = 1;
- /** index of validity insert method */
- private static int VALIDITY_INSERT = 2;
- /** index of validity update method */
- private static int VALIDITY_UPDATE = 3;
- /** the shared validity engine instance as Object to eliminate compile errors
- * if validity is not linked and should not be used
- */
- private static Object sharedGSVEngineInstance;
- /** Boolean that gets initialized on first use to indicate if validity should
- * be used or not, remember that the call System.getProperty acts synchronized
- * so this saves some time in multithreaded apps.
- */
- private static Boolean useValidity;
- public static final String ValidateEntityClassAvailability = "ERXEntityClassDescription.validateEntityClassAvailability";
- /**
- * This factory inner class is registered as the observer
- * for three notifications: modelWasAdded, classDescriptionNeededForEntity
- * and classDescriptionNeededForClass. If you wish to provide your own
- * subclass of ERXEntityClassDescription then you need to create a
- * subclass of Factory and set that class name in the system properties
- * under the key: <code>er.extensions.ERXClassDescription.factoryClass</code>
- * In your Factory subclass override the method: newClassDescriptionForEntity
- * to provide your own ERXEntityClassDescription subclass.
- */
- public static class Factory {
- /** Public constructor */
- public Factory() {
- // Need to be able to preempt the model registering descriptions.
- NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("modelWasAdded", ERXConstant.NotificationClassArray), EOModelGroup.ModelAddedNotification, null);
- NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("modelGroupWasAdded", ERXConstant.NotificationClassArray), ERXModelGroup.ModelGroupAddedNotification, null);
- NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("classDescriptionNeededForEntityName", ERXConstant.NotificationClassArray), EOClassDescription.ClassDescriptionNeededForEntityNameNotification, null);
- NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("classDescriptionNeededForClass", ERXConstant.NotificationClassArray), EOClassDescription.ClassDescriptionNeededForClassNotification, null);
- }
- public void reset() {
- _registeredModelNames = new NSMutableArray();
- _entitiesForClass = new NSMutableDictionary();
- _classDescriptionForEntity = new NSMutableDictionary();
- }
- protected boolean isRapidTurnaroundEnabled() {
- return ERXProperties.booleanForKey("er.extensions.ERXEntityClassDescription.isRapidTurnaroundEnabled");
- }
- protected boolean isFixingRelationshipsEnabled() {
- return ERXProperties.booleanForKey("er.extensions.ERXEntityClassDescription.isFixingRelationshipsEnabled");
- }
- /**
- * Method called when a model group did load.
- */
- public final void modelGroupWasAdded(NSNotification n) {
- log.debug("modelGroupWasAdded: " + n);
- EOModelGroup group = (EOModelGroup) n.object();
- processModelGroup(group);
- }
- /**
- * Called when a model group finished loading. Checks foreign keys by default. Override to to more...
- * @param group
- */
- protected void processModelGroup(EOModelGroup group) {
- for (Enumeration ge = group.models().objectEnumerator(); ge.hasMoreElements();) {
- EOModel model = (EOModel)ge.nextElement();
- String frameworkName = null;
- String modelPath = null;
- log.debug("ApplicationDidFinishLaunching: " + model.name());
- if(isRapidTurnaroundEnabled()) {
- for(Enumeration e = NSArray.componentsSeparatedByString(model.pathURL().getFile(), File.separator).reverseObjectEnumerator(); e.hasMoreElements(); ) {
- String a = (String)e.nextElement();
- if(a.indexOf(".framework") > 0) {
- frameworkName = a.substring(0, a.indexOf(".framework"));
- break;
- }
- }
- if(frameworkName == null) {
- frameworkName = "app";
- }
- modelPath = ERXFileUtilities.pathForResourceNamed(model.name() + ".eomodeld", frameworkName, null);
- defaultLog.debug("Path for model <" + model.name() + "> in framework <"+frameworkName+">: " + modelPath);
- }
- for (Enumeration ee = model.entities().objectEnumerator(); ee.hasMoreElements();) {
- EOEntity entity = (EOEntity)ee.nextElement();
- checkForeignKeys(entity);
- EOClassDescription cd = EOClassDescription.classDescriptionForEntityName(entity.name());
- defaultLog.debug("Reading defaults for: " + entity.name());
- if(cd instanceof ERXEntityClassDescription) {
- ((ERXEntityClassDescription)cd).readDefaultValues();
- if(isRapidTurnaroundEnabled() && modelPath != null) {
- String path = modelPath + File.separator + entity.name() + ".plist";
- ERXFileNotificationCenter.defaultCenter().addObserver(cd, new NSSelector("modelFileDidChange", ERXConstant.NotificationClassArray), path);
- }
- } else {
- defaultLog.warn("Entity classDescription is not ERXEntityClassDescription: " + entity.name());
- }
- }
- }
- }
- /**
- * Method called by the {@link com.webobjects.foundation.NSNotificationCenter NSNotificationCenter}
- * when an EOModel is loaded.
- * This method just calls the method
- * <code>registerDescriptionForEntitiesInModel</code>
- *
- * @param n notification that has the EOModel that was loaded.
- */
- public final void modelWasAdded(NSNotification n) {
- EOModel model = ((EOModel)n.object());
- log.debug("ModelWasAddedNotification: " + model.name());
- // Don't want this guy getting in our way.
- NSNotificationCenter.defaultCenter().removeObserver(model);
- try {
- registerDescriptionForEntitiesInModel(model);
- } catch (RuntimeException e) {
- log.error("Error registering model: " + model.name(), e);
- throw e;
- }
- }
- /**
- * Method called by the {@link com.webobjects.foundation.NSNotificationCenter NSNotificationCenter}
- * when a class description is needed
- * for a given entity. Usually this method isn't needed seeing
- * as we preempt the on demand loading of class descriptions
- * by loading all of them when the EOModel is loaded.
- * This method just calls the method
- * <code>registerDescriptionForEntity</code>
- *
- * @param n notification that has the name of the entity
- * that needs the class description.
- */
- public void classDescriptionNeededForEntityName(NSNotification n) {
- log.debug("classDescriptionNeededForEntityName: " + (String)n.object());
- String name = (String)n.object();
- EOEntity e = ERXEOAccessUtilities.entityNamed(null,name);
- if(e == null) log.error("Entity " + name + " not found in the default model group!");
- if (e != null) {
- registerDescriptionForEntity(e);
- }
- }
- /**
- * Method called by the {@link com.webobjects.foundation.NSNotificationCenter NSNotificationCenter}
- * when a class description is needed
- * for a given Class. Usually this method isn't needed seeing
- * as we preempt the on demand loading of class descriptions
- * by loading all of them when the EOModel is loaded.
- * This method just calls the method
- * <code>registerDescriptionForClass</code>
- * @param n notification that has the Class object
- * that needs a class description.
- */
- public void classDescriptionNeededForClass(NSNotification n) {
- Class c = (Class)n.object();
- log.debug("classDescriptionNeededForClass: " + c.getName());
- registerDescriptionForClass(c);
- }
- /**
- * Factory method that is used to create a new class
- * description for a given entity. Sub classes that
- * wish to provide a sub class of ERXEntityClassDescription
- * should override this method to create that custom
- * description. By default this method returns a new
- * ERXEntityClassDescription.
- * @param entity to create the class description for
- * @return new class description for the given entity
- */
- protected ERXEntityClassDescription newClassDescriptionForEntity(EOEntity entity) {
- String key = entity.name();
- EOModel model = entity.model();
- if (model != null) {
- key = model.name() + " " + key;
- }
- ERXEntityClassDescription classDescription = (ERXEntityClassDescription)_classDescriptionForEntity.objectForKey(key);
- if (classDescription == null) {
- classDescription = new ERXEntityClassDescription(entity);
- _classDescriptionForEntity.setObjectForKey(classDescription, key);
- }
- return classDescription;
- }
- /** holds a reference to all of the registered model names */
- private NSMutableArray _registeredModelNames = new NSMutableArray();
- /** holds a mapping of class to entities */
- private NSMutableDictionary _entitiesForClass = new NSMutableDictionary();
- /** holds a mapping of entity to class descriptions */
- private NSMutableDictionary _classDescriptionForEntity = new NSMutableDictionary();
- /**
- * Allows for entities to be altered
- * before they have a custom class description
- * registered. Sub classes can override this method
- * to provide any extra alterings before the description
- * is registered. However be sure to call super as this
- * method does convert the class name from EOGenericRecord
- * to ERXGenericRecord, which unfortunately is required
- * for custom validation to work at the moment.
- * @param eoentity to be prepared for registration
- */
- protected void prepareEntityForRegistration(EOEntity eoentity) {
- String className = eoentity.className();
- String defaultClassName = ERXProperties.stringForKeyWithDefault("er.extensions.ERXEntityClassDescription.defaultClassName", ERXGenericRecord.class.getName());
- String alternateClassName = ERXProperties.stringForKey("er.extensions.ERXEntityClassDescription." + eoentity.name() + ".ClassName");
- if (alternateClassName != null) {
- log.debug(eoentity.name() + ": setting class from: " + className + " to: " + alternateClassName);
- eoentity.setClassName(alternateClassName);
- } else if (className.equals("EOGenericRecord")) {
- eoentity.setClassName(defaultClassName);
- }
- }
- /**
- * Handles errors when an optional relationship has a source attribute
- * that is set to allow null values. Subclasses can override this to do more specific handling.
- */
- protected void handleOptionalRelationshipError(EOEntity eoentity, EORelationship relationship, EOAttribute attribute) {
- if(isFixingRelationshipsEnabled()) {
- relationship.setIsMandatory(true);
- log.info(eoentity.name() + ": relationship '"
- + relationship.name() + "' was switched to mandatory, because the foreign key '"
- + attribute.name() + "' does NOT allow NULL values");
- } else {
- log.warn(eoentity.name() + ": relationship '"
- + relationship.name() + "' is marked to-one and optional, but the foreign key '"
- + attribute.name() + "' does NOT allow NULL values");
- }
- }
- /**
- * Handles errors when a mandatory relationship has a source attribute
- * that is set to not allow null values. Subclasses can override this to do more specific handling.
- */
- protected void handleMandatoryRelationshipError(EOEntity eoentity, EORelationship relationship, EOAttribute attribute) {
- if(isFixingRelationshipsEnabled()) {
- relationship.setIsMandatory(false);
- log.info(eoentity.name() + ": relationship '"
- + relationship.name() + "' was switched to optional, because the foreign key '"
- + attribute.name() + "' allows NULL values");
- } else {
- log.warn(eoentity.name() + ": relationship '"
- + relationship.name() + "' is marked to-one and mandatory, but the foreign key '"
- + attribute.name() + "' allows NULL values");
- }
- }
- /**
- * Checks for foreign keys that are <code>NOT NULL</code>,
- * but whose relationship is marked as non-mandatory and vice-versa. This
- * error is not checked by EOModeler, so we do it here.
- * @param eoentity to be check
- */
- public void checkForeignKeys(EOEntity eoentity) {
- NSArray primaryKeys = eoentity.primaryKeyAttributes();
- for(Enumeration relationships = eoentity.relationships().objectEnumerator(); relationships.hasMoreElements(); ) {
- EORelationship relationship = (EORelationship)relationships.nextElement();
- if(!relationship.isToMany()) {
- if(relationship.isMandatory()) {
- for(Enumeration attributes = relationship.sourceAttributes().objectEnumerator(); attributes.hasMoreElements(); ) {
- EOAttribute attribute = (EOAttribute)attributes.nextElement();
- if(attribute.allowsNull()) {
- handleMandatoryRelationshipError(eoentity, relationship, attribute);
- }
- }
- } else {
- for(Enumeration attributes = relationship.sourceAttributes().objectEnumerator(); attributes.hasMoreElements(); ) {
- EOAttribute attribute = (EOAttribute)attributes.nextElement();
- if(!attribute.allowsNull() && !primaryKeys.containsObject(attribute)) {
- handleOptionalRelationshipError(eoentity, relationship, attribute);
- }
- }
- }
- }
- }
- }
- /**
- * This method registers custom class descriptions for all
- * of the entities in a given model. This method is called
- * when a model is loaded. The reason for this method is
- * to preempt the usual class description loading mechanism
- * which has a race condition involved for the order in
- * which the notifications are recieved.
- * @param model that contains all of the entities to be registerd
- */
- protected void registerDescriptionForEntitiesInModel(EOModel model) {
- if (!_registeredModelNames.containsObject(model.name())) {
- for (Enumeration e = model.entities().objectEnumerator(); e.hasMoreElements();) {
- EOEntity eoentity = (EOEntity)e.nextElement();
- String className = eoentity.className();
- prepareEntityForRegistration(eoentity);
- NSMutableArray array = (NSMutableArray)_entitiesForClass.objectForKey(className);
- if(array == null) {
- array = new NSMutableArray();
- }
- if (log.isDebugEnabled())
- log.debug("Adding entity " +eoentity.name()+ " with class " + eoentity.className());
- array.addObject(eoentity);
- _entitiesForClass.setObjectForKey(array, eoentity.className());
- //HACK ALERT: (ak) We work around classDescriptionForNewInstances() of EOEntity being broken here...
- registerDescriptionForEntity(eoentity);
- }
- _registeredModelNames.addObject(model.name());
- }
- // Don't want this guy getting in our way later on ;
- NSNotificationCenter.defaultCenter().removeObserver(model);
- }
- /**
- * This is a hack to work around RadarBug:2867501. EOEntity
- * is hardwired to return an EOEntityClassdescription for the
- * method classDescriptionForNewInstances, this causes a serious
- * problem when using custom class descriptions with D2W which
- * makes use of this method. What this hack does is use the magic
- * of key-value coding to push our custom class description onto
- * a given entity. In order to do this we needed to add the
- * custom {@link KVCProtectedAccessor} to the package
- * com.webobjects.eoaccess.
- * @param entity to have the custom class description set on
- * @param cd class description to set on the entity
- */
- private void _setClassDescriptionOnEntity(EOEntity entity, ERXEntityClassDescription cd) {
- try {
- //HACK ALERT: (ak) We push the cd rather rudely into the entity to have it ready when classDescriptionForNewInstances() is called on it. We will have to add a com.webobjects.eoaccess.KVCProtectedAccessor to make this work
- NSKeyValueCoding.Utility.takeValueForKey(entity, cd, "classDescription");
- } catch(RuntimeException ex) {
- log.warn("_setClassDescriptionOnEntity: " + ex);
- }
- }
- /**
- * Registers a custom class description for the given
- * entity using the method <code>newClassDescriptionForEntity</code>
- * which can be overridden by subclasses to provide a
- * different class description subclass.
- * @param entity to register the class description for
- */
- protected void registerDescriptionForEntity(EOEntity entity) {
- Class entityClass = EOGenericRecord.class;
- String className = entity.className();
- if (log.isDebugEnabled()) {
- log.debug("Registering description for entity: " + entity.name() + " with class: " + className);
- }
- if (ERXProperties.booleanForKeyWithDefault(ValidateEntityClassAvailability, true)) { // Make it possible to opt-out of this check.
- try {
- entityClass = className.endsWith("EOGenericRecord") ? EOGenericRecord.class : Class.forName(className);
- } catch (java.lang.ClassNotFoundException ex) {
- throw new RuntimeException("Invalid class name '" + className + "' for entity '" + entity.name() + "'." + (!className.contains(".") ? " (The class name should include the full package path of the class.)" : ""), ex);
- }
- }
- ERXEntityClassDescription cd = newClassDescriptionForEntity(entity);
- EOClassDescription.registerClassDescription(cd, entityClass);
- _setClassDescriptionOnEntity(entity, cd);
- }
- /**
- * This method is called when a class description is
- * needed for a particular class. Here we use the
- * previous cache that we constructed of class to
- * entity map when the models were loaded. In this
- * way we can register all of the custom class
- * descriptions for a given class if need be.
- * @param class1 class object to have a custom class
- * description registered for.
- */
- protected void registerDescriptionForClass(Class class1) {
- NSArray entities = (NSArray)_entitiesForClass.objectForKey(class1.getName());
- if (entities != null) {
- if (log.isDebugEnabled())
- log.debug("Registering descriptions for class: " + class1.getName() + " found entities: " + entities.valueForKey("name"));
- for (Enumeration e = entities.objectEnumerator(); e.hasMoreElements();) {
- EOEntity entity = (EOEntity)e.nextElement();
- ERXEntityClassDescription cd = newClassDescriptionForEntity(entity);
- EOClassDescription.registerClassDescription(cd, class1);
- _setClassDescriptionOnEntity(entity, cd);
- }
- } else {
- if(class1.getName().indexOf('$') < 0) {
- log.error("Unable to register descriptions for class: " + class1.getName(), new RuntimeException("Dummy"));
- }
- }
- }
- }
- /** getter for the factory */
- public static Factory factory() {
- return _factory;
- }
- /**
- * This method is called by the principal class
- * of the framework when the framework's NSBundle is
- * loaded. This method registers an observer, either
- * a Factory object, which is an inner class of this class
- * or a custom Factory subclass specified in the property:
- * <b>er.extensions.ERXClassDescription.factoryClass</b>.
- * This observer listens for notifications when a model
- * is loaded or a class description is needed and responds
- * by creating and registering custom class descriptions.
- */
- public static void registerDescription() {
- if (_factory == null) {
- _factory = null;
- try {
- String className = ERXProperties.stringForKey("er.extensions.ERXClassDescription.factoryClass");
- if (className != null) {
- _factory = (Factory)Class.forName(className).newInstance();
- }
- } catch(Exception ex) {
- log.warn("Exception while registering factory, using default: " + ex );
- }
- if(_factory == null)
- _factory=new Factory();
- }
- }
- /**
- * Public constructor
- * @param entity that this class description corresponds to
- */
- public ERXEntityClassDescription(EOEntity entity) {
- super(entity);
- _validationInfo = ERXValueUtilities.dictionaryValue(entity.userInfo().objectForKey("ERXValidation"));
- _validationQualiferCache = ERXMutableDictionary.synchronizedDictionary();
- }
- public void modelFileDidChange(NSNotification n) {
- File file = (File)n.object();
- try {
- defaultLog.debug("Reading .plist for entity <"+entity()+">");
- NSDictionary userInfo = (NSDictionary)NSPropertyListSerialization.propertyListFromString(ERXFileUtilities.stringFromFile(file));
- entity().setUserInfo((NSDictionary)userInfo.objectForKey("userInfo"));
- _validationInfo = ERXValueUtilities.dictionaryValue(entity().userInfo().objectForKey("ERXValidation"));
- _validationQualiferCache = ERXMutableDictionary.synchronizedDictionary();
- _initialDefaultValues = null;
- readDefaultValues();
- } catch(Exception ex) {
- defaultLog.error("Can't read file <"+file.getAbsolutePath()+">", ex);
- }
- }
- /**
- * This method is called when an object is
- * about to be deleted. If any validation
- * exceptions occur they are converted to an
- * {@link ERXValidationException} and that is
- * thrown.
- * @param obj enterprise object to be deleted
- * @throws NSValidation.ValidationException validation exception
- */
- @Override
- public void validateObjectForDelete(EOEnterpriseObject obj) throws NSValidation.ValidationException {
- try {
- if (useValidity()) {
- invokeValidityMethodWithType(VALIDITY_DELETE, obj);
- }
- super.validateObjectForDelete(obj);
- validateObjectWithUserInfo(obj, null, "validateForDelete", "validateForDelete");
- } catch (ERXValidationException eov) {
- throw eov;
- } catch (NSValidation.ValidationException eov) {
- if (log.isDebugEnabled())
- log.debug("Caught validation exception: " + eov);
- ERXValidationException erv = ERXValidationFactory.defaultFactory().convertException(eov, obj);
- throw (erv != null ? erv : eov);
- }
- }
- /**
- * Overridden to perform a check if the entity is still in a model group.
- * This can happen if you remove the entity, clone it to change things and re-add it afterwards.
- */
- @Override
- public EOEntity entity() {
- checkEntity();
- return super.entity();
- }
- protected void checkEntity() {
- if(_entity.model() == null) {
- try {
- EOEntity registeredEntity = ERXEOAccessUtilities.entityNamed(null,_entity.name());
- if(registeredEntity != null) {
- _entity = registeredEntity;
- } else {
- EOModel model = _entity.model();
- if(model == null) {
- model = ERXEOAccessUtilities.modelGroup(null).models().lastObject();
- }
- model.addEntity(_entity);
- log.warn("Added <" + _entity.name() + "> to default model group.");
- }
- } catch (Exception ex) {
- throw new RuntimeException("Model or modelgroup for <" + _entity.name() + "> is null: " + entity().model(), ex);
- }
- }
- }
- @Override
- public EOEnterpriseObject createInstanceWithEditingContext(EOEditingContext ec, EOGlobalID gid) {
- checkEntity();
- return super.createInstanceWithEditingContext(ec, gid);
- }
- /**
- * This method is called when an object is
- * about to be updated. If any validation
- * exceptions occur they are converted to an
- * {@link ERXValidationException} and that is
- * thrown.
- * @param obj enterprise object to be deleted
- * @throws NSValidation.ValidationException validation exception
- */
- public void validateObjectForUpdate(EOEnterpriseObject obj) throws NSValidation.ValidationException {
- try {
- if (useValidity()) {
- invokeValidityMethodWithType(VALIDITY_UPDATE, obj);
- }
- validateObjectWithUserInfo(obj, null, "validateForUpdate", "validateForUpdate");
- } catch (ERXValidationException eov) {
- throw eov;
- } catch (NSValidation.ValidationException eov) {
- if (log.isDebugEnabled())
- log.debug("Caught validation exception: " + eov);
- ERXValidationException erv = ERXValidationFactory.defaultFactory().convertException(eov, obj);
- throw (erv != null ? erv : eov);
- }
- }
- /**
- * This method is called when an object is
- * about to be inserted. If any validation
- * exceptions occur they are converted to an
- * {@link ERXValidationException} and that is
- * thrown.
- * @param obj enterprise object to be deleted
- * @throws NSValidation.ValidationException validation exception
- */
- public void validateObjectForInsert(EOEnterpriseObject obj) throws NSValidation.ValidationException {
- try {
- if (useValidity()) {
- invokeValidityMethodWithType(VALIDITY_INSERT, obj);
- }
- validateObjectWithUserInfo(obj, null, "validateForInsert", "validateForInsert");
- } catch (ERXValidationException eov) {
- throw eov;
- } catch (NSValidation.ValidationException eov) {
- if (log.isDebugEnabled())
- log.debug("Caught validation exception: " + eov);
- ERXValidationException erv = ERXValidationFactory.defaultFactory().convertException(eov, obj);
- throw (erv != null ? erv : eov);
- }
- }
- /**
- * This method is called to validate a value
- * for a particular key. Typical validation
- * exceptions that might occur are non-null
- * constraints or string is greater in length
- * than is allowed. If a validation
- * exception does occur they are converted to an
- * {@link ERXValidationException} and that is
- * thrown.
- * @param obj value to be validated
- * @param s property key to validate the value
- * against.
- * @throws NSValidation.ValidationException validation exception
- */
- @Override
- public Object validateValueForKey(Object obj, String s) throws NSValidation.ValidationException {
- Object validated = null;
- if (log.isDebugEnabled())
- log.debug("Validate value: " + obj + " for key: " + s);
- try {
- if(obj instanceof ERXConstant) {
- validated = obj;
- } else {
- validated = super.validateValueForKey(obj, s);
- }
- } catch (ERXValidationException eov) {
- throw eov;
- } catch (NSValidation.ValidationException eov) {
- if (log.isDebugEnabled())
- log.debug("Caught validation exception: " + eov);
- ERXValidationException erv = ERXValidationFactory.defaultFactory().convertException(eov, obj);
- throw (erv != null ? erv : eov);
- }
- return validated;
- }
- /**
- * This method is called when an object is
- * about to be saved. Adds support for extra validation keys to
- * be set in an array in the entity's userInfo under the keypath
- * <code>ERXValidation.additionalValidationKeys</code>. If any validation
- * exceptions occur they are converted to an
- * {@link ERXValidationException} and that is
- * thrown.
- * @param obj enterprise object to be saved
- * @throws NSValidation.ValidationException validation exception
- */
- @Override
- public void validateObjectForSave(EOEnterpriseObject obj) throws NSValidation.ValidationException {
- try {
- if (useValidity()) {
- invokeValidityMethodWithType(VALIDITY_SAVE, obj);
- }
- if(_validationInfo != null) {
- NSArray additionalValidationKeys = (NSArray)_validationInfo.objectForKey("additionalValidationKeys");
- if(additionalValidationKeys != null) {
- for(Enumeration e = additionalValidationKeys.objectEnumerator(); e.hasMoreElements();) {
- String key = (String)e.nextElement();
- NSSelector selector = new NSSelector(key);
- if(selector.implementedByObject(obj)) {
- try {
- selector.invoke(obj);
- } catch (Exception ex) {
- if(ex instanceof NSValidation.ValidationException)
- throw (NSValidation.ValidationException)ex;
- log.error(ex);
- }
- } else {
- validateObjectWithUserInfo(obj, null, "validateForKey." + key, key);
- }
- }
- }
- }
- validateObjectWithUserInfo(obj, null, "validateForSave", "validateForSave");
- } catch (ERXValidationException eov) {
- throw eov;
- } catch (NSValidation.ValidationException eov) {
- if (log.isDebugEnabled())
- log.debug("Caught validation exception: " + eov);
- ERXValidationException erv = ERXValidationFactory.defaultFactory().convertException(eov, obj);
- throw (erv != null ? erv : eov);
- }
- }
- public static class ValidationObjectValue {
- protected EOEnterpriseObject object;
- protected Object value;
- public ValidationObjectValue(EOEnterpriseObject object, Object value) {
- this.object = object;
- this.value = value;
- }
- public Object value() { return value;}
- public EOEnterpriseObject object() { return object;}
- }
- public static class QualiferValidation implements EOQualifierEvaluation {
- protected EOQualifier qualifier;
- public QualiferValidation(Object info) {
- NSDictionary dict =(NSDictionary)info;
- qualifier = EOQualifier.qualifierWithQualifierFormat((String)dict.objectForKey("qualifier"), null);
- }
- public boolean evaluateWithObject(Object o) {
- return qualifier.evaluateWithObject(o);
- }
- }
- protected boolean validateObjectValueDictWithInfo(ValidationObjectValue values, NSDictionary info, String cacheKey) {
- EOQualifierEvaluation q = (EOQualifierEvaluation)_validationQualiferCache.objectForKey(cacheKey);
- if(q == null) {
- try {
- String className = (String)info.objectForKey("className");
- if(className == null) {
- className = QualiferValidation.class.getName();
- }
- Class cl = ERXPatcher.classForName(className);
- Constructor co = cl.getConstructor(new Class [] {Object.class});
- Object o = co.newInstance(new Object[] {info});
- q = (EOQualifierEvaluation)o;
- } catch(Exception ex) {
- throw new NSForwardException(ex);
- }
- _validationQualiferCache.setObjectForKey(q, cacheKey);
- }
- if(values.value() == null && "true".equals(info.objectForKey("ignoreIfNull")))
- return true;
- if(q != null)
- return q.evaluateWithObject(values);
- return true;
- }
- public void validateObjectWithUserInfo(EOEnterpriseObject object, Object value, String validationTypeString, String property) {
- if(_validationInfo != null) {
- NSArray qualifiers = (NSArray)_validationInfo.valueForKeyPath(validationTypeString);
- if(qualifiers != null) {
- ValidationObjectValue values = new ValidationObjectValue(object, value);
- int i = 0;
- for(Enumeration e = qualifiers.objectEnumerator(); e.hasMoreElements();) {
- NSDictionary info = (NSDictionary)e.nextElement();
- if(validationLog.isDebugEnabled())
- validationLog.debug("Validate " + validationTypeString +"."+ property +" with <"+ value + "> on " + object + "\nRule: " + info);
- if(!validateObjectValueDictWithInfo(values, info, validationTypeString+property+i)) {
- String message = (String)info.objectForKey("message");
- String keyPaths = (String)info.objectForKey("keyPaths");
- property = keyPaths == null ? property : keyPaths;
- if(validationLog.isDebugEnabled())
- validationLog.info("Validation failed " + validationTypeString +"."+ property +" with <"+ value + "> on " + object);
- throw ERXValidationFactory.defaultFactory().createException(object, property, value,message);
- }
- i = i+1;
- }
- }
- }
- }
- /**
- * Calculates a display name for a key using
- * localization of entityname.key if found
- * otherwise an improved method.
- * @param key to be converted
- * @return pretty display name
- */
- @Override
- public String displayNameForKey(String key) {
- if (ERXLocalizer.isLocalizationEnabled()) {
- return ERXLocalizer.currentLocalizer().localizedDisplayNameForKey(this, key);
- }
- return ERXStringUtilities.displayNameForKey(key);
- }
- /**** default handling */
- // Default handling from here on
- protected String defaultKey = "default";
- public static interface Default {
- public static final int AdaptorNumberType = 0;
- public static final int AdaptorCharactersType = 1;
- public static final int AdaptorBytesType = 2;
- public static final int AdaptorDateType = 3;
- public void setValueInObject(EOEnterpriseObject eo);
- }
- public static class AttributeDefault implements Default {
- String key;
- String stringValue;
- int adaptorType;
- EOAttribute attribute;
- public AttributeDefault(EOAttribute attribute, String stringValue) {
- this(attribute.name(), stringValue, attribute.adaptorValueType());
- this.attribute = attribute;
- }
- public AttributeDefault(String key, String stringValue, int adaptorType) {
- this.key = key;
- this.stringValue = stringValue;
- this.adaptorType = adaptorType;
- }
- public AttributeDefault(String key, String stringValue) {
- this(key, stringValue, AdaptorCharactersType);
- }
- public void setValueInObject(EOEnterpriseObject eo) {
- Object defaultValue = stringValue;
- if(stringValue.startsWith("@threadStorage.")) {
- String keyPath = stringValue.substring("@threadStorage.".length());
- defaultValue = ERXThreadStorage.valueForKeyPath(keyPath);
- } else {
- if(attribute != null && attribute.valueFactoryMethodName() != null && attribute.factoryMethodArgumentType() == EOAttribute.FactoryMethodArgumentIsString) {
- defaultValue = attribute.newValueForString(stringValue);
- }
- }
- if(defaultValue != null) {
- String s = defaultValue.toString();
- s = s.substring(s.indexOf("@")+1);
- if(adaptorType == AdaptorDateType) {
- defaultValue = ERXTimestampUtilities.timestampForString(s);
- } else if (adaptorType == AdaptorNumberType) {
- NSTimestamp temp = ERXTimestampUtilities.timestampForString(s);
- if(temp != null) {
- defaultValue = ERXTimestampUtilities.unixTimestamp(temp);
- } else {
- //the value will be coerced by the eo...
- defaultValue = ERXValueUtilities.bigDecimalValue(s);
- }
- }
- }
- eo.takeValueForKey(defaultValue, key);
- }
- }
- public static class RelationshipDefault implements Default {
- String key;
- String stringValue;
- int adaptorType;
- String relationshipEntityName;
- public RelationshipDefault(String key, String stringValue, int adaptorType, String relationshipEntityName) {
- this.key = key;
- this.stringValue = stringValue;
- this.adaptorType = adaptorType;
- this.relationshipEntityName = relationshipEntityName;
- }
- public void setValueInObject(EOEnterpriseObject eo) {
- Object defaultValue = stringValue;
- EOEditingContext ec = eo.editingContext();
- if(stringValue.charAt(0) == '@') { // computed key
- if(stringValue.equals("@new")) {
- EOClassDescription cd = EOClassDescription.classDescriptionForEntityName(relationshipEntityName);
- EOEnterpriseObject newObject = cd.createInstanceWithEditingContext(eo.editingContext(), null);
- ec.insertObject(newObject);
- eo.addObjectToBothSidesOfRelationshipWithKey(newObject,key);
- } else if(stringValue.startsWith("@threadStorage.")) {
- String keyPath = stringValue.substring("@threadStorage.".length());
- Object o = ERXThreadStorage.valueForKey(keyPath);
- if(keyPath.indexOf(".") > 0) {
- keyPath = stringValue.substrin