/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys.context; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.kfs.sys.ConfigureContext; import org.kuali.kfs.sys.document.datadictionary.FinancialSystemMaintenanceDocumentEntry; import org.kuali.kfs.sys.suite.AnnotationTestSuite; import org.kuali.kfs.sys.suite.PreCommitSuite; import org.kuali.rice.core.api.mo.common.active.MutableInactivatable; import org.kuali.rice.kns.datadictionary.BusinessObjectEntry; import org.kuali.rice.kns.datadictionary.InquiryDefinition; import org.kuali.rice.kns.datadictionary.InquirySectionDefinition; import org.kuali.rice.kns.datadictionary.LookupDefinition; import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition; import org.kuali.rice.krad.datadictionary.AttributeDefinition; import org.kuali.rice.krad.datadictionary.DataDictionary; import org.kuali.rice.krad.datadictionary.DocumentEntry; import org.kuali.rice.krad.service.DataDictionaryService; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory; @AnnotationTestSuite(PreCommitSuite.class) @ConfigureContext public class DataDictionaryConfigurationTest extends KualiTestBase { private static final Logger LOG = Logger.getLogger(DataDictionaryConfigurationTest.class); private DataDictionary dataDictionary; public final static String KFS_PACKAGE_NAME_PREFIX = "org.kuali.kfs"; public final static String BUSINESS_OBJECT_PATH_QUALIFIER = "businessobject/datadictionary"; public final static String DOCUMENT_PATH_QUALIFIER = "document/datadictionary"; public final static String RICE_PACKAGE_NAME_PREFIX = "org.kuali.rice"; public final static String INACTIVATEABLE_INTERFACE_CLASS = MutableInactivatable.class.getName(); public final static String ACTIVE_FIELD_NAME = "active"; public void testAllDataDictionaryDocumentTypesExistInWorkflowDocumentTypeTable() throws Exception { HashSet<String> workflowDocumentTypeNames = new HashSet<String>(); DataSource mySource = SpringContext.getBean(DataSource.class); Connection dbCon = null; try { dbCon = mySource.getConnection(); Statement dbAsk = dbCon.createStatement(); ResultSet dbAnswer = dbAsk.executeQuery("select DOC_TYP_NM from KREW_DOC_TYP_T where CUR_IND = 1"); while (dbAnswer.next()) { String docName = dbAnswer.getString(1); if (StringUtils.isNotBlank(docName)) { workflowDocumentTypeNames.add(docName); } } } catch (Exception e) { throw (e); } // Using HashSet since duplicate objects would otherwise be returned HashSet<DocumentEntry> documentEntries = new HashSet<DocumentEntry>(dataDictionary.getDocumentEntries().values()); List<String> ddEntriesWithMissingTypes = new ArrayList<String>(); for (DocumentEntry documentEntry : documentEntries) { String name = documentEntry.getDocumentTypeName(); String testName = new String(" "); if (documentEntry instanceof FinancialSystemMaintenanceDocumentEntry){ testName=((FinancialSystemMaintenanceDocumentEntry)documentEntry).getBusinessObjectClass().getName(); }else{ testName=documentEntry.getDocumentClass().getName(); } if (!workflowDocumentTypeNames.contains(name) && !"RiceUserMaintenanceDocument".equals(name) && !testName.contains("rice")) { ddEntriesWithMissingTypes.add(name); } else { workflowDocumentTypeNames.remove(name); } } if (workflowDocumentTypeNames.size() > 0) { try{ //If documents are parent docs, then they aren't superfluous. String queryString = "select distinct doc_typ_nm from KREW_DOC_TYP_T" +" where doc_typ_id in (select parnt_id from KREW_DOC_TYP_T" +" where actv_ind = 1" +" and cur_ind = 1)"; Statement dbAsk = dbCon.createStatement(); ResultSet dbAnswer = dbAsk.executeQuery(queryString); while (dbAnswer.next()) { String docName = dbAnswer.getString(1); if (StringUtils.isNotBlank(docName)) { workflowDocumentTypeNames.remove(docName); } } }catch (Exception e){ throw (e); } System.err.print("superfluousTypesDefinedInWorkflowDatabase: " + workflowDocumentTypeNames); } assertEquals("documentTypesNotDefinedInWorkflowDatabase: " + ddEntriesWithMissingTypes, 0, ddEntriesWithMissingTypes.size()); } private final static List<String> INACTIVATEABLE_LOOKUP_IGNORE_CLASSES = new ArrayList<String>(); static { // org.kuali.kfs.coa.businessobject.Account is excepted from testActiveFieldExistInLookupAndResultSection because it uses the active-derived Closed? indicator instead (KFSMI-1393) INACTIVATEABLE_LOOKUP_IGNORE_CLASSES.add( "org.kuali.kfs.coa.businessobject.Account" ); INACTIVATEABLE_LOOKUP_IGNORE_CLASSES.add( "org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition" ); INACTIVATEABLE_LOOKUP_IGNORE_CLASSES.add( "org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding" ); } private static final List<String> INACTIVATEABLE_LOOKUP_IGNORE_PACKAGES = new ArrayList<String>(); static { INACTIVATEABLE_LOOKUP_IGNORE_PACKAGES.add( "org.kuali.kfs.pdp.businessobject" ); INACTIVATEABLE_LOOKUP_IGNORE_PACKAGES.add( "org.kuali.kfs.module.external.kc.businessobject" ); } public void testActiveFieldExistInLookupAndResultSection() throws Exception{ List<Class<?>> noActiveFieldClassList = new ArrayList<Class<?>>(); List<Class<?>> notImplementInactivatableList = new ArrayList<Class<?>>(); List<Class<?>> defaultValueWrongList = new ArrayList<Class<?>>(); for(org.kuali.rice.krad.datadictionary.BusinessObjectEntry kradBusinessObjectEntry:dataDictionary.getBusinessObjectEntries().values()){ BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) kradBusinessObjectEntry; if ( !businessObjectEntry.getBusinessObjectClass().getName().startsWith(RICE_PACKAGE_NAME_PREFIX) && !INACTIVATEABLE_LOOKUP_IGNORE_CLASSES.contains(businessObjectEntry.getBusinessObjectClass().getName()) && !INACTIVATEABLE_LOOKUP_IGNORE_PACKAGES.contains(businessObjectEntry.getBusinessObjectClass().getPackage().getName()) ) { try { LookupDefinition lookupDefinition = businessObjectEntry.getLookupDefinition(); // Class implements MutableInactivatable but active field not used on Lookup. if(Class.forName(INACTIVATEABLE_INTERFACE_CLASS).isAssignableFrom(businessObjectEntry.getBusinessObjectClass())) { if(lookupDefinition != null && !(lookupDefinition.getLookupFieldNames().contains(ACTIVE_FIELD_NAME) && lookupDefinition.getResultFieldNames().contains(ACTIVE_FIELD_NAME))){ noActiveFieldClassList.add(businessObjectEntry.getBusinessObjectClass()); if ( lookupDefinition.getLookupField(ACTIVE_FIELD_NAME) != null ) { //Default must be 'Y' not 'true' if (!StringUtils.equals(lookupDefinition.getLookupField(ACTIVE_FIELD_NAME).getDefaultValue(), "Y")) { defaultValueWrongList.add(businessObjectEntry.getBusinessObjectClass()); } } } }else{ // Lookup show active flag, but class does not implement MutableInactivatable. if(lookupDefinition != null && (lookupDefinition.getLookupFieldNames().contains(ACTIVE_FIELD_NAME) || lookupDefinition.getResultFieldNames().contains(ACTIVE_FIELD_NAME))){ notImplementInactivatableList.add(businessObjectEntry.getBusinessObjectClass()); } } } catch (ClassNotFoundException e) { throw(e); } } } String errorString = ""; if (noActiveFieldClassList.size()!=0) errorString=errorString+"Missing Active Field: "+formatErrorStringGroupByModule(noActiveFieldClassList); if (notImplementInactivatableList.size()!=0) errorString=errorString+"Inactivatable not implemented: "+formatErrorStringGroupByModule(notImplementInactivatableList); if (defaultValueWrongList.size()!=0) errorString=errorString+"Wrong default value: "+formatErrorStringGroupByModule(defaultValueWrongList); assertEquals(errorString, 0, noActiveFieldClassList.size()+notImplementInactivatableList.size()+defaultValueWrongList.size()); } private String formatErrorStringGroupByModule(List<Class<?>> failedList){ Map<String,Set<String>> listMap = new HashMap<String, Set<String>>(); String module = null; String itemName = null; for (Class<?> item :failedList){ itemName=item.getName(); module = itemName.substring(0, itemName.lastIndexOf('.')); if (!listMap.keySet().contains(module)){ listMap.put(module, new HashSet<String>()); } listMap.get(module).add(itemName.substring(itemName.lastIndexOf('.')+1)); } String tempString=""; for (String moduleName : listMap.keySet()){ tempString = tempString+"Module :"+moduleName+"\n"; for (String errorClass : (Set<String>)listMap.get(moduleName)){ tempString = tempString + " "+errorClass+"\n"; } } return "\n"+tempString; } public void testAllBusinessObjectsHaveObjectLabel() throws Exception { List<Class<?>> noObjectLabelClassList = new ArrayList<Class<?>>(); for(org.kuali.rice.krad.datadictionary.BusinessObjectEntry kradBusinessObjectEntry:dataDictionary.getBusinessObjectEntries().values()){ BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) kradBusinessObjectEntry; if (StringUtils.isBlank(businessObjectEntry.getObjectLabel())) { noObjectLabelClassList.add(businessObjectEntry.getBusinessObjectClass()); } } assertEquals(noObjectLabelClassList.toString(), 0, noObjectLabelClassList.size()); } public void testAllParentBeansAreAbstract() throws Exception { Field f = dataDictionary.getClass().getDeclaredField("ddBeans"); f.setAccessible(true); KualiDefaultListableBeanFactory ddBeans = (KualiDefaultListableBeanFactory)f.get(dataDictionary); List<String> failingBeanNames = new ArrayList<String>(); for ( String beanName : ddBeans.getBeanDefinitionNames() ) { BeanDefinition beanDef = ddBeans.getMergedBeanDefinition(beanName); String beanClass = beanDef.getBeanClassName(); // skip Rice classes if ( beanClass != null && beanClass.startsWith("org.kuali.rice") ) { continue; } if ( (beanName.endsWith("-parentBean") || beanName.endsWith("-baseBean")) && !beanDef.isAbstract() ) { failingBeanNames.add(beanName + " : " + beanDef.getResourceDescription()+"\n"); } } assertEquals( "The following parent beans are not defined as abstract:\n" + failingBeanNames, 0, failingBeanNames.size() ); } public void testBusinessObjectEntriesShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(BusinessObjectEntry.class, new ArrayList<String>()); } public void testDocumentEntriesShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(DocumentEntry.class, new ArrayList<String>()); } protected static final List<String> EXCLUDED_ATTRIBUTE_DEFINITIONS = new ArrayList<String>(); static { EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "Country-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "County-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "State-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "PostalCode-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "PersonImpl-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "RoleMemberBo-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "KimAttributes-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "KimDocRoleMember-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "DocRoleMember-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "Responsibility-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "PermissionBo-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "PermissionImpl-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "UberPermission-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "ReviewResponsibility-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "ResponsibilityImpl-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "UberPermissionBo-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "RuleTemplateAttribute-" ); EXCLUDED_ATTRIBUTE_DEFINITIONS.add( "-versionNumber" ); } public void testAttributeDefinitionsShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(AttributeDefinition.class, EXCLUDED_ATTRIBUTE_DEFINITIONS); } public void testMaintenanceSectionsShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(MaintainableSectionDefinition.class, new ArrayList<String>()); } public void testInquirySectionsShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(InquirySectionDefinition.class, new ArrayList<String>()); } public void testLookupDefinitionsShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(LookupDefinition.class, new ArrayList<String>()); } public void testInquiryDefinitionsShouldHaveParentBeans() throws Exception { somethingShouldHaveParentBeans(InquiryDefinition.class, new ArrayList<String>() ); } protected boolean doesBeanNameMatchList( String beanName, List<String> exclusions ) { for ( String excl : exclusions ) { if ( beanName.contains(excl) ) { return true; } } return false; } protected void somethingShouldHaveParentBeans( Class<?> baseClass, List<String> exclusions ) throws Exception { Field f = dataDictionary.getClass().getDeclaredField("ddBeans"); f.setAccessible(true); KualiDefaultListableBeanFactory ddBeans = (KualiDefaultListableBeanFactory)f.get(dataDictionary); List<String> failingBeanNames = new ArrayList<String>(); for ( String beanName : ddBeans.getBeanDefinitionNames() ) { if ( doesBeanNameMatchList(beanName, exclusions)) { continue ; } BeanDefinition beanDef = ddBeans.getMergedBeanDefinition(beanName); String beanClass = beanDef.getBeanClassName(); if ( beanClass == null ) { System.err.println( "ERROR: Bean " + beanName + " has a null class." ); } if ( !beanDef.isAbstract() && beanClass != null && baseClass.isAssignableFrom(Class.forName(beanClass) ) ) { try { BeanDefinition parentBean = ddBeans.getBeanDefinition(beanName + "-parentBean"); } catch ( NoSuchBeanDefinitionException ex ) { failingBeanNames.add(beanName + " : " + beanDef.getResourceDescription() +"\n"); } } } assertEquals( "The following " + baseClass.getSimpleName() + " beans do not have \"-parentBean\"s:\n" + failingBeanNames, 0, failingBeanNames.size() ); } private void reportErrorAttribute(Map<String, Set<String>> reports, AttributeDefinition attributeDefinition, String boClassName) { Set<String> attributeSet = reports.containsKey(boClassName) ? reports.get(boClassName) : new TreeSet<String>(); attributeSet.add(attributeDefinition.getName()); reports.put(boClassName, attributeSet); } private StringBuilder convertReportsAsText(Map<String, Set<String>> reports) { StringBuilder reportsAsText = new StringBuilder(); for(String key : new TreeSet<String>(reports.keySet())) { reportsAsText.append(key + "\n"); for(String value : reports.get(key)) { reportsAsText.append("\t--").append(value).append("\n"); } } return reportsAsText; } private void printReport(Map<String, Set<String>> reports) { StringBuilder reportsAsText = convertReportsAsText(reports); System.out.println(reportsAsText); LOG.info("\n" + reportsAsText); } @Override protected void setUp() throws Exception { super.setUp(); dataDictionary = SpringContext.getBean(DataDictionaryService.class).getDataDictionary(); } }