001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.validation;
017:
018: import java.io.File;
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.logging.Level;
027: import java.util.logging.Logger;
028:
029: import org.geotools.feature.FeatureCollection;
030: import org.geotools.feature.FeatureIterator;
031: import org.geotools.feature.Feature;
032: import org.geotools.feature.FeatureType;
033: import org.geotools.resources.TestData;
034: import org.geotools.validation.dto.ArgumentDTO;
035: import org.geotools.validation.dto.PlugInDTO;
036: import org.geotools.validation.dto.TestDTO;
037: import org.geotools.validation.dto.TestSuiteDTO;
038: import org.geotools.validation.xml.XMLReader;
039:
040: import com.vividsolutions.jts.geom.Envelope;
041:
042: /**
043: * ValidationProcessor Runs validation tests against Features and reports the
044: * outcome of the tests.
045: *
046: * <p>
047: * The validation processor contains two main data structures. Each one is a
048: * HashMap of ArrayLists that hold Validations. The first one, featureLookup,
049: * holds per-feature validation tests (tests that operate on one feature at a
050: * time with no knowledge of any other features. The second one,
051: * integrityLookup, holds integrity validations (validations that span
052: * multiple features and/or multiple feature types).
053: * </p>
054: *
055: * <p>
056: * Each HashMap of validations is hashed with a key whose value is a
057: * FeatureTypeName. This key provides access to an ArrayList of validations
058: * that are to be performed on this FeatureTypeInfo.
059: * </p>
060: *
061: * <p>
062: * Validations are added via the two addValidation() methods.
063: * </p>
064: *
065: * <p>
066: * The validations are run when runFeatureTests() and runIntegrityTests() are
067: * called. It is recommended that the user call runFeatureTests() before
068: * runIntegrityTests() as it is usually the case that integrity tests are much
069: * more time consuming. If a Feature is incorrect, it can probably be
070: * detected early on, and quickly, in the feature validation tests.
071: * </p>
072: *
073: * <p>
074: * For validations that are performed on every FeatureTypeInfo, a value called
075: * ANYTYPENAME has been created and can be stored in the validationLookup
076: * tables if a validation specifies that it is run against all FeatureTypes.
077: * The value that causes a validation to be run against all FeatureTypes is
078: * null. Or Validation.ALL
079: * </p>
080: *
081: * <p>
082: * Results of the validation tests are handled using a Visitor pattern. This
083: * visitor is a ValidationResults object that is passed into the
084: * runFeatureTests() and runIntegrityTests() methods. Each individual
085: * validation will record error messages in the ValidationResults visitor.
086: * </p>
087: *
088: * <p>
089: * Example Use:
090: * <pre><code>
091: * ValidationProcessor processor = new ValidationProcessor();<br>
092: * processor.addValidation(FeatureValidation1);<br>
093: * processor.addValidation(FeatureValidation2);<br>
094: * processor.addValidation(IntegrityValidation1);<br>
095: * processor.addValidation(FeatureValidation3);<br>
096: * <p>
097: * processor.runFeatureTests(FeatureTypeInfo, Feature, ValidationResults);<br>
098: * processor.runIntegrityTests(layers, Envelope, ValidationResults);<br>
099: * </code></pre>
100: * </p>
101: *
102: * @author bowens, Refractions Research, Inc.
103: * @author $Author: jive $ (last modification)
104: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/validation/src/main/java/org/geotools/validation/ValidationProcessor.java $
105: * @version $Id: ValidationProcessor.java 27862 2007-11-12 19:51:19Z desruisseaux $
106: */
107: public class ValidationProcessor {
108: private static final Logger LOGGER = org.geotools.util.logging.Logging
109: .getLogger("org.geotools.validation");
110:
111: /** Magic key used to hold the place of any featureType */
112: final Object ANYTYPENAME = new Object();
113:
114: /**
115: * Stores Lists of FeatureTests by featureType.
116: *
117: * <p>
118: * Map of ArrayLists by featureType (the lists contain FeatureValidation
119: * instances)
120: * </p>
121: */
122: protected Map featureLookup;
123:
124: /**
125: * Stores Lists of IntegrityValidation by featureType.
126: *
127: * <p>
128: * A Map with featureTypes as keys that map to array lists of integrity
129: * validation tests.
130: * </p>
131: *
132: * <p>
133: * How are tests that map against all FeatureTypes stored?
134: * </p>
135: */
136: protected Map integrityLookup; //
137:
138: /** List of feature types that have been modified. */
139: protected ArrayList modifiedFeatureTypes;
140:
141: /**
142: * ValidationProcessor constructor.
143: *
144: * <p>
145: * Initializes the data structure to hold the validations.
146: * </p>
147: */
148: public ValidationProcessor() {
149: featureLookup = new HashMap();
150: integrityLookup = new HashMap();
151: }
152:
153: /**
154: * addToLookup
155: *
156: * <p>
157: * Description: Add the validation test to the map for every featureType
158: * that it validates. If the FeatureTypes array is ALL, then the
159: * validation is added to the ANYTYPENAME entry.
160: * </p>
161: *
162: * @param validation
163: */
164: private void addToFVLookup(FeatureValidation validation) {
165: String[] featureTypeList = validation.getTypeRefs();
166:
167: if (featureTypeList == Validation.ALL) // if null (ALL)
168: {
169: ArrayList tests = (ArrayList) featureLookup
170: .get(ANYTYPENAME);
171:
172: if (tests == null) { // if an ALL test doesn't exist yet
173: tests = new ArrayList(); // create it
174: }
175:
176: tests.add(validation);
177: featureLookup.put(ANYTYPENAME, tests); // add the ALL test to it
178: } else // a non ALL FeatureTypeInfo validation
179: {
180: for (int i = 0; i < featureTypeList.length; i++) {
181: ArrayList tests = (ArrayList) featureLookup
182: .get(featureTypeList[i]);
183:
184: if (tests == null) { // if this FeatureTypeInfo doesn't have a validation test yet
185: tests = new ArrayList(); // put it in the list
186: }
187:
188: tests.add(validation);
189: featureLookup.put(featureTypeList[i], tests); // add a validation to it
190: }
191: }
192: }
193:
194: /**
195: * addToIVLookup
196: *
197: * <p>
198: * Add the validation test to the map for every featureType that it
199: * validates. If the FeatureTypes array is ALL, then the validation is
200: * added to the ANYTYPENAME entry.
201: * </p>
202: *
203: * @param validation
204: */
205: private void addToIVLookup(IntegrityValidation validation) {
206: String[] integrityTypeList = validation.getTypeRefs();
207:
208: if (integrityTypeList == Validation.ALL) // if null (ALL)
209: {
210: ArrayList tests = (ArrayList) integrityLookup
211: .get(ANYTYPENAME);
212:
213: if (tests == null) { // if an ALL test doesn't exist yet
214: tests = new ArrayList(); // create it
215: }
216:
217: tests.add(validation);
218: integrityLookup.put(ANYTYPENAME, tests); // add the ALL test to it
219: } else {
220: for (int i = 0; i < integrityTypeList.length; i++) {
221: ArrayList tests = (ArrayList) integrityLookup
222: .get(integrityTypeList[i]);
223:
224: if (tests == null) { // if this FeatureTypeInfo doesn't have a validation test yet
225: tests = new ArrayList(); // put it in the list
226: }
227:
228: tests.add(validation);
229: integrityLookup.put(integrityTypeList[i], tests); // add a validation to it
230: }
231: }
232: }
233:
234: /**
235: * addValidation
236: *
237: * <p>
238: * Add a FeatureValidation to the list of Feature tests
239: * </p>
240: *
241: * @param validation
242: */
243: public void addValidation(FeatureValidation validation) {
244: addToFVLookup((FeatureValidation) validation);
245: }
246:
247: /**
248: * addValidation
249: *
250: * <p>
251: * Add an IntegrityValidation to the list of Integrity tests
252: * </p>
253: *
254: * @param validation
255: */
256: public void addValidation(IntegrityValidation validation) {
257: addToIVLookup((IntegrityValidation) validation);
258: }
259:
260: /**
261: * getDependencies purpose.
262: *
263: * <p>
264: * Gets all the FeatureTypes that this FeatureTypeInfo uses.
265: * </p>
266: *
267: * @param typeName the FeatureTypeName
268: *
269: * @return all the FeatureTypes that this FeatureTypeInfo uses.
270: */
271: public Set getDependencies(String typeName) {
272: ArrayList validations = (ArrayList) integrityLookup
273: .get(typeName);
274: HashSet s = new HashSet();
275:
276: if (validations != null) {
277: for (int i = 0; i < validations.size(); i++) // for each validation
278: {
279: String[] types = ((Validation) validations.get(i))
280: .getTypeRefs();
281:
282: for (int j = 0; j < types.length; j++)
283: // for each FeatureTypeInfo
284:
285: s.add(types[j]); // add it to the list
286: }
287: }
288:
289: return s;
290: }
291:
292: /**
293: * runFeatureTests Change: Uses a FeatureIterator now instead of a
294: * FeatureCollection.
295: *
296: * <p>
297: * Performs a lookup on the FeatureTypeInfo name to determine what
298: * FeatureTests need to be performed. Once these tests are gathered, they
299: * are run on each feature in the FeatureCollection. The first validation
300: * test lookup checks to see if there are any validations that are to be
301: * performed on every FeatureTypeInfo. An example of this could be an
302: * isValid() test on all geometries in all FeatureTypes. Once those tests
303: * have been gathered, a lookup is performed on the TypeName of the
304: * FeatureTypeInfo to check for specific FeatureTypeInfo validation tests.
305: * A list of validation tests is returned from each lookup, if any exist.
306: * When all the validation tests have been gathered, each test is iterated
307: * through then run on each Feature, with the ValidationResults coming
308: * along for the ride, collecting error information. Parameter
309: * "FeatureCollection collection" should be changed later to take in a
310: * FeatureSource so not everything is loaded into memory.
311: * </p>
312: *
313: * @param dsID data Store id.
314: * @param type The FeatureTypeInfo of the features being tested.
315: * @param features The collection of features, of a particulare
316: * FeatureTypeInfo "type", that are to be validated.
317: * @param results Storage for the results of the validation tests.
318: *
319: * @throws Exception FeatureValidations throw Exceptions
320: */
321: public void runFeatureTests(String dsID,
322: FeatureCollection collection, ValidationResults results)
323: throws Exception {
324: FeatureType type = collection.getSchema();
325:
326: // check for any tests that are to be performed on ALL features
327: ArrayList tests = (ArrayList) featureLookup.get(ANYTYPENAME);
328:
329: // check for any FeatureTypeInfo specific tests
330: String typeRef = dsID + ":" + type.getTypeName();
331: ArrayList FT_tests = (ArrayList) featureLookup.get(typeRef);
332:
333: // append featureType specific tests to the list of tests
334: if (FT_tests != null) {
335: if (tests != null) {
336: Iterator it = FT_tests.iterator();
337:
338: while (it.hasNext())
339: tests.add((FeatureValidation) it.next());
340: } else {
341: tests = FT_tests;
342: }
343: }
344:
345: if (tests != null) // if we found some tests to be performed on this FeatureTypeInfo
346: {
347: FeatureIterator features = collection.features();
348: try {
349: while (features.hasNext()) // iterate through each feature and run the test on it
350: {
351: Feature feature = (Feature) features.next();
352:
353: // for each test that is to be performed on this feature
354: for (int i = 0; i < tests.size(); i++) {
355: FeatureValidation validator = (FeatureValidation) tests
356: .get(i);
357: results.setValidation(validator);
358: try {
359: validator.validate(feature, type, results);
360: } catch (Throwable e) {
361: results.error(feature, e.getMessage());
362: }
363: }
364: }
365: } finally {
366: collection.close(features);
367: }
368: }
369: }
370:
371: /**
372: * runIntegrityTests
373: *
374: * <p>
375: * Performs a lookup on the FeatureTypeInfo name to determine what
376: * IntegrityTests need to be performed. Once these tests are gathered,
377: * they are run on the collection features in the Envelope, defined by a
378: * FeatureSource (not a FeatureCollection!). The first validation test
379: * lookup checks to see if there are any validations that are to be
380: * performed on every FeatureTypeInfo. An example of this could be a
381: * uniqueID() test on a unique column value in all FeatureTypes. Once
382: * those tests have been gathered, a lookup is performed on the TypeName
383: * of the FeatureTypeInfo to check for specific Integrity validation
384: * tests. A list of validation tests is returned from each lookup, if any
385: * exist. When all the validation tests have been gathered, each test is
386: * iterated through then run on each Feature, with the ValidationResults
387: * coming along for the ride, collecting error information.
388: * </p>
389: *
390: * @param typeRefs List of modified features, or null to use
391: * stores.keySet()
392: * @param stores the Map of effected features (Map of key=typeRef,
393: * value="featureSource"
394: * @param envelope The bounding box that contains all modified Features
395: * @param results Storage for the results of the validation tests.
396: *
397: * @throws Exception Throws an exception if the HashMap contains a value
398: * that is not a FeatureSource
399: */
400: public void runIntegrityTests(Set typeRefs, Map stores,
401: Envelope envelope, ValidationResults results)
402: throws Exception {
403: if ((integrityLookup == null) || (integrityLookup.size() == 0)) {
404: LOGGER
405: .fine("No tests defined by integrityLookup - validation not needed");
406:
407: return;
408: }
409:
410: LOGGER.fine("Starting validation tests for:" + typeRefs);
411: LOGGER.fine("Marshalled " + stores.size()
412: + " FeatureSources for testing");
413: LOGGER.fine("Testing limited to " + envelope);
414:
415: if (typeRefs == null) {
416: LOGGER.finer("Using default typeRegs for stores");
417: typeRefs = stores.keySet();
418: } else if (typeRefs.isEmpty()) {
419: LOGGER
420: .finer("Validation test abandond - nothing was modified");
421: }
422:
423: // convert each HashMap element into FeatureSources
424: //
425: List tests = new ArrayList();
426:
427: // check for any tests that are to be performed on ALL features
428: //
429: LOGGER.finer("Finding tests for everybody");
430:
431: List anyTests = (List) integrityLookup.get(ANYTYPENAME);
432:
433: if ((anyTests != null) && !anyTests.isEmpty()) {
434: tests.addAll(anyTests);
435: }
436:
437: LOGGER.finer("Found " + tests.size() + " tests (so far)");
438:
439: // for each modified FeatureTypeInfo
440: //
441: LOGGER.finer("Finding tests for modified typeRefs");
442:
443: for (Iterator i = typeRefs.iterator(); i.hasNext();) {
444: String typeRef = (String) i.next();
445: LOGGER.finer("Finding tests for typeRef:" + typeRef);
446:
447: List moreTests = (List) integrityLookup.get(typeRef);
448:
449: if ((moreTests != null) && !moreTests.isEmpty()) {
450: tests.addAll(moreTests);
451: }
452: }
453:
454: if (tests.isEmpty()) {
455: LOGGER
456: .finer("Validation test abandond - no tests found to run");
457:
458: return;
459: }
460:
461: LOGGER.finer("Validation test about to run - " + tests.size()
462: + " tests found");
463:
464: for (Iterator j = tests.iterator(); j.hasNext();) {
465: IntegrityValidation validator = (IntegrityValidation) j
466: .next();
467:
468: LOGGER.finer("Running test:" + validator.getName());
469: results.setValidation(validator);
470:
471: try {
472: boolean success = validator.validate(stores, envelope,
473: results);
474:
475: if (!success) {
476: results.error(null, "Was not successful");
477: }
478: } catch (Throwable e) {
479: LOGGER.finer("Validation test died:"
480: + validator.getName());
481:
482: String error = e.getClass().getName();
483:
484: if (e.getMessage() != null) {
485: error += (" - " + e.getMessage());
486: }
487:
488: LOGGER.log(Level.WARNING, validator.getName()
489: + " failed with " + error, e);
490: e.printStackTrace();
491: results.error(null, error);
492: }
493: }
494: }
495:
496: protected static final Set queryPlugInNames(Map testSuiteDTOs) {
497: Set plugInNames = new HashSet();
498:
499: Iterator i = testSuiteDTOs.keySet().iterator();
500:
501: // go through each test suite
502: // and gather up all the required plugInNames
503: //
504: while (i.hasNext()) {
505: String testSuite = (String) i.next();
506: TestSuiteDTO dto = (TestSuiteDTO) testSuiteDTOs
507: .get(testSuite);
508: Iterator j = dto.getTests().keySet().iterator();
509:
510: // go through each test plugIn
511: //
512: while (j.hasNext()) {
513: TestDTO tdto = (TestDTO) dto.getTests().get(j.next());
514: plugInNames.add(tdto.getPlugIn().getName());
515: }
516: }
517:
518: return plugInNames;
519: }
520:
521: /**
522: * Load testsuites from provided directories.
523: *
524: * <p>
525: * This is mostly useful for testing, you may want to write your own load
526: * method with enhanced error reporting.
527: * </p>
528: *
529: * @param plugins DOCUMENT ME!
530: * @param testsuites DOCUMENT ME!
531: *
532: * @throws Exception DOCUMENT ME!
533: */
534: public void load(File plugins, File testsuites) throws Exception {
535: Map pluginDTOs = XMLReader.loadPlugIns(TestData.file(this ,
536: "plugins"));
537: Map testSuiteDTOs = XMLReader.loadValidations(TestData.file(
538: this , "validation"), pluginDTOs);
539: load(testSuiteDTOs, pluginDTOs);
540: }
541:
542: /**
543: * Populates this validation processor against the provided DTO objects.
544: *
545: * <p>
546: * This method is useful for testing, it is not forgiving and will error
547: * out if things go bad.
548: * </p>
549: *
550: * @param plugInDTOs
551: * @param testSuiteDTOs
552: *
553: * @throws Exception
554: * @throws ClassNotFoundException DOCUMENT ME!
555: */
556: public void load(Map plugInDTOs, Map testSuiteDTOs)
557: throws Exception {
558: // step 1 make a list required plug-ins
559: //
560: Set plugInNames = queryPlugInNames(testSuiteDTOs);
561:
562: // step 2 set up real plug-ins
563: // configured with defaults
564: //
565: // (This is a map of PlugIn by name)
566: Map plugIns = new HashMap(plugInNames.size());
567:
568: // go through each plugIn
569: //
570: for (Iterator i = plugInNames.iterator(); i.hasNext();) {
571: String plugInName = (String) i.next();
572: PlugInDTO dto = (PlugInDTO) plugInDTOs.get(plugInName);
573: Class plugInClass = null;
574:
575: plugInClass = Class.forName(dto.getClassName());
576:
577: if (plugInClass == null) {
578: throw new ClassNotFoundException("Could class for "
579: + plugInName + ": class " + dto.getClassName()
580: + " not found");
581: }
582:
583: Map plugInArgs = dto.getArgs();
584:
585: if (plugInArgs == null) {
586: plugInArgs = new HashMap();
587: }
588:
589: PlugIn plugIn = new PlugIn(plugInName, plugInClass, dto
590: .getDescription(), plugInArgs);
591: plugIns.put(plugInName, plugIn);
592: }
593:
594: // step 3
595: // set up tests and add to processor
596: //
597: for (Iterator i = testSuiteDTOs.keySet().iterator(); i
598: .hasNext();) {
599: TestSuiteDTO tdto = (TestSuiteDTO) testSuiteDTOs.get(i
600: .next());
601: Iterator j = tdto.getTests().keySet().iterator();
602:
603: // for each TEST in the test suite
604: while (j.hasNext()) {
605: TestDTO dto = (TestDTO) tdto.getTests().get(j.next());
606:
607: // deal with test
608: Map testArgs = dto.getArgs();
609:
610: if (testArgs == null) {
611: testArgs = new HashMap();
612: } else {
613: Map m = new HashMap();
614: Iterator k = testArgs.keySet().iterator();
615:
616: while (k.hasNext()) {
617: ArgumentDTO adto = (ArgumentDTO) testArgs.get(k
618: .next());
619: m.put(adto.getName(), adto.getValue());
620: }
621:
622: testArgs = m;
623: }
624:
625: PlugIn plugIn = (org.geotools.validation.PlugIn) plugIns
626: .get(dto.getPlugIn().getName());
627: Validation validation = plugIn.createValidation(dto
628: .getName(), dto.getDescription(), testArgs);
629:
630: if (validation instanceof FeatureValidation) {
631: addValidation((FeatureValidation) validation);
632: }
633:
634: if (validation instanceof IntegrityValidation) {
635: addValidation((IntegrityValidation) validation);
636: }
637: }
638: } // end each test suite
639: } // load method
640: }
|