Overview

From KFSMI-5945:

  1. Lookup Definition: Fields added to the existing list of search results are added after the active indicator which should always be at the end of the list. Please change the logic to place active indicator at the bottom of the list if merge=true is used.
  2. Inquiry Definition: Fields added to the existing list on an inquiry are placed in a new tab if merge=true is used. Change the logic to place the additional fields in the same tab as the original list of fields by default, but make sure to place the active indicator at the bottom of the list.

The UC Davis KFS team developed a solution which allows client developers to add institutional fields to Inquiry Definitions and Lookup Definitions and place those fields before, after, or in between out-of-the-box fields (the box being KFS, KC, or other Rice client applications). See comments by Jonathan in KULRICE-4513

Implementation in Rice 1.0.3

  1. Create Bean Override: Used to perform overrides on the content of a Data Dictionary bean.
    public interface BeanOverride {
       /**
         * Return the name of the bean to perform the override.
         * @return
         */
        public String getBeanName();   
        /**
         * Returns the list of fields to perform the override.
         */
        public List<FieldOverride> getFieldOverrides();
        
        /**
         * Perform the override logic on the specific bean.
         */
        public void performOverride(Object bean); 
    }
    
  2. Create Field Override: Used to override properties in a Data Dictionary bean.
    public interface FieldOverride {
       /**
         * Return the property name to perform the override.
         * @return
         */
        public String getPropertyName();
        /**
         * perform the override.
         * 
         * @param bean
         * @param property
         * @return
         */
        public Object performFieldOverride(Object bean, Object property);
    }
    
  3. Declare beans as Data Dictionary Base Types
    ...
    <bean id="DataDictionaryBeanOverride" class="org.kuali.rice.kns.datadictionary.impl.BeanOverrideImpl" abstract="true">
        <property name="beanName" value="" />
        <property name="fieldOverrides">
            <list />
        </property>
    </bean>
      		
    <bean id="FieldOverrideForValueReplace" class="org.kuali.rice.kns.datadictionary.impl.FieldOverrideForValueReplaceImpl" abstract="true">
        <property name="propertyName" value="" />
        <property name="value" value="" />
    </bean>  		
    
    <bean id="FieldOverrideForListElementDelete" class="org.kuali.rice.kns.datadictionary.impl.FieldOverrideForListElementDeleteImpl" abstract="true">
        <property name="propertyNameForElementCompare" value="name" />
        <property name="propertyName" value="" />
        <property name="element" value="" />
    </bean>  		
    
    <bean id="FieldOverrideForListElementInsert" class="org.kuali.rice.kns.datadictionary.impl.FieldOverrideForListElementInsertImpl" abstract="true">
        <property name="propertyNameForElementCompare" value="name" />
        <property name="propertyName"><null /></property>
        <property name="element"><null /></property>
        <property name="insertAfter"><null /></property>
        <property name="insertBefore"><null /></property>
    </bean>  		
    
    <bean id="FieldOverrideForListElementReplace" class="org.kuali.rice.kns.datadictionary.impl.FieldOverrideForListElementReplaceImpl" abstract="true">    
        <property name="propertyNameForElementCompare" value="name" />
        <property name="propertyName"><null /></property>
        <property name="element"><null /></property>
        <property name="replaceWith"><null /></property>
    </bean>
    
  4. Add a performOverrides() method to DataDictionary.
    ...
    public void performBeanOverrides()
        {
        	Collection<BeanOverride> beanOverrides = ddBeans.getBeansOfType(BeanOverride.class).values();
        	
        	if (beanOverrides.isEmpty()){
        		LOG.info("DataDictionary.performOverrides(): No beans to override");
        	}
    		for (BeanOverride beanOverride : beanOverrides) {
    			
    			Object bean = ddBeans.getBean(beanOverride.getBeanName());
    			beanOverride.performOverride(bean);
    			LOG.info("DataDictionary.performOverrides(): Performing override on bean: " + bean.toString());
    		}
        }
    ...
    
  5. Modify KNS Configurer to call performOverrides() after the Data Dictionary has been loaded and validated.
    ...
    public void onEvent(RiceConfigEvent event) throws Exception {
        if (event instanceof AfterStartEvent) {
            if (isLoadDataDictionary()) {
                LOG.info("KNS Configurer - Loading DD");
                DataDictionaryService dds = KNSServiceLocator.getDataDictionaryService();
                if ( dds == null ) {
                    dds = (DataDictionaryService)RiceResourceLoaderFactory.getSpringResourceLoader().getContext().getBean( KNSServiceLocator.DATA_DICTIONARY_SERVICE );
                }
                dds.getDataDictionary().parseDataDictionaryConfigurationFiles(false);
    
                if ( isValidateDataDictionary() ) {
                    LOG.info("KNS Configurer - Validating DD");
                    dds.getDataDictionary().validateDD( validateDataDictionaryEboReferences );
                }
                // KULRICE-4513 After the Data Dictionary is loaded and validated, perform Data Dictionary bean overrides.
                dds.getDataDictionary().performBeanOverrides();
            }
            KNSServiceLocator.getDateTimeService().initializeDateTimeService();
        }
    }
    

