/*
* 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.validation;
import java.util.Objects;
import com.webobjects.appserver.WOMessage;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSValidation;
import com.webobjects.foundation.NSValidation.ValidationException;
import er.extensions.localization.ERXLocalizer;
/**
* ERXValidationExceptions extends the regular
* {@link com.webobjects.foundation.NSValidation.ValidationException NSValidation.ValidationException}
* to add template based resolution of the validation exception. See more
* information about resolving templates in the {@link ERXValidationFactory ERXValidationFactory}.
*/
public class ERXValidationException extends NSValidation.ValidationException implements NSKeyValueCoding {
/**
* 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;
// Validation Exception Types
/** corresponds to a model thrown 'null property' exception */
public static final String NullPropertyException = "NullPropertyException";
/** corresponds to a number formatter exception */
public static final String InvalidNumberException = "InvalidNumberException";
/** corresponds to a generic 'invalid value' exception */
public static final String InvalidValueException = "InvalidValueException";
/** corresponds to a model thrown 'mandatory toOne relationship' exception */
public static final String MandatoryToOneRelationshipException = "MandatoryToOneRelationshipException";
/** corresponds to a model thrown 'mandatory toMany relationship' exception */
public static final String MandatoryToManyRelationshipException = "MandatoryToManyRelationshipException";
/** corresponds to a model thrown 'object removal' exception */
public static final String ObjectRemovalException = "ObjectRemovalException";
/** corresponds to a model thrown 'objects removal' exception */
public static final String ObjectsRemovalException = "ObjectsRemovalException";
/** corresponds to a model thrown 'maximum length of attribute exceeded' exception */
public static final String ExceedsMaximumLengthException = "ExceedsMaximumLengthException";
/** corresponds to a model thrown 'Error converting value of class' exception */
public static final String ValueConversionException = "ValueConversionException";
/** corresponds to a custom method exception */
public static final String CustomMethodException = "CustomMethodException";
/**
* Default constructor that builds a validation exception
* without the failed value specified. If you want to have
* validation templates that refer to the value that didn't
* pass validation use the four argument constructor. Usually
* for creating custom validation exceptions the
* {@link ERXValidationFactory} should be used.
*
* @param type of the exception, should be one of the constraints
* defined in this class.
* @param object that is throwing the exception
* @param key property key that failed validation
*/
public ERXValidationException(String type, Object object, String key) {
this(type, object, key, null);
}
/**
* Default constructor that builds a validation exception
* based on the type, object, key and failed value.Usually
* for creating custom validation exceptions the
* {@link ERXValidationFactory} should be used.
*
* @param type of the exception, should be one of the constraints
* defined in this class.
* @param object that is throwing the exception
* @param key property key that failed validation
* @param value that failed validation
*/
public ERXValidationException(String type, Object object, String key, Object value) {
super(type, object, key);
setType(type);
setValue(value);
}
/** caches the validation message */
protected String message;
/** holds the method if one is provided */
protected String method;
/** holds the type of the exception */
protected String type;
/** holds the value that failed validation */
protected Object value;
/** holds the object that failed validation */
protected Object object;
/** holds the target language if provided */
protected String targetLanguage;
/** caches any set additionalExceptions */
protected NSArray<ValidationException> additionalExceptions;
/** holds a reference to the context of the exception */
protected volatile NSKeyValueCoding _context;
/** holds a reference to the exception delegate */
protected volatile Object delegate;
/**
* Gets the message for this exception.
* @return the correctly formatted validation exception.
*/
@Override
public String getMessage() {
if (message == null)
message = ERXValidationFactory.defaultFactory().messageForException(this);
return message;
}
protected String _getMessage() {
if(message == null) {
return type;
}
return message;
}
/**
* Implementation of key value coding.
* Uses the default implementation.
* @param key to look up
* @return result of the lookup on the object
*/
public Object valueForKey(String key) {
try {
return NSKeyValueCoding.DefaultImplementation.valueForKey(this, key);
} catch(NSKeyValueCoding.UnknownKeyException ex) {
// AK: when we try to fix the bug in ERDirectToWeb templates that specify "context." explicitly
// by setting up ourselves as the context, we could still run into keys we can't resolve
// (like "indefiniteArticle") and we just return null for that. Of course we should fix the templates instead
// and then ask the context *before* we ask ourself, so the displayPropertyKey of the context gets
// precedence over our version
if(context() != null && context() != this) {
return context().valueForKey(key);
}
throw ex;
}
}
/**
* Implementation of the key value coding.
* Uses the default implementation.
* @param obj value to be set on this exception
* @param key to be set
*/
public void takeValueForKey(Object obj, String key) {
NSKeyValueCoding.DefaultImplementation.takeValueForKey(this, obj, key);
}
/**
* Convenience method to determine if this exception
* was a custom thrown exception instead of a model
* thrown exception. A custom exception would be
* an exception that you throw in your validateFoo
* method if a particular constraint is not valid.
* @return if this exception is a custom thrown exception.
*/
public boolean isCustomMethodException() { return type() == CustomMethodException; }
/**
* Returns method name. The method name is only set if the validation
* exception is a custom validation exception.
* @return custom method name.
*/
public String method() { return method; }
/**
* Sets the custom method name that threw the
* validation exception.
* @param aMethod name to be set.
*/
public void setMethod(String aMethod) { method = aMethod; }
/**
* Cover method that casts the <code>object</code> of
* the validation exception to an EOEnterpriseObject.
* @return object cast as an enterprise object.
*/
public EOEnterpriseObject eoObject() { return object() instanceof EOEnterpriseObject ? (EOEnterpriseObject)object() : null; }
/**
* Overrides super implementation to allow for settable object value.
* @return object for this exception.
*/
@Override
public Object object() {
if(object == null)
object = super.object();
return object;
}
/**
* Cover method for returning the <code>key</code> of
* the validation exception under the name propertyKey.
* @return the key of the validation exception
*/
public String propertyKey() { return key(); }
/**
* Cover method for getting the attribute corresponding
* to the <b>propertyKey</b> and <b>entity</b> off of
* the object.
* @return EOAttribute corresponding to the propertyKey
* and entity.
*/
public EOAttribute attribute() {
EOAttribute attribute = null;
if (eoObject() != null) {
EOEntity entity = EOUtilities.entityForObject(eoObject().editingContext(), eoObject());
attribute = entity != null ? entity.attributeNamed(propertyKey()) : null;
}
return attribute;
}
/**
* Cover method to return the type of the validation
* exception. The corresponds to one of the constant
* strings defined in this class.
* @return the type of this validation exception.
*/
public String type() { return type; }
/**
* Sets the validation type of this exception.
* Should correspond to one of the type constants
* defined in this class. All of the model thrown
* validation exceptions should have the correct
* type already set.
* @param aType name to set on this validation
* exception.
*/
public void setType(String aType) { type = aType; }
/**
* Returns the value that failed validation.
* @return failed validation value.
*/
public Object value() { return value; }
/**
* Provides an escaped value to use in validation template string.
* @return escaped value
* @see #value()
* @see WOMessage#stringByEscapingHTMLString(String)
*/
public String escapedValue() {
if(value() != null) {
return WOMessage.stringByEscapingHTMLString(value().toString());
}
return null;
}
/**
* Sets the value that failed validation.
* @param aValue that failed validation
*/
public void setValue(Object aValue) { value = aValue; }
/**
* Sets the object that failed validation.
* @param aValue object that failed validation
*/
public void setObject(Object aValue) { object = aValue; }
/**
* Returns the target language to display the validation message in.
* If a target language is not set then the current default
* language for the current thread is used.
* @return target language if one is set.
*/
public String targetLanguage() { return targetLanguage; }
/**
* Sets the target language to use when rendering the validation
* message. Only set a target language if you want to override
* the current language of the thread.
* @param aValue name of the language to render the validation
* exception in.
*/
public void setTargetLanguage(String aValue) { targetLanguage = aValue; }
/**
* Gets the current delegate for this validation exception.
* If one is not set then the default delegate for the
* {@link ERXValidationFactory ERXValidationFactory} is returned.
* @return delegate for this validation exception.
*/
public Object delegate() { return delegate != null ? delegate : ERXValidationFactory.defaultDelegate(); }
/**
* Sets the delegate for the current validation exception.
* The delegate can intervene to provide a different template
* for the validation exception or resolve the template in a
* different manner.
* @param obj delegate to be used for this validation exception.
*/
public void setDelegate(Object obj) { delegate = obj; }
/**
* The current context of the validation exception. Context
* objects are mainly used for resolving keys in validation
* templates. When validation exceptions are thrown in D2W
* pages the current {@link com.webobjects.directtoweb.D2WContext D2WContext}
* is set as the current context on the exceptions. If a context
* is not set then the <code>contextForException</code> is called
* off of the default {@link ERXValidationFactory ERXValidationFactory}.
* When this also returns null, then the exception will be used as its context.
* This is needed because of some of the templates in ERDirectToWeb which use
* <code>context.propertyKey</code> and will display <b>?</b> if none is given.
* <p>
* @return current context for the validation exception.
*/
// CHECKME: Now with WO 5 this doesn't need to implement the NSKeyValueCoding interface
public NSKeyValueCoding context() {
if (_context == null)
_context = ERXValidationFactory.defaultFactory().contextForException(this);
if (_context == null)
return this;
return _context;
}
/**
* Sets the context that can be used to resolve key bindings
* in validation templates.
* @param context of the current exception
*/
public void setContext(NSKeyValueCoding context) { _context = context; }
/**
* Sets the array of additional exceptions.
* @param exceptions array of additional exceptions
*/
public void setAdditionalExceptions(NSArray<ValidationException> exceptions) {
additionalExceptions = exceptions;
}
/**
* Cover method to return any additional exceptions that
* occurred. The reason this method is needed is because
* we wanted to have the ability to set the additional
* exceptions.
* @return array of additional exceptions
*/
@Override
public NSArray<ValidationException> additionalExceptions() {
if (additionalExceptions == null) {
additionalExceptions = super.additionalExceptions();
if (additionalExceptions == null)
return NSArray.EmptyArray;
}
return additionalExceptions;
}
/**
* Generates a displayable and localized version of the
* current propertyKey (also called key).
* @return localized displayable version of the current
* propertyKey.
*/
public String displayNameForProperty() {
return propertyKey() != null ? localizedDisplayNameForKey(propertyKey()) : null;
}
/**
* Generates a displayable and localized version of the
* current object's entity name.
* @return localized displayable version of an object's
* entity name.
*/
public String displayNameForEntity() {
return eoObject() != null ? localizedDisplayNameForKey(eoObject().entityName()) : null;
}
/**
* Creates a localized display name for a given key
* trying to localize it with the current "targetLanguage" or the
* current localizer. If there is an eoObject first try to
* localize "entityName.key" then "key" if nothing found.
* @param key to be translated
* @return localized display version of the given key.
*/
protected String localizedDisplayNameForKey(String key) {
ERXLocalizer localizer = null;
if (targetLanguage() != null) {
localizer = ERXLocalizer.localizerForLanguage(targetLanguage());
} else {
localizer = ERXLocalizer.currentLocalizer();
}
return ERXValidation.localizedDisplayNameForKey(eoObject() != null ? eoObject().classDescription() : null, key, localizer);
}
@Override
public int hashCode() {
return (type() == null ? 1 : type().hashCode()) * (key() == null ? 1 : key().hashCode()) * (object() == null ? 1 : object().hashCode()) * (value() == null ? 1 : value().hashCode()) * (additionalExceptions() == null ? 1 : additionalExceptions().hashCode());
}
/**
* Compares this exception to anything else.
* @return description of the validation exception
*/
@Override
public boolean equals(Object anotherObject) {
if(anotherObject != null && anotherObject instanceof ERXValidationException) {
ERXValidationException ex = (ERXValidationException)anotherObject;
return Objects.equals(type(), ex.type()) && Objects.equals(key(), ex.key()) && Objects.equals(object(), ex.object())
&& Objects.equals(value(), ex.value()) && Objects.equals(additionalExceptions(), ex.additionalExceptions());
}
return super.equals(anotherObject);
}
/**
* Returns the formatted description of the validation exception
* without calling <code>getMessage</code>.
* @return description of the validation exception
*/
@Override
public String toString() {
try {
return "<" + getClass().getName() + " object: " + object() + "; propertyKey: " + propertyKey() + "; type: " + type() + "; additionalExceptions: " + additionalExceptions() + ">";
}
catch (Throwable t) {
return "<" + getClass().getName() + " object of type " + ((object() == null) ? "null" : object().getClass().getSimpleName()) + "; propertyKey: " + propertyKey() + "; type: " + type() + "; additionalExceptions: " + additionalExceptions() + ">";
}
}
}