View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.datadictionary.validator;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.kuali.rice.krad.datadictionary.DataDictionary;
26  import org.kuali.rice.krad.datadictionary.DataDictionaryEntry;
27  import org.kuali.rice.krad.datadictionary.DataDictionaryException;
28  import org.kuali.rice.krad.datadictionary.DefaultListableBeanFactory;
29  import org.kuali.rice.krad.uif.component.Component;
30  import org.kuali.rice.krad.uif.util.ExpressionUtils;
31  import org.kuali.rice.krad.uif.util.UifBeanFactoryPostProcessor;
32  import org.kuali.rice.krad.uif.view.View;
33  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
34  import org.springframework.core.io.FileSystemResource;
35  import org.springframework.core.io.Resource;
36  import org.springframework.core.io.ResourceLoader;
37  
38  /**
39   * A validator for Rice Dictionaries that stores the information found during its validation.
40   *
41   * @author Kuali Rice Team (rice.collab@kuali.org)
42   */
43  public class Validator {
44      private static final Log LOG = LogFactory.getLog(Validator.class);
45  
46      private static ArrayList<ErrorReport> errorReports = new ArrayList<ErrorReport>();
47  
48      private ValidationTrace tracerTemp;
49      private int numberOfErrors;
50      private int numberOfWarnings;
51  
52      /**
53       * Constructor creating an empty validation report
54       */
55      public Validator() {
56          tracerTemp = new ValidationTrace();
57          numberOfErrors = 0;
58          numberOfWarnings = 0;
59      }
60  
61      public static void addErrorReport(ErrorReport report) {
62          errorReports.add(report);
63      }
64  
65      public static void resetErrorReport() {
66          errorReports = new ArrayList<ErrorReport>();
67      }
68  
69      /**
70       * Runs the validations on a collection of beans
71       *
72       * @param beans - Collection of beans being validated
73       * @param failOnWarning - Whether detecting a warning should cause the validation to fail
74       * @return Returns true if the beans past validation
75       */
76      private boolean runValidations(DefaultListableBeanFactory beans, boolean failOnWarning) {
77          LOG.info("Starting Dictionary Validation");
78          resetErrorReport();
79          Map<String, View> uifBeans;
80  
81          try {
82              uifBeans = beans.getBeansOfType(View.class);
83              for (View views : uifBeans.values()) {
84                  try {
85                      ValidationTrace tracer = tracerTemp.getCopy();
86                      if (doValidationOnUIFBean(views)) {
87                          tracer.setValidationStage(ValidationTrace.START_UP);
88                          runValidationsOnComponents(views, tracer);
89                      }
90                  } catch (Exception e) {
91                      String value[] = {views.getId(), "Exception = " + e.getMessage()};
92                      tracerTemp.createError("Error Validating Bean View", value);
93                  }
94              }
95          } catch (Exception e) {
96              String value[] = {"Validation set = views", "Exception = " + e.getMessage()};
97              tracerTemp.createError("Error in Loading Spring Beans", value);
98          }
99  
100         Map<String, DataDictionaryEntry> ddBeans;
101 
102         try {
103             ddBeans = beans.getBeansOfType(DataDictionaryEntry.class);
104             for (DataDictionaryEntry entry : ddBeans.values()) {
105                 try {
106 
107                     ValidationTrace tracer = tracerTemp.getCopy();
108                     tracer.setValidationStage(ValidationTrace.BUILD);
109                     entry.completeValidation(tracer);
110 
111                 } catch (Exception e) {
112                     String value[] = {"Validation set = Data Dictionary Entries", "Exception = " + e.getMessage()};
113                     tracerTemp.createError("Error in Loading Spring Beans", value);
114                 }
115             }
116         } catch (Exception e) {
117             String value[] = {"Validation set = Data Dictionary Entries", "Exception = " + e.getMessage()};
118             tracerTemp.createError("Error in Loading Spring Beans", value);
119         }
120 
121         compileFinalReport();
122 
123         LOG.info("Completed Dictionary Validation");
124 
125         if (numberOfErrors > 0) {
126             return false;
127         }
128         if (failOnWarning) {
129             if (numberOfWarnings > 0) {
130                 return false;
131             }
132         }
133 
134         return true;
135     }
136 
137     /**
138      * Validates a UIF Component
139      *
140      * @param object - The UIF Component to be validated
141      * @param failOnWarning - Whether the validation should fail if warnings are found
142      * @return Returns true if the validation passes
143      */
144     public boolean validate(Component object, boolean failOnWarning) {
145         LOG.info("Starting Dictionary Validation");
146 
147         if (doValidationOnUIFBean(object)) {
148             ValidationTrace tracer = tracerTemp.getCopy();
149             resetErrorReport();
150 
151             tracer.setValidationStage(ValidationTrace.BUILD);
152 
153             LOG.debug("Validating Component: " + object.getId());
154             object.completeValidation(tracer.getCopy());
155 
156             runValidationsOnLifecycle(object, tracer.getCopy());
157 
158             runValidationsOnPrototype(object, tracer.getCopy());
159         }
160 
161         compileFinalReport();
162 
163         LOG.info("Completed Dictionary Validation");
164 
165         if (numberOfErrors > 0) {
166             return false;
167         }
168         if (failOnWarning) {
169             if (numberOfWarnings > 0) {
170                 return false;
171             }
172         }
173 
174         return true;
175     }
176 
177     /**
178      * Validates the beans in a collection of xml files
179      *
180      * @param failOnWarning - Whether detecting a warning should cause the validation to fail
181      * @return Returns true if the beans past validation
182      */
183     public boolean validate(String[] xmlFiles, boolean failOnWarning) {
184         DefaultListableBeanFactory beans = loadBeans(xmlFiles);
185 
186         return runValidations(beans, failOnWarning);
187     }
188 
189     /**
190      * Validates a collection of beans
191      *
192      * @param xmlFiles - The collection of xml files used to load the provided beans
193      * @param loader - The source that was used to load the beans
194      * @param beans - Collection of preloaded beans
195      * @param failOnWarning - Whether detecting a warning should cause the validation to fail
196      * @return Returns true if the beans past validation
197      */
198     public boolean validate(String xmlFiles[], ResourceLoader loader, DefaultListableBeanFactory beans,
199             boolean failOnWarning) {
200         tracerTemp = new ValidationTrace(xmlFiles, loader);
201         return runValidations(beans, failOnWarning);
202     }
203 
204     /**
205      * Runs the validations on a component
206      *
207      * @param component - The component being checked
208      * @param tracer - The current bean trace for the validation line
209      */
210     private void runValidationsOnComponents(Component component, ValidationTrace tracer) {
211 
212         try {
213             ExpressionUtils.populatePropertyExpressionsFromGraph(component, false);
214         } catch (Exception e) {
215             String value[] = {"view = " + component.getId()};
216             tracerTemp.createError("Error Validating Bean View while loading expressions", value);
217         }
218 
219         LOG.debug("Validating View: " + component.getId());
220 
221         try {
222             component.completeValidation(tracer.getCopy());
223         } catch (Exception e) {
224             String value[] = {component.getId()};
225             tracerTemp.createError("Error Validating Bean View", value);
226         }
227 
228         try {
229             runValidationsOnLifecycle(component, tracer.getCopy());
230         } catch (Exception e) {
231             String value[] = {component.getId(), component.getComponentsForLifecycle().size() + "",
232                     "Exception " + e.getMessage()};
233             tracerTemp.createError("Error Validating Bean Lifecycle", value);
234         }
235 
236         try {
237             runValidationsOnPrototype(component, tracer.getCopy());
238         } catch (Exception e) {
239             String value[] = {component.getId(), component.getComponentPrototypes().size() + "",
240                     "Exceptions : " + e.getLocalizedMessage()};
241             tracerTemp.createError("Error Validating Bean Prototypes", value);
242         }
243     }
244 
245     /**
246      * Runs the validations on a components lifecycle items
247      *
248      * @param component - The component whose lifecycle items are being checked
249      * @param tracer - The current bean trace for the validation line
250      */
251     private void runValidationsOnLifecycle(Component component, ValidationTrace tracer) {
252         List<Component> nestedComponents = component.getComponentsForLifecycle();
253         if (nestedComponents == null) {
254             return;
255         }
256         if (!doValidationOnUIFBean(component)) {
257             return;
258         }
259         tracer.addBean(component);
260         for (Component temp : nestedComponents) {
261             if (temp == null) {
262                 continue;
263             }
264             if (tracer.getValidationStage() == ValidationTrace.START_UP) {
265                 ExpressionUtils.populatePropertyExpressionsFromGraph(temp, false);
266             }
267             if (temp.isRender()) {
268                 temp.completeValidation(tracer.getCopy());
269                 runValidationsOnLifecycle(temp, tracer.getCopy());
270             }
271         }
272     }
273 
274     /**
275      * Runs the validations on a components prototypes
276      *
277      * @param component - The component whose prototypes are being checked
278      * @param tracer - The current bean trace for the validation line
279      */
280     private void runValidationsOnPrototype(Component component, ValidationTrace tracer) {
281         List<Component> componentPrototypes = component.getComponentPrototypes();
282         if (componentPrototypes == null) {
283             return;
284         }
285         if (!doValidationOnUIFBean(component)) {
286             return;
287         }
288         tracer.addBean(component);
289         for (Component temp : componentPrototypes) {
290             if (temp == null) {
291                 continue;
292             }
293             if (tracer.getValidationStage() == ValidationTrace.START_UP) {
294                 ExpressionUtils.populatePropertyExpressionsFromGraph(temp, false);
295             }
296             if (temp.isRender()) {
297                 temp.completeValidation(tracer.getCopy());
298                 runValidationsOnPrototype(temp, tracer.getCopy());
299             }
300         }
301     }
302 
303     /**
304      * Checks if the component being checked is a default or template component by seeing if its id starts with "uif"
305      *
306      * @param component - The component being checked
307      * @return Returns true if the component is not a default or template
308      */
309     private boolean doValidationOnUIFBean(Component component) {
310         if (component.getId() == null) {
311             return true;
312         }
313         if (component.getId().length() < 3) {
314             return true;
315         }
316         String temp = component.getId().substring(0, 3).toLowerCase();
317         if (temp.contains("uif")) {
318             return false;
319         }
320         return true;
321     }
322 
323     /**
324      * Validates an expression string for correct Spring Expression language syntax
325      *
326      * @param expression - The expression being validated
327      * @return Returns true if the expression is of correct SpringEL syntax
328      */
329     public static boolean validateSpringEL(String expression) {
330         if (expression == null) {
331             return true;
332         }
333         if (expression.compareTo("") == 0) {
334             return true;
335         }
336         if (expression.length() <= 3) {
337             return false;
338         }
339 
340         if (!expression.substring(0, 1).contains("@") || !expression.substring(1, 2).contains("{") ||
341                 !expression.substring(expression.length() - 1, expression.length()).contains("}")) {
342             return false;
343         }
344 
345         expression = expression.substring(2, expression.length() - 2);
346 
347         ArrayList<String> values = getExpressionValues(expression);
348 
349         for (int i = 0; i < values.size(); i++) {
350             checkPropertyName(values.get(i));
351         }
352 
353         return true;
354     }
355 
356     /**
357      * Gets the list of properties from an expression
358      *
359      * @param expression - The expression being validated.
360      * @return A list of properties from the expression.
361      */
362     private static ArrayList<String> getExpressionValues(String expression) {
363         expression = StringUtils.replace(expression, "!=", " != ");
364         expression = StringUtils.replace(expression, "==", " == ");
365         expression = StringUtils.replace(expression, ">", " > ");
366         expression = StringUtils.replace(expression, "<", " < ");
367         expression = StringUtils.replace(expression, "<=", " <= ");
368         expression = StringUtils.replace(expression, ">=", " >= ");
369 
370         String stack = "";
371         ArrayList<String> controlNames = new ArrayList<String>();
372 
373         boolean expectingSingleQuote = false;
374         boolean ignoreNext = false;
375         for (int i = 0; i < expression.length(); i++) {
376             char c = expression.charAt(i);
377             if (!expectingSingleQuote && !ignoreNext && (c == '(' || c == ' ' || c == ')')) {
378                 ExpressionUtils.evaluateCurrentStack(stack.trim(), controlNames);
379                 //reset stack
380                 stack = "";
381                 continue;
382             } else if (!ignoreNext && c == '\'') {
383                 stack = stack + c;
384                 expectingSingleQuote = !expectingSingleQuote;
385             } else if (c == '\\') {
386                 stack = stack + c;
387                 ignoreNext = !ignoreNext;
388             } else {
389                 stack = stack + c;
390                 ignoreNext = false;
391             }
392         }
393 
394         if (StringUtils.isNotEmpty(stack)) {
395             ExpressionUtils.evaluateCurrentStack(stack.trim(), controlNames);
396         }
397 
398         return controlNames;
399     }
400 
401     /**
402      * Checks the property for a valid name.
403      *
404      * @param name - The property name.
405      * @return True if the validation passes, false if not
406      */
407     private static boolean checkPropertyName(String name) {
408         if (!Character.isLetter(name.charAt(0))) {
409             return false;
410         }
411 
412         return true;
413     }
414 
415     /**
416      * Checks if a property of a Component is being set by expressions
417      *
418      * @param object - The Component being checked
419      * @param property - The property being set
420      * @return Returns true if the property is contained in the Components property expressions
421      */
422     public static boolean checkExpressions(Component object, String property) {
423         if (object.getPropertyExpressions().containsKey(property)) {
424             return true;
425         }
426         return false;
427     }
428 
429     /**
430      * Compiles general information on the validation from the list of generated error reports
431      */
432     private void compileFinalReport() {
433         ArrayList<ErrorReport> reports = Validator.errorReports;
434         for (int i = 0; i < reports.size(); i++) {
435             if (reports.get(i).getErrorStatus() == ErrorReport.ERROR) {
436                 numberOfErrors++;
437             } else if (reports.get(i).getErrorStatus() == ErrorReport.WARNING) {
438                 numberOfWarnings++;
439             }
440         }
441     }
442 
443     /**
444      * Loads the Spring Beans from a list of xml files
445      *
446      * @param xmlFiles
447      * @return The Spring Bean Factory for the provided list of xml files
448      */
449     public DefaultListableBeanFactory loadBeans(String[] xmlFiles) {
450 
451         LOG.info("Starting XML File Load");
452         DefaultListableBeanFactory beans = new DefaultListableBeanFactory();
453         XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(beans);
454 
455         DataDictionary.setupProcessor(beans);
456 
457         ArrayList<String> coreFiles = new ArrayList<String>();
458         ArrayList<String> testFiles = new ArrayList<String>();
459 
460         for (int i = 0; i < xmlFiles.length; i++) {
461             if (xmlFiles[i].contains("classpath")) {
462                 coreFiles.add(xmlFiles[i]);
463             } else {
464                 testFiles.add(xmlFiles[i]);
465             }
466         }
467         String core[] = new String[coreFiles.size()];
468         coreFiles.toArray(core);
469 
470         String test[] = new String[testFiles.size()];
471         testFiles.toArray(test);
472 
473         try {
474             xmlReader.loadBeanDefinitions(core);
475         } catch (Exception e) {
476             LOG.error("Error loading bean definitions", e);
477             throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage());
478         }
479 
480         try {
481             xmlReader.loadBeanDefinitions(getResources(test));
482         } catch (Exception e) {
483             LOG.error("Error loading bean definitions", e);
484             throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage());
485         }
486 
487         UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor();
488         factoryPostProcessor.postProcessBeanFactory(beans);
489 
490         tracerTemp = new ValidationTrace(xmlFiles, xmlReader.getResourceLoader());
491 
492         LOG.info("Completed XML File Load");
493 
494         return beans;
495     }
496 
497     /**
498      * Converts the list of file paths into a list of resources
499      *
500      * @param files The list of file paths for conversion
501      * @return A list of resources created from the file paths
502      */
503     private Resource[] getResources(String files[]) {
504         Resource resources[] = new Resource[files.length];
505         for (int i = 0; i < files.length; i++) {
506             resources[0] = new FileSystemResource(files[i]);
507         }
508 
509         return resources;
510     }
511 
512     /**
513      * Retrieves the number of errors found in the validation
514      *
515      * @return The number of errors found in the validation
516      */
517     public int getNumberOfErrors() {
518         return numberOfErrors;
519     }
520 
521     /**
522      * Retrieves the number of warnings found in the validation
523      *
524      * @return The number of warnings found in the validation
525      */
526     public int getNumberOfWarnings() {
527         return numberOfWarnings;
528     }
529 
530     /**
531      * Retrieves an individual error report for errors found during the validation
532      *
533      * @param index
534      * @return The error report at the provided index
535      */
536     public ErrorReport getErrorReport(int index) {
537         return errorReports.get(index);
538     }
539 
540     /**
541      * Retrieves the number of error reports generated during the validation
542      *
543      * @return The number of ErrorReports
544      */
545     public int getErrorReportSize() {
546         return errorReports.size();
547     }
548 }