001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.context;
017    
018    import java.io.File;
019    import java.io.FileInputStream;
020    import java.io.FileNotFoundException;
021    import java.io.FileOutputStream;
022    import java.io.InputStream;
023    import java.net.URL;
024    import java.text.MessageFormat;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.HashMap;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Properties;
032    import java.util.Scanner;
033    import java.util.logging.Level;
034    
035    import javax.xml.parsers.DocumentBuilder;
036    import javax.xml.parsers.DocumentBuilderFactory;
037    
038    import org.apache.commons.collections.bidimap.TreeBidiMap;
039    import org.apache.commons.lang.StringUtils;
040    import org.apache.cxf.common.logging.LogUtils;
041    import org.apache.cxf.endpoint.ServerImpl;
042    import org.kuali.kfs.sys.KFSConstants;
043    import org.kuali.rice.core.util.ClassLoaderUtils;
044    import org.kuali.rice.kns.datadictionary.AttributeDefinition;
045    import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
046    import org.kuali.rice.kns.datadictionary.DataDictionary;
047    import org.kuali.rice.kns.datadictionary.DocumentEntry;
048    import org.kuali.rice.kns.datadictionary.InactivationBlockingDefinition;
049    import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
050    import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
051    import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
052    import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
053    import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
054    import org.kuali.rice.kns.datadictionary.TransactionalDocumentEntry;
055    import org.kuali.rice.kns.datadictionary.control.ControlDefinition;
056    import org.kuali.rice.kns.service.DataDictionaryService;
057    import org.kuali.rice.kns.service.KualiModuleService;
058    import org.kuali.rice.kns.service.ModuleService;
059    import org.springframework.core.io.DefaultResourceLoader;
060    import org.w3c.dom.Document;
061    import org.w3c.dom.Element;
062    import org.w3c.dom.NodeList;
063    
064    import uk.ltd.getahead.dwr.impl.DTDEntityResolver;
065    import uk.ltd.getahead.dwr.util.LogErrorHandler;
066    
067    public class CheckModularization {
068    
069        private static final Map<String, String> OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX = new HashMap<String, String>();
070        static {
071            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-AR", "ar");
072            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-BC", "bc");
073            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-CAB", "cab");
074            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-CAM", "cam");
075            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-CG", "cg");
076            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-EC", "ec");
077            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-LD", "ld");
078            OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-PURAP", "purap");
079        }
080        
081        private static final Map<String, String> OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES =
082                new TreeBidiMap(OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX).inverseBidiMap();
083        
084        private static final Map<String, String> SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX = new HashMap<String, String>();
085        static {
086            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-COA", "coa");
087            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-FP", "fp");
088            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-GL", "gl");
089            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-PDP", "pdp");
090            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-SYS", "sys");
091            SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.put("KFS-VND", "vnd");
092        }
093        
094        private static Map<String,List<String>> PACKAGE_PREFIXES_BY_MODULE = new HashMap<String, List<String>>();
095        private static Map<String,List<String>> OJB_FILES_BY_MODULE = new HashMap<String, List<String>>();
096        private static Map<String,List<String>> DWR_FILES_BY_MODULE = new HashMap<String, List<String>>();
097        
098        private static String MODULE_SPRING_PATH_PATTERN = "org/kuali/kfs/module/{0}/spring-{0}.xml";
099        
100        /*
101         * open up classpath:configuration.properties - get locations of spring files?
102         * alter the spring.source.files property and re-save
103         * hold original version and restore?
104         * 
105         * use location of config.properties as the class root for scanning source files?
106         * How do you test .class files for symbols?
107         */
108        static String coreSpringFiles; 
109        static String coreSpringTestFiles; 
110        static File configPropertiesFile;
111        public static void main(String[] args) {
112            CheckModularization mt = new CheckModularization();
113            try {
114                Properties configProps = new Properties();
115                URL propLocation = CheckModularization.class.getClassLoader().getResource("configuration.properties" );
116                System.out.println( "URL: " + propLocation );
117                System.out.println( "Path: " + propLocation.getPath() );
118                configPropertiesFile = new File( propLocation.getPath() );
119                configProps.load( CheckModularization.class.getClassLoader().getResourceAsStream("configuration.properties") );
120                coreSpringFiles = configProps.getProperty("core.spring.source.files");
121                coreSpringTestFiles = configProps.getProperty("core.spring.test.files");
122    
123                LogUtils.getL7dLogger(ServerImpl.class).setLevel(Level.SEVERE);
124                try {
125                    SpringContext.initializeTestApplicationContext();                
126                    KualiModuleService kualiModuleService = (KualiModuleService)SpringContext.getBean(KualiModuleService.class);
127                    
128                    for ( ModuleService module : kualiModuleService.getInstalledModuleServices() ) {
129                        PACKAGE_PREFIXES_BY_MODULE.put(module.getModuleConfiguration().getNamespaceCode(), module.getModuleConfiguration().getPackagePrefixes() );
130                        OJB_FILES_BY_MODULE.put(module.getModuleConfiguration().getNamespaceCode(), module.getModuleConfiguration().getDatabaseRepositoryFilePaths() );
131                        DWR_FILES_BY_MODULE.put(module.getModuleConfiguration().getNamespaceCode(), module.getModuleConfiguration().getScriptConfigurationFilePaths() );
132                    }
133                    
134                } catch ( Exception ex ) {
135                    ex.printStackTrace();
136                } finally {
137                    stopSpringContext();
138                }
139                
140                // bring up Spring once to get all the configuration information, store by namespace code
141                // list of core namespaces, all others must be independent
142                
143                // test class references
144                boolean testsPassed = true;
145                System.out.println( "**************************************************");
146                System.out.println( "Testing Spring Startup");
147                System.out.println( "**************************************************");
148                if ( !mt.testSpring() ) {
149                    System.out.println( "FAILED" );
150                    testsPassed = false;
151                } else {
152                    System.out.println( "SUCCEEDED" );
153                }
154                System.out.println( "**************************************************");
155                System.out.println( "Testing OJB References");
156                System.out.println( "**************************************************");
157                if ( !mt.testOjb() ) {
158                    System.out.println( "FAILED" );
159                    testsPassed = false;
160                } else {
161                    System.out.println( "SUCCEEDED" );
162                }
163                System.out.println( "**************************************************");
164                System.out.println( "Testing DWR References");
165                System.out.println( "**************************************************");
166                if ( !mt.testDwr() ) {
167                    System.out.println( "FAILED" );
168                    testsPassed = false;
169                } else {
170                    System.out.println( "SUCCEEDED" );
171                }
172                System.out.println( "**************************************************");
173                System.out.println( "Testing DD Class References");
174                System.out.println( "**************************************************");
175                if ( !mt.testDd() ) {
176                    System.out.println( "FAILED" );
177                    testsPassed = false;
178                } else {
179                    System.out.println( "SUCCEEDED" );
180                }
181    //            testsPassed &= mt.testDd();
182                
183                if ( !testsPassed ) {
184                    System.exit(1);
185                }
186            }
187            catch (Exception e) {
188                e.printStackTrace();
189                System.exit(1);
190            }
191            System.exit(0);
192        }
193        
194        
195        protected String buildOptionalModuleSpringFileList( ModuleGroup moduleGroup ) {
196            StringBuffer sb = new StringBuffer();
197            sb.append( MessageFormat.format(MODULE_SPRING_PATH_PATTERN, OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.get( moduleGroup.namespaceCode ) ) );
198            for ( String depMod : moduleGroup.optionalModuleDependencyNamespaceCodes ) {
199                sb.append( ',' );            
200                sb.append( MessageFormat.format(MODULE_SPRING_PATH_PATTERN, OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.get( depMod ) ) );
201            }
202            return sb.toString();
203        }
204    
205        StringBuffer dwrErrorMessage = new StringBuffer("The following optional modules have interdependencies in DWR configuration:");
206        boolean dwrTestSucceeded = true;
207        StringBuffer ddErrorMessage = new StringBuffer("The following optional modules have interdependencies in DD class references:");
208        boolean ddTestSucceeded = true;
209        
210        public boolean testSpring() throws Exception {
211            boolean testSucceeded = true;
212            StringBuffer errorMessage = new StringBuffer();
213            // test the core modules alone
214            System.out.println( "\n\n------>Testing for core modules:");
215            System.out.println( "------>Using Base Configuration:   " + coreSpringFiles );
216            testSucceeded &= testOptionalModuleSpringConfiguration(new ModuleGroup(KFSConstants.ParameterNamespaces.KFS), coreSpringFiles, errorMessage);
217            if ( !testSucceeded ) {
218                errorMessage.insert( 0, "The Core modules have dependencies on the optional modules:\n" );
219            }
220            
221            errorMessage.append( "The following optional modules have interdependencies in Spring configuration:\n");
222            List<ModuleGroup> optionalModuleGroups = retrieveOptionalModuleGroups();
223            for (ModuleGroup optionalModuleGroup : optionalModuleGroups) {
224    //            if ( !optionalModuleGroup.namespaceCode.equals( "KFS-AR" ) ) continue;
225                System.out.println( "\n\n------>Testing for optional module group: " + optionalModuleGroup );
226                System.out.println( "------>Using Base Configuration:   " + coreSpringFiles );
227                String moduleConfigFiles = buildOptionalModuleSpringFileList(optionalModuleGroup);
228                System.out.println( "------>Module configuration files: " + moduleConfigFiles );
229                testSucceeded &= testOptionalModuleSpringConfiguration(optionalModuleGroup, coreSpringFiles+","+moduleConfigFiles, errorMessage);
230            }
231            if (!testSucceeded) {
232                System.out.print(errorMessage.append("\n\n").toString());
233            }
234            return testSucceeded;
235        }
236    
237        protected boolean testOptionalModuleSpringConfiguration(ModuleGroup optionalModuleGroup, String springConfigFiles, StringBuffer errorMessage) {
238            try {
239                // update the configuration.properties file
240                Properties configProps = new Properties();
241                configProps.load( new FileInputStream( configPropertiesFile ) );
242                configProps.setProperty( "spring.source.files", springConfigFiles );
243                configProps.setProperty( "spring.test.files", coreSpringTestFiles );
244                configProps.setProperty( "validate.ebo.references", "false" );
245                configProps.store( new FileOutputStream( configPropertiesFile ), "Testing Module: " + optionalModuleGroup.namespaceCode );
246                configProps.load( new FileInputStream( configPropertiesFile ) );
247                SpringContext.initializeTestApplicationContext();
248                dwrTestSucceeded &= testDwrModuleConfiguration(optionalModuleGroup, dwrErrorMessage);
249                ddTestSucceeded &= testDdModuleConfiguration(optionalModuleGroup, ddErrorMessage);
250                return true;
251            } catch (Exception e) {
252                errorMessage.append("\n\n").append(optionalModuleGroup.namespaceCode).append("\n\t").append(e.getMessage());
253                dwrErrorMessage.append( "\n\n" + optionalModuleGroup.namespaceCode + " : Unable to test due to Spring test failure." );
254                ddErrorMessage.append( "\n\n" + optionalModuleGroup.namespaceCode + " : Unable to test due to Spring test failure." );
255                ddTestSucceeded &= false;
256                dwrTestSucceeded &= false;
257                e.printStackTrace();
258                return false;
259            }
260            finally {
261                stopSpringContext();
262            }
263        }
264    
265        public boolean testOjb() throws Exception {
266            boolean testSucceeded = true;
267            StringBuffer errorMessage = new StringBuffer("The following optional modules have interdependencies in OJB configuration:");
268            List<ModuleGroup> allModuleGroups = retrieveModuleGroups();
269            for (ModuleGroup moduleGroup : allModuleGroups) {
270                testSucceeded = testSucceeded & testOptionalModuleOjbConfiguration(moduleGroup, errorMessage);
271            }
272            if (!testSucceeded) {
273                System.out.print(errorMessage.append("\n\n").toString());
274            }
275            return testSucceeded;
276        }
277    
278        protected boolean testOptionalModuleOjbConfiguration(ModuleGroup moduleGroup, StringBuffer errorMessage) throws FileNotFoundException {
279            boolean testSucceeded = true;
280            for (String referencedNamespaceCode : OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.keySet()) {
281                if (!(moduleGroup.namespaceCode.equals(referencedNamespaceCode) || moduleGroup.optionalModuleDependencyNamespaceCodes.contains(referencedNamespaceCode))) {
282                    if ( OJB_FILES_BY_MODULE.get(moduleGroup.namespaceCode).isEmpty() ) continue;
283                    String firstDatabaseRepositoryFilePath = OJB_FILES_BY_MODULE.get(moduleGroup.namespaceCode).iterator().next();
284                    // the first database repository file path is typically the file that comes shipped with KFS.  If institutions override it, this unit test will not test them
285                    Scanner scanner = new Scanner(new File("work/src/" + firstDatabaseRepositoryFilePath));
286                    int count = 0;
287                    while (scanner.hasNext()) {
288                        String token = scanner.next();
289                        String firstPackagePrefix = PACKAGE_PREFIXES_BY_MODULE.get( referencedNamespaceCode ).iterator().next();
290                        // A module may be responsible for many packages, but the first one should be the KFS built-in package that is *not* the module's integration package
291                        if (token.contains(firstPackagePrefix)) {
292                            count++;
293                        }
294                    }
295                    if (count > 0) {
296                        if (testSucceeded) {
297                            testSucceeded = false;
298                            errorMessage.append("\n").append(moduleGroup.namespaceCode).append(": ");
299                        }
300                        else {
301                            errorMessage.append(", ");
302                        }
303                        errorMessage.append(count).append(" references to ").append(referencedNamespaceCode);
304                    }
305                }
306            }
307            return testSucceeded;
308        }
309        
310        protected boolean testDwr() throws Exception {
311            if (!dwrTestSucceeded) {
312                System.out.print(dwrErrorMessage.append("\n\n").toString());
313            }
314            return dwrTestSucceeded;
315        }
316        
317        protected boolean testDwrModuleConfiguration(ModuleGroup moduleGroup, StringBuffer errorMessage) throws Exception {
318            List<String> dwrFiles = DWR_FILES_BY_MODULE.get(moduleGroup.namespaceCode);
319            boolean testSucceeded = true;
320            if (dwrFiles != null && dwrFiles.size() > 0) {
321                // the DWR file delivered with KFS (i.e. the base) should be the first element of the list
322                String baseDwrFileName = dwrFiles.get(0);
323                Document dwrDocument = generateDwrConfigDocument(baseDwrFileName);
324                testSucceeded = testDwrModuleConfiguration(baseDwrFileName, dwrDocument, moduleGroup, errorMessage);
325            }
326            return testSucceeded;
327        }
328        
329        protected boolean testDwrModuleConfiguration(String dwrFileName, Document dwrDocument, ModuleGroup moduleGroup, StringBuffer errorMessage) throws Exception {
330           boolean beanClassNamesOK = testDwrBeanClassNames(dwrFileName, dwrDocument, moduleGroup, errorMessage);
331           boolean springServicesOK = testDwrSpringServices(dwrFileName, dwrDocument, moduleGroup, errorMessage);
332           return beanClassNamesOK && springServicesOK;
333        }
334        
335        protected boolean testDwrBeanClassNames(String dwrFileName, Document dwrDocument, ModuleGroup moduleGroup, StringBuffer errorMessage) {
336            boolean testSucceeded = true;
337            List<String> dwrBeanClassNames = retrieveDwrBeanClassNames(dwrDocument);
338            for (String referencedNamespaceCode : OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.keySet()) {
339                if (!(referencedNamespaceCode.equals(moduleGroup.namespaceCode) || moduleGroup.optionalModuleDependencyNamespaceCodes.contains(referencedNamespaceCode))) {
340                    String firstPackagePrefix = PACKAGE_PREFIXES_BY_MODULE.get(referencedNamespaceCode).iterator().next();
341                    // A module may be responsible for many packages, but the first one should be the KFS built-in package that is *not* the module's integration package
342                    if (!firstPackagePrefix.endsWith(".")) {
343                        firstPackagePrefix = firstPackagePrefix + ".";
344                    }
345                    int count = 0;
346                    for (String className : dwrBeanClassNames) {
347                        if (className.contains(firstPackagePrefix)) {
348                            count++;
349                        }
350                    }
351                    if (count > 0) {
352                        testSucceeded = false;
353                        errorMessage.append("\n\n").append(dwrFileName).append(" (in module ").append(moduleGroup.namespaceCode).append(") has ").append(count).append(" references to business objects from ").append(referencedNamespaceCode);
354                    }
355                }
356            }
357            return testSucceeded;
358        }
359        
360        protected boolean testDwrSpringServices(String dwrFileName, Document dwrDocument, ModuleGroup moduleGroup, StringBuffer errorMessage) {
361            boolean testSucceeded = true;
362            
363            try {
364                List<String> serviceNames = retrieveDwrServiceNames(dwrDocument);
365                for (String serviceName : serviceNames) {
366                    try {
367                        SpringContext.getBean(serviceName);
368                    } catch ( Exception ex ) {
369                        testSucceeded = false;
370                        errorMessage.append("\n")
371                                .append(dwrFileName)
372                                .append(" (in module ")
373                                .append(moduleGroup.namespaceCode)
374                                .append(") has references to spring bean \"")
375                                .append(serviceName).append("\" that is not defined in the available spring files");
376                    }
377                }
378            }
379            catch (Exception e) {
380                errorMessage.append("\n").append(moduleGroup.namespaceCode).append("\n\t").append(e.getMessage());
381                e.printStackTrace();
382                return testSucceeded = false;
383            }
384            
385            return testSucceeded;
386        }
387    
388        
389        protected Document generateDwrConfigDocument(String fileName) throws Exception {
390            DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader());
391            InputStream in = resourceLoader.getResource(fileName).getInputStream();
392            
393            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
394            dbf.setValidating(true);
395    
396            DocumentBuilder db = dbf.newDocumentBuilder();
397            db.setEntityResolver(new DTDEntityResolver());
398            db.setErrorHandler(new LogErrorHandler());
399    
400            Document doc = db.parse(in);
401            return doc;
402        }
403        
404        protected List<String> retrieveDwrServiceNames(Document dwrDocument) {
405            List<String> serviceNames = new ArrayList<String>();
406            // service names are in "create" elements
407            Element root = dwrDocument.getDocumentElement();
408            NodeList allows = root.getElementsByTagName("allow");
409            for (int i = 0; i < allows.getLength(); i++) {
410                Element allowElement = (Element) allows.item(i);
411                NodeList creates = allowElement.getElementsByTagName("create");
412                for (int j = 0; j < creates.getLength(); j++) {
413                    Element createElement = (Element) creates.item(j);
414                    if ("spring".equals(createElement.getAttribute("creator"))) {
415                        NodeList params = createElement.getElementsByTagName("param");
416                        for (int k = 0; k < params.getLength(); k++) {
417                            Element paramElement = (Element) params.item(k);
418                            if ("beanName".equals(paramElement.getAttribute("name"))) {
419                                serviceNames.add(paramElement.getAttribute("value"));
420                            }
421                        }
422                    }
423                    
424                }
425            }
426            return serviceNames;
427        }
428        
429        protected List<String> retrieveDwrBeanClassNames(Document dwrDocument) {
430            List<String> classNames = new ArrayList<String>();
431            // class names are in "convert" elements
432            Element root = dwrDocument.getDocumentElement();
433            NodeList allows = root.getElementsByTagName("allow");
434            for (int i = 0; i < allows.getLength(); i++) {
435                Element allowElement = (Element) allows.item(i);
436                NodeList converts = allowElement.getElementsByTagName("convert");
437                for (int j = 0; j < converts.getLength(); j++) {
438                    Element convertElement = (Element) converts.item(j);
439                    if ("bean".equals(convertElement.getAttribute("converter"))) {
440                        classNames.add(convertElement.getAttribute("match"));
441                    }
442                }
443            }
444            return classNames;
445        }
446        
447        protected boolean testDd() throws Exception {
448            if (!ddTestSucceeded) {
449                System.out.print(ddErrorMessage.append("\n\n").toString());
450            }
451            return ddTestSucceeded;
452        }
453        
454        protected boolean testDdModuleConfiguration( ModuleGroup moduleGroup, StringBuffer errorMessage ) {
455            boolean testPassed = true;
456            
457            List<String> disallowedPackagesForModule = new ArrayList<String>();
458            for ( String otherNamespace : PACKAGE_PREFIXES_BY_MODULE.keySet() ) {
459                // if an optional module
460                if ( OPTIONAL_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.containsKey( otherNamespace ) ) {
461                    // and not the current module
462                    if ( !otherNamespace.equals( moduleGroup.namespaceCode ) ) {
463                        // add to disallowed list
464                        disallowedPackagesForModule.addAll( PACKAGE_PREFIXES_BY_MODULE.get(otherNamespace) );
465                    }
466                }
467            }
468            System.out.println( "---Processing DD for Module: " + moduleGroup.namespaceCode );
469            System.out.println( "---Disallowed packages: " + disallowedPackagesForModule );
470            DataDictionary dd = SpringContext.getBean(DataDictionaryService.class).getDataDictionary();
471            Collection<BusinessObjectEntry> bos = dd.getBusinessObjectEntries().values();
472            for ( BusinessObjectEntry bo : bos ) {
473                // only check bos for the current module (or all modules if checking the core)
474                if ( ("KFS-SYS".equals( moduleGroup.namespaceCode) 
475                        || doesPackagePrefixMatch( bo.getFullClassName(), PACKAGE_PREFIXES_BY_MODULE.get( moduleGroup.namespaceCode ) ))
476                        && !bo.getFullClassName().startsWith("org.kuali.rice") ) {                               
477                    try {
478                        if ( bo.getInactivationBlockingDefinitions() != null ) {
479                            for ( InactivationBlockingDefinition ibd : bo.getInactivationBlockingDefinitions() ) {
480                                validateDdBusinessObjectClassReference("Invalid Blocked BO Class", ibd.getBlockedBusinessObjectClass(), moduleGroup.namespaceCode, bo.getFullClassName(), null, disallowedPackagesForModule);
481                                validateDdBusinessObjectClassReference("Invalid Blocking Reference BO Class", ibd.getBlockingReferenceBusinessObjectClass(), moduleGroup.namespaceCode, bo.getFullClassName(), null, disallowedPackagesForModule);
482                                if ( ibd.getInactivationBlockingDetectionServiceBeanName() != null ) {
483                                    try {
484                                        SpringContext.getBean( ibd.getInactivationBlockingDetectionServiceBeanName() );
485                                    } catch (Exception ex ) {
486                                        addDdBusinessObjectError("Invalid inactivation blocking service", moduleGroup.namespaceCode, bo.getFullClassName(), null, ibd.getInactivationBlockingDetectionServiceBeanName());
487                                    }
488                                }
489                            }
490                        }
491                        
492                        for ( AttributeDefinition ad : bo.getAttributes() ) {
493                            try {
494                                ControlDefinition cd = ad.getControl();
495                                validateDdBusinessObjectClassReference("Invalid Formatter Class", ad.getFormatterClass(), moduleGroup.namespaceCode, bo.getFullClassName(), ad.getName(), disallowedPackagesForModule);
496                                if ( cd != null ) {
497                                    validateDdBusinessObjectClassReference("Invalid Control Value Finder", cd.getValuesFinderClass(), moduleGroup.namespaceCode, bo.getFullClassName(), ad.getName(), disallowedPackagesForModule);
498                                    validateDdBusinessObjectClassReference("Invalid BO class for KeyLabelBusinessObjectValueFinder", cd.getBusinessObjectClass(), moduleGroup.namespaceCode, bo.getFullClassName(), ad.getName(), disallowedPackagesForModule);
499                                }
500                            } catch ( Exception ex ) {
501                                addDdBusinessObjectError("Exception Testing BO", moduleGroup.namespaceCode, bo.getFullClassName(), ad.getName(), ex.getClass().getName() + " : " + ex.getMessage() );
502                                System.err.println( "Exception testing BO: " + bo.getFullClassName() + "/" + ad.getName() );
503                                ex.printStackTrace();
504                                testPassed = false;
505                            }
506                        }
507                    } catch( Exception ex ) {
508                        addDdBusinessObjectError("Exception Testing BO", moduleGroup.namespaceCode, bo.getFullClassName(), null, ex.getClass().getName() + " : " + ex.getMessage() );
509                        System.err.println( "Exception testing BO: " + bo.getFullClassName() );
510                        ex.printStackTrace();
511                        testPassed = false;
512                    }
513                }
514            }
515            
516            for ( DocumentEntry de : dd.getDocumentEntries().values() ) {
517                if ( (de instanceof MaintenanceDocumentEntry && ("KFS-SYS".equals( moduleGroup.namespaceCode) || doesPackagePrefixMatch( ((MaintenanceDocumentEntry)de).getBusinessObjectClass().getName(), PACKAGE_PREFIXES_BY_MODULE.get( moduleGroup.namespaceCode )) ))
518                        || (de instanceof TransactionalDocumentEntry && ("KFS-SYS".equals( moduleGroup.namespaceCode) || doesPackagePrefixMatch( de.getDocumentClass().getName(), PACKAGE_PREFIXES_BY_MODULE.get( moduleGroup.namespaceCode ))) ) ) {
519                    try {
520                        if ( de instanceof MaintenanceDocumentEntry ) {
521                            MaintenanceDocumentEntry mde = (MaintenanceDocumentEntry)de;
522                            validateDdDocumentClassReference("Invalid Maintainable Class", mde.getMaintainableClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
523                            for ( MaintainableSectionDefinition msd : mde.getMaintainableSections() ) {
524                                for ( MaintainableItemDefinition mid : msd.getMaintainableItems() ) {
525                                    if ( mid instanceof MaintainableCollectionDefinition ) {
526                                        testPassed &= checkMaintainableCollection(moduleGroup.namespaceCode, de.getDocumentTypeName(), (MaintainableCollectionDefinition)mid, disallowedPackagesForModule);
527                                    }
528                                    if ( mid instanceof MaintainableFieldDefinition ) {
529                                        testPassed &= checkMaintainableField( moduleGroup.namespaceCode, de.getDocumentTypeName(), (MaintainableFieldDefinition)mid, disallowedPackagesForModule);
530                                    }
531                                }
532                            }
533                        } else { // trans doc
534                            
535                        }
536                        validateDdDocumentClassReference("Invalid Business Rules Class", de.getBusinessRulesClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
537                        validateDdDocumentClassReference("Invalid DerivedValuesSetterClass", de.getDerivedValuesSetterClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
538                        validateDdDocumentClassReference("Invalid DocumentAuthorizerClass", de.getDocumentAuthorizerClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
539                        validateDdDocumentClassReference("Invalid DocumentPresentationControllerClass", de.getDocumentPresentationControllerClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
540                        validateDdDocumentClassReference("Invalid DocumentSearchGeneratorClass", de.getDocumentSearchGeneratorClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
541                        validateDdDocumentClassReference("Invalid PromptBeforeValidationClass", de.getPromptBeforeValidationClass(), moduleGroup.namespaceCode, de.getDocumentTypeName(), null, disallowedPackagesForModule);
542                    } catch ( Exception ex ) {
543                        addDdDocumentError("Exception validating Document", moduleGroup.namespaceCode, de.getDocumentTypeName(), null, ex.getClass().getName() + " : " + ex.getMessage() );
544                        System.err.println( "Exception validating Document: " + de.getDocumentTypeName() );
545                        testPassed = false;
546                    }
547                }
548            }
549            
550            return testPassed;
551        }
552        
553        protected void addDdBusinessObjectError( String errorType, String namespaceCode, String businessObjectClassName, String attributeName, String problemClassName ) {
554            ddErrorMessage.append( "\n" ).append( namespaceCode ).append( " - BO: " );
555            ddErrorMessage.append( businessObjectClassName );
556            if ( attributeName != null ) {
557                ddErrorMessage.append( " / Attrib: " ).append( attributeName );
558            }
559            ddErrorMessage.append( " / " ).append( errorType ).append( ": " ).append( problemClassName );
560        }
561    
562        protected boolean validateDdBusinessObjectClassReference( String errorType, String testClassName, String namespaceCode, String businessObjectClassName, String attributeName, List<String> disallowedPackages ) {
563            if (StringUtils.isBlank(testClassName)) {
564                return true;
565            }
566            try {
567                Class<?> testClass = Class.forName(testClassName);
568                return validateDdBusinessObjectClassReference(errorType, testClass, namespaceCode, businessObjectClassName, attributeName, disallowedPackages);
569            }
570            catch (ClassNotFoundException e) {
571                throw new RuntimeException(e);
572            }
573        }
574        
575        protected boolean validateDdBusinessObjectClassReference( String errorType, Class<? extends Object> testClass, String namespaceCode, String businessObjectClassName, String attributeName, List<String> disallowedPackages ) {
576            if ( testClass != null ) {
577                if ( doesPackagePrefixMatch(testClass.getName(), disallowedPackages) ) {
578                    addDdBusinessObjectError(errorType, namespaceCode, businessObjectClassName, attributeName, testClass.getName());
579                    return false;
580                }
581            }
582            return true;
583        }
584    
585        protected void addDdDocumentError( String errorType, String namespaceCode, String documentTypeName, String fieldName, String problemClassName ) {
586            ddErrorMessage.append( "\n" ).append( namespaceCode ).append( " - Doc: " );
587            ddErrorMessage.append( documentTypeName );
588            if ( fieldName != null ) {
589                ddErrorMessage.append( " / Field: " ).append( fieldName );
590            }
591            ddErrorMessage.append( " / " ).append( errorType ).append( ": " ).append( problemClassName );
592        }
593    
594        protected boolean validateDdDocumentClassReference( String errorType, Class<? extends Object> testClass, String namespaceCode, String documentTypeName, String fieldName, List<String> disallowedPackages ) {
595            if ( testClass != null ) {
596                if ( doesPackagePrefixMatch(testClass.getName(), disallowedPackages) ) {
597                    addDdDocumentError(errorType, namespaceCode, documentTypeName, fieldName, testClass.getName());
598                    return false;
599                }
600            }
601            return true;
602        }
603        
604        protected boolean checkMaintainableCollection( String namespaceCode, String documentTypeName, MaintainableCollectionDefinition collection, List<String> disallowedPackages ) {
605            boolean testPassed = true;
606            testPassed &= validateDdDocumentClassReference("Invalid Collection BO Class", collection.getBusinessObjectClass(), namespaceCode, documentTypeName, collection.getName(), disallowedPackages);
607            testPassed &= validateDdDocumentClassReference("Invalid Collection Source Class", collection.getSourceClassName(), namespaceCode, documentTypeName, collection.getName(), disallowedPackages);
608            for ( MaintainableFieldDefinition mfd : collection.getMaintainableFields() ) {
609                testPassed &= checkMaintainableField( namespaceCode, documentTypeName, mfd, disallowedPackages);
610            }
611            for ( MaintainableCollectionDefinition mcd : collection.getMaintainableCollections() ) {
612                testPassed &= checkMaintainableCollection( namespaceCode, documentTypeName, mcd, disallowedPackages);            
613            }
614            
615            return testPassed;
616        }
617        protected boolean checkMaintainableField( String namespaceCode, String documentTypeName, MaintainableFieldDefinition field, List<String> disallowedPackages ) {
618            boolean testPassed = true;
619            try {
620                testPassed &= validateDdDocumentClassReference("Invalid Default Value Finder Class", field.getDefaultValueFinderClass(), namespaceCode, documentTypeName, field.getName(), disallowedPackages);
621                testPassed &= validateDdDocumentClassReference("Invalid Override Lookup Class", field.getOverrideLookupClass(), namespaceCode, documentTypeName, field.getName(), disallowedPackages);
622            } catch ( Exception ex ) {
623                addDdDocumentError("Exception validating Maint Doc Field", namespaceCode, documentTypeName, field.getName(), ex.getClass().getName() + " : " + ex.getMessage() );
624                System.err.println( "Exception validating Maint Doc Field: " + documentTypeName + "/" + field.getName() );
625                ex.printStackTrace();
626                testPassed = false;
627            }
628            return testPassed;
629        }
630        
631        protected boolean doesPackagePrefixMatch( String className, List<String> packagePrefixList ) {
632            for ( String pkg : packagePrefixList ) {
633                if ( className.startsWith(pkg) ) {
634                    return true;
635                }
636            }
637            return false;
638        }        
639        
640        public class ModuleGroup {
641            public String namespaceCode;
642            public HashSet<String> optionalModuleDependencyNamespaceCodes = new HashSet<String>();
643            
644            public ModuleGroup() {
645                // TODO Auto-generated constructor stub
646            }
647            
648            public ModuleGroup( String namespaceCode ) {
649                this.namespaceCode = namespaceCode;
650            }
651            
652            @Override
653            public String toString() {
654                return namespaceCode + (optionalModuleDependencyNamespaceCodes.isEmpty()?"":(" - depends on: " + optionalModuleDependencyNamespaceCodes));
655            }
656        }
657        
658        public List<ModuleGroup> retrieveModuleGroups() throws Exception {
659            List<ModuleGroup> moduleGroups = new ArrayList<ModuleGroup>();
660            
661            for (String systemNamespaceCode : SYSTEM_NAMESPACE_CODES_TO_SPRING_FILE_SUFFIX.keySet()) {
662                ModuleGroup systemModuleGroup = new ModuleGroup();
663                systemModuleGroup.namespaceCode = systemNamespaceCode;
664                moduleGroups.add(systemModuleGroup);
665            }
666            
667            moduleGroups.addAll(retrieveOptionalModuleGroups());
668            
669            return moduleGroups;
670        }
671        
672        public List<ModuleGroup> retrieveOptionalModuleGroups() throws Exception {
673            Document designXmlDocument = getDesignXmlDocument();
674            List<Element> optionalModuleDefinitions = retrieveOptionalModuleDefinitions(designXmlDocument);
675            List<ModuleGroup> optionalModuleGroups = new ArrayList<ModuleGroup>();
676            
677            for (Element optionalModuleDefinition : optionalModuleDefinitions) {
678                ModuleGroup optionalModuleGroup = buildOptionalModuleGroup(optionalModuleDefinition);
679                if (optionalModuleGroup != null) {
680                    optionalModuleGroups.add(optionalModuleGroup);
681                }
682            }
683            
684            return optionalModuleGroups;
685        }
686        
687        public Document getDesignXmlDocument() throws Exception {
688            DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader());
689            InputStream in = resourceLoader.getResource(DefaultResourceLoader.CLASSPATH_URL_PREFIX + "design.xml").getInputStream();
690            
691            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
692    
693            DocumentBuilder db = dbf.newDocumentBuilder();
694    
695            Document doc = db.parse(in);
696            return doc;
697        }
698        
699        public List<Element> retrieveOptionalModuleDefinitions(Document designXmlDocument) throws Exception {
700            List<Element> optionalModuleDefinitions = new ArrayList<Element>();
701            Element root = designXmlDocument.getDocumentElement();
702            
703            // in the design.xml file, an optional module/package is specified by a <package> tag that does not have the needdeclarations attribute equal false
704            NodeList packages = root.getElementsByTagName("package");
705            for (int i = 0; i < packages.getLength(); i++) {
706                Element packageElement = (Element) packages.item(i);
707                if (!"false".equals(packageElement.getAttribute("needdeclarations"))) {
708                    optionalModuleDefinitions.add(packageElement);
709                }
710            }
711            return optionalModuleDefinitions;
712        }
713        
714        public ModuleGroup buildOptionalModuleGroup(Element optionalPackageElement) {
715            ModuleGroup moduleGroup = null;
716            if (OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.containsKey(optionalPackageElement.getAttribute("name"))) {
717                moduleGroup = new ModuleGroup();
718                moduleGroup.namespaceCode = OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.get(optionalPackageElement.getAttribute("name"));
719                if (StringUtils.isNotBlank(optionalPackageElement.getAttribute("depends"))) {
720                    if (OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.containsKey(optionalPackageElement.getAttribute("depends"))) {
721                        moduleGroup.optionalModuleDependencyNamespaceCodes.add(OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.get(optionalPackageElement.getAttribute("depends")));
722                    }
723                }
724                NodeList dependsElements = optionalPackageElement.getElementsByTagName("depends");
725                for (int i = 0; i < dependsElements.getLength(); i++) {
726                    Element dependsElement = (Element) dependsElements.item(i);
727                    if (OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.containsKey(StringUtils.trim(dependsElement.getTextContent()))) {
728                        moduleGroup.optionalModuleDependencyNamespaceCodes.add(OPTIONAL_SPRING_FILE_SUFFIX_TO_NAMESPACE_CODES.get(StringUtils.trim(dependsElement.getTextContent())));
729                    }
730                }
731            }
732            return moduleGroup;
733        }
734        
735        protected static void stopSpringContext() {
736            try {
737                SpringContext.close();
738            } catch (Exception e) {
739                System.out.println("Caught exception shutting down spring");
740                e.printStackTrace();
741            }
742        }
743    }