Implementation in Travel Application 1.0.3

Scenario

  1. We want to add a field called First Name to Fiscal Officer.
  2. We want this First Name rendered between ID and User Name in the lookup, result and inquiry screens.

Implementation

  1. Modify the class descriptor of TRV_ACCT_FO.
    <descriptor-repository>
    ...
        <class-descriptor class="edu.sampleu.travel.bo.FiscalOfficer" table="TRV_ACCT_FO">
    	<field-descriptor name="id" column="acct_fo_id" jdbc-type="BIGINT" primarykey="true" autoincrement="true" sequence-name="TRV_FO_ID_S" />
    	<field-descriptor name="userName" column="acct_fo_user_name" jdbc-type="VARCHAR" />
    	<field-descriptor name="firstName" column="acct_fo_user_name" jdbc-type="VARCHAR" />
            ...
    </class-descriptor>
    ...
    </descriptor-repository>
    
  2. Add support in FiscalOfficer object.
    public class FiscalOfficer extends PersistableBusinessObjectBase {
    ...
        @Column(name="acct_fo_user_name")
        private String firstName;
    ...
        public String getFirstName() {
    	return this.firstName;
        }
        public void setFirstName(String firstName) {
    	this.firstName = firstName;
        }
    ...
    

    In this implementation, we created a member called firstName but mapped it to the same column as username.
    Optionally, we can:

    1. Add column ACCT_FO_FIRST_NAME to table TRV_ACCT_FO and populate.
    2. Modify the class descriptor of TRV_ACCT_FO
      ...
      	<field-descriptor name="firstName" column="acct_fo_first_name" jdbc-type="VARCHAR" />
      ...
      
    3. Add support in FiscalOfficer object.
      public class FiscalOfficer extends PersistableBusinessObjectBase {
      ...
          @Column(name="acct_fo_first_name")
          private String firstName;
      ...
      
  3. Create a Data Dictionary entry for First Name (in general, any additional entries to existing ones). This is where we use FieldOverride to place First Name where we want.
    <beans>
      
      <bean id="FiscalOfficer" parent="FiscalOfficer-parentBean">
        <property name="attributes">
          <list merge="true">
            <ref bean="FiscalOfficer-firstName"/>
          </list>
        </property>
      </bean>
      
      <bean id="FiscalOfficer-firstName" parent="FiscalOfficer-firstName-parentBean"/>
    
      <bean id="FiscalOfficer-firstName-parentBean" abstract="true" parent="AttributeDefinition">
        <property name="forceUppercase" value="false"/>
        <property name="shortLabel" value="First Name"/>
        <property name="maxLength" value="30"/>
        <property name="validationPattern">
          <bean parent="AnyCharacterValidationPattern"/>
        </property>
        <property name="control">
          <bean parent="TextControlDefinition" p:size="30"/>
        </property>
        <property name="summary" value="First Name"/>
        <property name="name" value="firstName"/>
        <property name="label" value="First Name"/>
        <property name="description" value="First Name"/>
      </bean>
    
      <bean id="FiscalOfficer-inquiryDefinition-override" parent="DataDictionaryBeanOverride">
          <property name="beanName" value="FiscalOfficer-inquiryDefinition" />
              <property name="fieldOverrides">
      	      <list>
                      <bean parent="FieldOverrideForListElementInsert" >
      		  <!-- Place First Name in the "Primary Info" section (inquirySections[0]) -->
      		      <property name="propertyName" value="inquirySections[0].inquiryFields" />
    		      <property name="propertyNameForElementCompare" value="attributeName" />
      		      <property name="element">
    			  <bean parent="FieldDefinition" p:attributeName="id" />
      		      </property>
      		      <property name="insertAfter">
      		          <list>			
    			      <bean parent="FieldDefinition" p:attributeName="firstName" />
    			  </list>	        			
      		      </property>
      		   </bean>
                  </list>
          </property>
      </bean>
      
      <bean id="FiscalOfficer-lookupDefinition-override" parent="DataDictionaryBeanOverride">
          <property name="beanName" value="FiscalOfficer-lookupDefinition" />
          <property name="fieldOverrides">
               <list>
      	       <!-- Place First Name lookup field under ID -->
      	       <bean parent="FieldOverrideForListElementInsert" >
      	           <property name="propertyName" value="lookupFields" />
    		   <property name="propertyNameForElementCompare" value="attributeName" />
      		   <property name="element">
    		       <bean parent="FieldDefinition" p:attributeName="id" />
      		   </property>
      		   <property name="insertAfter">
      		       <list>
      		           <bean parent="FieldDefinition" p:attributeName="firstName" />
    		       </list>	        			
      		   </property>
      	       </bean>
      	       <!-- Place First Name column after ID -->
      	       <bean parent="FieldOverrideForListElementInsert" >
      	           <property name="propertyName" value="resultFields" />
    		   <property name="propertyNameForElementCompare" value="attributeName" />
      		   <property name="element">
    		       <bean parent="FieldDefinition" p:attributeName="id" />
      		   </property>
      	           <property name="insertAfter">
      		       <list>
    		           <bean parent="FieldDefinition" p:attributeName="firstName" />
    		       </list>	        			
      		   </property>
      		</bean>
      	   </list>
           </property>
      </bean>
    
    </beans>
    

    Notice that the bean FiscalOfficer uses <list merge="true"> to "append" firstName to the Data Dictionary entry of the same name in the "out-of-the-box" FiscalOfficer.xml file.

  4. Declare the new Data Dictionary entries in the Module Configuration. This will be injected into the Travel application's Module Configurer.
    <beans>
    	 	  
        <bean id="sampleAppModuleConfiguration" class="org.kuali.rice.kns.bo.ModuleConfiguration">
            <property name="namespaceCode" value="tv"/>
            <property name="initializeDataDictionary" value="true"/>
            <property name="dataDictionaryPackages">
                <list>
                    <value>classpath:edu/sampleu/travel/datadictionary/FiscalOfficer.xml</value>
                    <value>classpath:edu/sampleu/travel/datadictionary/FiscalOfficer-Extension.xml</value>
                    ...
                </list>
            </property>
            ...
        </bean>
        ...
    </beans>
    

Without Bean Overrides

Lookup and Results

Inquiry

Bean Overrides at Work

2010-09-30 16:41:52,203 [main] u:/d: INFO  org.kuali.rice.kns.config.KNSConfigurer - KNS Configurer - Loading DD
2010-09-30 16:41:52,203 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionary - Starting DD XML File Load
2010-09-30 16:41:55,500 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionary - Completed DD XML File Load
2010-09-30 16:41:55,500 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionaryIndex - Starting DD Index Building
2010-09-30 16:41:56,546 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionaryIndex - Completed DD Index Building
2010-09-30 16:41:56,546 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionaryIndex - Started DD Inactivation Blocking Index Building
2010-09-30 16:41:56,562 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionaryIndex - Completed DD Inactivation Blocking Index Building
2010-09-30 16:41:56,562 [main] u:/d: INFO  org.kuali.rice.kns.config.KNSConfigurer - KNS Configurer - Validating DD
2010-09-30 16:41:56,937 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionary - DataDictionary.performOverrides(): Performing override on bean: InquiryDefinition 'Travel Fiscal Officer Inquiry'
2010-09-30 16:41:56,953 [main] u:/d: INFO  org.kuali.rice.kns.datadictionary.DataDictionary - DataDictionary.performOverrides(): Performing override on bean: LookupDefinition 'Travel Fiscal Officer Lookup'

Lookup and Results

Inquiry