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