Fork me on GitHub

n. Slang a rough lawless young Kuali developer.
[perhaps variant of Houlihan, Irish surname]
kualiganism n

Blog of an rSmart Java Developer. Full of code examples, solutions, best practices, et al.

Friday, August 12, 2011

UPDATE: Kuali Days 2011 Presentation Proposals 2

The following presentation is on the bubble and may possibly be dropped.

Abstact

One of the necessities implementers have is to remotely execute batch processes. Many institutions already have existing enterprise scheduling systems that are far more robust than Quartz which is shipped with KFS. The cases for this are when implementing institutions use a third-party scheduling system (Peoplesoft or BMC). Institutions certainly will want to put in place something more robust or may even already have a campus-wide scheduling system.

This is an informative talk on how to connect an enterprise scheduling system with KFS. Attendees will see examples of how to integrate using shell-scripting and web-services.

Objectives

  • Learn about caveats of normal shell-scripting of batch processes.
  • Learn of creating a simple web service that isn't published to Kuali Service Bus (KSB) for executing isolated batch processes.
  • Learn how to create a client for communicating with the batch Web Services Definition Language (WSDL).
  • Batch processes and WS-SEC with Rice and KFS

Audience

  • Developers on a technical

I received an email today from the Kuali Foundation letting me know there are indeed a large number of proposals this year. Some very good ones will be dropped. If you have a favorite presentation or a presentation you really want to see, please let them know at the Kuali Foundation

Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 3

How the DateChangedListener looks to a 3-year old with a box of crayons


Wednesday, August 10, 2011

Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 2

Overview

I am continuing the saga on this. In Part 1, I described the scenario:
For example, checkbox toggling. What if when a check box is selected 1, 2, or even 3 other boxes are deselected. Maybe a select list is modified. Perhaps a set of checkboxes are disabled after checking a box somewhere on the form. How do we handle this? The instinctive thing to do is to write some rather messy code in the Action class.


I want to revisit this. Before I do though, a little diagram on the aforementioned Observer Pattern.



Events and Listeners

Here is the problem I ran into. I needed to add a note to my document whenever the start or end date changed. Not when it was updated, but when it was changed. This is troublesome because I will have to use Spring to access my DocumentService and the change will occur via event from the UI. Further, the properties I want to watch are on the Document. All around, this spells one huge mess. Probably means I will be either calling the SpringContext.getBean() method from the Document or from my Struts class. How about I do neither?

Here's my plan. In my Struts Action, there's already a createDocument() method that calls the DocumentService. How about I reuse this in some way? Maybe instead of using the generic DocumentService, I use my own TravelDocumentService that creates the Document for me with a newDocument() method, right? How about in this newDocument() method, I inject some services as PropertyChangeListeners? That way, my Document is making calls to transient services that are injected at creation. Sound awesome? Let's make it happen.

TravelReimbursementAction

Here's my new createDocument() method.
/*
* Copyright 2010 The Kuali Foundation.
* 
* Licensed under the Educational Community License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.opensource.org/licenses/ecl1.php
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kuali.kfs.module.tem.document.web.struts;
...
...
/***
* Action methods for the {@link TravelReimbursementDocument}
*
* @author Leo Przybylski (leo [at] rsmart.com)
*/
public class TravelReimbursementAction extends TravelActionBase {
...
...
/**
* Do initialization for a new {@link TravelReimbursementDocument}
* 
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
*/
@Override
protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
super.createDocument(kualiDocumentFormBase);
final TravelReimbursementForm travelForm = (TravelReimbursementForm) kualiDocumentFormBase;
final TravelReimbursementDocument document = (TravelReimbursementDocument) travelForm.getDocument();
getTravelDocumentService().addListenersTo(document);
addContactInformation(document);
}
...
...
}

TravelDocumentService

Ok, let's see how I inject those listeners.
/*
* Copyright 2010 The Kuali Foundation
* 
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.opensource.org/licenses/ecl2.php
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kuali.kfs.module.tem.service.impl;
...
...
/**
* Travel Reimbursement Service Implementation
* 
*/
public class TravelReimbursementServiceImpl implements TravelReimbursementService {
...
...
public void addListenersTo(final TravelReimbursementDocument reimbursement) {
reimbursement.setPropertyChangeListeners(getPropertyChangeListeners());
}
...
...
/**
* Sets the propertyChangeListener attribute value.
* 
* @param propertyChangeListener The propertyChangeListener to set.
*/
public void setPropertyChangeListeners(final List<PropertyChangeListener> propertyChangeListeners) {
this.propertyChangeListeners = propertyChangeListeners;
}

/**
* Gets the propertyChangeListeners attribute.
* 
* @return Returns the propertyChangeListenerDetailId.
*/
public List<PropertyChangeListener> getPropertyChangeListeners() {
return this.propertyChangeListeners;
}
...
...
}

So where do these PropertyChangeListeners get set? Obviously, they're injected into the service, right? But how? Let's look.

spring-tem.xml

