001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.rules;
051:
052: import org.apache.log4j.Logger;
053: import java.util.*;
054: import org.jaffa.persistence.UOW;
055: import org.jaffa.datatypes.ValidationException;
056: import org.jaffa.exceptions.FrameworkException;
057: import org.jaffa.rules.fieldvalidators.IFieldValidator;
058: import org.jaffa.rules.metadata.*;
059: import org.jaffa.security.VariationContext;
060: import org.jaffa.util.BeanHelper;
061: import java.io.FileNotFoundException;
062: import org.jaffa.util.MessageHelper;
063: import java.net.MalformedURLException;
064: import org.jaffa.persistence.IPersistent;
065: import org.jaffa.persistence.util.PersistentHelper;
066:
067: /** This class provides methods to validate a field or a domain object. It will utilise the XML configuration files to determine the
068: * rules to invoke for the validations. If the variation has been set for the Thread [for eg. the Portlet Servlet or
069: * WebServicesWrapper calling the VariationContext.setVariation()], then that variation will be used to determine the rules.
070: */
071: public class RulesEngine {
072:
073: private static final Logger log = Logger
074: .getLogger(RulesEngine.class);
075: private static final String LABEL_TOKEN_PREFIX = "label.";
076:
077: /** This will only invoke the 'Mandatory' Rules defined for the field.
078: * @param domainClassName The domain class to which the field belongs.
079: * @param fieldName The field to be validated.
080: * @param fieldValue The value to be validated.
081: * @param uow The UOW.
082: * @throws ValidationException if any validation rule fails.
083: * @throws FrameworkException if any framework error occurs.
084: */
085: public static void doMandatoryValidationsForDomainField(
086: String domainClassName, String fieldName,
087: Object fieldValue, UOW uow) throws ValidationException,
088: FrameworkException {
089: doValidationsForDomainField(domainClassName, fieldName,
090: fieldValue, uow, true);
091: }
092:
093: /** This will invoke all the Rules defined for the field.
094: * @param domainClassName The domain class to which the field belongs.
095: * @param fieldName The field to be validated.
096: * @param fieldValue The value to be validated.
097: * @param uow The UOW.
098: * @throws ValidationException if any validation rule fails.
099: * @throws FrameworkException if any framework error occurs.
100: */
101: public static void doAllValidationsForDomainField(
102: String domainClassName, String fieldName,
103: Object fieldValue, UOW uow) throws ValidationException,
104: FrameworkException {
105: doValidationsForDomainField(domainClassName, fieldName,
106: fieldValue, uow, false);
107: }
108:
109: /** This will only invoke the 'Mandatory' Rules defined for the fields of the Domain object.
110: * @param domainObject The domain object to be validated.
111: * @param uow The UOW.
112: * @throws ValidationException if any validation rule fails.
113: * @throws FrameworkException if any framework error occurs.
114: */
115: public static void doMandatoryValidationsForDomainObject(
116: Object domainObject, UOW uow) throws ValidationException,
117: FrameworkException {
118: doValidationsForDomainObject(domainObject, uow, true);
119: }
120:
121: /** This will invoke all the Rules defined for the fields of the Domain object.
122: * @param domainObject The domain object to be validated.
123: * @param uow The UOW.
124: * @throws ValidationException if any validation rule fails.
125: * @throws FrameworkException if any framework error occurs.
126: */
127: public static void doAllValidationsForDomainObject(
128: Object domainObject, UOW uow) throws ValidationException,
129: FrameworkException {
130: doValidationsForDomainObject(domainObject, uow, false);
131: }
132:
133: /** Returns the ClassMetaData object for the given className + variation. A null will be returned, in case the rules-file is not found */
134: private static ClassMetaData getClassMetaData(String className,
135: String variation) throws FrameworkException {
136: ClassMetaData classMetaData = null;
137: try {
138: classMetaData = RulesMetaDataService.getClassMetaData(
139: className, variation);
140: } catch (FileNotFoundException e) {
141: // do nothing
142: } catch (MalformedURLException e) {
143: // do nothing
144: } catch (Exception e) {
145: String str = "Error thrown while trying to read the rules for the class '"
146: + className + "', variation '" + variation + '\'';
147: log.error(str, e);
148: throw new RulesEngineException(
149: RulesEngineException.LOADING_RULES_FAILED,
150: new Object[] { className, variation }, e);
151: }
152: return classMetaData;
153: }
154:
155: /** Uses introspection to determine the value of a field from a bean */
156: private static Object getFieldValue(Object bean, String field)
157: throws FrameworkException {
158: Object fieldValue = null;
159: try {
160: fieldValue = BeanHelper.getField(bean, field);
161: } catch (Exception e) {
162: String str = "Error thrown while trying to read the value for the field '"
163: + field + "', from the object " + bean;
164: log.error(str, e);
165: throw new RulesEngineException(
166: RulesEngineException.FIELD_READ_FAILED,
167: new Object[] { field, bean.getClass().getName() },
168: e);
169: }
170: return fieldValue;
171: }
172:
173: private static String getLabelToken(String domainClassName,
174: String fieldName) throws FrameworkException {
175: String labelToken = null;
176: try {
177: labelToken = PersistentHelper.getLabelToken(
178: domainClassName, fieldName);
179: } catch (Exception e) {
180: // don't do anything.. just return the domainClassName + fieldName
181: }
182: if (labelToken == null)
183: labelToken = MessageHelper.tokenize(LABEL_TOKEN_PREFIX
184: + domainClassName + '.' + fieldName);
185: return labelToken;
186: }
187:
188: /** Invokes the doFieldValidaions for each field of the ClassMetaData object for the domainObject. */
189: private static void doValidationsForDomainObject(
190: Object domainObject, UOW uow, boolean doOnlyMandatory)
191: throws ValidationException, FrameworkException {
192: String variation = VariationContext.getVariation();
193:
194: boolean localUow = false;
195: try {
196: // create a UOW, if not passed in
197: if (uow == null) {
198: uow = new UOW();
199: localUow = true;
200: }
201:
202: // Determine the domain class. A persistent object could very well be a proxy. Hence invoke the appropriate method
203: String domainClassName = domainObject instanceof IPersistent ? uow
204: .getActualPersistentClass(domainObject).getName()
205: : domainObject.getClass().getName();
206:
207: // determine the ClassMetaData
208: ClassMetaData classMetaData = getClassMetaData(
209: domainClassName, variation);
210:
211: // determine the core ClassMetaData, if required
212: ClassMetaData coreClassMetaData = VariationContext.DEFAULT_VARIATION
213: .equals(variation) ? null
214: : getClassMetaData(domainClassName,
215: VariationContext.DEFAULT_VARIATION);
216:
217: // perform validations for all the fields
218: if (classMetaData != null) {
219: FieldMetaData[] fields = classMetaData.getFields();
220: for (int i = 0; i < fields.length; i++) {
221: FieldMetaData fieldMetaData = fields[i];
222: FieldMetaData coreFieldMetaData = coreClassMetaData != null ? coreClassMetaData
223: .getField(fieldMetaData.getName())
224: : null;
225: String labelToken = getLabelToken(domainClassName,
226: fieldMetaData.getName());
227: Object fieldValue = getFieldValue(domainObject,
228: fieldMetaData.getName());
229: doFieldValidaions(fieldValue, uow, doOnlyMandatory,
230: labelToken, fieldMetaData,
231: coreFieldMetaData);
232: }
233: }
234:
235: // perform validations for the fields in the core file, which are not defined in the variant file
236: if (coreClassMetaData != null) {
237: FieldMetaData[] fields = coreClassMetaData.getFields();
238: for (int i = 0; i < fields.length; i++) {
239: FieldMetaData fieldMetaData = fields[i];
240: if (classMetaData == null
241: || classMetaData.getField(fieldMetaData
242: .getName()) == null) {
243: // create a UOW, if not passed in
244: if (uow == null) {
245: uow = new UOW();
246: localUow = true;
247: }
248: String labelToken = getLabelToken(
249: domainClassName, fieldMetaData
250: .getName());
251: Object fieldValue = getFieldValue(domainObject,
252: fieldMetaData.getName());
253: doFieldValidaions(fieldValue, uow,
254: doOnlyMandatory, labelToken,
255: fieldMetaData, null);
256: }
257: }
258: }
259:
260: } finally {
261: if (localUow && uow != null)
262: uow.rollback();
263: }
264: }
265:
266: /** Invokes the doFieldValidaions for the field. */
267: private static void doValidationsForDomainField(
268: String domainClassName, String fieldName,
269: Object fieldValue, UOW uow, boolean doOnlyMandatory)
270: throws ValidationException, FrameworkException {
271: String variation = VariationContext.getVariation();
272:
273: // determine the ClassMetaData
274: ClassMetaData classMetaData = getClassMetaData(domainClassName,
275: variation);
276:
277: // determine the core ClassMetaData, if required
278: ClassMetaData coreClassMetaData = VariationContext.DEFAULT_VARIATION
279: .equals(variation) ? null : getClassMetaData(
280: domainClassName, VariationContext.DEFAULT_VARIATION);
281:
282: boolean localUow = false;
283: try {
284: String labelToken = null;
285:
286: // perform validations for the field
287: if (classMetaData != null) {
288: FieldMetaData fieldMetaData = classMetaData
289: .getField(fieldName);
290: if (fieldMetaData != null) {
291: // create a UOW, if not passed in
292: if (uow == null) {
293: uow = new UOW();
294: localUow = true;
295: }
296: FieldMetaData coreFieldMetaData = coreClassMetaData != null ? coreClassMetaData
297: .getField(fieldName)
298: : null;
299: labelToken = getLabelToken(domainClassName,
300: fieldMetaData.getName());
301: doFieldValidaions(fieldValue, uow, doOnlyMandatory,
302: labelToken, fieldMetaData,
303: coreFieldMetaData);
304: }
305: }
306:
307: // perform validations for the field in the core file, if not defined in the variant file
308: if (coreClassMetaData != null) {
309: if (classMetaData == null
310: || classMetaData.getField(fieldName) == null) {
311: FieldMetaData fieldMetaData = coreClassMetaData
312: .getField(fieldName);
313: if (fieldMetaData != null) {
314: // create a UOW, if not passed in
315: if (uow == null) {
316: uow = new UOW();
317: localUow = true;
318: }
319: if (labelToken == null)
320: labelToken = getLabelToken(domainClassName,
321: fieldMetaData.getName());
322: doFieldValidaions(fieldValue, uow,
323: doOnlyMandatory, labelToken,
324: fieldMetaData, null);
325: }
326: }
327: }
328:
329: } finally {
330: if (localUow && uow != null)
331: uow.rollback();
332: }
333: }
334:
335: // Invokes doValidationsForDomainField() for the parent-field, if any. Finally it invokes the invokeRules() method.
336: private static void doFieldValidaions(Object fieldValue, UOW uow,
337: boolean doOnlyMandatory, String labelToken,
338: FieldMetaData fieldMetaData, FieldMetaData coreFieldMetaData)
339: throws ValidationException, FrameworkException {
340: // Perform the core validations first, if not overridden.
341: if (coreFieldMetaData != null
342: && !fieldMetaData.getOverridesDefault()) {
343: // Perform the validations for the field that core-field extends (recursive)
344: if (coreFieldMetaData.getExtendsClass() != null
345: && coreFieldMetaData.getExtendsField() != null)
346: doValidationsForDomainField(coreFieldMetaData
347: .getExtendsClass(), coreFieldMetaData
348: .getExtendsField(), fieldValue, uow,
349: doOnlyMandatory);
350:
351: // Now perform the validations for the core-field
352: invokeRules(fieldValue, uow, doOnlyMandatory, labelToken,
353: coreFieldMetaData);
354: }
355:
356: // Perform the validations for the field that this extends (recursive).
357: if (fieldMetaData.getExtendsClass() != null
358: && fieldMetaData.getExtendsField() != null) {
359: // Ensure the check against the core to prevent infinite loop
360: if (coreFieldMetaData == null
361: || !(fieldMetaData.getExtendsClass().equals(
362: coreFieldMetaData.getExtendsClass()) && fieldMetaData
363: .getExtendsField()
364: .equals(coreFieldMetaData.getExtendsField())))
365: doValidationsForDomainField(fieldMetaData
366: .getExtendsClass(), fieldMetaData
367: .getExtendsField(), fieldValue, uow,
368: doOnlyMandatory);
369: }
370:
371: // Now perform the validations for the field
372: invokeRules(fieldValue, uow, doOnlyMandatory, labelToken,
373: fieldMetaData);
374: }
375:
376: /** This will invoke all the rules for a field. */
377: private static void invokeRules(Object fieldValue, UOW uow,
378: boolean doOnlyMandatory, String labelToken,
379: FieldMetaData fieldMetaData) throws ValidationException,
380: FrameworkException {
381: RuleMetaData[] rules = fieldMetaData.getRules();
382: for (int i = 0; i < rules.length; i++) {
383: RuleMetaData rule = rules[i];
384: IFieldValidator validator = null;
385: try {
386: FieldValidatorMetaData fieldValidatorMetaData = ValidatorMetaDataService
387: .getFieldValidatorMetaData(rule.getName());
388: if (!doOnlyMandatory
389: || fieldValidatorMetaData.getMandatory()) {
390: // Create an instance of the validator
391: validator = (IFieldValidator) Class.forName(
392: fieldValidatorMetaData.getClassName())
393: .newInstance();
394:
395: // Perform the initialization
396: validator.init();
397:
398: // Set some values
399: validator.setValue(fieldValue);
400: validator.setLabelToken(labelToken);
401: validator.setUow(uow);
402:
403: // Set the Parameters, if specified in the validators.xml file
404: if (fieldValidatorMetaData.getParameters() != null) {
405: for (Iterator itr = fieldValidatorMetaData
406: .getParameters().entrySet().iterator(); itr
407: .hasNext();) {
408: Map.Entry me = (Map.Entry) itr.next();
409: BeanHelper.setField(validator, (String) me
410: .getKey(), (String) me.getValue());
411: }
412: }
413:
414: // Set the Parameters provided in the rules.xml file
415: for (Iterator itr = rule.getParameters().entrySet()
416: .iterator(); itr.hasNext();) {
417: Map.Entry me = (Map.Entry) itr.next();
418: BeanHelper.setField(validator, (String) me
419: .getKey(), (String) me.getValue());
420: }
421:
422: // Now perform validation
423: if (log.isDebugEnabled())
424: log.debug("Invoking the rule " + rule.getName()
425: + " for field "
426: + fieldMetaData.getName());
427: validator.validate();
428: }
429: } catch (ValidationException e) {
430: throw e;
431: } catch (FrameworkException e) {
432: throw e;
433: } catch (Exception e) {
434: String str = "Error thrown while trying to invoke the rules for the field '"
435: + labelToken
436: + "', having the value '"
437: + fieldValue
438: + "', using the fieldMetaData - "
439: + fieldMetaData;
440: log.error(str, e);
441: throw new RulesEngineException(
442: RulesEngineException.RULE_INVOCATION_FAILED,
443: new Object[] { labelToken, fieldValue }, e);
444: } finally {
445: if (validator != null) {
446: validator.cleanup();
447: validator = null;
448: }
449: }
450: }
451: }
452:
453: }
|