This is where we setup our TravelDocumentService to be injected with our property change listeners.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2006-2008 The Kuali Foundation Licensed under the Educational 
Community License, Version 2.0 (the "License"); you may not use this file 
except in compliance with the License. You may obtain a copy of the License 
at http://www.opensource.org/licenses/ecl2.php Unless required by applicable 
law or agreed to in writing, software distributed under the License is distributed 
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied. See the License for the specific language governing permissions 
and limitations under the License. -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
...
...
<!-- Property Listeners -->
<bean id="TravelReimbursementDocument-dateChangedListener" class="org.kuali.kfs.module.tem.document.listener.DateChangedListener">
<property name="documentService" ref="documentService" />
</bean>
...
...
<bean id="temTravelDocumentService" parent="temTravelDocumentService-parentBean" />
<bean   id="temTravelDocumentService-parentBean" 
class="org.kuali.kfs.module.tem.service.impl.TravelDocumentServiceImpl" 
abstract="true">
<property name="propertyChangeListeners">
<list>
<ref bean="TravelReimbursementDocument-dateChangedListener" />
</list>
</property>
</bean>
...
...
</beans>

You can see that there's a dateChangedEvent and it is added as a listener to the document. Great! But wait! How does it get called?

PropertyChangeEvent

In my TravelReimbursementDocument, I'm going to trigger a PropertyChangeEvent on the listener.
/*
* Copyright 2010 The Kuali Foundation.
* 
* Licensed under the Educational Community License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.opensource.org/licenses/ecl1.php
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kuali.kfs.module.tem.document;
...
...
import java.beans.PropertyChangeEvent;
...
...
/**
* Abstract Travel Document Base
*/
public abstract class TravelDocumentBase extends AccountingDocumentBase implements TravelDocument, Copyable {
...
...
@Transient
private List<PropertyChangeListener> propertyChangeListeners;
...
...
/**
* Sets the propertyChangeListener attribute value.
* 
* @param propertyChangeListener The propertyChangeListener to set.
*/
public void setPropertyChangeListeners(final List<PropertyChangeListener> propertyChangeListeners) {
this.propertyChangeListeners = propertyChangeListeners;
}

/**
* Gets the propertyChangeListeners attribute.
* 
* @return Returns the propertyChangeListenerDetailId.
*/
public List<PropertyChangeListener> getPropertyChangeListeners() {
return this.propertyChangeListeners;
}
...
...
/**
* This method sets the trip begin date for this request
* 
* @param tripBegin
*/
public void setTripBegin(Date tripBegin) {
notifyChangeListeners(new PropertyChangeEvent(this, "tripBegin", this.tripBegin, tripBegin));
this.tripBegin = tripBegin;
}
...
...
}

This is just showing the trip begin change, but the end date is pretty much identical except it is for the tripEnd. You can see that before the change occurs, we notify the listeners. The reason for this is because I will lose the changed information after the change is made, so I have to notify my listeners before. You can see I am passing in the object being modified, the old value and the new value!

DateChangedListener

Now let's look at what happens when the date is changed
/*
* Copyright 2010 The Kuali Foundation
* 
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.opensource.org/licenses/ecl2.php
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kuali.kfs.module.tem.document.listener;
...
...
/**
* Executed when a trip date is modified
* 
* @author Leo Przybylski (leo [at] rsmart.com
*/
public class DateChangedListener implements PropertyChangeListener, java.io.Serializable {
...
...
public void propertyChange(final PropertyChangeEvent event) {
final TravelReimbursementDocument reimbursement = (TravelReimbursementDocument) event.getSource();
final Date oldDate = (Date) event.getOldValue();
final Date newDate = (Date) event.getNewValue();

if (hasDateChanged(oldDate, newDate)) {
notifyDateChangedOn(reimbursement, newDate, "tripBegin".equals(event.getPropertyName()));
}
}


protected Boolean hasDateChanged(final Date oldDate, final Date newDate) {
final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
final String oldDateStr = formatter.format(oldDate);
final String newDateStr = formatter.format(newDate);
return oldDateStr.equals(newDateStr);
}

public void notifyDateChangedOn(final TravelReimbursementDocument reimbursement, final Date newDate, boolean start) throws Exception {
final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
final String origStartDateStr  = formatter.format(reimbursement.getTripBegin());
final String origEndDateStr    = formatter.format(reimbursement.getTripEnd());
String newStartDateStr = origStartDateStr;
String newEndDateStr   = origEndDateStr;

if (start) {
newStartDateStr = formatter.format(newDate);
}
else {
newEndDateStr = formatter.format(newDate);
}

final String noteText = String.format(DATE_CHANGED_MESSAGE, origStartDateStr, origEndDateStr, newStartDateStr, newEndDateStr);

final Note noteToAdd = getDocumentService().createNoteFromDocument(reimbursement, noteText);
getDocumentService().addNoteToDocument(reimbursement, noteToAdd);
}

public void setDocumentService(final DocumentService documentService) {
this.documentService = documentService;
}

protected DocumentService getDocumentService() {
return documentService;
}
}

Conclusion

That's how you can inject services into documents that react on modifications to the Document.