<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2356890446425422475</id><updated>2012-01-03T09:04:26.223-08:00</updated><category term='logging'/><category term='impex'/><category term='kuali days'/><category term='java'/><category term='documentation'/><category term='mylyn'/><category term='2011'/><category term='howto'/><category term='programming'/><category term='development'/><category term='jstl'/><category term='jsp'/><category term='coeus'/><category term='implementation'/><category term='maven'/><category term='tomcat'/><category term='hudson'/><category term='rdbms'/><category term='overlay'/><category term='cas'/><category term='book'/><category term='blog'/><category term='kim'/><category term='kd2011'/><category term='antipattern'/><category term='kuali'/><category term='configuration management'/><category term='rpm'/><category term='oracle'/><category term='ldap'/><category term='struts'/><category term='sql'/><category term='git'/><category term='jetty'/><category term='spring'/><category term='kc'/><category term='liquibase'/><category term='performance'/><category term='eclipse'/><category term='constants'/><category term='testing'/><category term='kfs'/><category term='error'/><category term='rice'/><category term='database'/><category term='screencast'/><category term='presentations'/><title type='text'>Kualigan</title><subtitle type='html'>Blog of a Kuali Foundation Release Manager. Full of code examples, solutions, best practices, et al.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>47</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-8073299999708963891</id><published>2011-12-21T21:16:00.000-08:00</published><updated>2011-12-23T20:47:50.972-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='jstl'/><category scheme='http://www.blogger.com/atom/ns#' term='jsp'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Kuali Parameterized ValueFinder Implementation</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;Sometimes it is necessary to dynamically build a values finder based on some outside influence (sometimes another values finder). This is how to create a value finder that uses input from another source. I am going to use an example where we want to give a list of available mileages for a user to select from. We only want to show mileages to the user based on the date their travel arrangements are made during. If we don't limit in this way, the number of available mileages to choose from will be infinite and indistinguishable. What we need is a list of mileages based on a date determined by the trip information already in the form. This is how we do that.&lt;br /&gt;&lt;h2&gt;Steps&lt;/h2&gt;&lt;h3&gt;1. Create a JstlFunctions class with static methods&lt;/h3&gt;&lt;pre class="brush: java"&gt;package org.kuali.kfs.module.tem.web;&lt;br /&gt;&lt;br /&gt;import static java.lang.Class.forName;&lt;br /&gt;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;import org.apache.commons.beanutils.BeanUtils;&lt;br /&gt;import org.kuali.rice.kns.lookup.keyvalues.KeyValuesFinder;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Full of static methods for JSTL function access.&lt;br /&gt; * &lt;br /&gt; */&lt;br /&gt;public final class JstlFunctions {&lt;br /&gt;    private static final String SETTING_PARAMS_PROLOG = "Setting params ";&lt;br /&gt;    private static final String PROPERTY_SETTING_EXC_PROLOG = "Could not set property ";&lt;br /&gt;    private static final String IN_PREPOSITION = " in ";&lt;br /&gt;    private static final String VALUES_FINDER_CLASS_EXC_PROLOG = "Could not find valuesFinder class ";&lt;br /&gt;    private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(JstlFunctions.class);&lt;br /&gt;&lt;br /&gt;    private JstlFunctions() {}&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Returns a list of key/value pairs for displaying in an HTML option for a select list. This is a customized approach to retrieving&lt;br /&gt;     * key/value data from database based on criteria specified in the &amp;lt;code&amp;gt;params {@link Map}&amp;lt;/code&amp;gt;&amp;lt;br/&amp;gt;&lt;br /&gt;     * &amp;lt;br/&amp;gt;&lt;br /&gt;     * Here is an example of how the code is used from a JSP:&amp;lt;br/&amp;gt;&lt;br /&gt;     * &amp;lt;code&amp;gt;&lt;br /&gt;     * &amp;lt;jsp:useBean id="paramMap" class="java.util.HashMap"/&amp;gt;&lt;br /&gt;                    &amp;lt;c:set target="${paramMap}" property="forAddedPerson" value="true" /&amp;gt;&lt;br /&gt;                    &amp;lt;kul:checkErrors keyMatch="${proposalPerson}.proposalPersonRoleId" auditMatch="${proposalPerson}.proposalPersonRoleId"/&amp;gt;  &lt;br /&gt;                    &amp;lt;c:set var="roleStyle" value=""/&amp;gt;&lt;br /&gt;                    &amp;lt;c:if test="${hasErrors==true}"&amp;gt;&lt;br /&gt;                        &amp;lt;c:set var="roleStyle" value="background-color:#FFD5D5"/&amp;gt;&lt;br /&gt;                    &amp;lt;/c:if&amp;gt;&lt;br /&gt;                    &amp;lt;html:select property="${proposalPerson}.proposalPersonRoleId" tabindex="0" style="${roleStyle}"&amp;gt;&lt;br /&gt;                    &amp;lt;c:forEach items="${krafn:getOptionList('org.kuali.kra.proposaldevelopment.lookup.keyvalue.ProposalPersonRoleValuesFinder', paramMap)}" var="option"&amp;gt;&lt;br /&gt;                    &amp;lt;c:choose&amp;gt;&lt;br /&gt;                        &amp;lt;c:when test="${KualiForm.document.proposalPersons[personIndex].proposalPersonRoleId == option.key}"&amp;gt;&lt;br /&gt;                        &amp;lt;option value="${option.key}" selected&amp;gt;${option.label}&amp;lt;/option&amp;gt;&lt;br /&gt;                        &amp;lt;/c:when&amp;gt;&lt;br /&gt;                        &amp;lt;c:otherwise&amp;gt;&lt;br /&gt;                        &amp;lt;option value="${option.key}"&amp;gt;${option.label}&amp;lt;/option&amp;gt;&lt;br /&gt;                        &amp;lt;/c:otherwise&amp;gt;&lt;br /&gt;                    &amp;lt;/c:choose&amp;gt;&lt;br /&gt;                    &amp;lt;/c:forEach&amp;gt;&lt;br /&gt;                    &amp;lt;/html:select&amp;gt;&lt;br /&gt;       &amp;lt;/code&amp;gt;&lt;br /&gt;     * &lt;br /&gt;     * &lt;br /&gt;     * @param valuesFinderClassName&lt;br /&gt;     * @param params mapped parameters&lt;br /&gt;     * @return List of key values&lt;br /&gt;     */&lt;br /&gt;    @SuppressWarnings("unchecked")&lt;br /&gt;    public static List getOptionList(String valuesFinderClassName, Map params) {&lt;br /&gt;        return setupValuesFinder(valuesFinderClassName, (Map&amp;lt;String, Object&amp;gt;) params).getKeyValues();&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Initiates the values finder by its &amp;lt;code&amp;gt;valuesFinderClassName&amp;lt;/code&amp;gt;. First locates the class in the class path. Then, &lt;br /&gt;     * creates an instance of it. A &amp;lt;code&amp;gt;{@link Map}&amp;lt;/code&amp;gt; of key/values &amp;lt;code&amp;gt;{@link String}&amp;lt;/code&amp;gt; instances a is used&lt;br /&gt;     * to set properties on the values finder instance. Uses the apache &amp;lt;code&amp;gt;{@link PropertyUtils}&amp;lt;/code&amp;gt; class to set properties&lt;br /&gt;     * by the name of the key in the &amp;lt;code&amp;gt;{@link Map}&amp;lt;/code&amp;gt;.&amp;lt;br/&amp;gt;&lt;br /&gt;     * &amp;lt;br/&amp;gt;&lt;br /&gt;     * Basically, a new values finder is created. the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; parameter is a &amp;lt;code&amp;gt;{@link Map}&amp;lt;/code&amp;gt; of arbitrary values&lt;br /&gt;     * mapped to properties of the values finder class.&amp;lt;br/&amp;gt;&lt;br /&gt;     * &amp;lt;br/&amp;gt;&lt;br /&gt;     * Since this is so flexible and the ambiguity of properties referenced in the &amp;lt;code&amp;gt;{@link Map}&amp;lt;/code&amp;gt;, a number of exceptions are caught&lt;br /&gt;     * if a property cannot be set or if the values finder cannot be instantiated. All of these exceptions are handled within the method. None&lt;br /&gt;     * of these exceptions are thrown back.&lt;br /&gt;     * &lt;br /&gt;     * &lt;br /&gt;     * @param valuesFinderClassName&lt;br /&gt;     * @param params&lt;br /&gt;     * @return KeyValuesFinder&lt;br /&gt;     * @see PropertyUtils#setProperty(Object, String, Object)&lt;br /&gt;     */&lt;br /&gt;    private static KeyValuesFinder setupValuesFinder(String valuesFinderClassName, Map&amp;lt;String, Object&amp;gt; params) {&lt;br /&gt;        KeyValuesFinder retval = getKeyFinder(valuesFinderClassName);&lt;br /&gt;        &lt;br /&gt;        if(LOG.isDebugEnabled()) {&lt;br /&gt;            LOG.debug(SETTING_PARAMS_PROLOG + params);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        addParametersToFinder(params, retval);&lt;br /&gt;&lt;br /&gt;        return retval;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static void addParametersToFinder(Map&amp;lt;String, Object&amp;gt; params, KeyValuesFinder finder) {&lt;br /&gt;        if (finder != null &amp;&amp; params != null) {&lt;br /&gt;            for (Map.Entry&amp;lt;String, Object&amp;gt; entry : params.entrySet()) {&lt;br /&gt;                try {&lt;br /&gt;                    BeanUtils.setProperty(finder, entry.getKey(), entry.getValue());&lt;br /&gt;//                    setProperty(finder, entry.getKey(), entry.getValue());&lt;br /&gt;                } catch (Exception e) {&lt;br /&gt;                    warn(PROPERTY_SETTING_EXC_PROLOG + entry.getKey(), e);&lt;br /&gt;                    e.printStackTrace();&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static KeyValuesFinder getKeyFinder(String valuesFinderClassName) {&lt;br /&gt;        KeyValuesFinder retval = null;&lt;br /&gt;        try {&lt;br /&gt;            retval = (KeyValuesFinder) forName(valuesFinderClassName).newInstance();                        &lt;br /&gt;        } catch (ClassNotFoundException e) {&lt;br /&gt;            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);&lt;br /&gt;        } catch (InstantiationException e) {&lt;br /&gt;            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);&lt;br /&gt;        } catch (IllegalAccessException e) {&lt;br /&gt;            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);&lt;br /&gt;        }&lt;br /&gt;        return retval;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static void warnAboutValueFinderClassExceptions(String valuesFinderClassName, Exception e) {&lt;br /&gt;        warn(VALUES_FINDER_CLASS_EXC_PROLOG + valuesFinderClassName, e);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static void warn(String message, Exception e) {&lt;br /&gt;        if (LOG.isWarnEnabled()) {&lt;br /&gt;            LOG.warn(new StringBuilder(message).append(IN_PREPOSITION).append(buildTraceMessage(e)));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Get the stack trace from a &amp;lt;code&amp;gt;{@link Throwable}&amp;lt;/code&amp;gt; and create a log message from it for tracing purposes&lt;br /&gt;     * &lt;br /&gt;     * @param thrownObj &lt;br /&gt;     * @return String log message&lt;br /&gt;     */&lt;br /&gt;    private static String buildTraceMessage(Throwable thrownObj) {&lt;br /&gt;        StackTraceElement stackTraceElement = thrownObj.getStackTrace()[0];&lt;br /&gt;        return new StringBuilder(stackTraceElement.getClassName())&lt;br /&gt;                        .append("#") &lt;br /&gt;                        .append(stackTraceElement.getMethodName())&lt;br /&gt;                        .append(":") &lt;br /&gt;                        .append(stackTraceElement.getLineNumber())&lt;br /&gt;                        .append(" ")&lt;br /&gt;                        .append(thrownObj.getClass().getSimpleName())&lt;br /&gt;                        .append("\n")&lt;br /&gt;                        .append(thrownObj.getMessage())&lt;br /&gt;                        .toString();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;getOptionsList&lt;/b&gt; is the only public method in the class.&lt;pre class="brush: java"&gt;@SuppressWarnings("unchecked")&lt;br /&gt;    public static List getOptionList(String valuesFinderClassName, Map params) {&lt;br /&gt;        return setupValuesFinder(valuesFinderClassName, (Map&amp;lt;String, Object&amp;gt;) params).getKeyValues();&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;You can see it returns a &lt;b&gt;List&lt;/b&gt; because this will eventually be used with a JSP or JSTL tag file which do not support parameterized types. You can see that parameters are passed in as a &lt;b&gt;Map&lt;/b&gt; called &lt;b&gt;params&lt;/b&gt;. This is used via the &lt;b&gt;addParametersToFinder&lt;/b&gt; method here:&lt;pre class="brush:java"&gt;private static void addParametersToFinder(Map&amp;lt;String, Object&amp;gt; params, KeyValuesFinder finder) {&lt;br /&gt;        if (finder != null &amp;&amp; params != null) {&lt;br /&gt;            for (Map.Entry&amp;lt;String, Object&amp;gt; entry : params.entrySet()) {&lt;br /&gt;                try {&lt;br /&gt;                    BeanUtils.setProperty(finder, entry.getKey(), entry.getValue());&lt;br /&gt;//                    setProperty(finder, entry.getKey(), entry.getValue());&lt;br /&gt;                } catch (Exception e) {&lt;br /&gt;                    warn(PROPERTY_SETTING_EXC_PROLOG + entry.getKey(), e);&lt;br /&gt;                    e.printStackTrace();&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;&lt;h3&gt;2. Create a JSTL TLD&lt;/h3&gt;Now that we have our class and our public method that will create our finder for us, we will need some way to invoke it. The best way to do this is by using the static method through a tag. We can create this tag easily with a TLD.&lt;pre class="brush:xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" ?&amp;gt;&lt;br /&gt;&amp;lt;taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;description&amp;gt;TEM functions library&amp;lt;/description&amp;gt;&lt;br /&gt;    &amp;lt;display-name&amp;gt;TEM functions&amp;lt;/display-name&amp;gt;&lt;br /&gt;    &amp;lt;tlib-version&amp;gt;1.0&amp;lt;/tlib-version&amp;gt;&lt;br /&gt;    &amp;lt;short-name&amp;gt;fn&amp;lt;/short-name&amp;gt;&lt;br /&gt;    &amp;lt;uri&amp;gt;http://www.kuali.org/jsp/jstl/functions&amp;lt;/uri&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;function&amp;gt;&lt;br /&gt;        &amp;lt;description&amp;gt;Parameterized Values Finder List!!&amp;lt;/description&amp;gt;&lt;br /&gt;        &amp;lt;name&amp;gt;getOptionList&amp;lt;/name&amp;gt;&lt;br /&gt;        &amp;lt;function-class&amp;gt;org.kuali.kfs.module.tem.web.JstlFunctions&amp;lt;/function-class&amp;gt;&lt;br /&gt;        &amp;lt;function-signature&amp;gt;java.util.List getOptionList(java.lang.String, java.util.Map)&amp;lt;/function-signature&amp;gt;&lt;br /&gt;        &amp;lt;example&amp;gt;&amp;lt;c:forEach items="${tem-fn:getOptionList()}"&amp;gt;&amp;gt;&amp;lt;/example&amp;gt;&lt;br /&gt;    &amp;lt;/function&amp;gt;&lt;br /&gt;&amp;lt;/taglib&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;3. Use it in a JSP&lt;/h3&gt;In our JSP, we will need to define a Map to add parameters to. You saw earlier that our &lt;b&gt;getOptionsList&lt;/b&gt; method takes a &lt;b&gt;Map&lt;/b&gt; with parameters in it. &lt;pre class="brush:xml"&gt;&amp;lt;jsp:useBean id="paramMap" class="java.util.HashMap" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;Then, we need to assign some value to the &lt;b&gt;Map&lt;/b&gt;: &lt;pre class="brush:xml"&gt;&amp;lt;c:set target="${paramMap}" property="queryDate"&lt;br /&gt;                                        value="${perDiemExpense.mileageDate}" /&amp;gt;&lt;br /&gt;                                &lt;/pre&gt;Above we have assigned &lt;b&gt;${perDiemExpense.mileageDate}&lt;/b&gt; to the &lt;b&gt;queryDate&lt;/b&gt; parameter in our Map! Now we just need our dropdown. &lt;pre class="brush:xml"&gt;&amp;lt;c:forEach items="${temfunc:getOptionList('org.kuali.kfs.module.tem.businessobject.options.MileageRateValuesFinder', paramMap)}" var="option"&amp;gt;&lt;br /&gt;  &amp;lt;c:set var="mileageSelected" value="" /&amp;gt;&lt;br /&gt;  &amp;lt;c:if test="${option.key} == KualiForm.document.perDiemExpenses[perDiemIndex.count - 1].mileageRateId}"&amp;gt;&lt;br /&gt;    &amp;lt;c:set var="mileageSelected" value="selected" /&amp;gt;&lt;br /&gt;  &amp;lt;/c:if&amp;gt;&lt;br /&gt;  &amp;lt;option value="${option.key}"${mileageSelected}&amp;gt;${option.label}&amp;lt;/option&amp;gt;&lt;br /&gt;&amp;lt;/c:forEach&amp;gt;&lt;/pre&gt;Now you can see where &lt;b&gt;getOptionList&lt;/b&gt; is actually used. Now we have a dropdown list of mileage rates based on the date of the mileage since mileage rates can vary by date.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-8073299999708963891?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/8073299999708963891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/12/kuali-parameterized-valuefinder.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/8073299999708963891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/8073299999708963891'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/12/kuali-parameterized-valuefinder.html' title='Kuali Parameterized ValueFinder Implementation'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3468198901045066532</id><published>2011-12-01T11:52:00.001-08:00</published><updated>2011-12-01T12:01:48.895-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='error'/><category scheme='http://www.blogger.com/atom/ns#' term='tomcat'/><title type='text'>SEVERE: Error listenerStart</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I saw on the rice mailinglist recently someone posted a question where essentially, the application dies before a logger can be initialized. Here is the result: &lt;pre class="brush: plain"&gt;INFO: Deploying web application archive kr-dev.war&lt;br /&gt;log4j:WARN No appenders could be found for logger (org.kuali.rice.core.web.listener.KualiInitializeListener).&lt;br /&gt;log4j:WARN Please initialize the log4j system properly.&lt;br /&gt;log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.&lt;br /&gt;Dec 1, 2011 1:35:19 PM org.apache.catalina.core.StandardContext start&lt;br /&gt;SEVERE: Error listenerStart&lt;br /&gt;Dec 1, 2011 1:35:19 PM org.apache.catalina.core.StandardContext start&lt;br /&gt;SEVERE: Context [/kr-dev] startup failed due to previous errors&lt;/pre&gt;&lt;br /&gt;What has happened here is the listener failed to start. The listener is what starts up the application (in this case, Rice). If Rice does not start, there is no logger to tell you what is wrong. Thus, you get no error. Usually, what causes this is a dependency is not included in tomcat. &lt;br /&gt;&lt;br /&gt;So far, I have not seen a case where the problem is caused by anything but a missing library. It is usually the database driver, because that is the only thing not included in the rice war by default. It can possibly be something else though. I have seen KFS not start because of a missing log4j file. Since KFS actually requires the application to deploy additional libraries to the application server before startup, it is the only exceptional case.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Solution&lt;/h2&gt;Of course, the solution is to make sure your database driver is in the tomcat/lib folder, right? What if that doesn't do the job? What if it's another library? Read on.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Alternate&lt;/h2&gt;Sometimes you need to figure out what library is missing. This is almost never the case. Nearly always it is the database driver that is missing. Almost, right? If this happens and the database driver is loading correctly, then you need to figure out what the problem is. This means consulting the tomcat logger. I am going to assume that you have installed the rice war file in your tomcat installation. The war file is now unpacked and there's a webapp directory somewhere. Within the &lt;b&gt;WEB-INF/classes&lt;/b&gt; path of that webapp directory, you can create a &lt;b&gt;logging.properties&lt;/b&gt; with the following contents.&lt;pre class="brush: plain"&gt;org.apache.catalina.core.ContainerBase.[Catalina].level = INFO&lt;br /&gt;org.apache.catalina.core.ContainerBase.[Catalina].handlers = java.util.logging.ConsoleHandler&lt;br /&gt;&lt;/pre&gt;Why? The reason is that rice uses commons-logging and log4j while tomcat uses java.util.logging. Essentially, separate things. Also, since Kuali did not start, it did not start the logging configuration. What we can do is add a logging configuration that tomcat will pick up and use when it tries to start Rice. That is essentially what we are doing here. &lt;br /&gt;&lt;br /&gt;Now try to restart the server. You should see some kind of &lt;b&gt;NoClassDefFoundError&lt;/b&gt; somewhere explaining what library you are missing.&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3468198901045066532?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3468198901045066532/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/12/severe-error-listenerstart.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3468198901045066532'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3468198901045066532'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/12/severe-error-listenerstart.html' title='SEVERE: Error listenerStart'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6851711707991377493</id><published>2011-11-15T12:50:00.001-08:00</published><updated>2011-11-15T12:50:54.692-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='presentations'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali days'/><category scheme='http://www.blogger.com/atom/ns#' term='kd2011'/><title type='text'>Getting Started with Rice 2.0</title><content type='html'>This is my &lt;a href="http://r351574nc3.github.com/KD2011/SettingUpRice2.0/"&gt;"Getting Started with Rice 2.0" presentation&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Enjoy&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6851711707991377493?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6851711707991377493/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/11/getting-started-with-rice-20.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6851711707991377493'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6851711707991377493'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/11/getting-started-with-rice-20.html' title='Getting Started with Rice 2.0'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-5034348632230322885</id><published>2011-11-09T07:24:00.000-08:00</published><updated>2011-11-09T07:24:41.790-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='documentation'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Current KIM Refactoring Documentation</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;There are 2 ways to add objects to KIM. &lt;ul&gt;&lt;li&gt;Rice User Interface&lt;/li&gt;&lt;li&gt;SQL insert statements when building the application&lt;/lI&gt;&lt;/ul&gt;. Since Kuali is venturing into the use of liquibase, I have put together custom refactorings that simplify adding objects to KIM and other Rice modules.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Using SQL&lt;/h2&gt;This is how one would normally create an attribute, create a responsibility, add the responsibility to a role, and add an attribute to a responsibility.&lt;br /&gt;&lt;h3&gt;Example&lt;/h3&gt;&lt;pre class="brush: sql"&gt;-- International Travel Reviewer Node&lt;br /&gt;insert into krim_rsp_t (rsp_id, nmspc_cd, nm, actv_ind, rsp_tmpl_id, ver_nbr, obj_id)&lt;br /&gt;values(krim_rsp_id_s.nextval, 'KFS-TEM', 'Review', 'Y', 1, 1, sys_guid());&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;insert into krim_role_t (role_id, obj_id, ver_nbr, nmspc_cd, role_nm, kim_typ_id, actv_ind, last_updt_dt)&lt;br /&gt;   values (krim_role_id_s.nextval, sys_guid(), 1, 'KFS-TEM', 'International Travel Reviewer',&lt;br /&gt;   (select kim_typ_id from krim_typ_t where nm = 'Default' and nmspc_cd = 'KUALI' and actv_ind = 'Y'),&lt;br /&gt;    'Y', SYSDATE);&lt;br /&gt;&lt;br /&gt;insert into krim_rsp_attr_data_t (attr_data_id, obj_id, ver_nbr, rsp_id, kim_typ_id, kim_attr_defn_id, attr_val)&lt;br /&gt;values(krim_rsp_rqrd_attr_id_s.nextval, sys_guid(), 1, krim_rsp_id_s.currval, 7, 16, 'InternationalTravelReviewer');&lt;br /&gt;&lt;br /&gt;insert into krim_rsp_attr_data_t (attr_data_id, obj_id, ver_nbr, rsp_id, kim_typ_id, kim_attr_defn_id, attr_val)&lt;br /&gt;values(krim_rsp_rqrd_attr_id_s.nextval, sys_guid(), 1, krim_rsp_id_s.currval, 7, 13, 'TA');&lt;br /&gt;&lt;br /&gt;insert into krim_rsp_attr_data_t (attr_data_id, obj_id, ver_nbr, rsp_id, kim_typ_id, kim_attr_defn_id, attr_val)&lt;br /&gt;values(krim_rsp_rqrd_attr_id_s.nextval, sys_guid(), 1, krim_rsp_id_s.currval, 7, 41, 'false');&lt;br /&gt;&lt;br /&gt;insert into krim_rsp_attr_data_t (attr_data_id, obj_id, ver_nbr, rsp_id, kim_typ_id, kim_attr_defn_id, attr_val)&lt;br /&gt;values(krim_rsp_rqrd_attr_id_s.nextval, sys_guid(), 1, krim_rsp_id_s.currval, 7, 40, 'false');&lt;br /&gt;&lt;br /&gt;insert into krim_role_rsp_t (role_rsp_id, obj_id, ver_nbr, role_id, rsp_id, actv_ind)&lt;br /&gt;values (krim_role_rsp_id_s.nextval, sys_guid(), 1, (select role_id from krim_role_t where role_nm = 'International Travel Reviewer' and nmspc_cd= 'KFS-TEM'), krim_rsp_id_s.currval, 'Y');&lt;br /&gt;&lt;br /&gt;insert into krim_role_rsp_actn_t (role_rsp_actn_id, obj_id, ver_nbr, actn_typ_cd, priority_nbr, actn_plcy_cd, role_mbr_id, role_rsp_id, frc_actn)&lt;br /&gt;   values (krim_role_rsp_actn_id_s.nextval, sys_guid(), 1, 'A', 1, 'F', '*',&lt;br /&gt;   (select role_rsp_id from krim_role_rsp_t where role_id = (select role_id from krim_role_t where role_nm = 'International Travel Reviewer' and nmspc_cd = 'KFS-TEM')),&lt;br /&gt;    'N');&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;New Liquibase Refactoring Method&lt;/h2&gt;I have documented a better way on the &lt;a href="http://r351574nc3.github.com/rice-lb-ext/kim_refactorings.html"&gt;Rice Liquibase Extensions&lt;/a&gt; page.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-5034348632230322885?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/5034348632230322885/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/11/current-kim-refactoring-documentation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5034348632230322885'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5034348632230322885'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/11/current-kim-refactoring-documentation.html' title='Current KIM Refactoring Documentation'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6119177014907146806</id><published>2011-11-08T07:53:00.000-08:00</published><updated>2011-11-08T07:54:58.921-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Project Updates and the Coming Onslaught of Documentation</title><content type='html'>I have been working on improving the documentation in the projects surrounding liquibase. That's right. The changes aren't all just for looks. A lot of documentation that is going out to wikis will be updated on the official project pages first. Also, among the documentation improvements are:&lt;ul&gt;&lt;li&gt;javadoc APIs&lt;/li&gt;&lt;li&gt;Usage instructions&lt;/li&gt;&lt;li&gt;Screencasts (most importantly is that they're better and more informative screencasts)&lt;/li&gt;&lt;li&gt;Working example (examples taken directly from implemented scenarios&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Updated Projects&lt;/h2&gt;Here is a list of projects recently updated:&lt;ul&gt;&lt;li&gt;&lt;a href="http://r351574nc3.github.com/lbcopy"&gt;rsmart-lb-ant (Liquibase Database Migration Ant tasks)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://r351574nc3.github.com/lb-maven-plugin"&gt;lb-maven-plugin(Liquibase Maven plugin focusing on fast/easy developer migrations and environment migrations/upgrades)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://r351574nc3.github.com/rice-lb-ext"&gt;rice-lb-ext (Rice Liquibase Custom Refactorings)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://r351574nc3.github.com/lbext"&gt;rsmart-lb-extensions (Liquibase Extensions for database agnostic changelogs)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://r351574nc3.github.com/maven-kuali-skin"&gt;maven-kuali-skin&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;rice-lb-extensions&lt;/h3&gt;This is a new project that is going to focus on custom refactorings. Instead of having a bunch of SQL inserts, database specific sql, and having to keep track of your previous ID, there will be meaningful code behind your database object changes. Here are some up and coming &lt;a href="http://r351574nc3.github.com/rice-lb-ext/kim_refactorings.html"&gt;KIM Refactorings&lt;/a&gt;.&lt;br/&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6119177014907146806?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6119177014907146806/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/11/project-updates-and-coming-onslaught-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6119177014907146806'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6119177014907146806'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/11/project-updates-and-coming-onslaught-of.html' title='Project Updates and the Coming Onslaught of Documentation'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4328955960294986810</id><published>2011-11-07T15:14:00.000-08:00</published><updated>2011-11-07T15:16:24.772-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blog'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>New GitHub Project</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I've created a new &lt;a href="http://www.github.com/r351574nc3/maven-kuali-skin"&gt;project&lt;/a&gt; on &lt;a href="http://www.github.com/r351574nc3/"&gt;GitHub&lt;/a&gt;. It is called &lt;a href="http://www.github.com/r351574nc3/maven-kuali-skin"&gt;project&lt;/a&gt;. What it is, is a maven skin that will make your maven site look just like the &lt;a href="http://www.kuali.org/"&gt;Kuali&lt;/a&gt; site.&lt;br /&gt;&lt;h2&gt;Sites Used&lt;/h2&gt;I am currently using this skin for my &lt;a href="http://www.github.com/r351574nc3/lbcopy"&gt;rsmart-lb-ant&lt;/a&gt; project. Which brings me to my next update. I am currently working on improving my documentation on &lt;a href="http://www.github.com/r351574nc3/lbcopy"&gt;rsmart-lb-ant&lt;/a&gt; and &lt;a href="http://www.github.com/r351574nc3/lbext"&gt;rsmart-lb-extensions&lt;/a&gt;. I will be providing better examples and videos on how to use these too. Examples, are from real, working implementations.&lt;br /&gt;&lt;br /&gt;I hope you enjoy it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4328955960294986810?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4328955960294986810/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/11/new-github-project.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4328955960294986810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4328955960294986810'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/11/new-github-project.html' title='New GitHub Project'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3677625980978780213</id><published>2011-09-28T12:24:00.001-07:00</published><updated>2011-09-28T12:25:14.656-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kim'/><category scheme='http://www.blogger.com/atom/ns#' term='documentation'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><category scheme='http://www.blogger.com/atom/ns#' term='ldap'/><title type='text'>LDAP KIM Integration Documentation</title><content type='html'>I have added documentation in a &lt;a href="https://wiki.kuali.org/display/KULRICE/KIM+Entity+LDAP+Integration"&gt;permanent location&lt;/a&gt; that will be updated and maintained for KIM LDAP Integration &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3677625980978780213?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3677625980978780213/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/ldap-kim-integration-documentation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3677625980978780213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3677625980978780213'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/ldap-kim-integration-documentation.html' title='LDAP KIM Integration Documentation'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3649181686858052994</id><published>2011-09-21T22:04:00.000-07:00</published><updated>2011-09-21T22:08:20.267-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>Testing with lb-maven-plugin</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;These are some instructions for developers using the &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt; on how to test your changelogs to verify they work on varying database platforms before committing changes to your VCS.&lt;h2&gt;Setup&lt;/h2&gt;I'm going to assume you have already installed the &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt;. Here are some preliminary setups we need to do before actual testing:&lt;ul&gt;&lt;li&gt;Add &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt; to the pom.xml&lt;/li&gt;&lt;li&gt;Create connection information for each database to test.&lt;/li&gt;&lt;li&gt;Create an actual changelog&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Setup the &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt;&lt;/h3&gt;This is what I added to my pom.xml. This ensures my changelogs are run whenever I do any kind of testing. Since I am testing both mysql and oracle databases, it requires the drivers during testing.&lt;pre class="brush: xml"&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;com.rsmart.kuali.tools&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;lb-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;0.0.1&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;configuration&amp;gt;&lt;br /&gt;      &amp;lt;changeLogTagUrl&amp;gt;https://svn.rsmart.com/svn/kuali/contribution/community/travel_module/tags/&amp;lt;/changeLogTagUrl&amp;gt;&lt;br /&gt;    &amp;lt;/configuration&amp;gt;&lt;br /&gt;    &amp;lt;executions&amp;gt;&lt;br /&gt;      &amp;lt;execution&amp;gt;&lt;br /&gt;        &amp;lt;id&amp;gt;test-liquibase-changelogs&amp;lt;/id&amp;gt;&lt;br /&gt;        &amp;lt;phase&amp;gt;test&amp;lt;/phase&amp;gt;&lt;br /&gt;        &amp;lt;goals&amp;gt;&lt;br /&gt;          &amp;lt;goal&amp;gt;test&amp;lt;/goal&amp;gt;&lt;br /&gt;        &amp;lt;/goals&amp;gt;&lt;br /&gt;      &amp;lt;/execution&amp;gt;&lt;br /&gt;    &amp;lt;/executions&amp;gt;&lt;br /&gt;    &amp;lt;dependencies&amp;gt;&lt;br /&gt;      &amp;lt;dependency&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;com.rsmart.kuali.tools.liquibase&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;rsmart-lb-extensions&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;1.0.0&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;&lt;br /&gt;      &amp;lt;/dependency&amp;gt;&lt;br /&gt;      &amp;lt;dependency&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;${mysql.version}&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;&lt;br /&gt;      &amp;lt;/dependency&amp;gt;&lt;br /&gt;      &amp;lt;dependency&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;com.oracle&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;ojdbc14&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;${oracle.version}&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;&lt;br /&gt;      &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;/dependencies&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt;&lt;/pre&gt;This may seem a little weird, but I also modified the &lt;b&gt;resources plugin&lt;/b&gt;&lt;pre class="brush: xml"&gt;  &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;maven-resources-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;2.5&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;executions&amp;gt;&lt;br /&gt;      &amp;lt;execution&amp;gt;&lt;br /&gt;        &amp;lt;id&amp;gt;copy-test-changelogs&amp;lt;/id&amp;gt;&lt;br /&gt;        &amp;lt;!-- here the phase you need --&amp;gt;&lt;br /&gt;        &amp;lt;phase&amp;gt;validate&amp;lt;/phase&amp;gt;&lt;br /&gt;        &amp;lt;goals&amp;gt;&lt;br /&gt;          &amp;lt;goal&amp;gt;copy-resources&amp;lt;/goal&amp;gt;&lt;br /&gt;        &amp;lt;/goals&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;outputDirectory&amp;gt;${basedir}/target/changelogs/update&amp;lt;/outputDirectory&amp;gt;&lt;br /&gt;          &amp;lt;resources&amp;gt;          &lt;br /&gt;            &amp;lt;resource&amp;gt;&lt;br /&gt;              &amp;lt;directory&amp;gt;src/main/changelogs/update&amp;lt;/directory&amp;gt;&lt;br /&gt;            &amp;lt;/resource&amp;gt;&lt;br /&gt;          &amp;lt;/resources&amp;gt;              &lt;br /&gt;        &amp;lt;/configuration&amp;gt;            &lt;br /&gt;      &amp;lt;/execution&amp;gt;&lt;br /&gt;    &amp;lt;/executions&amp;gt;&lt;br /&gt;  &amp;lt;/plugin&amp;gt;&lt;br /&gt;&lt;/pre&gt;The reason is because the &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt; assumes by convention changelogs are located in &lt;b&gt;src/main/changelogs&lt;/b&gt; and your updates are within the &lt;b&gt;update&lt;/b&gt; directory. Further, the target location of the changelogs is in &lt;b&gt;target/changelogs/update&lt;/b&gt;. The resources plugin needs to be modified to recognize this.&lt;h3&gt;Setup the Liquibase Properties Files&lt;/h3&gt;The &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;lb-maven-plugin&lt;/a&gt; regards any properties files in &lt;b&gt;target/test-classes/liquibase&lt;/b&gt; to be considered a liquibase properties file. It will iterate over each one and run the changelog in &lt;b&gt;target/changelogs/update&lt;/b&gt; for each one. In order to get properties files into &lt;b&gt;target/test-classes/liquibase&lt;/b&gt;, I created a directory called &lt;b&gt;src/test/resources/liquibase&lt;/b&gt;. These are already moved to the appropriate location. All I need to do is create the files:&lt;pre class="brush: plain"&gt;driver: oracle.jdbc.driver.OracleDriver&lt;br /&gt;url: jdbc:oracle:thin:@localhost:1521:KFS&lt;br /&gt;username: TEM&lt;br /&gt;password: TEMPORARY&lt;br /&gt;&lt;/pre&gt;&lt;pre class="brush: plain"&gt;driver: com.mysql.jdbc.Driver&lt;br /&gt;url: jdbc:mysql://localhost:3306/TEM&lt;br /&gt;username: TEM&lt;br /&gt;password: TEMPORARY&lt;/pre&gt;&lt;h3&gt;Create a Changelog&lt;/h3&gt;Next, I just need to make a change to test out. I'm just going to do something simple:&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"&amp;gt;	&lt;br /&gt;  &amp;lt;changeSet author="kuali (generated)" id="CM-156-1"&amp;gt;&lt;br /&gt;    &amp;lt;comment&amp;gt;Adding System Parameter for testing&amp;lt;/comment&amp;gt;&lt;br /&gt;    &amp;lt;sql&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;insert into KRNS_PARM_T (NMSPC_CD, PARM_DTL_TYP_CD, PARM_NM, OBJ_ID, VER_NBR, PARM_TYP_CD, TXT, PARM_DESC_TXT, CONS_CD, APPL_NMSPC_CD) &lt;br /&gt;values ('KFS-TEM','TravelReimbursement','TEST_PARAMETER',sys_guid(),1,'CONFG','Y','System parameter to test update and rollback','A','KFS');&lt;br /&gt;    &lt;br /&gt;    ]]&amp;gt;&amp;lt;/sql&amp;gt;&lt;br /&gt;    &amp;lt;rollback&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;    delete from KRNS_PARM_T where PARM_DTL_TYP_CD = 'TravelReimbursement' AND PARM_NM = 'TEST_PARAMETER';&lt;br /&gt;    ]]&amp;gt;&amp;lt;/rollback&amp;gt;&lt;br /&gt;  &amp;lt;/changeSet&amp;gt;&lt;br /&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/pre&gt;I'm just adding your basic system parameter. Testing requires that I rollback the change when I'm done, so I'm forced into providing a rollback. Imagine that. Testing forcing good practices on developers. That's the way it should be.&lt;h3&gt;Prepare for Testing&lt;/h3&gt;Now that I have created these files, I simply run the following:&lt;pre class="brush: plain"&gt;mvn validate testResources&lt;/pre&gt;. You may recall that the changelogs are copied during the &lt;b&gt;validate&lt;/b&gt; goal. I run &lt;b&gt;testResources&lt;/b&gt; to copy my properties files to the appropriate locations. After doing that, I should see this in &lt;b&gt;target/changelogs/update/&lt;/b&gt;&lt;pre class="brush: plain"&gt;leo@behemoth~/.workspace/kfs/release-4-0-overlay&lt;br /&gt;(21:38:19) [540] ls target/changelogs/update&lt;br /&gt;CM-156.xml&lt;/pre&gt;I see the changelog I created. Good. I should also see my properties in &lt;b&gt;target/test-classes/liquibase&lt;/b&gt;&lt;pre class="brush: plain"&gt;leo@behemoth~/.workspace/kfs/release-4-0-overlay&lt;br /&gt;(22:02:22) [541] ls target/test-classes/liquibase/&lt;br /&gt;TEM.properties			TEMNIGHTLY.properties		liquibase.properties.template&lt;/pre&gt;There you have it. Now we're ready to test.&lt;pre class="brush: plain"&gt;leo@behemoth~/.workspace/kfs/release-4-0-overlay&lt;br /&gt;(21:29:10) [537] mvn validate lb:test&lt;br /&gt;[INFO] Scanning for projects...&lt;br /&gt;[INFO]                                                                         &lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Building kfs 4.0M2&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] &lt;br /&gt;[INFO] --- maven-resources-plugin:2.5:copy-resources (copy-test-changelogs) @ kfs ---&lt;br /&gt;[debug] execute contextualize&lt;br /&gt;[INFO] Using 'UTF-8' encoding to copy filtered resources.&lt;br /&gt;[INFO] Copying 1 resource&lt;br /&gt;[INFO] &lt;br /&gt;[INFO] --- lb-maven-plugin:0.0.1:test (default-cli) @ kfs ---&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'commons-lang:commons-lang'&lt;br /&gt;[WARNING] Artifact with no actual file, 'com.lowagie:itext'&lt;br /&gt;[WARNING] Artifact with no actual file, 'jasperreports:jasperreports'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'mysql:mysql-connector-java'&lt;br /&gt;[WARNING] Artifact with no actual file, 'junit:junit'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:servlet-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jstl'&lt;br /&gt;[WARNING] Artifact with no actual file, 'taglibs:standard'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jsp-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-deploy'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-jsp-2.1'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-server'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-webapp'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.hamcrest:hamcrest-library'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-beans'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context-support'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-core'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-jdbc'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-tx'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springmodules:spring-modules-ojb'&lt;br /&gt;[INFO] Parsing Liquibase Properties File&lt;br /&gt;[INFO]   File: /Users/leo/.workspace/kfs/release-4-0-overlay/target/test-classes/liquibase/TEM.properties&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'commons-lang:commons-lang'&lt;br /&gt;[WARNING] Artifact with no actual file, 'com.lowagie:itext'&lt;br /&gt;[WARNING] Artifact with no actual file, 'jasperreports:jasperreports'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'mysql:mysql-connector-java'&lt;br /&gt;[WARNING] Artifact with no actual file, 'junit:junit'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:servlet-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jstl'&lt;br /&gt;[WARNING] Artifact with no actual file, 'taglibs:standard'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jsp-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-deploy'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-jsp-2.1'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-server'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-webapp'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.hamcrest:hamcrest-library'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-beans'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context-support'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-core'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-jdbc'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-tx'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springmodules:spring-modules-ojb'&lt;br /&gt;[INFO] Parsing Liquibase Properties File&lt;br /&gt;[INFO]   File: /Users/leo/.workspace/kfs/release-4-0-overlay/target/test-classes/liquibase/TEM.properties&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Executing on Database: jdbc:mysql://localhost:3306/TEM&lt;br /&gt;[INFO] Tagging the database&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Reading from `DATABASECHANGELOG`&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] Doing update&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Reading from `DATABASECHANGELOG`&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: ChangeSet /Users/leo/.workspace/kfs/release-4-0-overlay/target/changelogs/update/CM-156.xml::CM-156-1::kuali (generated) ran successfully in 37ms&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] Doing rollback&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Rolling Back Changeset:/Users/leo/.workspace/kfs/release-4-0-overlay/target/changelogs/update/CM-156.xml::CM-156-1::kuali (generated)::(Checksum: 3:85a5e658332342fba8b60df3a29fc393)&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] &lt;br /&gt;[INFO] Parsing Liquibase Properties File&lt;br /&gt;[INFO]   File: /Users/leo/.workspace/kfs/release-4-0-overlay/target/test-classes/liquibase/TEMNIGHTLY.properties&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'commons-lang:commons-lang'&lt;br /&gt;[WARNING] Artifact with no actual file, 'com.lowagie:itext'&lt;br /&gt;[WARNING] Artifact with no actual file, 'jasperreports:jasperreports'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.kuali.kfs:kfs'&lt;br /&gt;[WARNING] Artifact with no actual file, 'mysql:mysql-connector-java'&lt;br /&gt;[WARNING] Artifact with no actual file, 'junit:junit'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:servlet-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jstl'&lt;br /&gt;[WARNING] Artifact with no actual file, 'taglibs:standard'&lt;br /&gt;[WARNING] Artifact with no actual file, 'javax.servlet:jsp-api'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-deploy'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-jsp-2.1'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-server'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.eclipse.jetty:jetty-webapp'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.hamcrest:hamcrest-library'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-beans'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-context-support'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-core'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-jdbc'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springframework:spring-tx'&lt;br /&gt;[WARNING] Artifact with no actual file, 'org.springmodules:spring-modules-ojb'&lt;br /&gt;[INFO] Parsing Liquibase Properties File&lt;br /&gt;[INFO]   File: /Users/leo/.workspace/kfs/release-4-0-overlay/target/test-classes/liquibase/TEMNIGHTLY.properties&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Executing on Database: jdbc:oracle:thin:@heisenberg.rsmart.com:1521:KFS&lt;br /&gt;[INFO] Tagging the database&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Reading from DATABASECHANGELOG&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] Doing update&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Reading from DATABASECHANGELOG&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: ChangeSet /Users/leo/.workspace/kfs/release-4-0-overlay/target/changelogs/update/CM-156.xml::CM-156-1::kuali (generated) ran successfully in 205ms&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] Doing rollback&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully acquired change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Rolling Back Changeset:/Users/leo/.workspace/kfs/release-4-0-overlay/target/changelogs/update/CM-156.xml::CM-156-1::kuali (generated)::(Checksum: 3:85a5e658332342fba8b60df3a29fc393)&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;INFO 9/21/11 9:29 PM:liquibase: Successfully released change log lock&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] &lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] BUILD SUCCESS&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Total time: 28.920s&lt;br /&gt;[INFO] Finished at: Wed Sep 21 21:29:49 MST 2011&lt;br /&gt;[INFO] Final Memory: 11M/262M&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;leo@behemoth~/.workspace/kfs/release-4-0-overlay&lt;br /&gt;(21:29:49) [538]&lt;/pre&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;You can see that it ran against two separate databases relatively quickly. It's a good way to test your changes and make sure they work everywhere you need them to.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3649181686858052994?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3649181686858052994/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/testing-with-lb-maven-plugin.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3649181686858052994'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3649181686858052994'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/testing-with-lb-maven-plugin.html' title='Testing with lb-maven-plugin'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-2704828791598361539</id><published>2011-09-20T10:33:00.000-07:00</published><updated>2011-09-20T15:39:09.382-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>lb-maven-plugin: Why Another Liquibase Maven Plugin?</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I've started a &lt;a href="https://github.com/r351574nc3/lb-maven-plugin"&gt;project at github&lt;/a&gt; for an extension of the liquibase maven plugin. It's true that liquibase already has a maven plugin, but liquibase is so configuration intensive. I believe there needs to be a more standardized approach to developing with liquibase. Maven is a paragon for the methodology of "convention over configuration". I sincerely believe this methodology is a simpler way to go as far as configuration is concerned. With that in mind, my goal is to make development with liquibase more constrained and simpler for developers. This is especially the case in Kuali.&lt;h2&gt;The Problem&lt;/h2&gt;You may recall from &lt;a href="/2010/04/kismet-game-changer-structuring-project.html"&gt;a previous blog post&lt;/a&gt; the structure and style of Liquibase implementation I recommend. To summarize, I recommend only publishing updates to your VCS. This makes it easier on developers and also eliminates confusion when making essentially the same change to multiple files. This approach does have its complications though. For example, it raises the question, "How do you create a database to start with?" Also, "What if an environment or a developer gets several revisions behind? How do we bring them back up to the current version?" The maven plugin basically answers those questions. Especially, for developers.&lt;h2&gt;lb-maven-plugin&lt;/h2&gt;This plugin is an extension of the liquibase-maven-plugin, so it has all the same configuration and goals. In addition, it has 2 more goals: migrate and test.&lt;h3&gt;migrate&lt;/h3&gt;You may recall that there used to be a migrate task in the liquibase-maven-plugin. I think they renamed it to be consistent with the CLI and Ant. I have brought it back to be more consistent with RoR migrations. The objective here is to not just update the database, but to bring the database up to the current version. Right now, liquibase update just runs whatever changelogs you want to update with. They might not be the ones that will get you to the right version. Liquibase doesn't know what changelogs to run to get you to the right version. This goal takes into consideration the structure outlined in &lt;a href="/2010/04/kismet-game-changer-structuring-project.html"&gt;a previous blog post&lt;/a&gt;, and retrieves from the SCM the relevant changelogs to update your database.&lt;h3&gt;test&lt;/h3&gt;There's already an updateAndRollback goal. Unfortunately, this tests the update and rollback, but does another update. This is great if you want to test your script while updating your database. In development, this isn't as realistic though. Sometimes, you want to group your changes. Developers are likely to continually add to a change log, then commit it to the VCS. This is especially the case if the changes are related to the same Jira issue. If you update, you can't really keep updating. You could get around this by being really hacky with your changelogs, but it would just be better if the tool did what you expected: run the test, and then rollback to a state prior to do more testing. That's exactly what this goal does. It basically tests your changelogs out. You can define multiple databases to run it against. If you are concerned about changelog compatibility, you can run the changes against as many databases as you want (local or remote).Continue to follow this blog, and I'll post shortly an example on testing your changelogs with this tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-2704828791598361539?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/2704828791598361539/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/lb-maven-plugin-why-another-liquibase.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2704828791598361539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2704828791598361539'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/lb-maven-plugin-why-another-liquibase.html' title='lb-maven-plugin: Why Another Liquibase Maven Plugin?'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4382312254047985275</id><published>2011-09-16T20:44:00.000-07:00</published><updated>2011-09-16T22:41:06.536-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='configuration management'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Liquibase: Semi-Database Agnostic Changelogs</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;Changelogs in Liquibase are typically RDBMS specific. For a database change management system that advertises being database agnostic, it is pretty weird to still be writing changelogs that are database specific. For example, check this out: &lt;pre class="brush: xml"&gt;&lt;br /&gt;&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;&lt;br /&gt;&lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"&gt;&lt;br /&gt;    &lt;changeSet author="leo (generated)" id="1315806809798-1"&gt;&lt;br /&gt;        &lt;createTable schemaName="TEM" tableName="a21_lbr_bld_nbr_seq"&gt;&lt;br /&gt;            &lt;column autoIncrement="true" name="id" type="BIGINT"&gt;&lt;br /&gt;                &lt;constraints nullable="false" primaryKey="true"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;        &lt;/createTable&gt;&lt;br /&gt;    &lt;/changeSet&gt;&lt;br /&gt;    &lt;changeSet author="leo (generated)" id="1315806809798-2"&gt;&lt;br /&gt;        &lt;createTable schemaName="TEM" tableName="acct_dd_attr_doc"&gt;&lt;br /&gt;            &lt;column defaultValue="" name="DOC_HDR_ID" type="VARCHAR(14)"&gt;&lt;br /&gt;                &lt;constraints nullable="false" primaryKey="true"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="OBJ_ID" type="VARCHAR(36)"/&gt;&lt;br /&gt;            &lt;column name="VER_NBR" type="DECIMAL(14,0)"/&gt;&lt;br /&gt;            &lt;column name="ACCT_NUM" type="DECIMAL(14,0)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_OWNR" type="VARCHAR(50)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_BAL" type="DECIMAL(16,2)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_OPN_DAT" type="DATETIME"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_STAT" type="VARCHAR(30)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;        &lt;/createTable&gt;&lt;br /&gt;    &lt;/changeSet&gt;&lt;br /&gt;&lt;/databaseChangeLog&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Above is a changelog specific to MySQL. You can tell because of the &lt;b&gt;VARCHAR&lt;/b&gt; type used. If it were Oracle, that would probably be a &lt;b&gt;VARCHAR2&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This makes it a little difficult to reuse changelogs across database platforms. End up locked into one database platform which is a bit troublesome for projects that use more than one (or support more than one like Kuali). &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Semi-Database Agnostic Changelogs&lt;/h2&gt;Not all RDBMS-specific issues can be corrected. Liquibase suggests using contexts in these kinds of scenarios. However, it is possible to directly address the field type issue. Liquibase supports generic SQL Types defined in the &lt;b&gt;java.sql.Types&lt;/b&gt; class. Here's an example:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;&lt;br /&gt;&lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"&gt;&lt;br /&gt;    &lt;changeSet author="leo (generated)" id="1316218188939-1"&gt;&lt;br /&gt;        &lt;createTable schemaName="TEM" tableName="a21_lbr_bld_nbr_seq"&gt;&lt;br /&gt;            &lt;column autoIncrement="true" name="id" type="java.sql.Types.BIGINT"&gt;&lt;br /&gt;                &lt;constraints nullable="false" primaryKey="true"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;        &lt;/createTable&gt;&lt;br /&gt;    &lt;/changeSet&gt;&lt;br /&gt;    &lt;changeSet author="leo (generated)" id="1316218188939-2"&gt;&lt;br /&gt;        &lt;createTable schemaName="TEM" tableName="acct_dd_attr_doc"&gt;&lt;br /&gt;            &lt;column defaultValue="" name="DOC_HDR_ID" type="java.sql.Types.VARCHAR(14)"&gt;&lt;br /&gt;                &lt;constraints nullable="false" primaryKey="true"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="OBJ_ID" type="java.sql.Types.VARCHAR(36)"/&gt;&lt;br /&gt;            &lt;column name="VER_NBR" type="java.sql.Types.DECIMAL(14,0)"/&gt;&lt;br /&gt;            &lt;column name="ACCT_NUM" type="java.sql.Types.DECIMAL(14,0)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_OWNR" type="java.sql.Types.VARCHAR(50)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_BAL" type="java.sql.Types.DECIMAL(16,2)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_OPN_DAT" type="java.sql.Types.TIMESTAMP"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;            &lt;column name="ACCT_STAT" type="java.sql.Types.VARCHAR(30)"&gt;&lt;br /&gt;                &lt;constraints nullable="false"/&gt;&lt;br /&gt;            &lt;/column&gt;&lt;br /&gt;      &lt;/createTable&gt;&lt;br /&gt;    &lt;/changeSet&gt;&lt;br /&gt;&lt;/databaseChangeLog&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You start seeing &lt;b&gt;java.sql.Types.VARCHAR(30)&lt;/b&gt; where the &lt;b&gt;30&lt;/b&gt; is the length of the field. This will work in Oracle as well. The &lt;b&gt;java.sql.Types.VARCHAR&lt;/b&gt; refers to the field in the Java API. During processing, Liquibase will substitute the native Oracle type for the java.sql type.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Generating Semi-Database Agnostic Changelogs&lt;/h2&gt;You may be asking right away, "Why do you want to generate changelogs? Why not just have developers maintain them?" I've tried this method before, and I found it distracting and cumbersome for developers to duplicate work on changelogs. Whenever a database is modified, then the install changelog and the update changelogs need to be modified. The install changelog is to recreate the database, and the update change log is for updating existing databases. Indeed, this does eliminate the "Master database" antipattern, but it forces some extra effort and testing. I've found this to be unnecessary. I much rather prefer and recommend sticking with the "Master database" antipattern. Just apply update scripts to your "Master" and run generate changelogs against it. Since liquibase is database agnostic, we should get the same changelog from any database and be able to apply it to any other database; therefore, it doesn't really matter which database is used as the master.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Problem&lt;/h3&gt;Of course, anyone can start doing their changelogs differently to support the new types, but what about when you generate your changelogs? Well, Liquibase will just use the standard RDBMS targeted changelogs. You could just do post-processing on your changelogs and cleanup after generating. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Solution&lt;/h3&gt;I have put together some Liquibase extensions to get around this. They are in a &lt;a href="http://www.github.com/r351574nc3/lbext"&gt;Git Hub repository&lt;/a&gt;. Anyone can download or check them out. You simply put them into your classpath when you run Liquibase, and it will load the extensions automagically. Among the extensions are numerous fixes for generalizing changelogs. One very prominent one is the lack of sequence support in MySQL databases. Even though MySQL does not require sequences, Kuali does use sequences. This causes conflict. It is especially the case when generating changelogs. &lt;br /&gt;&lt;br /&gt;For examples on how to use the extensions, you can look at my &lt;a href="http://www.github.com/r351574nc3/lbcopy/"&gt;other github project lbcopy&lt;/a&gt;. it's a database copy tool based on Liquibase. It can export/import databases as well as directly migrate from one to another.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4382312254047985275?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4382312254047985275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/liquibase-semi-database-agnostic.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4382312254047985275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4382312254047985275'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/liquibase-semi-database-agnostic.html' title='Liquibase: Semi-Database Agnostic Changelogs'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4292463082244467867</id><published>2011-09-10T21:35:00.000-07:00</published><updated>2011-09-10T21:37:34.694-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Screencast: Database Export with Liquibase</title><content type='html'>&lt;h2&gt;Screencast&lt;/h2&gt;This screencast is of me demoing a tool called &lt;a href="http://www.github.com/r351574nce/lbcopy"&gt;lbcopy&lt;/a&gt;. It is a tool built with liquibase and a few liquibase extensions. The idea is a fast and simple database migration. One of the greatest advantages of this tool is its database agnostic personality. You can export from MySQL and import the schema and content directly into an Oracle database without any modification. The same goes for just about any database. &lt;br /&gt;&lt;br /&gt;Another distinguishing concept between this tool and others is that data is exported in its native environment. Not SQL, but a database. When exporting data, an H2 or HSQLDB jar file read-only database is produced. The data can then be observed and queried using a normal database query tool like Aqua Data Studio. This gives users a much better visualization of the data vs. observing raw SQL or a CSV. &lt;br /&gt;&lt;br /&gt;That's what I demo here. I demo how to export a database and gain access to the exported data.&lt;br /&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/-ti14cfPeSw?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4292463082244467867?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4292463082244467867/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/screencast-database-export-with.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4292463082244467867'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4292463082244467867'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/screencast-database-export-with.html' title='Screencast: Database Export with Liquibase'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1666975928184028053</id><published>2011-09-10T15:49:00.000-07:00</published><updated>2011-09-10T16:19:07.313-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='impex'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Screencast: Database Migration with Liquibase</title><content type='html'>&lt;h2&gt;Screencast&lt;/h2&gt;This screencast is of me demoing a tool called &lt;a href="http://www.github.com/r351574nce/lbcopy"&gt;lbcopy&lt;/a&gt;. It is a tool built with liquibase and a few liquibase extensions. The idea is a fast and simple database migration. One of the greatest advantages of this tool is its database agnostic personality. You can export from MySQL and import the schema and content directly into an Oracle database without any modification. The same goes for just about any database. &lt;br /&gt;&lt;br /&gt;Another distinguishing concept between this tool and others is that data is exported in its native environment. Not SQL, but a database. When exporting data, an H2 or HSQLDB jar file read-only database is produced. The data can then be observed and queried using a normal database query tool like Aqua Data Studio. This gives users a much better visualization of the data vs. observing raw SQL or a CSV. &lt;br /&gt;&lt;br /&gt;There are 3 primary uses for this tool: Export, Import, and Migrate. This demo is on Migrate. Migrate is direct copy of the database from one location to another without export or import. It is just one step.&lt;br /&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/sB0qgNKfZbg?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1666975928184028053?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1666975928184028053/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/09/database-migration-with-liquibase.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1666975928184028053'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1666975928184028053'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/09/database-migration-with-liquibase.html' title='Screencast: Database Migration with Liquibase'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1572841232197452275</id><published>2011-08-12T14:55:00.000-07:00</published><updated>2011-08-16T20:24:34.289-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='presentations'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kc'/><category scheme='http://www.blogger.com/atom/ns#' term='2011'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali days'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><title type='text'>UPDATE: Kuali Days 2011 Presentation Proposals 2</title><content type='html'>The following presentation is on the bubble and may possibly be dropped.&lt;br /&gt;&lt;blockquote&gt;&lt;h3&gt;Abstact&lt;/h3&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Learn about caveats of normal shell-scripting of batch processes.&lt;/li&gt;&lt;li&gt;Learn of creating a simple web service that isn't published to Kuali Service Bus (KSB) for executing isolated batch processes.&lt;/li&gt;&lt;li&gt;Learn how to create a client for communicating with the batch Web Services Definition Language (WSDL).&lt;/li&gt;&lt;li&gt;Batch processes and WS-SEC with Rice and KFS&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Developers on a technical&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;&lt;br /&gt;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 &lt;a href="http://www.kuali.org/contact"&gt;Kuali Foundation&lt;/a&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1572841232197452275?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1572841232197452275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/08/update-kuali-days-2011-presentation.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1572841232197452275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1572841232197452275'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/08/update-kuali-days-2011-presentation.html' title='UPDATE: Kuali Days 2011 Presentation Proposals 2'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-9062940670742328337</id><published>2011-08-12T04:01:00.000-07:00</published><updated>2011-08-12T04:03:01.449-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 3</title><content type='html'>&lt;h3&gt;How the DateChangedListener looks to a 3-year old with a box of crayons&lt;/h3&gt;&lt;br /&gt;&lt;img src="http://24.media.tumblr.com/tumblr_lptalqXeNA1qzuu0lo1_500.png" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-9062940670742328337?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/9062940670742328337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/08/struts-1-spring-propertychangeevent-and_12.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/9062940670742328337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/9062940670742328337'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/08/struts-1-spring-propertychangeevent-and_12.html' title='Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 3'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1759554757792111639</id><published>2011-08-10T21:18:00.000-07:00</published><updated>2011-08-11T00:58:53.331-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 2</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I am continuing the saga on this. In &lt;a href="/2011/07/struts-1-spring-propertychangeevent-and.html"&gt;Part 1&lt;/a&gt;, I described the scenario:&lt;blockquote&gt;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.&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;I want to revisit this. Before I do though, a little diagram on the aforementioned Observer Pattern.&lt;br /&gt;&lt;img src="http://26.media.tumblr.com/tumblr_lpr6usVQWN1qzuu0lo1_500.png" /&gt;&lt;br /&gt;&lt;img src="http://26.media.tumblr.com/tumblr_lpr7hizsSZ1qzuu0lo1_500.png" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Events and Listeners&lt;/h2&gt;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?&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;h3&gt;TravelReimbursementAction&lt;/h3&gt;Here's my new createDocument() method.&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2010 The Kuali Foundation.&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 1.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl1.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.document.web.struts;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;/***&lt;br /&gt; * Action methods for the {@link TravelReimbursementDocument}&lt;br /&gt; *&lt;br /&gt; * @author Leo Przybylski (leo [at] rsmart.com)&lt;br /&gt; */&lt;br /&gt;public class TravelReimbursementAction extends TravelActionBase {&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    /**&lt;br /&gt;     * Do initialization for a new {@link TravelReimbursementDocument}&lt;br /&gt;     * &lt;br /&gt;     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)&lt;br /&gt;     */&lt;br /&gt;    @Override&lt;br /&gt;    protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {&lt;br /&gt;        super.createDocument(kualiDocumentFormBase);&lt;br /&gt;        final TravelReimbursementForm travelForm = (TravelReimbursementForm) kualiDocumentFormBase;&lt;br /&gt;        final TravelReimbursementDocument document = (TravelReimbursementDocument) travelForm.getDocument();&lt;br /&gt;        getTravelDocumentService().addListenersTo(document);&lt;br /&gt;        addContactInformation(document);&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;TravelDocumentService&lt;/h3&gt;Ok, let's see how I inject those listeners.&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2010 The Kuali Foundation&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.service.impl;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;/**&lt;br /&gt; * Travel Reimbursement Service Implementation&lt;br /&gt; * &lt;br /&gt; */&lt;br /&gt;public class TravelReimbursementServiceImpl implements TravelReimbursementService {&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    public void addListenersTo(final TravelReimbursementDocument reimbursement) {&lt;br /&gt;        reimbursement.setPropertyChangeListeners(getPropertyChangeListeners());&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    /**&lt;br /&gt;     * Sets the propertyChangeListener attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param propertyChangeListener The propertyChangeListener to set.&lt;br /&gt;     */&lt;br /&gt;    public void setPropertyChangeListeners(final List&amp;lt;PropertyChangeListener&gt; propertyChangeListeners) {&lt;br /&gt;        this.propertyChangeListeners = propertyChangeListeners;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the propertyChangeListeners attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the propertyChangeListenerDetailId.&lt;br /&gt;     */&lt;br /&gt;    public List&amp;lt;PropertyChangeListener&amp;gt; getPropertyChangeListeners() {&lt;br /&gt;        return this.propertyChangeListeners;&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So where do these PropertyChangeListeners get set? Obviously, they're injected into the service, right? But how? Let's look.&lt;br /&gt;&lt;h3&gt;spring-tem.xml&lt;/h3&gt;This is where we setup our TravelDocumentService to be injected with our property change listeners.&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!-- Copyright 2006-2008 The Kuali Foundation Licensed under the Educational &lt;br /&gt;	Community License, Version 2.0 (the "License"); you may not use this file &lt;br /&gt;	except in compliance with the License. You may obtain a copy of the License &lt;br /&gt;	at http://www.opensource.org/licenses/ecl2.php Unless required by applicable &lt;br /&gt;	law or agreed to in writing, software distributed under the License is distributed &lt;br /&gt;	on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either &lt;br /&gt;	express or implied. See the License for the specific language governing permissions &lt;br /&gt;	and limitations under the License. --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt;	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt;	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt;	xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/tx&lt;br /&gt;                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/aop&lt;br /&gt;                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    &amp;lt;!-- Property Listeners --&amp;gt;&lt;br /&gt;    &amp;lt;bean id="TravelReimbursementDocument-dateChangedListener" class="org.kuali.kfs.module.tem.document.listener.DateChangedListener"&amp;gt;&lt;br /&gt;        &amp;lt;property name="documentService" ref="documentService" /&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    &amp;lt;bean id="temTravelDocumentService" parent="temTravelDocumentService-parentBean" /&amp;gt;&lt;br /&gt;    &amp;lt;bean   id="temTravelDocumentService-parentBean" &lt;br /&gt;         class="org.kuali.kfs.module.tem.service.impl.TravelDocumentServiceImpl" &lt;br /&gt;      abstract="true"&amp;gt;&lt;br /&gt;      &amp;lt;property name="propertyChangeListeners"&amp;gt;&lt;br /&gt;        &amp;lt;list&amp;gt;&lt;br /&gt;          &amp;lt;ref bean="TravelReimbursementDocument-dateChangedListener" /&amp;gt;&lt;br /&gt;        &amp;lt;/list&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;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?&lt;br /&gt;&lt;h3&gt;PropertyChangeEvent&lt;/h3&gt;In my TravelReimbursementDocument, I'm going to trigger a PropertyChangeEvent on the listener.&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2010 The Kuali Foundation.&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 1.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl1.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.document;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;import java.beans.PropertyChangeEvent;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;/**&lt;br /&gt; * Abstract Travel Document Base&lt;br /&gt; */&lt;br /&gt;public abstract class TravelDocumentBase extends AccountingDocumentBase implements TravelDocument, Copyable {&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    @Transient&lt;br /&gt;    private List&amp;lt;PropertyChangeListener&amp;gt; propertyChangeListeners;&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    /**&lt;br /&gt;     * Sets the propertyChangeListener attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param propertyChangeListener The propertyChangeListener to set.&lt;br /&gt;     */&lt;br /&gt;    public void setPropertyChangeListeners(final List&amp;lt;PropertyChangeListener&amp;gt; propertyChangeListeners) {&lt;br /&gt;        this.propertyChangeListeners = propertyChangeListeners;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the propertyChangeListeners attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the propertyChangeListenerDetailId.&lt;br /&gt;     */&lt;br /&gt;    public List&amp;lt;PropertyChangeListener&amp;gt; getPropertyChangeListeners() {&lt;br /&gt;        return this.propertyChangeListeners;&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    /**&lt;br /&gt;     * This method sets the trip begin date for this request&lt;br /&gt;     * &lt;br /&gt;     * @param tripBegin&lt;br /&gt;     */&lt;br /&gt;    public void setTripBegin(Date tripBegin) {&lt;br /&gt;        notifyChangeListeners(new PropertyChangeEvent(this, "tripBegin", this.tripBegin, tripBegin));&lt;br /&gt;        this.tripBegin = tripBegin;&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;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!&lt;br /&gt;&lt;h3&gt;DateChangedListener&lt;/h3&gt;Now let's look at what happens when the date is changed&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2010 The Kuali Foundation&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.document.listener;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;/**&lt;br /&gt; * Executed when a trip date is modified&lt;br /&gt; * &lt;br /&gt; * @author Leo Przybylski (leo [at] rsmart.com&lt;br /&gt; */&lt;br /&gt;public class DateChangedListener implements PropertyChangeListener, java.io.Serializable {&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    public void propertyChange(final PropertyChangeEvent event) {&lt;br /&gt;        final TravelReimbursementDocument reimbursement = (TravelReimbursementDocument) event.getSource();&lt;br /&gt;        final Date oldDate = (Date) event.getOldValue();&lt;br /&gt;        final Date newDate = (Date) event.getNewValue();&lt;br /&gt;&lt;br /&gt;        if (hasDateChanged(oldDate, newDate)) {&lt;br /&gt;            notifyDateChangedOn(reimbursement, newDate, "tripBegin".equals(event.getPropertyName()));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    protected Boolean hasDateChanged(final Date oldDate, final Date newDate) {&lt;br /&gt;        final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");&lt;br /&gt;        final String oldDateStr = formatter.format(oldDate);&lt;br /&gt;        final String newDateStr = formatter.format(newDate);&lt;br /&gt;        return oldDateStr.equals(newDateStr);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void notifyDateChangedOn(final TravelReimbursementDocument reimbursement, final Date newDate, boolean start) throws Exception {&lt;br /&gt;        final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");&lt;br /&gt;        final String origStartDateStr  = formatter.format(reimbursement.getTripBegin());&lt;br /&gt;        final String origEndDateStr    = formatter.format(reimbursement.getTripEnd());&lt;br /&gt;        String newStartDateStr = origStartDateStr;&lt;br /&gt;        String newEndDateStr   = origEndDateStr;&lt;br /&gt;&lt;br /&gt;        if (start) {&lt;br /&gt;            newStartDateStr = formatter.format(newDate);&lt;br /&gt;        }&lt;br /&gt;        else {&lt;br /&gt;            newEndDateStr = formatter.format(newDate);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        final String noteText = String.format(DATE_CHANGED_MESSAGE, origStartDateStr, origEndDateStr, newStartDateStr, newEndDateStr);&lt;br /&gt;        &lt;br /&gt;        final Note noteToAdd = getDocumentService().createNoteFromDocument(reimbursement, noteText);&lt;br /&gt;        getDocumentService().addNoteToDocument(reimbursement, noteToAdd);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setDocumentService(final DocumentService documentService) {&lt;br /&gt;        this.documentService = documentService;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected DocumentService getDocumentService() {&lt;br /&gt;        return documentService;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;That's how you can inject services into documents that react on modifications to the Document.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1759554757792111639?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1759554757792111639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/08/struts-1-spring-propertychangeevent-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1759554757792111639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1759554757792111639'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/08/struts-1-spring-propertychangeevent-and.html' title='Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali Part 2'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-2375439328282024624</id><published>2011-07-23T22:57:00.000-07:00</published><updated>2011-07-25T06:03:40.904-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='impex'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Lightning IMPEX</title><content type='html'>&lt;h2&gt;Screencast&lt;/h2&gt;I put together a screencast that shows how a certain branch of the impex tool is really really fast and resource friendly for KFS 3.0 installations. Here it is.&lt;br /&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/vEIK3YoJE9E?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Steps&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;1 Get kfs-cfg-dbs&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ svn co https://test.kuali.org/svn/kfs-cfg-dbs/branches/release-3-0-1/ kfs-cfg-dbs-3-0-1&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;2 Get rice-cfg-dbs&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ svn co https://test.kuali.org/svn/rice-cfg-dbs/branches/rice-release-1-0-1-1-br rice-cfg-dbs-1-0-1-1&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;3 Get kul-cfg-dbs&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ svn co https://test.kuali.org/svn/kul-cfg-dbs/branches/kul-handle_dollar_signs_in_tables-br kul-cfg-dbs&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;4 Copy sample properties file&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ cp impex/impex-build.properties.sample $HOME/rice-impex-build.properties &lt;br /&gt;$ cp impex/impex-build.properties.sample $HOME/kfs-impex-build.properties&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;5 Copy library file&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ cd kul-cfg-dbs/impex;&lt;br /&gt;$ cp kuali-impextasks.jar lib&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;6 Run import in rice-cfg-dbs&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ cd rice-cfg-dbs-1-0-1-1;&lt;br /&gt;$ export ANT_OPTS="-Xmx2048m -XX:MaxPermSize=256m"&lt;br /&gt;$ ant -f ../kul-cfg-dbs/build.xml -Dimpex.build.properties=$HOME/rice-impex-build.properties import&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;7 Run import in kfs-cfg-dbs&lt;/h3&gt;&lt;pre class="brush: plain"&gt;$ cd kfs-cfg-dbs-3-0-1;&lt;br /&gt;$ ant -f ../kul-cfg-dbs/build.xml -Dimpex.build.properties=$HOME/kfs-impex-build.properties import&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-2375439328282024624?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/2375439328282024624/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/07/lightning-impex.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2375439328282024624'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2375439328282024624'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/07/lightning-impex.html' title='Lightning IMPEX'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3152152695908625883</id><published>2011-07-23T22:51:00.000-07:00</published><updated>2011-07-24T02:04:24.473-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='struts'/><title type='text'>Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;There are a couple things that have plagued me as a developer on Kuali.&lt;ul&gt;&lt;li&gt;No abstraction layer between struts and spring&lt;/li&gt;&lt;li&gt;No subsystem for reacting to business object changes other than validation&lt;/lI&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;There is this fantastic &lt;a href="https://wiki.kuali.org/display/KULDOC/Validation+and+Error+Handling+3"&gt;validation framework&lt;/a&gt; in KFS/Kuali software. However, validation is a passive action. It simply checks for inconsistencies and reports on them. A validation can never change the state in any way whatsoever. 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.&lt;br /&gt;&lt;br /&gt;I'll come back to that later. Speaking of the Action class, I have noticed that it is very difficult for me as a developer to resist putting business logic in the Action class. The Action and Form classes are intended to be part of an abstraction layer between the Struts M-V-C components. With Spring providing an IOC with services, it isn't very clear where certain logic should live. What do I do if I want as little logic in my Action and Form as possible, so that the services are doing most of the work? &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Observer Pattern&lt;/h2&gt;Below is how I implemented the &lt;a href="http://en.wikipedia.org/wiki/Observer_pattern"&gt;Observer pattern&lt;/a&gt; for Struts to communicate with Spring and not have any coupling between SOA and MVC. Here's how I did it:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1 Define Spring Service Beans&lt;/h3&gt;It is easiest to just build your framework first with SOA. That way everything else can fit into place around it. I'm going to create all my Observables and Observers in Spring. &lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!-- Copyright 2006-2008 The Kuali Foundation Licensed under the Educational &lt;br /&gt; Community License, Version 2.0 (the "License"); you may not use this file &lt;br /&gt; except in compliance with the License. You may obtain a copy of the License &lt;br /&gt; at http://www.opensource.org/licenses/ecl2.php Unless required by applicable &lt;br /&gt; law or agreed to in writing, software distributed under the License is distributed &lt;br /&gt; on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either &lt;br /&gt; express or implied. See the License for the specific language governing permissions &lt;br /&gt; and limitations under the License. --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/tx&lt;br /&gt;                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/aop&lt;br /&gt;                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    &amp;lt;!-- Struts Events --&amp;gt;&lt;br /&gt;    &amp;lt;bean id="addOtherExpenseEvent" class="org.kuali.kfs.module.tem.document.web.struts.AddOtherExpenseEvent"&amp;gt;&lt;br /&gt;      &amp;lt;property name="travelReimbursementService" ref="temTravelReimbursementService" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="ruleService"                ref="kualiRuleService" /&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;    &amp;lt;bean id="removeOtherExpenseEvent" class="org.kuali.kfs.module.tem.document.web.struts.RemoveOtherExpenseEvent"&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;    &amp;lt;bean id="addExpenseDetailEvent" class="org.kuali.kfs.module.tem.document.web.struts.AddExpenseDetailEvent"&amp;gt;&lt;br /&gt;      &amp;lt;property name="travelReimbursementService" ref="temTravelReimbursementService" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="ruleService"                ref="kualiRuleService" /&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;    &amp;lt;bean id="removeExpenseDetail" class="org.kuali.kfs.module.tem.document.web.struts.AddExpenseDetailEvent"&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Struts Observable Pattern --&amp;gt;&lt;br /&gt;    &amp;lt;bean class="org.kuali.kfs.module.tem.document.web.struts.TravelStrutsObservable"&amp;gt;&lt;br /&gt;      &amp;lt;property name="observers"&amp;gt;&lt;br /&gt;        &amp;lt;map&amp;gt;&lt;br /&gt;          &amp;lt;entry key="addOtherExpenseLine"&amp;gt;&lt;br /&gt;            &amp;lt;list&amp;gt;&lt;br /&gt;              &amp;lt;ref bean="addOtherExpenseEvent" /&amp;gt;&lt;br /&gt;            &amp;lt;/list&amp;gt;&lt;br /&gt;          &amp;lt;/entry&amp;gt;&lt;br /&gt;          &amp;lt;entry key="deleteOtherExpenseLine"&amp;gt;&lt;br /&gt;            &amp;lt;list&amp;gt;&lt;br /&gt;              &amp;lt;ref bean="removeOtherExpenseEvent" /&amp;gt;&lt;br /&gt;            &amp;lt;/list&amp;gt;&lt;br /&gt;          &amp;lt;/entry&amp;gt;&lt;br /&gt;          &amp;lt;entry key="addOtherExpenseDetailLine"&amp;gt;&lt;br /&gt;            &amp;lt;list&amp;gt;&lt;br /&gt;              &amp;lt;ref bean="addExpenseDetailEvent" /&amp;gt;&lt;br /&gt;            &amp;lt;/list&amp;gt;&lt;br /&gt;          &amp;lt;/entry&amp;gt;&lt;br /&gt;          &amp;lt;entry key="deleteOtherExpenseDetailLine"&amp;gt;&lt;br /&gt;            &amp;lt;list&amp;gt;&lt;br /&gt;              &amp;lt;ref bean="removeExpenseDetailEvent" /&amp;gt;&lt;br /&gt;            &amp;lt;/list&amp;gt;&lt;br /&gt;          &amp;lt;/entry&amp;gt;&lt;br /&gt;        &amp;lt;/map&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Each event is an Observer. The Observer is basically waiting for the Observable (by observing) to notify it. For example, &lt;pre class="brush: xml"&gt;...&lt;br /&gt;...&lt;br /&gt;    &lt;bean id="addOtherExpenseEvent" class="org.kuali.kfs.module.tem.document.web.struts.AddOtherExpenseEvent"&gt;&lt;br /&gt;      &lt;property name="travelReimbursementService" ref="temTravelReimbursementService" /&gt;&lt;br /&gt;      &lt;property name="ruleService"                ref="kualiRuleService" /&gt;&lt;br /&gt;    &lt;/bean&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;/pre&gt; is an Observer.&lt;br /&gt;&lt;br /&gt;The Observable is just another Spring bean/service. It has a Map of observers. The MVC action or methodToCall is mapped to the Observer. When certain button clicks happen (addOtherExpenseLine for example), the Observable will notify the appropriate Observer/Event.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2 Create the Observable&lt;/h3&gt;This is the interesting part. Not only do we need to create an Observable, but it needs to be flexible or dynamic enough that we aren't actually creating more work for ourselves by trying to solve this problem. Here's how I did it. I added the Observable to the TravelFormBase class. Now TravelFormBase is my base Form class that I use to store common code for all the TEM documents. This ensures that all the TEM documents are going to get the same Observable. &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;import java.util.Observable;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;public abstract class TravelFormBase extends KualiAccountingDocumentFormBase implements TravelMvcWrapperBean {&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    private Observable observable;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the observable attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the observable.&lt;br /&gt;     */&lt;br /&gt;    public Observable getObservable() {&lt;br /&gt;        return SpringContext.getBean(TravelStrutsObservable.class);&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;I cut out a lot, but notice a couple things. My TravelFormBase is NOT the Observable. Just as I showed before in my Spring configuration, the TravelStrutsObservable is my Observable. I am getting that through my handy dandy SpringContext class. This is great because now I passively rely on the Observable to manage the Spring work. I only have to ever lookup one Spring bean and that is the Observable. The rest is handled through Dependency Injection!&lt;br /&gt;&lt;br /&gt;Now let's look at that Observable. &lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2011 The Kuali Foundation&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.document.web.struts;&lt;br /&gt;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Map;&lt;br /&gt;import java.util.Observable;&lt;br /&gt;import java.util.Observer;&lt;br /&gt;&lt;br /&gt;import org.kuali.kfs.module.tem.document.web.bean.TravelMvcWrapperBean;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; *&lt;br /&gt; * @author Leo Przybylski (leo [at] rsmart.com)&lt;br /&gt; */&lt;br /&gt;public class TravelStrutsObservable extends Observable {&lt;br /&gt;    public Map&amp;lt;String, List&amp;lt;Observer&amp;gt;&amp;gt; observers;&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * deprecating this since the best practice is to use Spring&lt;br /&gt;     */&lt;br /&gt;    @Deprecated&lt;br /&gt;    public void addObserver(final Observer observer) {      &lt;br /&gt;        super.addObserver(observer);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void notifyObservers(final Object arg) {&lt;br /&gt;        TravelMvcWrapperBean wrapper = null;&lt;br /&gt;        if (arg instanceof TravelMvcWrapperBean) {&lt;br /&gt;            wrapper = (TravelMvcWrapperBean) arg;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        final String eventName = wrapper.getMethodToCall();&lt;br /&gt;        for (final Observer observer : getObservers().get(eventName)) {&lt;br /&gt;            observer.update(this, wrapper);&lt;br /&gt;        }&lt;br /&gt;        clearChanged();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the observers attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the observers.&lt;br /&gt;     */&lt;br /&gt;    public Map&amp;lt;String, List&amp;lt;Observer&amp;gt;&amp;gt; getObservers() {&lt;br /&gt;        return observers;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the observers attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param observers The observers to set.&lt;br /&gt;     */&lt;br /&gt;    public void setObservers(final Map&amp;lt;String,List&amp;lt;Observer&amp;gt;&amp;gt; observers) {&lt;br /&gt;        this.observers = observers;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;What? That's it?! Yeah, that's it. You can see that notifyObservers() is overridden to iterate through the observer map and notifies the Observer mapped to the methodToCall on the wrapper.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3 TravelMvcWrapperBean&lt;/h3&gt;You may be wondering what in the world that TravelMvcWrapperBean is. You may recall that one of the goals is to abstract the MVC layer. I don't want further coupling to Struts. Even though I have a TravelStrutsObservable,  that's about as deep as the coupling gets. Actually, you can probably tell from the Observable code that besides the name, nothing really couples that to Struts. Not even the getMethodToCall(). Everything is abstracted by this new TravelMvcWrapperBean which is just an interface. &lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2010 The Kuali Foundation.&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 1.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl1.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.document.web.bean;&lt;br /&gt;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.Map;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;import org.kuali.rice.kns.bo.Note;&lt;br /&gt;import org.kuali.rice.kns.document.Document;&lt;br /&gt;import org.kuali.rice.kns.web.ui.ExtraButton;&lt;br /&gt;import org.kuali.kfs.module.tem.document.TravelDocument;&lt;br /&gt;&lt;br /&gt;public interface TravelMvcWrapperBean {&lt;br /&gt;&lt;br /&gt;    Integer getTravelerId();&lt;br /&gt;&lt;br /&gt;    TravelDocument getTravelDocument();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    void setTravelerId(Integer travelerId);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    Integer getTempTravelerId();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    void setTempTravelerId(Integer tempTravelerId);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the empPrincipalId attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the empPrincipalId.&lt;br /&gt;     */&lt;br /&gt;    String getEmpPrincipalId();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the empPrincipalId attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param empPrincipalId The empPrincipalId to set.&lt;br /&gt;     */&lt;br /&gt;    void setEmpPrincipalId(String empPrincipalId);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the tempEmpPrincipalId attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the tempEmpPrincipalId.&lt;br /&gt;     */&lt;br /&gt;    String getTempEmpPrincipalId();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the tempEmpPrincipalId attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param tempEmpPrincipalId The tempEmpPrincipalId to set.&lt;br /&gt;     */&lt;br /&gt;    void setTempEmpPrincipalId(String tempEmpPrincipalId);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    Map&amp;lt;String, String&amp;gt; getModesOfTransportation();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the showLodging attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the showLodging.&lt;br /&gt;     */&lt;br /&gt;    boolean isShowLodging();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the showLodging attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param showLodging The showLodging to set.&lt;br /&gt;     */&lt;br /&gt;    void setShowLodging(boolean showLodging);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the showMileage attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the showMileage.&lt;br /&gt;     */&lt;br /&gt;    boolean isShowMileage();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the showMileage attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param showMileage The showMileage to set.&lt;br /&gt;     */&lt;br /&gt;    void setShowMileage(boolean showMileage);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the showPerDiem attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the showPerDiem.&lt;br /&gt;     */&lt;br /&gt;    boolean isShowPerDiem();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the canReturn attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @return canReturn The canReturn to set.&lt;br /&gt;     */&lt;br /&gt;    boolean canReturn();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the canReturn attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param canReturn The canReturn to set.&lt;br /&gt;     */&lt;br /&gt;    void setCanReturn(final boolean canReturn);&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the showPerDiem attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param showPerDiem The showPerDiem to set.&lt;br /&gt;     */&lt;br /&gt;    void setShowPerDiem(boolean showPerDiem);&lt;br /&gt;&lt;br /&gt;    boolean isShowAllPerDiemCategories();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * This method takes a string parameter from the db and converts it to an int suitable for using in our calculations&lt;br /&gt;     * &lt;br /&gt;     * @param perDiemPercentage&lt;br /&gt;     */&lt;br /&gt;    void setPerDiemPercentage(String perDiemPercentage);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the perDiemPercentage attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the perDiemPercentage.&lt;br /&gt;     */&lt;br /&gt;    int getPerDiemPercentage();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the perDiemPercentage attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param perDiemPercentage The perDiemPercentage to set.&lt;br /&gt;     */&lt;br /&gt;    void setPerDiemPercentage(int perDiemPercentage);&lt;br /&gt;&lt;br /&gt;    Map&amp;lt;String, List&amp;lt;Document&amp;gt;&amp;gt; getRelatedDocuments();&lt;br /&gt;&lt;br /&gt;    void setRelatedDocuments(Map&amp;lt;String, List&amp;lt;Document&amp;gt;&amp;gt; relatedDocuments);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the relatedDocumentNotes attribute.&lt;br /&gt;     * &lt;br /&gt;     * @return Returns the relatedDocumentNotes.&lt;br /&gt;     */&lt;br /&gt;    Map&amp;lt;String, List&amp;lt;Note&amp;gt;&amp;gt; getRelatedDocumentNotes();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the relatedDocumentNotes attribute value.&lt;br /&gt;     * &lt;br /&gt;     * @param relatedDocumentNotes The relatedDocumentNotes to set.&lt;br /&gt;     */&lt;br /&gt;    void setRelatedDocumentNotes(Map&amp;lt;String, List&amp;lt;Note&amp;gt;&amp;gt; relatedDocumentNotes);&lt;br /&gt;&lt;br /&gt;    boolean isCalculated();&lt;br /&gt;&lt;br /&gt;    void setCalculated(boolean calculated);&lt;br /&gt;&lt;br /&gt;    List&amp;lt;ExtraButton&amp;gt; getExtraButtons();&lt;br /&gt;&lt;br /&gt;    String getMethodToCall();&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The interface is implemented by my TravelFormBase that I showed you earlier. &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;public abstract class TravelFormBase extends KualiAccountingDocumentFormBase implements TravelMvcWrapperBean {&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;}&lt;/pre&gt;This means that when you see the TravelMvcWrapperBean referenced, it's really the struts Form class. Keep that in mind as we delve further in. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;4 Observers&lt;/h3&gt;Now we discuss what those events were all about in my Spring configuration. Those events are my Observers. They get triggered by the Observable. I will show you how. In a normal request, the Controller hands the Form off to the Action and calls the execute() method. It has already set the methodToCall on the form, so execute() will use this to delegate further in the Action. If my methodToCall is addOtherExpenseLine, then the addOtherExpenseLine() method is called. Here &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * Action method for adding an {@link OtherExpense} instance to the {@link TravelReimbursementDocument}&lt;br /&gt;     * &lt;br /&gt;     * @param mapping&lt;br /&gt;     * @param form&lt;br /&gt;     * @param request&lt;br /&gt;     * @param response&lt;br /&gt;     * @return&lt;br /&gt;     * @throws Exception&lt;br /&gt;     */&lt;br /&gt;    public ActionForward addOtherExpenseLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {&lt;br /&gt;        final TravelReimbursementForm reimbForm = (TravelReimbursementForm) form;&lt;br /&gt;        final TravelReimbursementMvcWrapperBean mvcWrapper = newMvcDelegate(form);&lt;br /&gt;        reimbForm.getObservable().notifyObservers(mvcWrapper);&lt;br /&gt;&lt;br /&gt;        return mapping.findForward(KFSConstants.MAPPING_BASIC);&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;/pre&gt;&lt;br /&gt;Here you can see that it gets the Observable from the Form and then passes in the TravelReimbursementMvcWrapperBean which is really the Form. Why not just pass in the Form? Well, that would couple us to Struts. I will illustrate that later on. From what we already know, the Observable will examine the wrapper for its methodToCall, and grab the appropriate Observer(s) for it. It then calls update on them. This is what happens for the AddOtherExpenseEvent &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    public void update(final Observable observable, Object arg) { &lt;br /&gt;        if (!(arg instanceof TravelReimbursementMvcWrapperBean)) {&lt;br /&gt;            return;&lt;br /&gt;        }&lt;br /&gt;        final TravelReimbursementMvcWrapperBean wrapper = (TravelReimbursementMvcWrapperBean) arg;&lt;br /&gt;&lt;br /&gt;        final TravelReimbursementDocument document = wrapper.getTravelReimbursementDocument();&lt;br /&gt;        final ReimbursementOtherExpense newOtherExpenseLine = wrapper.getNewOtherExpenseLine();&lt;br /&gt;        newOtherExpenseLine.refreshReferenceObject("travelExpenseTypeCode");&lt;br /&gt;&lt;br /&gt;        getTravelReimbursementService().handleNewOtherExpense(newOtherExpenseLine);            &lt;br /&gt;&lt;br /&gt;        boolean rulePassed = true;&lt;br /&gt;&lt;br /&gt;        // check any business rules&lt;br /&gt;        rulePassed &amp;= getRuleService().applyRules(new AddOtherExpenseLineEvent(NEW_OTHER_EXPENSE_LINE, document, newOtherExpenseLine));&lt;br /&gt;&lt;br /&gt;        if (rulePassed) {&lt;br /&gt;            document.addOtherExpense(newOtherExpenseLine);&lt;br /&gt;            final OtherExpenseDetail newDetail = new OtherExpenseDetail();&lt;br /&gt;            newDetail.setExpenseDate(newOtherExpenseLine.getExpenseDate());&lt;br /&gt;            wrapper.getNewOtherExpenseDetailLines().add(newDetail);&lt;br /&gt;            wrapper.setNewOtherExpenseLine(new ReimbursementOtherExpense());&lt;br /&gt;            wrapper.getNewOtherExpenseLine().setDetails(new ArrayList&lt;OtherExpenseDetail&gt;());&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        wrapper.setCalculated(false);&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;/pre&gt;&lt;br /&gt;You can see there is no mention of Struts anywhere in this Spring bean. It uses 2 services that are injected (KualiRuleService and TravelReimbursementService), so it never has to call SpringContext to get to those. You can see it uses the TravelReimbursementMvcWrapperBean as wrapper to communicate with the Form object without knowing or caring that is a Form.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;5 Proxying the Form&lt;/h3&gt;Why not just use the Form? Why does the Action class have to lookup an MVC Delegate and pass that as the wrapper? This is to further abstract away from Struts. Instead of calling the Struts object directly, a proxy is made on it. I added the following method to my TravelActionBase so that all my Action classes would have access to it &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    public &amp;lt;T&amp;gt; T newMvcDelegate(final ActionForm form) throws Exception {&lt;br /&gt;        T retval = (T) Proxy.newProxyInstance(getClass().getClassLoader(),&lt;br /&gt;                                              new Class[] { getMvcWrapperInterface() },&lt;br /&gt;                                              new TravelMvcWrapperInvocationHandler(form));&lt;br /&gt;        return retval;&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * Just a passthru {@link InvocationHandler}. It's used when creating a proxy, to access methods in a class&lt;br /&gt;     * without knowing what that class really is. This allows us to put a facade layer on top of whatever MVC we use;&lt;br /&gt;     * hence, the name {@link TravelMvcWrapperInvocationHandler}&lt;br /&gt;     *&lt;br /&gt;     * @author Leo Przybylski leo [at] rsmart.com&lt;br /&gt;     */&lt;br /&gt;    class TravelMvcWrapperInvocationHandler&amp;lt;MvcClass&amp;gt; implements InvocationHandler {&lt;br /&gt;        private MvcClass mvcObj;&lt;br /&gt;&lt;br /&gt;        public TravelMvcWrapperInvocationHandler(final MvcClass mvcObj) {&lt;br /&gt;            this.mvcObj = mvcObj;&lt;br /&gt;        }        &lt;br /&gt;        &lt;br /&gt;        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {&lt;br /&gt;            return method.invoke(mvcObj, args);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;/pre&gt; This will allow me to make changes to my Form that normally would not be acceptable or whatever.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;PropertyChangeEvent&lt;/h2&gt;I think I will save this for another post.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;That's how you can abstract your code out of Struts and add more to your SOA. This will of course make your code easier to unit test. It will also strip any non-UI related logic from your Action.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3152152695908625883?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3152152695908625883/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/07/struts-1-spring-propertychangeevent-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3152152695908625883'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3152152695908625883'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/07/struts-1-spring-propertychangeevent-and.html' title='Struts 1, Spring, PropertyChangeEvent, and the Observer Pattern in Kuali'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1729413261230032335</id><published>2011-07-14T17:06:00.001-07:00</published><updated>2011-07-14T17:09:11.065-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='presentations'/><category scheme='http://www.blogger.com/atom/ns#' term='2011'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali days'/><title type='text'>UPDATE: Kuali Days 2011 Presentation Proposals</title><content type='html'>"The Status is Not Quo!" JSR-286 and Rice has been curbed. I and a few others may try to put together an informal meetup regarding this, but it was not accepted as a presentation. I will definitely add it again for next year.&lt;br /&gt;&lt;br /&gt;Thanks everyone for the feedback!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1729413261230032335?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1729413261230032335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/07/update-kuali-days-2011-presentation.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1729413261230032335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1729413261230032335'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/07/update-kuali-days-2011-presentation.html' title='UPDATE: Kuali Days 2011 Presentation Proposals'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-847741917111562744</id><published>2011-07-13T10:09:00.001-07:00</published><updated>2011-07-14T08:38:23.404-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='presentations'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kc'/><category scheme='http://www.blogger.com/atom/ns#' term='2011'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali days'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><title type='text'>Kuali Days 2011 Presentation Proposals</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt; Below are proposals I am submitting for Kuali Days. Please vote in a comment!&lt;br /&gt;&lt;br /&gt; &lt;h2&gt;The Impact of Dev Ops on Kuali Development and Hosting&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Abstract&lt;/h3&gt;There is a movement occurring acrossed the OSS universe. It is DEVOPS. The concept is where developers are also the operational staff. They become basically swiss-army knives of software. In organizations spread out remotely, it can be difficult for developers to interact with support staff. Timezones can produce bottlenecks that delay projects. DEVOPS basically come out of necessity. &lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Implementations will see how to utilize DEVOPS in their organization.&lt;/li&gt;&lt;li&gt;Developers will be discover how to use their skills at their organization to improve development productivity.&lt;/li&gt;&lt;li&gt;SAAS systems hugely benefit from DEVOPS to provide the best possible uptime for your hosted software. Implementors and will see how SAAS solutions are more reliable, cost-effective, and secure because of utilizing DEVOPS.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Implementation Track&lt;/li&gt;&lt;li&gt;Developers&lt;/li&gt;&lt;li&gt;Project Managers&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;The Status is Not Quo! JSR-286 and Rice&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Abstract&lt;/h3&gt;JSR-286 is the 2.0 portlet specification. This is a walkthrough of implementing web services as a portlet.&lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Developers will see what goes into producing a portlet implementation&lt;/li&gt;&lt;li&gt;Developers will learn/understand how to create a web services client in java to communicate with Kuali Rice&lt;/li&gt;&lt;li&gt;Developers will learn how to deploy a portlet to a portlet container&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Technical track for developers&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Rice 1.1.1(2.0) Project: From the Cradle to the Grave &lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Abstract&lt;/h3&gt;A major version update for Kuali Rice is released, but not much is known about implementing it. There will undoubtably be numerous panels and presentations on taking advantage of the new version’s greatest features; however, this will be the one presentation that shows how to set up a new project, prepare it for Continuous Integration and deployment with use cases and best practices. This presentation overviews the new version project setup start to end.&lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Best practices for setting up your SVN project to pull in your rice codebase&lt;/li&gt;&lt;li&gt;Best practices for structuring your rice project&lt;/li&gt;&lt;li&gt;Setting up a strong Continuous Integration environment for development&lt;/li&gt;&lt;li&gt;Developing with overlays&lt;/li&gt;&lt;li&gt;Deploying to tomcat&lt;/li&gt;&lt;li&gt;Deploying to jetty&lt;/li&gt;&lt;li&gt;Maven best practices&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Technical Rice track for developers and configuration managers&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Scripting Batch Processing with Web Services &lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Abstact&lt;/h3&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Learn about caveats of normal shell-scripting of batch processes.&lt;/li&gt;&lt;li&gt;Learn of creating a simple web service that isn't published to Kuali Service Bus (KSB) for executing isolated batch processes.&lt;/li&gt;&lt;li&gt;Learn how to create a client for communicating with the batch Web Services Definition Language (WSDL).&lt;/li&gt;&lt;li&gt;Batch processes and WS-SEC with Rice and KFS&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Developers on a technical&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Database Change Migrations with Liquibase &lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Abstact&lt;/h3&gt;Database changes via DDL/DML can be difficult to manage. Much of the time, DDL changes will be unreversable. For example, a table rename may require a table drop and recreate. This is usually favorable because then developers can ignore the proprietary language nature of SQL across RDBMS. Liquibase provides a common language and method supporting almost every database there is. Liquibase can also provide a common methodology for applying updates from the Kuali Foundation across different software systems. This can be very useful in apply upgrades from the Kuali Foundation or managing changes for your local institution.&lt;br /&gt;&lt;br /&gt;This session covers intuitive and simple database change management process and how to integrate it with existing data migration, change management, and foundation KC, KFS and Rice updates.&lt;br /&gt;&lt;h3&gt;Objectives&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Implementors will learn best practices for structuring their projects&lt;/li&gt;&lt;li&gt;Implementors will learn how to accept database changes from the foundation and structure theirs around&lt;/li&gt;&lt;li&gt;Developers will learn how to test changes and the effects of change management within their development process&lt;/li&gt;&lt;li&gt;Implementors will learn how to rollback software versions including database changes&lt;/li&gt;&lt;li&gt;Implementors will learn how to play/fast-forward changes acrossed several revisions to update to a later version of database and source code&lt;/li&gt;&lt;li&gt;Business Intelligence Analysts will learn how to integrate database change management and ETL processes so that source code changes have minimal impact upon their conversion processes&lt;/li&gt;&lt;li&gt;Introduce a common methodology for apply upgrades across projects (KC, KFS, and Rice) using Maven and Liquibase&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Audience&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Technical track for DBA’s, developers, and DevOps&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-847741917111562744?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/847741917111562744/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/07/kuali-days-2011-presentation-proposals.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/847741917111562744'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/847741917111562744'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/07/kuali-days-2011-presentation-proposals.html' title='Kuali Days 2011 Presentation Proposals'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4199453513994549817</id><published>2011-06-27T16:52:00.000-07:00</published><updated>2011-06-27T16:57:44.783-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><title type='text'>UPDATE (ATTN: Eclipse Users!): Hot Code Replacement for KFS Development With Tomcat</title><content type='html'>Ok. A lot of Eclipse users out there are having a lot of trouble. They are encountering a lot of errors like this &lt;blockquote&gt;"Access restriction: Class is not accessible due to restriction on required library"&lt;/blockquote&gt;. The best way to get around this is to go to Window-&gt;Preferences-&gt;Java-&gt;Compiler. A dialog will appear. Make sure yours looks like this &lt;img src="http://30.media.tumblr.com/tumblr_lnh3l4nsya1qzuu0lo1_500.png" /&gt; &lt;b&gt;Forbidden reference (access rules)&lt;/b&gt; needs to be set to &lt;b&gt;Warning&lt;/b&gt;. That will solve your problem and you will be able to use &lt;a href="http://kualigan.blogspot.com/2011/06/hot-code-replacement-for-kfs.html"&gt;Hot Code Replacement for KFS&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4199453513994549817?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4199453513994549817/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/06/update-attn-eclipse-users-hot-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4199453513994549817'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4199453513994549817'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/06/update-attn-eclipse-users-hot-code.html' title='UPDATE (ATTN: Eclipse Users!): Hot Code Replacement for KFS Development With Tomcat'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-7918722151113250015</id><published>2011-06-26T21:51:00.000-07:00</published><updated>2011-06-26T23:59:22.973-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='tomcat'/><title type='text'>Hot Code Replacement for KFS Development With Tomcat</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;This is not hot deployment as in &lt;b&gt;reloadable="true"&lt;/b&gt; like here: &lt;pre class="brush: xml"&gt;&amp;lt;Context path="/kfs-dev" reloadable="true"  docBase="/Users/leo/.workspace/kfs/release-4-0-overlay/target/kfs-dev"&amp;gt;&lt;/pre&gt;No no. That will force the webapp context to reload which is the trouble with KFS in tomcat. When the context reloads there are often memory problems. Worst of all, Spring has to reload everything. That's just a joy and a pleasure to experience for all developers too. Not only do you wait forever and a day for Spring to reload your DD, but then it finishes to remind you there's a memory leak with context redeployment. Pleasant, isn't it? &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Solution 1: Use Maven&lt;/h3&gt;Honestly, the best thing you can do is this &lt;a href="/2011/06/convert-your-kfs-distribution-to-maven.html"&gt;Convert Your KFS Distribution to Maven&lt;/a&gt;. You can then use the much coveted &lt;a href="http://mojo.codehaus.org/tomcat-maven-plugin/plugin-info.html"&gt;Tomcat Maven Plugin&lt;/a&gt; oooor the lesser &lt;a href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin"&gt;Jetty Maven Plugin&lt;/a&gt;. These will support not only hot redeploys, but &lt;i&gt;Hot Code Replacement&lt;/i&gt;. You may even avoid Spring reloading.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Solution 2: JDI (Java Debugging Interface)&lt;/h3&gt;There may be some of you that would rather not be bothered with converting to maven. You may just not be ready yet, but you love the idea of &lt;i&gt;Hot Code Replacement&lt;/i&gt; because you're a developer and developers hate 2 things&lt;ul&gt;&lt;li&gt;Waiting around&lt;/li&gt;&lt;li&gt;Waiting around when they don't need to.&lt;/li&gt;&lt;/ul&gt;What's a developer to do? Brilliantly, before Sun was overtaken by the evil empire (Oracle), they left us with a few magical treasures to save our dying world from the evil that is upon us poised to deliver its reign of destruction and bind and afflict us. One is JDI (Java Debugging Interface) or &lt;a href="http://download.oracle.com/javase/6/docs/technotes/guides/jpda/"&gt;JPDA (Java Platform Debugger Architecture)&lt;/a&gt; as it is sometimes referred. A veritable sword of omens as you will soon find out.&lt;br /&gt;&lt;br /&gt;This is actually how Eclipse handles its &lt;i&gt;Hot Code Replacement&lt;/i&gt; in debug mode. It's actually using JDI/JPDA. Given is that this will run slower than normal applications. It is also &lt;b&gt;Debug&lt;/b&gt; mode. That is, you would never ever ever ever use this in production...ever. This is a development tool. The great part is that since Eclipse is also using JDI, that means that attaching a debugger or starting Eclipse in debug mode works seamlessly with this technique. &lt;br /&gt;&lt;br /&gt;&lt;h4&gt;In a Nutshell&lt;/h4&gt;The simple explanation for my solution is:&lt;ol&gt;&lt;li&gt;Create a Quartz Job (WebappWatchJob) with a step (WebappWatchStep)&lt;/li&gt;&lt;li&gt;Set the job to run every 30 seconds&lt;/li&gt;&lt;li&gt;Add necessary configuration to directory.properties and configuration.properties so this only shows up when &lt;b&gt;dev.mode&lt;/b&gt; is &lt;b&gt;true&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Implement WebappWatchJob to traverse your webapp installation path for .class files and check for last modified updates to reload the class on change&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;...in a nutshell.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Details and Steps&lt;/h2&gt;Deep breath...now go!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1. Setup Properties&lt;/h3&gt;In configuration.properties I added &lt;pre class="brush: plain"&gt;##############################################################################################################&lt;br /&gt;## Properties from institutional.configuration.file (${institutional.configuration.file}) are appended after this point.&lt;br /&gt;##############################################################################################################&lt;br /&gt;webapp.classes.directory=${webapp.classes.directory}&lt;/pre&gt;&lt;br /&gt;I then set in directory.properties &lt;pre class="brush: plain"&gt;# Application server directories - these assume the Tomcat 5.5 structure&lt;br /&gt;tomcat.version=5&lt;br /&gt;#appserver.lib.dir=${appserver.home}/common/lib&lt;br /&gt;#appserver.classes.dir=${appserver.home}/common/classes&lt;br /&gt;appserver.deploy.dir=${appserver.home}/webapps&lt;br /&gt;appserver.config.dir=${appserver.home}/conf&lt;br /&gt;appserver.localhost.dir=${appserver.config.dir}/Catalina/localhost&lt;br /&gt;appserver.work.dir=${appserver.home}/work/Catalina/localhost&lt;br /&gt;webapp.classes.directory=${appserver.deploy.dir}/${project.name}-${build.environment}/WEB-INF/classes&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above defines a property we will eventually use to set which path  to watch for changes in.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2. Create WebappWatchJob&lt;/h3&gt;I add the following to my spring configuration. In KFS, my module is tem, so I add it to my spring-tem.xml:&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!-- Copyright 2006-2008 The Kuali Foundation Licensed under the Educational &lt;br /&gt; Community License, Version 2.0 (the "License"); you may not use this file &lt;br /&gt; except in compliance with the License. You may obtain a copy of the License &lt;br /&gt; at http://www.opensource.org/licenses/ecl2.php Unless required by applicable &lt;br /&gt; law or agreed to in writing, software distributed under the License is distributed &lt;br /&gt; on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either &lt;br /&gt; express or implied. See the License for the specific language governing permissions &lt;br /&gt; and limitations under the License. --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/tx&lt;br /&gt;                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/aop&lt;br /&gt;                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;property name="jobNames"&amp;gt;&lt;br /&gt; &amp;lt;list&amp;gt;&lt;br /&gt;   &amp;lt;value&amp;gt;webappWatchJob&amp;lt;/value&amp;gt;&lt;br /&gt; &amp;lt;/list&amp;gt;&lt;br /&gt;&amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;property name="triggerNames"&amp;gt;&lt;br /&gt;  &amp;lt;list&amp;gt;&lt;br /&gt; &amp;lt;value&amp;gt;webappWatchTrigger&amp;lt;/value&amp;gt;&lt;br /&gt;  &amp;lt;/list&amp;gt;&lt;br /&gt;&amp;lt;/property&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Clearly, I have declared a &lt;b&gt;webappWatchJob&lt;/b&gt; and a &lt;b&gt;webappWatchTrigger&lt;/b&gt;. I later define those in the same file this way:&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!-- Copyright 2006-2008 The Kuali Foundation Licensed under the Educational &lt;br /&gt; Community License, Version 2.0 (the "License"); you may not use this file &lt;br /&gt; except in compliance with the License. You may obtain a copy of the License &lt;br /&gt; at http://www.opensource.org/licenses/ecl2.php Unless required by applicable &lt;br /&gt; law or agreed to in writing, software distributed under the License is distributed &lt;br /&gt; on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either &lt;br /&gt; express or implied. See the License for the specific language governing permissions &lt;br /&gt; and limitations under the License. --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/tx&lt;br /&gt;                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/aop&lt;br /&gt;                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;bean id="webappWatchStep" class="org.kuali.kfs.module.tem.batch.WebappWatchStep" parent="step"&amp;gt;&lt;br /&gt;  &amp;lt;property name="webappPath" value="${webapp.classes.directory}" /&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;bean id="webappWatchJob" parent="scheduledJobDescriptor"&amp;gt;&lt;br /&gt;  &amp;lt;property name="steps"&amp;gt;&lt;br /&gt;    &amp;lt;list&amp;gt;&lt;br /&gt;      &amp;lt;ref bean="webappWatchStep" /&amp;gt;&lt;br /&gt;    &amp;lt;/list&amp;gt;&lt;br /&gt;  &amp;lt;/property&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;bean id="webappWatchTrigger" parent="cronTrigger"&amp;gt;&lt;br /&gt;  &amp;lt;property name="jobName" value="webappWatchJob" /&amp;gt;&lt;br /&gt;  &amp;lt;property name="cronExpression" value="0/30 * * * * ?" /&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice that our property we created before is used here. It is injected into the &lt;b&gt;step&lt;/b&gt; so that it may be watched. Also, the &lt;b&gt;cronExpression&lt;/b&gt; is set for every 0 and 30th second or every 30 seconds.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3. Implement WebappWatchStep&lt;/h3&gt;Now that we have the spring definitions setup, we need to implement the &lt;b&gt;WebappWatchStep&lt;/b&gt;. I will first explain its pieces and then paste the full class implementation. &lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Properties&lt;/h4&gt;There are just 2 properties.&lt;pre class="brush:java"&gt;...&lt;br /&gt;... &lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of WebappPath&lt;br /&gt;     *&lt;br /&gt;     * @return the value of WebappPath&lt;br /&gt;     */&lt;br /&gt;    public String getWebappPath() {&lt;br /&gt;        return this.webappPath;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of WebappPath&lt;br /&gt;     *&lt;br /&gt;     * @param argWebappPath Value to assign to this.WebappPath&lt;br /&gt;     */&lt;br /&gt;    public void setWebappPath(final String argWebappPath) {&lt;br /&gt;        this.webappPath = argWebappPath;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of LastUpdated&lt;br /&gt;     *&lt;br /&gt;     * @return the value of LastUpdated&lt;br /&gt;     */&lt;br /&gt;    public Date getLastUpdated() {&lt;br /&gt;        return this.lastUpdated;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of LastUpdated&lt;br /&gt;     *&lt;br /&gt;     * @param argLastUpdated Value to assign to this.LastUpdated&lt;br /&gt;     */&lt;br /&gt;    public void setLastUpdated(final Date argLastUpdated) {&lt;br /&gt;        this.lastUpdated = argLastUpdated;&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;webappPath&lt;/b&gt; - is the path injected to define what directory to recurse and watch&lt;/li&gt;&lt;li&gt;&lt;b&gt;lastUpdated&lt;/b&gt; - is a cached date of the last run. This is compared to the file &lt;b&gt;lastModified&lt;/b&gt; property to determine whether the file has changed or not&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Traversing webappPath&lt;/h4&gt;&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.Step#execute(java.lang.String, java.util.Date)&lt;br /&gt;     */&lt;br /&gt;    public boolean execute(String jobName, Date jobRunDate) throws InterruptedException {&lt;br /&gt;        info("WebappWatch triggered. I'm going in");&lt;br /&gt;        final Collection&amp;lt;File&amp;gt; classFiles = new ArrayList&amp;lt;File&amp;gt;();&lt;br /&gt;        traverseForClasses(new File(getWebappPath()), classFiles);&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * Recursively searches for classes. If &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; is a directory, it traverses recursively. If it is a&lt;br /&gt;     * file, then it compares the &amp;lt;code&amp;gt;lastModified&amp;lt;/code&amp;gt; date with the cached {@link #getLastModified()} value.&lt;br /&gt;     * &lt;br /&gt;     * @param file {@link File} instance for traversing&lt;br /&gt;     * @param classFiles is a {@link Collection} of {@link File} instances where the {@link File#lastUpdated()} value is&lt;br /&gt;     * before {@link #getLastUpdated()}&lt;br /&gt;     */&lt;br /&gt;    protected void traverseForClasses(final File file, final Collection&amp;lt;File&amp;gt; classFiles) {&lt;br /&gt;        if (file.isDirectory()) {&lt;br /&gt;            final String[] fileNames = file.list();&lt;br /&gt;            &lt;br /&gt;            for (final String fileName : fileNames) {&lt;br /&gt;                traverseForClasses(new File(file, fileName), classFiles);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        else if (new Date(file.lastModified()).after(lastUpdated) &amp;&amp; file.getName().endsWith(".class")) {&lt;br /&gt;            classFiles.add(file);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;...    &lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A &lt;b&gt;classFiles&lt;/b&gt; Collection is passed into a method that recursively adds class names to that collection. It is later iterated over in the &lt;b&gt;execute&lt;/b&gt; method like this:&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;        try {&lt;br /&gt;            for (final File classFile : classFiles) {&lt;br /&gt;                final byte[] classData = new byte[new Long(classFile.length()).intValue()];&lt;br /&gt;                FileInputStream classStream = null;&lt;br /&gt;                try {&lt;br /&gt;                    classStream = new FileInputStream(classFile);&lt;br /&gt;                    &lt;br /&gt;                    classStream.read(classData, 0, classData.length);&lt;br /&gt;                }&lt;br /&gt;                catch (Exception e) {&lt;br /&gt;                }&lt;br /&gt;                finally {&lt;br /&gt;                    if (classStream != null) {&lt;br /&gt;                        try {&lt;br /&gt;                            classStream.close();&lt;br /&gt;                        }&lt;br /&gt;                        catch (Exception e) { } // failure to close. Do nothing.&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                final String className = classFromFileName(classFile.getAbsolutePath()); &lt;br /&gt;                boolean classExists = false;&lt;br /&gt;                try {&lt;br /&gt;                    Class.forName(className);&lt;br /&gt;                    classExists = true;&lt;br /&gt;                }&lt;br /&gt;                catch (ClassNotFoundException cnfe) {&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                if (classExists) {&lt;br /&gt;                    addRedefineClass(toRedefineMap, className, classData);&lt;br /&gt;                }&lt;br /&gt;                else {&lt;br /&gt;                    defineClass(className, classData, 0, classData.length);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;            debug("Delivering the redefinition map");&lt;br /&gt;            vm.redefineClasses(toRedefineMap);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            warn(e.getMessage());&lt;br /&gt;            e.printStackTrace();&lt;br /&gt;            Throwable cause = e.getCause();&lt;br /&gt;            while (cause != null) {&lt;br /&gt;                cause.printStackTrace();&lt;br /&gt;                warn("Caused by: ");&lt;br /&gt;                cause = cause.getCause();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        finally {&lt;br /&gt;            try {&lt;br /&gt;                vm.resume();&lt;br /&gt;                if (conn != null &amp;&amp; conn.isOpen()) {&lt;br /&gt;                    debug("Closing the JPDA connection...");&lt;br /&gt;                    conn.close();&lt;br /&gt;                    debug("Connection closed");&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            catch (Exception e) {&lt;br /&gt;                e.printStackTrace();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;The Juicy Part&lt;/h4&gt;So what is this stuff we saw in the &lt;b&gt;execute&lt;/b&gt; method?&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.Step#execute(java.lang.String, java.util.Date)&lt;br /&gt;     */&lt;br /&gt;    public boolean execute(String jobName, Date jobRunDate) throws InterruptedException {&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;        try {&lt;br /&gt;            conn = openJdiConnection();&lt;br /&gt;            vm = Bootstrap.virtualMachineManager().createVirtualMachine(conn);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;            try {&lt;br /&gt;                vm.resume();&lt;br /&gt;                if (conn != null &amp;&amp; conn.isOpen()) {&lt;br /&gt;                    debug("Closing the JPDA connection...");&lt;br /&gt;                    conn.close();&lt;br /&gt;                    debug("Connection closed");&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            catch (Exception e) {&lt;br /&gt;                e.printStackTrace();&lt;br /&gt;            }&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Near the beginning of the &lt;b&gt;execute&lt;/b&gt; method, we establish a connection with JPDA using the &lt;b&gt;openJdiConnection&lt;/b&gt; described here &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * Creates a {@link SocketTransportService} instance and uses that to attach to localhost:8080 and return a {@link Connection} to the &lt;br /&gt;     * current running JVM instance. This is used by bootstrap to create a {@link VirtualMachine} instance.&lt;br /&gt;     * &lt;br /&gt;     * @throws Exception when there's a problem attaching to localhost:8080 or if it cannot create an instance of {@link SocketTransportService}&lt;br /&gt;     * @return an open {@link Connection} to the JPDA instance of the current running JVM&lt;br /&gt;     * @see com.sun.jdi.VirtualMachineManager#createVirtualMachine(Connection)&lt;br /&gt;     * @see com.sun.tools.jdi.SocketTransportService&lt;br /&gt;     * @see com.sun.jdi.Bootstrap#virtualMachineManager()&lt;br /&gt;     */&lt;br /&gt;    protected Connection openJdiConnection() throws Exception {&lt;br /&gt;        TransportService ts = null;&lt;br /&gt;        try {&lt;br /&gt;            Class c = Class.forName("com.sun.tools.jdi.SocketTransportService");&lt;br /&gt;            ts = (TransportService)c.newInstance();&lt;br /&gt;        } catch (Exception x) {&lt;br /&gt;            throw new Error(x);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        return ts.attach("localhost:8000", 5000, 5000);&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The builtin &lt;b&gt;com.sun.tools.jdi.SocketTransportService&lt;/b&gt; is used to connect to the JPDA. I've set it to default to port 8000. I believe that's a standard, but you may want to customize this further. Tomcat uses the following environment variables to define this. It may be leveraged here. &lt;pre class="brush: plain"&gt;#   JPDA_TRANSPORT  (Optional) JPDA transport used when the "jpda start"&lt;br /&gt;#                   command is executed. The default is "dt_socket".&lt;br /&gt;#&lt;br /&gt;#   JPDA_ADDRESS    (Optional) Java runtime options used when the "jpda start"&lt;br /&gt;#                   command is executed. The default is 8000.&lt;br /&gt;#&lt;br /&gt;#   JPDA_SUSPEND    (Optional) Java runtime options used when the "jpda start"&lt;br /&gt;#                   command is executed. Specifies whether JVM should suspend&lt;br /&gt;#                   execution immediately after startup. Default is "n".&lt;br /&gt;#&lt;br /&gt;#   JPDA_OPTS       (Optional) Java runtime options used when the "jpda start"&lt;br /&gt;#                   command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS,&lt;br /&gt;#                   and JPDA_SUSPEND are ignored. Thus, all required jpda&lt;br /&gt;#                   options MUST be specified. The default is:&lt;br /&gt;#&lt;br /&gt;#                   -Xdebug -Xrunjdwp:transport=$JPDA_TRANSPORT,&lt;br /&gt;#                       address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND&lt;br /&gt;#&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once the connection is made, it eventually also needs to be closed &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;            try {&lt;br /&gt;                vm.resume();&lt;br /&gt;                if (conn != null &amp;&amp; conn.isOpen()) {&lt;br /&gt;                    debug("Closing the JPDA connection...");&lt;br /&gt;                    conn.close();&lt;br /&gt;                    debug("Connection closed");&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            catch (Exception e) {&lt;br /&gt;                e.printStackTrace();&lt;br /&gt;            }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see that right before the connection is closed, the virtual machine is resumed with the &lt;b&gt;resume&lt;/b&gt; method. This is in case some process caused the vm to become suspended. It resumes all threads before disconnecting.&lt;br /&gt;&lt;br /&gt;While looping over the modified class names, we ran into this &lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;                final String className = classFromFileName(classFile.getAbsolutePath()); &lt;br /&gt;                boolean classExists = false;&lt;br /&gt;                try {&lt;br /&gt;                    Class.forName(className);&lt;br /&gt;                    classExists = true;&lt;br /&gt;                }&lt;br /&gt;                catch (ClassNotFoundException cnfe) {&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                if (classExists) {&lt;br /&gt;                    addRedefineClass(toRedefineMap, className, classData);&lt;br /&gt;                }&lt;br /&gt;                else {&lt;br /&gt;                    defineClass(className, classData, 0, classData.length);&lt;br /&gt;                }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Simply put, we use a method &lt;b&gt;defineClass&lt;/b&gt; for classes that are new and &lt;b&gt;addRedefineClass&lt;/b&gt; for methods that are already loaded with the current ClassLoader. Loading a class is easy reflection with the ClassLoader, so I am going to skip that. The tough part is handling classes that are already loaded. This is where JPDA comes in. Normally, you can only define a class once per ClassLoader, but with JDPA, you can use the &lt;b&gt;redefineClasses&lt;/b&gt; method in the &lt;b&gt;VirtualMachine&lt;/b&gt;. This &lt;b&gt;redefineClasses&lt;/b&gt; method takes a Map though. This means we do not typically redefine a class at a time. We have to round them all up into a Map, and then call &lt;b&gt;redefineClasses&lt;/b&gt; on the map. To do this, we created a &lt;b&gt;addRedefineClass&lt;/b&gt; method which takes our already created Map, and puts classes into it. Let's have a look &lt;pre class="brush: java"&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    /**&lt;br /&gt;     * Populates a map of {@link ReferenceType} and {@link byte[]} data. This {@link Map} is used by JDI for &lt;br /&gt;     * redefining classes.&lt;br /&gt;     *&lt;br /&gt;     * @param toRedefineMap {@link Map} of {@link byte[]} mapped by {@link ReferenceType}. This map is populated by the method, so it shouldn't be null.&lt;br /&gt;     * @see com.sun.jdi.VirtualMachine#redefineClasses(Map, byte[])&lt;br /&gt;     */&lt;br /&gt;    protected void addRedefineClass(final Map&amp;lt;ReferenceType,byte[]&amp;gt; toRedefineMap, final String className, final byte[] b) throws Exception { &lt;br /&gt;        debug("Found change in ", className);&lt;br /&gt;        for (ReferenceType ref : vm.classesByName(className)) {&lt;br /&gt;            if (ref.name().equals(className) &amp;&amp; className.indexOf("WebappWatch") &amp;lt; 0) {&lt;br /&gt;                debug("redefining ", className);&lt;br /&gt;                toRedefineMap.put(ref, b);&lt;br /&gt;                return;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Since I made &lt;b&gt;vm&lt;/b&gt; a local variable in the &lt;b&gt;WebappWatchStep&lt;/b&gt;, I can call it from &lt;b&gt;addRedefineClass&lt;/b&gt;. I use it for its nifty &lt;b&gt;classesByName&lt;/b&gt; method. I pass it my fully qualified class name, and it returns a list of &lt;b&gt;ReferenceType&lt;/b&gt; instances. Typically, this will return only one class. Actually, I cannot think of any case where it wouldn't, but just to be sure, I double check that. I also omit the WebappWatchStep class. We wouldn't want to reload the class we're currently accessing &lt;b&gt;VirtualMachine&lt;/b&gt; from. That would be bad. We simply add the &lt;b&gt;ReferenceType&lt;/b&gt; as a key to the byte array representing the actual class. This will get used later by the &lt;b&gt;VirtualMachine#redefineClasses&lt;/b&gt; method. For more information on &lt;b&gt;ReferenceType&lt;/b&gt;, check out &lt;a href="http://download.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/ReferenceType.html"&gt;http://download.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/ReferenceType.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;As promised&lt;/h3&gt;Here is the full source code&lt;br /&gt;&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2007 The Kuali Foundation&lt;br /&gt; * &lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; * &lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; * &lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package org.kuali.kfs.module.tem.batch;&lt;br /&gt;&lt;br /&gt;import java.lang.reflect.Method;&lt;br /&gt;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.FileInputStream;&lt;br /&gt;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;import java.util.Date;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;import com.sun.jdi.Bootstrap;&lt;br /&gt;import com.sun.jdi.ArrayType;&lt;br /&gt;import com.sun.jdi.ClassType;&lt;br /&gt;import com.sun.jdi.ReferenceType;&lt;br /&gt;import com.sun.jdi.InterfaceType;&lt;br /&gt;import com.sun.jdi.VirtualMachine;&lt;br /&gt;import com.sun.jdi.connect.spi.Connection;&lt;br /&gt;import com.sun.jdi.connect.spi.TransportService;&lt;br /&gt;&lt;br /&gt;import org.kuali.kfs.sys.batch.AbstractStep;&lt;br /&gt;&lt;br /&gt;import static org.kuali.kfs.module.tem.util.BufferedLogger.*;&lt;br /&gt;import static org.apache.commons.lang.StringUtils.replace;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Batch step that traverses the webapp for changed classes. It then reloads the classes into the current {@link ClassLoader}&lt;br /&gt; * this gives to appearance of hot redeploys.&lt;br /&gt; *&lt;br /&gt; * @author leo [at] rsmart.com&lt;br /&gt; */&lt;br /&gt;public class WebappWatchStep extends AbstractStep {&lt;br /&gt;    private Date lastUpdated;&lt;br /&gt;    private String webappPath;&lt;br /&gt;    private Connection conn;&lt;br /&gt;    private VirtualMachine vm;&lt;br /&gt;&lt;br /&gt;    public WebappWatchStep() throws Throwable {&lt;br /&gt;        super();&lt;br /&gt;        lastUpdated = new Date();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.Step#execute(java.lang.String, java.util.Date)&lt;br /&gt;     */&lt;br /&gt;    public boolean execute(String jobName, Date jobRunDate) throws InterruptedException {&lt;br /&gt;        info("WebappWatch triggered. I'm going in");&lt;br /&gt;        final Collection&amp;lt;File&amp;gt; classFiles = new ArrayList&amp;lt;File&amp;gt;();&lt;br /&gt;        traverseForClasses(new File(getWebappPath()), classFiles);&lt;br /&gt;        final Map&amp;lt;ReferenceType,byte[]&amp;gt; toRedefineMap = new HashMap&amp;lt;ReferenceType,byte[]&amp;gt;();&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            conn = openJdiConnection();&lt;br /&gt;            vm = Bootstrap.virtualMachineManager().createVirtualMachine(conn);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        try {&lt;br /&gt;            for (final File classFile : classFiles) {&lt;br /&gt;                final byte[] classData = new byte[new Long(classFile.length()).intValue()];&lt;br /&gt;                FileInputStream classStream = null;&lt;br /&gt;                try {&lt;br /&gt;                    classStream = new FileInputStream(classFile);&lt;br /&gt;                    &lt;br /&gt;                    classStream.read(classData, 0, classData.length);&lt;br /&gt;                }&lt;br /&gt;                catch (Exception e) {&lt;br /&gt;                }&lt;br /&gt;                finally {&lt;br /&gt;                    if (classStream != null) {&lt;br /&gt;                        try {&lt;br /&gt;                            classStream.close();&lt;br /&gt;                        }&lt;br /&gt;                        catch (Exception e) { } // failure to close. Do nothing.&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                final String className = classFromFileName(classFile.getAbsolutePath()); &lt;br /&gt;                boolean classExists = false;&lt;br /&gt;                try {&lt;br /&gt;                    Class.forName(className);&lt;br /&gt;                    classExists = true;&lt;br /&gt;                }&lt;br /&gt;                catch (ClassNotFoundException cnfe) {&lt;br /&gt;                }&lt;br /&gt;                &lt;br /&gt;                if (classExists) {&lt;br /&gt;                    addRedefineClass(toRedefineMap, className, classData);&lt;br /&gt;                }&lt;br /&gt;                else {&lt;br /&gt;                    defineClass(className, classData, 0, classData.length);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            &lt;br /&gt;            debug("Delivering the redefinition map");&lt;br /&gt;            vm.redefineClasses(toRedefineMap);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            warn(e.getMessage());&lt;br /&gt;            e.printStackTrace();&lt;br /&gt;            Throwable cause = e.getCause();&lt;br /&gt;            while (cause != null) {&lt;br /&gt;                cause.printStackTrace();&lt;br /&gt;                warn("Caused by: ");&lt;br /&gt;                cause = cause.getCause();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        finally {&lt;br /&gt;            try {&lt;br /&gt;                vm.resume();&lt;br /&gt;                if (conn != null &amp;&amp; conn.isOpen()) {&lt;br /&gt;                    debug("Closing the JPDA connection...");&lt;br /&gt;                    conn.close();&lt;br /&gt;                    debug("Connection closed");&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            catch (Exception e) {&lt;br /&gt;                e.printStackTrace();&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        setLastUpdated(new Date());&lt;br /&gt;        return false;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Defines a class. Only used for new classes that don't already exist in the {@link ClassLoader}&lt;br /&gt;     * @param className name of the {@link Class} to define&lt;br /&gt;     * @param b byte array of data&lt;br /&gt;     * @param offset where in the byte array to start&lt;br /&gt;     * @param len size of the byte array to read&lt;br /&gt;     * @throws Exception &amp;lt;code&amp;gt;defineClass()&amp;lt;/code&amp;gt; on the {@link ClassLoader} is protected. It cannot normally be called.&lt;br /&gt;     * an exception can be thrown if something goes wrong.&lt;br /&gt;     */&lt;br /&gt;    protected void defineClass(final String className, final byte[] b, final int offset, final int len) throws Exception {&lt;br /&gt;        info("Defining class ", className);&lt;br /&gt;        final ClassLoader classLoader = getClass().getClassLoader();&lt;br /&gt;&lt;br /&gt;        final Method method = ClassLoader.class.getDeclaredMethod("defineClass", &lt;br /&gt;                                                                  new Class[] { String.class, byte[].class, int.class, int.class });&lt;br /&gt;        method.setAccessible(true);&lt;br /&gt;        final Class definedClass = (Class) method.invoke(classLoader, className, b, offset, len);&lt;br /&gt;        info("Defined class ", definedClass);&lt;br /&gt;        // info("Loaded class ", getClass().getClassLoader().loadClass(className));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Populates a map of {@link ReferenceType} and {@link byte[]} data. This {@link Map} is used by JDI for &lt;br /&gt;     * redefining classes.&lt;br /&gt;     *&lt;br /&gt;     * @param toRedefineMap {@link Map} of {@link byte[]} mapped by {@link ReferenceType}. This map is populated by the method, so it shouldn't be null.&lt;br /&gt;     * @see com.sun.jdi.VirtualMachine#redefineClasses(Map, byte[])&lt;br /&gt;     */&lt;br /&gt;    protected void addRedefineClass(final Map&amp;lt;ReferenceType,byte[]&amp;gt; toRedefineMap, final String className, final byte[] b) throws Exception { &lt;br /&gt;        debug("Found change in ", className);&lt;br /&gt;        for (ReferenceType ref : vm.classesByName(className)) {&lt;br /&gt;            if (ref.name().equals(className) &amp;&amp; className.indexOf("WebappWatch") &amp;lt; 0) {&lt;br /&gt;                debug("redefining ", className);&lt;br /&gt;                toRedefineMap.put(ref, b);&lt;br /&gt;                return;&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Creates a {@link SocketTransportService} instance and uses that to attach to localhost:8080 and return a {@link Connection} to the &lt;br /&gt;     * current running JVM instance. This is used by bootstrap to create a {@link VirtualMachine} instance.&lt;br /&gt;     * &lt;br /&gt;     * @throws Exception when there's a problem attaching to localhost:8080 or if it cannot create an instance of {@link SocketTransportService}&lt;br /&gt;     * @return an open {@link Connection} to the JPDA instance of the current running JVM&lt;br /&gt;     * @see com.sun.jdi.VirtualMachineManager#createVirtualMachine(Connection)&lt;br /&gt;     * @see com.sun.tools.jdi.SocketTransportService&lt;br /&gt;     * @see com.sun.jdi.Bootstrap#virtualMachineManager()&lt;br /&gt;     */&lt;br /&gt;    protected Connection openJdiConnection() throws Exception {&lt;br /&gt;        TransportService ts = null;&lt;br /&gt;        try {&lt;br /&gt;            Class c = Class.forName("com.sun.tools.jdi.SocketTransportService");&lt;br /&gt;            ts = (TransportService)c.newInstance();&lt;br /&gt;        } catch (Exception x) {&lt;br /&gt;            throw new Error(x);&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        return ts.attach("localhost:8000", 5000, 5000);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Tries to create a canonical class name from a .class file.&lt;br /&gt;     *&lt;br /&gt;     * @param fileName is the fully qualified path of a .class file&lt;br /&gt;     * @return a canonical class name (java.lang.String)&lt;br /&gt;     */&lt;br /&gt;    protected String classFromFileName(final String fileName) {&lt;br /&gt;        String retval = fileName.substring(0, fileName.indexOf(".class"));&lt;br /&gt;        retval = retval.substring(getWebappPath().length() + 1);&lt;br /&gt;        retval = replace(retval, File.separator, ".");&lt;br /&gt;        retval = replace(retval, "/", ".");&lt;br /&gt;        return retval;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Recursively searches for classes. If &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; is a directory, it traverses recursively. If it is a&lt;br /&gt;     * file, then it compares the &amp;lt;code&amp;gt;lastModified&amp;lt;/code&amp;gt; date with the cached {@link #getLastModified()} value.&lt;br /&gt;     * &lt;br /&gt;     * @param file {@link File} instance for traversing&lt;br /&gt;     * @param classFiles is a {@link Collection} of {@link File} instances where the {@link File#lastUpdated()} value is&lt;br /&gt;     * before {@link #getLastUpdated()}&lt;br /&gt;     */&lt;br /&gt;    protected void traverseForClasses(final File file, final Collection&amp;lt;File&amp;gt; classFiles) {&lt;br /&gt;        if (file.isDirectory()) {&lt;br /&gt;            final String[] fileNames = file.list();&lt;br /&gt;            &lt;br /&gt;            for (final String fileName : fileNames) {&lt;br /&gt;                traverseForClasses(new File(file, fileName), classFiles);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        else if (new Date(file.lastModified()).after(lastUpdated) &amp;&amp; file.getName().endsWith(".class")) {&lt;br /&gt;            classFiles.add(file);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of WebappPath&lt;br /&gt;     *&lt;br /&gt;     * @return the value of WebappPath&lt;br /&gt;     */&lt;br /&gt;    public String getWebappPath() {&lt;br /&gt;        return this.webappPath;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of WebappPath&lt;br /&gt;     *&lt;br /&gt;     * @param argWebappPath Value to assign to this.WebappPath&lt;br /&gt;     */&lt;br /&gt;    public void setWebappPath(final String argWebappPath) {&lt;br /&gt;        this.webappPath = argWebappPath;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of LastUpdated&lt;br /&gt;     *&lt;br /&gt;     * @return the value of LastUpdated&lt;br /&gt;     */&lt;br /&gt;    public Date getLastUpdated() {&lt;br /&gt;        return this.lastUpdated;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of LastUpdated&lt;br /&gt;     *&lt;br /&gt;     * @param argLastUpdated Value to assign to this.LastUpdated&lt;br /&gt;     */&lt;br /&gt;    public void setLastUpdated(final Date argLastUpdated) {&lt;br /&gt;        this.lastUpdated = argLastUpdated;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it. Use this and you should have no problem modifying classes as you are developing without needing to restart Spring or Tomcat. Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-7918722151113250015?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/7918722151113250015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/06/hot-code-replacement-for-kfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7918722151113250015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7918722151113250015'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/06/hot-code-replacement-for-kfs.html' title='Hot Code Replacement for KFS Development With Tomcat'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-5900080080188300838</id><published>2011-06-14T15:33:00.000-07:00</published><updated>2011-06-14T16:28:36.253-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='overlay'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>Convert Your KFS Distribution to Maven</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I am going to cover the process/steps for converting your KFS distribution to use maven. The two greatest reasons for doing this is for development and building/releasing the software. Using maven will make your distribution more consistent with other Kuali projects, get your transitive dependency management, simplify your build process, and simplify your development process with convention over configuration.&lt;br /&gt;&lt;br /&gt;At first, this may seem to be a daunting task to move from ant to maven, but it is actually pretty simple. The properties file conventions in KFS do not make this any more difficult as one would think. We can still use maven.&lt;br /&gt;&lt;h2&gt;Steps&lt;/h2&gt;Before I begin, I want to point out that this assumes you are working with a fresh checkout of the KFS from &lt;a href="http://test.kuali.org/svn/kfs/branches/release-4-0-br"&gt;http://test.kuali.org/svn/kfs/branches/release-4-0-br&lt;/a&gt;. You can use 4.1.1 as well. The important thing to note is that it's KFS 4.x.&lt;br /&gt;&lt;h3&gt;1 Create path structure&lt;/h3&gt;In the directory where KFS is checked out, I suggest first creating all the necessary directories for maven. On Mac or Linux, this is as simple as the following command(s):&lt;pre class="code"&gt;&lt;br /&gt;% mkdir -p src/main/java src/main/resources src/main/webapp src/main/config src/test/resources src/test/config&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;2 Copy build components to necessary project configuration locations&lt;/h3&gt;build components are typically located in your &lt;b&gt;build/&lt;/b&gt; directory in you KFS project. To do this, I like to use rsync because then I can exclude .svn and .git  metadata.&lt;pre class="code"&gt;% rsync -avC distribution/ external/ helper-scripts/ project/ properties/ tomcat/ upgrades/ src/main/config/&lt;/pre&gt;I have left out the library directories because maven handles our dependencies for us. There is no longer any reason to include the jars in the project. I am not completely sure if tomcat is necessary either, but I left it until later when I can decide if I really need it or not.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3 Move any other project configuration&lt;/h3&gt;I consider workflow doctypes, ddl, and dml to be project configuration metadata. These are typically in &lt;b&gt;work/db&lt;/b&gt; and &lt;b&gt;work/workflow&lt;/b&gt; respectively. I want them to be in &lt;b&gt;src/main/config&lt;/b&gt; with the rest of my project configuration.&lt;pre class="code"&gt;% mv work/workflow work/db src/main/config&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;4 Relocate java sources and project resources&lt;/h3&gt;Project resources are really anything that isn't a java source. These are going into &lt;b&gt;src/main/resources&lt;/b&gt;. Java sources will go into &lt;b&gt;src/main/java&lt;/b&gt;. Currently, the KFS project lumps these up into &lt;b&gt;work/src&lt;/b&gt;. I am going to split this up.&lt;h4&gt;First, I want to copy the source.&lt;/h4&gt;&lt;pre class="code"&gt;% rsync -avC work/src src2&lt;/pre&gt;Now I can do with it what I want which is to remove all the java source from it.&lt;pre class="code"&gt;% find src2/ -name \*.java -exec {} \;&lt;/pre&gt;&lt;h4&gt;Copy project resources&lt;/h4&gt;Now that the java code is gone I copy the resources&lt;pre class="code"&gt;% rsync -avC src2/* src/main/resources&lt;br /&gt;% rm -rf src2&lt;/pre&gt;&lt;h4&gt;Copy java sources&lt;/h4&gt;I got all the resources, now I will move all my java source.&lt;pre class="code"&gt;% find work/src \! -name \*.java -type f -exec rm {} \&lt;br /&gt;% rsync -avC work/src/* src/main/java&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;5 Relocate tests&lt;/h3&gt;I like the way KFS has separated tests. They're interdependent still, but maven also differentiates between test types. I'll keep this separation and move the tests over to &lt;b&gt;src/test/java&lt;/b&gt;&lt;pre class="code"&gt;% rm -rf test/lib&lt;br /&gt;% mv test/* src/test/java&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;6 Clean up a little&lt;/h3&gt;Now I get rid of all the unnecessary stuff&lt;pre class="code"&gt;% rm -rf test work build *.xml&lt;/pre&gt;You should now have something that looks like this&lt;pre class="code"&gt;leo@behemoth~/.workspace/kfs/release-4-0-mvn&lt;br /&gt;(15:59:41) [6] ls&lt;br /&gt;cnv-build.properties ptd-build.properties src&lt;br /&gt;dev-build.properties reg-build.properties&lt;br /&gt;leo@behemoth~/.workspace/kfs/release-4-0-mvn&lt;br /&gt;(15:59:42) [7]&lt;/pre&gt; &lt;br /&gt;Crazy, right?&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;7 Get the pom.xml&lt;/h3&gt;To make things easy. I have a pom already. You can get it like this:&lt;pre class="code"&gt;% svn export https://svn.rsmart.com/svn/kuali/contribution/community/travel_module/branches/release-4-0-mvn/pom.xml&lt;/pre&gt;&lt;br /&gt;I'm not going to paste it in here. It's too long.&lt;br /&gt;For the most part, you will not need to change the pom.xml. The &lt;b&gt;groupId&lt;/b&gt;, &lt;b&gt;artifactid&lt;/b&gt;, or &lt;b&gt;version&lt;/b&gt; may be properties about the project you will want to change. Other than that, everything should be peachy and ready to go (with the pom.xml at least).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;8 Fix some properties in KFS&lt;/h3&gt;There are a couple things that do not jive with maven's conventions in the old KFS. Some  are directory structure related. Mostly is the use of &lt;b&gt;ant.project.name&lt;/b&gt; allover the place. This property is specific to ant only and not very generic. Therefore, it cannot be used with maven. Maven had the right idea by going with generic property name like &lt;b&gt;project.name&lt;/b&gt;&lt;h4&gt;Fix ant.project.name&lt;/h4&gt;First, thing to do is clean this up. I used grep to determine where al it's used&lt;pre class="code"&gt;leo@behemoth~/.workspace/kfs/travel_module/build/properties&lt;br /&gt;(16:07:09) [5] grep -l ant.project.name *&lt;br /&gt;build-foundation.properties&lt;br /&gt;directory.properties&lt;br /&gt;email.properties&lt;br /&gt;url.properties&lt;/pre&gt;I just went through those files and replaced &lt;i&gt;ant.&lt;/i&gt; with nothing.&lt;h4&gt;Fix directory properties&lt;/h4&gt;Next, is to fix all the properties that point to non-conventional places.&lt;pre class="code"&gt;&lt;br /&gt;webroot.directory=src/main/webapp&lt;br /&gt;build.directory=src/main/config&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Maven Overlay!&lt;/h2&gt;The following are more about setting up an overlay project.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;9 Install the war&lt;/h3&gt;We install the war in our local repository &lt;pre class="code"&gt;% mvn -Dmaven.test.skip=true install&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;10 Create a jar and install it&lt;/h3&gt;We need both jar and war to do the overlay, so let's create one and install it. &lt;pre class="code"&gt;% mvn jar:jar&lt;br /&gt;% mvn install:install-file -Dpackaging=jar -DgroupId=org.kuali.kfs -DartifactId=kfs -Dversion=4.0 -DgeneratePom=true -Dfile=target/kfs-dev.jar&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;11  Setup your overlay&lt;/h3&gt;Now you pretty much just repeat steps 1-3. This is mostly to get the build configuration going again. Everything will be overlaid on top of the existing war, so we don't need to add resources our java sources.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;12 Copy overlay pom.xml&lt;/h3&gt;Just like with the KFS project pom.xml, I have a good overlay pom with the dependencies setup just right!&lt;pre class="code"&gt;%  svn export https://svn.rsmart.com/svn/kuali/contribution/community/travel_module/branches/release-4-0-overlay/pom.xml&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it!!! You now have mavenized KFS and you're using an overlay to boot!&lt;br /&gt;&lt;br /&gt;Want to see how I did it? Checkout the &lt;a href="/2011/06/another-game-changer-mavenize-kfs.html"&gt;Mavenize KFS Screencasts&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-5900080080188300838?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/5900080080188300838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/06/convert-your-kfs-distribution-to-maven.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5900080080188300838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5900080080188300838'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/06/convert-your-kfs-distribution-to-maven.html' title='Convert Your KFS Distribution to Maven'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6614482219498847887</id><published>2011-06-06T14:29:00.001-07:00</published><updated>2011-06-06T16:01:40.912-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>Another Game Changer: Mavenize KFS Screencasts</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;I have put together what I believe to be as a reproducable methodology to converting a KFS project to Maven. There will be a formal blog post detailing the adventure, but for now wet your appetite with these screencasts.&lt;br /&gt;&lt;h2&gt;Screencasts&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Mavenize KFS Part 1&lt;/h3&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/v_at7RhOJAY?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;h3&gt;Mavenize KFS Part 2&lt;/h3&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/_8V0abqSPFE?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;h3&gt;Mavenize KFS Part 3: Create an Overlay Project&lt;/h3&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/n5tM2EV01uk?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6614482219498847887?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6614482219498847887/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/06/another-game-changer-mavenize-kfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6614482219498847887'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6614482219498847887'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/06/another-game-changer-mavenize-kfs.html' title='Another Game Changer: Mavenize KFS Screencasts'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4409584665107760271</id><published>2011-06-06T11:58:00.000-07:00</published><updated>2011-06-06T12:55:44.484-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='constants'/><title type='text'>Constants Done with Spring Part 2: Inheriting Constants</title><content type='html'>&lt;h3&gt;Motivation&lt;/h3&gt;In another chapter, I wrote about constants implemented through Spring. In one of my reasons for doing so, I illustrated that in Kuali, constraints are difficult to manipulate because of their static final nature. I called them an antipattern. Here's an example again:&lt;br /&gt;&lt;br /&gt;Notice again that these constants cannot be manipulated. In the other chapter, I approached the use case that one would want to modify an existing constant and change it for their institution. In this chapter, I am going to approach the use case of adding a new constant. Why would you do this? Why not just make a new constants class, right? Well, you could do that. I find it to be more elegant to reuse your constants interface instead of having more than one.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Pattern&lt;/h3&gt;Since we are using Spring, let's assume that we are using the aforementioned Spring constants implementation instead of the antipattern. Now that we know we are using Spring, there are a couple assumptions we can make.&lt;ul&gt;&lt;li&gt;&lt;b&gt;Inversion of Control&lt;/b&gt; - we can assume that we can overwrite any known beans with the name we want to occupy. That is, if there is an existing constants implementation, we can not only reuse it, but replace it as well.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Dependency Injection&lt;/b&gt; - we can assume that we can inject any known constants we may already have into our new constants implementation.&lt;/li&gt;&lt;li&gt;We can reuse the existing KFS &lt;i&gt;parentBean&lt;/i&gt; pattern.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;With that, our methodology here is to first inject our existing constants into a new bean, then overwrite our old bean with it.&lt;br /&gt;&lt;h2&gt;The Screencast&lt;/h2&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/XuZo6QlxYlU?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Steps&lt;/h3&gt;&lt;h4&gt;1 Create a new Constants bean to be the delegate.&lt;/h4&gt;I created the following as my delegate.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;?xml version="1.0" encoding="UTF-8"?&gt;&amp;lt;!--&lt;br /&gt;  Copyright 2007 The Kuali Foundation. Licensed under the Educational&lt;br /&gt;  Community License, Version 1.0 (the "License"); you may not use this&lt;br /&gt;  file except in compliance with the License. You may obtain a copy of&lt;br /&gt;  the License at http://www.opensource.org/licenses/ecl1.php Unless&lt;br /&gt;  required by applicable law or agreed to in writing, software&lt;br /&gt;  distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or&lt;br /&gt;  implied. See the License for the specific language governing&lt;br /&gt;  permissions and limitations under the License.&lt;br /&gt; --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;bean id="kimDelegateConstants" class="org.kuali.rice.kim.util.ConstantsImpl"&amp;gt;&lt;br /&gt;      &amp;lt;property name="kimLdapIdProperty"         value="uaid" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="kimLdapNameProperty"       value="uid" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="snLdapProperty"            value="sn" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="givenNameLdapProperty"     value="givenName" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="entityIdKimProperty"       value="entityId" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="employeeMailLdapProperty"  value="mail" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="employeePhoneLdapProperty" value="employeePhone" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="defaultCountryCode"        value="1" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="mappedParameterName"       value="KIM_TO_LDAP_FIELD_MAPPINGS" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="mappedValuesName"          value="KIM_TO_LDAP_VALUE_MAPPINGS" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="parameterNamespaceCode"    value="KR-SYS" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="parameterDetailTypeCode"   value="Config" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="personEntityTypeCode"      value="PERSON" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="employeeIdProperty"        value="emplId" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="departmentLdapProperty"    value="employeePrimaryDept" /&amp;gt;  &lt;br /&gt;      &amp;lt;property name="employeeTypeProperty"      value="employeeType" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="employeeStatusProperty"    value="employeeStatus" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="affiliationLdapProperty"   value="affiliationProperty" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="primaryAffiliationLdapProperty"   value="eduPersonPrimaryAffiliation" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="defaultCampusCode"         value="MC" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="defaultChartCode"          value="UA" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="taxExternalIdTypeCode"     value="TAX" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="externalIdProperty"        value="externalIdentifiers.externalId" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="externalIdTypeProperty"    value="externalIdentifiers.externalIdentifierTypeCode" /&amp;gt;&lt;br /&gt;      &amp;lt;property name="affiliationMappings"       value="staff=STAFF,faculty=FCLTY,employee=STAFF,student=STDNT,affilate=AFLT"/&amp;gt;&lt;br /&gt;      &amp;lt;property name="employeeAffiliationCodes"  value="STAFF,FCLTY" /&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;/pre&gt;The important thing to notice is that it's in a new bean and not &lt;i&gt;kimConstants&lt;/i&gt; any longer.&lt;h4&gt;2 Override the original kimConstants bean&lt;/h4&gt;Now you need to create a new kimConstants bean that uses your implementation and delegates to the delegate created in Step 1&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt; &amp;lt;!--&lt;br /&gt;  Copyright 2007 The Kuali Foundation. Licensed under the Educational&lt;br /&gt;  Community License, Version 1.0 (the "License"); you may not use this&lt;br /&gt;  file except in compliance with the License. You may obtain a copy of&lt;br /&gt;  the License at http://www.opensource.org/licenses/ecl1.php Unless&lt;br /&gt;  required by applicable law or agreed to in writing, software&lt;br /&gt;  distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or&lt;br /&gt;  implied. See the License for the specific language governing&lt;br /&gt;  permissions and limitations under the License.&lt;br /&gt; --&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    &amp;lt;bean id="kimConstants" class="edu.arizona.kim.util.ConstantsImpl"&amp;gt;&lt;br /&gt;        &amp;lt;property name="phoneNumberMask"           value="###-###-####" /&amp;gt;&lt;br /&gt;        &amp;lt;property name="delegate"                  value-ref="kimDelegateConstants" /&amp;gt;&lt;br /&gt;    &amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;br /&gt;&lt;/pre&gt;Here a new &lt;i&gt;kimConstants&lt;/i&gt; is created. It only has 2 properties. One is for the delegate and the other is the field I wanted to add.&lt;h4&gt;3. Create a new interface for your constants that includes your new field&lt;/h4&gt;&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2007-2009 The Kuali Foundation&lt;br /&gt; *&lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; *&lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; *&lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package edu.arizona.kim.util;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Extension interface for constants&lt;br /&gt; */&lt;br /&gt;public interface Constants extends org.kuali.rice.kim.util.Constants {&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Retrieve the phoneNumberMask constant&lt;br /&gt;     *&lt;br /&gt;     * @return String instance of the phoneNumberMask&lt;br /&gt;     */&lt;br /&gt;    String getPhoneNumberMask();&lt;br /&gt;}&lt;/pre&gt;You can see it extends the original constants interface and thereby gaining all its abstract methods for all the delegate method's properties.&lt;h4&gt;4 Create the implementation class for the new bean&lt;/h4&gt;&lt;pre class="brush: java"&gt;/*&lt;br /&gt; * Copyright 2007-2009 The Kuali Foundation&lt;br /&gt; *&lt;br /&gt; * Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; * you may not use this file except in compliance with the License.&lt;br /&gt; * You may obtain a copy of the License at&lt;br /&gt; *&lt;br /&gt; * http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; *&lt;br /&gt; * Unless required by applicable law or agreed to in writing, software&lt;br /&gt; * distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; * See the License for the specific language governing permissions and&lt;br /&gt; * limitations under the License.&lt;br /&gt; */&lt;br /&gt;package edu.arizona.kim.util;&lt;br /&gt;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;&lt;br /&gt;import org.kuali.rice.kim.bo.entity.dto.KimEntityInfo;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Extension implementation for constants&lt;br /&gt; *&lt;br /&gt; */&lt;br /&gt;class ConstantsImpl implements Constants {&lt;br /&gt;    private String phoneNumberMask;&lt;br /&gt;    private org.kuali.rice.kim.util.Constants delegate;&lt;br /&gt;&lt;br /&gt;    public void setDelegate(final org.kuali.rice.kim.util.Constants delegate) {&lt;br /&gt;        this.delegate = delegate;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public org.kuali.rice.kim.util.Constants getDelegate() {&lt;br /&gt;        return delegate;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Collection&lt;String&gt; getTestPrincipalNames() {&lt;br /&gt;        return getDelegate().getTestPrincipalNames();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityPrototype&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityPrototype&lt;br /&gt;     */&lt;br /&gt;    public KimEntityInfo getEntityPrototype() {&lt;br /&gt;        return getDelegate().getEntityPrototype();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public String getExternalIdTypeProperty() {&lt;br /&gt;        return getDelegate().getExternalIdTypeProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of taxExternalIdTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of taxExternalIdTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getTaxExternalIdTypeCode() {&lt;br /&gt;        return getDelegate().getTaxExternalIdTypeCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getExternalIdProperty() {&lt;br /&gt;        return getDelegate().getExternalIdProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeePhoneLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeePhoneLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeePhoneLdapProperty() {&lt;br /&gt;        return getDelegate().getEmployeePhoneLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeMailLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeMailLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeMailLdapProperty() {&lt;br /&gt;        return getDelegate().getEmployeeMailLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCountryCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCountryCode&lt;br /&gt;     */&lt;br /&gt;    public String getDefaultCountryCode() {&lt;br /&gt;        return getDelegate().getDefaultCountryCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of personEntityTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of personEntityTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getPersonEntityTypeCode() {&lt;br /&gt;        return getDelegate().getPersonEntityTypeCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of kimLdapIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of kimLdapIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getKimLdapIdProperty() {&lt;br /&gt;        return getDelegate().getKimLdapIdProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of kimLdapNameProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of kimLdapNameProperty&lt;br /&gt;     */&lt;br /&gt;    public String getKimLdapNameProperty() {&lt;br /&gt;        return getDelegate().getKimLdapNameProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of snLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of snLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getSnLdapProperty() {&lt;br /&gt;        return getDelegate().getSnLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of givenNameLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of givenNameLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getGivenNameLdapProperty() {&lt;br /&gt;        return getDelegate().getGivenNameLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityIdKimProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityIdKimProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEntityIdKimProperty() {&lt;br /&gt;        return getDelegate().getEntityIdKimProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterNamespaceCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterNamespaceCode&lt;br /&gt;     */&lt;br /&gt;    public String getParameterNamespaceCode() {&lt;br /&gt;        return getDelegate().getParameterNamespaceCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterDetailTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterDetailTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getParameterDetailTypeCode() {&lt;br /&gt;        return getDelegate().getParameterDetailTypeCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public String getMappedParameterName() {&lt;br /&gt;        return getDelegate().getMappedParameterName();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of unmappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of unmappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public String getUnmappedParameterName() {&lt;br /&gt;        return getDelegate().getUnmappedParameterName();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedValuesName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedValuesName&lt;br /&gt;     */&lt;br /&gt;    public String getMappedValuesName() {&lt;br /&gt;        return getDelegate().getMappedValuesName();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeIdProperty() {&lt;br /&gt;        return getDelegate().getEmployeeIdProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of departmentLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of departmentLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getDepartmentLdapProperty() {&lt;br /&gt;        return getDelegate().getDepartmentLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeTypeProperty() {&lt;br /&gt;        return getDelegate().getEmployeeTypeProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeStatusProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeStatusProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeStatusProperty() {&lt;br /&gt;        return getDelegate().getEmployeeStatusProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCampusCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCampusCode&lt;br /&gt;     */&lt;br /&gt;    public String getDefaultCampusCode() {&lt;br /&gt;        return getDelegate().getDefaultCampusCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getDefaultChartCode() {&lt;br /&gt;        return getDelegate().getDefaultChartCode();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getEmployeeAffiliationCodes() {&lt;br /&gt;        return getDelegate().getEmployeeAffiliationCodes();&lt;br /&gt;    }&lt;br /&gt;    public String getAffiliationMappings() {&lt;br /&gt;        return getDelegate().getAffiliationMappings();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the mappings for the affiliation ldap property&lt;br /&gt;     * @return mapping for KIM affiliation and LDAP&lt;br /&gt;     */&lt;br /&gt;    public String getAffiliationLdapProperty() {&lt;br /&gt;        return getDelegate().getAffiliationLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the mappings for the primary affiliation ldap property&lt;br /&gt;     * @return mapping for KIM primary affiliation and LDAP&lt;br /&gt;     */&lt;br /&gt;    public String getPrimaryAffiliationLdapProperty() {&lt;br /&gt;        return getDelegate().getPrimaryAffiliationLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * @see edu.arizona.kim.util.Constants#getPhoneNumberMask()&lt;br /&gt;     */&lt;br /&gt;    public String getPhoneNumberMask() {&lt;br /&gt;        return this.phoneNumberMask;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the phone number mask constant&lt;br /&gt;     *&lt;br /&gt;     * @param phoneNumberMask value to set for the phoneNumberMask constant&lt;br /&gt;     */&lt;br /&gt;    public void setPhoneNumberMask(final String phoneNumberMask) {&lt;br /&gt;        this.phoneNumberMask = phoneNumberMask;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;Notice the following allover the place &lt;pre class="brush: java"&gt;    /**&lt;br /&gt;     * Gets the mappings for the primary affiliation ldap property&lt;br /&gt;     * @return mapping for KIM primary affiliation and LDAP&lt;br /&gt;     */&lt;br /&gt;    public String getPrimaryAffiliationLdapProperty() {&lt;br /&gt;        return getDelegate().getPrimaryAffiliationLdapProperty();&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;The &lt;b&gt;getDelegate()&lt;/b&gt; call is accessing the delegate we setup in spring. The dependency injection takes care of everything for us, so we don't need to worry about it being a package level class either. We also only override the getters because this is a constant. We're not supposed to change it.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;There you have it. Another reason to use constants in spring. Not only are they easy to define, but now you can use object composition to inherit constants from other beans.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4409584665107760271?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4409584665107760271/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/06/constants-done-with-spring-part-2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4409584665107760271'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4409584665107760271'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/06/constants-done-with-spring-part-2.html' title='Constants Done with Spring Part 2: Inheriting Constants'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-7977357015183738551</id><published>2011-04-10T18:25:00.000-07:00</published><updated>2011-07-11T06:43:17.601-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coeus'/><category scheme='http://www.blogger.com/atom/ns#' term='kc'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='cas'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><category scheme='http://www.blogger.com/atom/ns#' term='ldap'/><title type='text'>Setting up CAS on KC/Rice</title><content type='html'>&lt;h3&gt;Screencast&lt;/h3&gt; Just a screencast to show where to get the files and how to set it up.&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/dxsZVwUtHwI?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;h3&gt;Instructions&lt;/h3&gt;&lt;br /&gt;&lt;h4&gt;1 Download Source from rSmart&lt;/h4&gt;&lt;pre class="code"&gt;% svn co  https://svn.rsmart.com/svn/kuali/rice/rsmart_rice_core/trunk rsmart_rice_core&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;2 Copy Example Config&lt;/h4&gt;&lt;pre class="code"&gt;% cp web/src/main/config/example-config/rice-config.xml $HOME/kuali/main/dev/&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;3 Copy Contents of LDAP Example Config&lt;/h4&gt;&lt;pre class="code"&gt;% cat ldap/src/main/config/example-config.xml&lt;/pre&gt;&lt;br /&gt;You should see something that looks like:&lt;pre class="brush: xml"&gt;&amp;lt;!--&lt;br /&gt; Copyright 2008-2009 The Kuali Foundation&lt;br /&gt; &lt;br /&gt; Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt; &lt;br /&gt; http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; &lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;config&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.url"&amp;gt;http://localhost:8080/${cas.context.name}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.require.https"&amp;gt;false&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.validate.password"&amp;gt;true&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.rice.server.name"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.login.class"&amp;gt;org.jasig.cas.client.authentication.AuthenticationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.casServerLoginUrl"&amp;gt;${cas.url}/login&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.login.1"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.validation.class"&amp;gt;org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.casServerUrlPrefix"&amp;gt;${cas.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.validation.2"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.caswrapper.class"&amp;gt;org.jasig.cas.client.util.HttpServletRequestWrapperFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.caswrapper.3"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="rice.ldap.username"&amp;gt;uid=user,ou=Ldap Users,dc=localhost&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.password"&amp;gt;[secret]&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.url"&amp;gt;ldaps://localhost:636&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.base"&amp;gt;ou=People,dc=localhost&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.additionalSpringFiles"&amp;gt;org/kuali/rice/kim/config/KIMLdapSpringBeans.xml&amp;lt;/param&amp;gt;&lt;br /&gt;&amp;lt;/config&amp;gt;&lt;/pre&gt;&lt;br /&gt;When you finish, your config should look like: &lt;pre class="brush: xml"&gt;&amp;lt;!--&lt;br /&gt; Copyright 2008-2009 The Kuali Foundation&lt;br /&gt; &lt;br /&gt; Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt; &lt;br /&gt; http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; &lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;config&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- Please fill in a value for this parameter! --&amp;gt;&lt;br /&gt; &amp;lt;param name="http.port"&amp;gt;8080&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="application.host"&amp;gt;http://yourserver&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="application.url"&amp;gt;${application.host}:${http.port}/${app.context.name}&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;param name="plugin.dir"&amp;gt;/usr/local/rice/plugins&amp;lt;/param&amp;gt; &lt;br /&gt;&lt;br /&gt; &amp;lt;!-- set some datasource defaults --&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.username"&amp;gt;rice&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.password"&amp;gt;*** password ***&amp;lt;/param&amp;gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;!-- MySQL example --&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.ojb.platform"&amp;gt;MySQL&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.platform"&amp;gt;org.kuali.rice.core.database.platform.MySQLDatabasePlatform&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.url"&amp;gt;jdbc:mysql://localhost:3306/${datasource.username}&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.driver.name"&amp;gt;com.mysql.jdbc.Driver&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.pool.validationQuery"&amp;gt;select 1&amp;lt;/param&amp;gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;!-- Oracle example &lt;br /&gt; &amp;lt;param name="datasource.ojb.platform"&amp;gt;Oracle9i&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.platform"&amp;gt;org.kuali.rice.core.database.platform.OracleDatabasePlatform&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.url"&amp;gt;jdbc:oracle:thin:@localhost:1521:XE&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.driver.name"&amp;gt;oracle.jdbc.driver.OracleDriver&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="datasource.pool.validationQuery"&amp;gt;select 1 from dual&amp;lt;/param&amp;gt;&lt;br /&gt; --&amp;gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;param name="attachment.dir.location"&amp;gt;/usr/local/rice/kew_attachments&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="data.xml.root.location"&amp;gt;/usr/local/rice/kew/xml&amp;lt;/param&amp;gt; &lt;br /&gt;&lt;br /&gt; &amp;lt;!-- log4j settings --&amp;gt;&lt;br /&gt; &amp;lt;param name="log4j.settings.path"&amp;gt;/usr/local/rice/log4j.properties&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="log4j.settings.reloadInterval"&amp;gt;5&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- Keystore Configuration --&amp;gt;&lt;br /&gt; &amp;lt;param name="keystore.file"&amp;gt;/usr/local/rice/rice.keystore&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="keystore.alias"&amp;gt;*** key alias ***&amp;lt;/param&amp;gt;&lt;br /&gt; &amp;lt;param name="keystore.password"&amp;gt;*** password ***&amp;lt;/param&amp;gt;&lt;br /&gt; &lt;br /&gt; &amp;lt;param name="mail.relay.server"&amp;gt;localhost&amp;lt;/param&amp;gt;&lt;br /&gt;    &amp;lt;param name="mailing.list.batch"&amp;gt;mailing.list.batch&amp;lt;/param&amp;gt;&lt;br /&gt;    &amp;lt;param name="encryption.key"&amp;gt;*** encryption key ***&amp;lt;/param&amp;gt; &lt;br /&gt;&lt;br /&gt;  &amp;lt;param name="cas.url"&amp;gt;http://localhost:8080/${cas.context.name}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.require.https"&amp;gt;false&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.validate.password"&amp;gt;true&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.rice.server.name"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.login.class"&amp;gt;org.jasig.cas.client.authentication.AuthenticationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.casServerLoginUrl"&amp;gt;${cas.url}/login&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.login.1"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.validation.class"&amp;gt;org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.casServerUrlPrefix"&amp;gt;${cas.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.validation.2"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.caswrapper.class"&amp;gt;org.jasig.cas.client.util.HttpServletRequestWrapperFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.caswrapper.3"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="rice.ldap.username"&amp;gt;uid=user,ou=Ldap Users,dc=localhost&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.password"&amp;gt;[secret]&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.url"&amp;gt;ldaps://localhost:636&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.base"&amp;gt;ou=People,dc=localhost&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.additionalSpringFiles"&amp;gt;com/rsmart/kuali/rice/ldap/KIMLdapSpringBeans.xml&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;!-- Sample Application Flag --&amp;gt;&lt;br /&gt;    &amp;lt;param name="sample.enabled"&amp;gt;false&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;param name="dev.mode"&amp;gt;false&amp;lt;/param&amp;gt; &lt;br /&gt;&amp;lt;/config&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice that the &lt;b&gt;DummyLoginFilter&lt;/b&gt; is not to be found. This is important. This handles logins by default. You don't want it around when you're configuring CAS.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;4 Make Changes to Config&lt;/h4&gt;Right now, CAS is configured for localhost. At your institution, you will want to point this to your REAL CAS server. Also, make sure to set the &lt;b&gt;cas.context.name&lt;/b&gt;. It is probably better to use https as well. &lt;pre class="brush: xml"&gt;    &amp;lt;param name="cas.context.name"&amp;gt;webauth&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.url"&amp;gt;https://webauth.arizona.edu/${cas.context.name}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.require.https"&amp;gt;false&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.validate.password"&amp;gt;true&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="cas.rice.server.name"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.login.class"&amp;gt;org.jasig.cas.client.authentication.AuthenticationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.casServerLoginUrl"&amp;gt;${cas.url}/login&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.login.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.login.1"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.validation.class"&amp;gt;org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.casServerUrlPrefix"&amp;gt;${cas.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filter.validation.serverName"&amp;gt;${appserver.url}&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.validation.2"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="filter.caswrapper.class"&amp;gt;org.jasig.cas.client.util.HttpServletRequestWrapperFilter&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="filtermapping.caswrapper.3"&amp;gt;/&amp;lt;/param&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;param name="rice.ldap.username"&amp;gt;uid=user,ou=Ldap Users,dc=eds,dc=arizona,dc=edu&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.password"&amp;gt;[secret]&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.url"&amp;gt;ldaps://eds.arizona.edu:636&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.base"&amp;gt;ou=People,dc=eds,dc=arizona,dc=edu&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.additionalSpringFiles"&amp;gt;com/rsmart/kuali/rice/ldap/KIMLdapSpringBeans.xml&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;5 Configure LDAP&lt;/h4&gt;At UA, we setup there is an institutional directory service called EDS. Your institution may also have one. I configured it like this, &lt;pre class="brush: xml"&gt;  &amp;lt;param name="rice.ldap.username"&amp;gt;uid=user,ou=Ldap Users,dc=eds,dc=arizona,dc=edu&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.password"&amp;gt;[secret]&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.url"&amp;gt;ldaps://eds.arizona.edu:636&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.ldap.base"&amp;gt;ou=People,dc=eds,dc=arizona,dc=edu&amp;lt;/param&amp;gt;&lt;br /&gt;  &amp;lt;param name="rice.additionalSpringFiles"&amp;gt;com/rsmart/kuali/rice/ldap/KIMLdapSpringBeans.xml&amp;lt;/param&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;6 Love&lt;/h4&gt;That's it. This is a runtime configuration, so simply restarting my application server will reload this configuration and make the changes live.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-7977357015183738551?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/7977357015183738551/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/04/setting-up-cas-on-kcrice.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7977357015183738551'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7977357015183738551'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/04/setting-up-cas-on-kcrice.html' title='Setting up CAS on KC/Rice'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3125121286174408322</id><published>2011-04-10T11:18:00.000-07:00</published><updated>2011-04-10T18:26:45.714-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='logging'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>The Only Logger You'll Ever Need</title><content type='html'>&lt;h3&gt;The Status Quo for Logging in Kuali&lt;/h3&gt;One of the most frustrating things about developing with &lt;a href="http://www.kuali.org"&gt;Kuali Foundation Software&lt;/a&gt; is the logging. Setup aside, just adding the logging is frustrating. Mostly because of the immense amounts of copy/paste that is encouraged by it. Here's an example of some of the boiler plate.&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;import org.apache.log4j.Logger;&lt;br /&gt;import org.apache.ojb.broker.query.Criteria;&lt;br /&gt;import org.apache.ojb.broker.query.QueryByCriteria;&lt;br /&gt;import org.kuali.kfs.coa.dataaccess.impl.ChartDaoOjb;&lt;br /&gt;import org.kuali.kfs.fp.businessobject.TravelMileageRate;&lt;br /&gt;import org.kuali.kfs.fp.document.dataaccess.TravelMileageRateDao;&lt;br /&gt;import org.kuali.rice.kns.dao.impl.PlatformAwareDaoBaseOjb;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * This class is the OJB implementation of the TravelMileageRate interface.&lt;br /&gt; */&lt;br /&gt;public class TravelMileageRateDaoOjb extends PlatformAwareDaoBaseOjb implements TravelMileageRateDao {&lt;br /&gt;    private static Logger LOG = Logger.getLogger(ChartDaoOjb.class);&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;Let's go through this. First, examine the import:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;import org.apache.log4j.Logger;&lt;br /&gt;&lt;/pre&gt;Every single class you write with logging will require this weird, foreign class that really has nothing to do with the functionality of your software. It's awkward, and it's boilerplate. It's everywhere needlessly, and in some cases can cause you to neglect that it's there. Next, is my favorite part. We declare the logger on top of having to import it:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;public class TravelMileageRateDaoOjb extends PlatformAwareDaoBaseOjb implements TravelMileageRateDao {&lt;br /&gt;    private static Logger LOG = Logger.getLogger(ChartDaoOjb.class);&lt;br /&gt;&lt;/pre&gt;See anything unusual? This is really what I was getting at behind the copy/paste. To my knowledge, this still exists in the KFS source code. It's misleading. This &lt;tt&gt;TravelMileageRateDao&lt;/tt&gt; is logging as the &lt;tt&gt;ChartDaoOjb&lt;/tt&gt; I doubt this is on purpose. Rather, it is the result of copy/pasting the logger declaration from another class. Many do this because it is tedious. As a result, many forget to change the class name. &lt;br /&gt;&lt;br /&gt;I am not going to blame the developer for this. In my mind, it shouldn't even be necessary to do this. Shouldn't the framework just know what class I'm logging from? Is that really so hard?&lt;br /&gt;&lt;h3&gt;Researching the Performance of Logging&lt;/h3&gt;&lt;p&gt;After putting together &lt;a href="http://blog.leosandbox.org/2008/05/string-concatenation-in-java-2-logging.html"&gt;this post&lt;/a&gt; on another blog, I became determined to devise a simpler way to handle logging. Here were my goals.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Limit Logger boilerplate to the import statement&lt;/li&gt;&lt;li&gt;Efficient logging where the full log message is not concatenated until it is determined whether the message would be used or not&lt;/li&gt;&lt;li&gt;printf style formatting if possible.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Back to the Question&lt;/h3&gt;Shouldn't the framework just know what class I'm logging from? Is that really so hard?&lt;br /&gt;&lt;br /&gt;No. It's not. &lt;br /&gt;&lt;br /&gt;I have created two classes called &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/BufferedLogger.java"&gt;BufferedLogger&lt;/a&gt;&lt;/tt&gt; and &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/FormattedLogger.java"&gt;FormattedLogger&lt;/a&gt;&lt;/tt&gt;. These are "The Only Loggers You'll Ever Need". &lt;br /&gt;&lt;br /&gt;&lt;h4&gt;How to use them&lt;/h4&gt;It's easy. Before now, you probably thought static imports are pretty useless. Think again.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;import static org.kuali.kra.logging.BufferedLogger.*;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's your boilerplate. Next, let's use it:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;    public ActionForward insertProposalPerson(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;        // if the rule evaluation passed, let's add it&lt;br /&gt;        if (rulePassed) {&lt;br /&gt;            document.getDevelopmentProposal().addProposalPerson(pdform.getNewProposalPerson());&lt;br /&gt;            info(ADDED_PERSON_MSG, pdform.getNewProposalPerson().getProposalNumber(), pdform.getNewProposalPerson().getProposalPersonNumber());&lt;br /&gt;            // handle lead unit for investigators respective to coi or pi&lt;br /&gt;            if (getKeyPersonnelService().isPrincipalInvestigator(pdform.getNewProposalPerson())) {&lt;br /&gt;                getKeyPersonnelService().assignLeadUnit(pdform.getNewProposalPerson(), document.getDevelopmentProposal().getOwnedByUnitNumber());&lt;br /&gt;            }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Very easy stuff. Notice that there are multiple objects being passed to the &lt;tt&gt;info&lt;/tt&gt; method. They are not concatenated yet. The info method first checks if the message will be used before concatenating. This is actually a huge timesaver if you consider that the '+' and '+=' concatenation is pretty time consuming.&lt;br /&gt;&lt;br /&gt;What about printf style logging? Here's another example:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;    void prepare(ActionForm form, HttpServletRequest request) {&lt;br /&gt;        ProposalDevelopmentForm pdform = (ProposalDevelopmentForm) form;&lt;br /&gt;        request.setAttribute(NEW_PERSON_LOOKUP_FLAG, EMPTY_STRING);&lt;br /&gt;        ProposalDevelopmentDocument document=pdform.getDocument();&lt;br /&gt;        List&lt;ProposalPerson&gt; proposalpersons=document.getDevelopmentProposal().getProposalPersons();&lt;br /&gt;        for (Iterator&lt;ProposalPerson&gt; iter = proposalpersons.iterator(); iter.hasNext();) {&lt;br /&gt;            ProposalPerson person=(ProposalPerson) iter.next();&lt;br /&gt;            if (person.getRole() != null) {&lt;br /&gt;                person.getRole().setReadOnly(getKeyPersonnelService().isRoleReadOnly(person.getRole()));&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        pdform.populatePersonEditableFields();&lt;br /&gt;        handleRoleChangeEvents(pdform.getDocument());&lt;br /&gt;        &lt;br /&gt;        debug(INV_SIZE_MSG, pdform.getDocument().getDevelopmentProposal().getInvestigators().size());&lt;br /&gt;    &lt;br /&gt;        try {&lt;br /&gt;            boolean creditSplitEnabled = this.getParameterService().getIndicatorParameter(ProposalDevelopmentDocument.class, CREDIT_SPLIT_ENABLED_RULE_NAME)&lt;br /&gt;                &amp;&amp; pdform.getDocument().getDevelopmentProposal().getInvestigators().size() &gt; 0;&lt;br /&gt;            request.setAttribute(CREDIT_SPLIT_ENABLED_FLAG, new Boolean(creditSplitEnabled));&lt;br /&gt;            pdform.setCreditSplitEnabled(creditSplitEnabled);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            warn(MISSING_PARAM_MSG, CREDIT_SPLIT_ENABLED_RULE_NAME);&lt;br /&gt;            warn(e.getMessage());&lt;br /&gt;        }        &lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;You can see that this looks no different than the &lt;tt&gt;info&lt;/tt&gt; illustrated earlier. There is one difference though. Examine the &lt;tt&gt;warn&lt;/tt&gt; statement:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;            warn(MISSING_PARAM_MSG, CREDIT_SPLIT_ENABLED_RULE_NAME);&lt;br /&gt;&lt;/pre&gt;It uses a constant called &lt;tt&gt;MISSING_PARAM_MSG&lt;/tt&gt;. This is actually a format string that looks like:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;    private static final String MISSING_PARAM_MSG = "Couldn't find parameter '%s'";&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Which is better? &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/BufferedLogger.java"&gt;BufferedLogger&lt;/a&gt;&lt;/tt&gt; or &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/FormattedLogger.java"&gt;FormattedLogger&lt;/a&gt;&lt;/tt&gt;&lt;/h4&gt;&lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/FormattedLogger.java"&gt;FormattedLogger&lt;/a&gt;&lt;/tt&gt; has its downside. Formatting actually takes more clock cycles than concatenation. It's friendlier to developers though. With a rather large number of parameters to format, it can be relatively fast. I fall on &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/BufferedLogger.java"&gt;BufferedLogger&lt;/a&gt;&lt;/tt&gt; the most, but &lt;tt&gt;&lt;a href="https://test.kuali.org/svn/kc_project/trunk/src/main/java/org/kuali/kra/logging/FormattedLogger.java"&gt;FormattedLogger&lt;/a&gt;&lt;/tt&gt; has its uses.&lt;br /&gt;&lt;br /&gt;There you have it. No more copy paste. printf style logging.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3125121286174408322?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3125121286174408322/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/03/only-logger-youll-ever-need.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3125121286174408322'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3125121286174408322'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/03/only-logger-youll-ever-need.html' title='The Only Logger You&apos;ll Ever Need'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1264494371080241712</id><published>2011-04-10T10:30:00.000-07:00</published><updated>2011-04-10T18:29:04.135-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpm'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='configuration management'/><title type='text'>KIS Me Kate - RPM Packaging KFS Part 1</title><content type='html'>The default packaging option from the Kuali Foundation is typically JAR or WAR packaging. These are my observations from modifying typically deployment of KFS at the University of Arizona, to using RPMs. This is fine for libraries and/or web applications. It is platform independent and follows the standards of software deployment. So&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Why Repackage KFS?&lt;/h3&gt;There are shortcomings tow WAR and JAR packaging. WAR packaging was created with the intent that the application is actually WebSphere, Weblogic, JBOSS, etc..., and the WAR is actually a webapp that is deployed within. With that intent comes the concept that everything is contained within the WAR. What is lacking is:&lt;ul&gt;&lt;li&gt;There is no notion of pre/post processing at installation and deployment&lt;/li&gt;&lt;li&gt;There is no verification of dependencies or requisites.&lt;/li&gt;&lt;li&gt;There is no workflow for software installation&lt;/li&gt;&lt;li&gt;No maintenance over documentation vs. configuration files.&lt;/li&gt;&lt;li&gt;No platform-specific task hooks.&lt;/li&gt;&lt;li&gt;Upgrade software management and configuration management&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;All of these things may not make sense. For example, "No platform-specific task hooks". WAR is platform independent. Why would you want that, right? Well, that's just it. I think it's great that WAR is platform-independent. It let's you independently define your own packaging around it. Again, why would you want to do that? Double packaging? Let's approach each of these.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Pre/Post-Processing at Installation and Deployment&lt;/h3&gt;There may be some actual server information that the application needs to be configured at the point of installation and deployment. For example, server name database configuration, ssh key generation, certificate authority verification, ssl configuration, etc... These are normally configured manually by the system administrator manually after installing a WAR. What if this needs to be installed on several servers in a cloud? Some of this information can be automated. It does not require interaction or input from a user, so why do we require it to be done manually? That shouldn't be necessary.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Verification of Dependencies or Requisites&lt;/h3&gt;Sure, WAR files can contain all required libraries. There's no guarantee of this. Further, what if the server has a configuration that supercedes the WAR configuration. There's no way to know that either. Libraries aren't the only requisites that a WAR can have. You can ship with the BSF (Bean Scripting Framework), but that does you absolutely no good whatsoever if you have no native scripting languages installed on your server. What about the application server? The WAR doesn't come with that. Wouldn't it be nice if installing the application meant that even the appserver was installed with it and any software (libraries or not) it depends on? Yes it would, that's why proper software packaging starts to look pretty good.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Workflow for Software Installation&lt;/h3&gt;Like your development process, installation itself can have its own phases. A good installation infrastructure allows you to augment or even create and define your own phases. For example, pre/post-processing scripts (described earlier), patching, build, file installation, permission assignments, documentation handling, cleanup, etc...&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Maintenance Over Documentation vs. Configuration Files&lt;/h3&gt;Documentation and Configuration files are very delicate items in your deployment. Any configuration deployed with the application is usually reference implementation. That is, it is typically replaced manually or at the first deployment. Upon upgrading, configuration files are not something you want to override. You typically want to back these up with each upgrade and identify changes in configuration formats between versions. For example, new configuration entries can be added to a configuration file with each version. Some may become obsolete. It is undesirable to keep these, but you typically do not want to sacrifice the rest of your configuration for this. Kuali software is not a stranger to this situation. Kuali Foundation projects have the concept of an "external" configuration directory that exists outside of the WAR. The purpose of this is to exclude sensitive information from the webapp itself like passwords, any uploaded financial information batch files, or even log files. Such information is kept out of reach for security purposes within the external configuration. The external configuration files will not likely change, so when deploying upgrades, these should remain unchanged. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Platform-specific Task Hooks&lt;/h3&gt;Trouble with WAR files is that their plaform independent. This is their greatest strength, but also a huge weakness. WAR files know nothing about the system your are deploying too, this is a hassle for system administrators because the software has to be treated separately from other installations. It is difficult to observe changes and possible security threats. For example, if there is an exploit in the version of bouncy castle that KFS uses, there is no way for a system administrator to know. Further, if installation can be simplified by using platform-specific knowledge about the installed system at installation time, this knowledge can be used to automate the process more. For example, knowing what software and what version is installed can help determine what arguments to pass to utilities at install time.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Upgrade Software Management and Configuration Management&lt;/h3&gt;Basically, this is software intelligence. Having metadata about your software before and after it's installed for dependency management (illustrated earlier), configuration file handling (illustrated earlier), bug reporting, vulnerability observing (illustrated earlier), etc... One huge use is when a system administrator needs to manage multiple software installations across several servers. Knowing what version, build number, and configurations are on servers should be as easy as checking the software database of that system. Each platform has one. If a webapp is packaged and installed using that system, the software database will know about it and be able to disseminate that information back to the system administrators. With further scripting and automation tools, system administrators can have much better control over the systems they maintain.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Why is Packaging Important to KFS?&lt;/h3&gt;&lt;br /&gt;KFS is victim to all of the above illustrated.&lt;ul&gt;&lt;li&gt;At the University of Arizona, there are application sanity and maintenance tasks that run before packaging and before package installation. Packaging is done on a system separate from installation because there are actually several installation systems. To provide a repeatable installation process, it was decided that if any were going to be on a remote system then all should. For security purposes I cannot go into much detail about what pre/post-processing the University of Arizona does, but I can say that I would expect just about any university to require it.&lt;/li&gt;&lt;li&gt;Since building, packaging, and installation happen on separate systems a workflow has to be maintained. Further, the software is not the only thing that is installed for some implementing institutions. Some may couple data or even database schema information to the software. Therefore, this information may be distributed and deployed with the software. It cannot happen all at once. If there is a problem anywhere during the installation, a fallover path must exist. Installation workflows help this work out.&lt;/li&gt;&lt;li&gt;KFS makes use of something called an "external" settings directory. This directory is created and populated with reference information at installation. However, this information is overwritten at each installation by default. It would be good to not have to do this each time. When dealing with configurations on several servers, it can be tedious and problematic to rebuild each time and reconfigure each time. Mistakes are made. It is best to just configure once, and then make modifications only when necessary.&lt;/li&gt;&lt;li&gt;I have noticed that some institutions like to give access to the shared files directory via setacls on &lt;a href="http://www.redhat.com"&gt;RedHat&lt;/a&gt; and other linux systems. Such capabilities are not available by dropping in a war.&lt;/li&gt;&lt;li&gt;Currently KFS does not install all other required software with it. Also, there are no reports or observations on vulnerabilities of libraries distributed with KFS. When upgrading from one release to another, there is no verification of version or software compatibility.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;How can KFS Further Benefit from Software Packaging?&lt;/h3&gt;&lt;br /&gt;I used RPM packaging of KFS at the University of Arizona. When I did, it greatly streamlined installation. System administrators were familiar already with RPM. They were adept with installing packages, handling logs and dependencies, and found it easy to modify and maintain configuration. We were able to integrate RPM building into our CI (continuous integration). Whenever a release was due, a new RPM was created. This RPM could then be manually or automatically installed by the package management system. RPM information could quickly be verified with &lt;p class="code"&gt;% rpm -qa | grep kuali&lt;br /&gt;kuali-coeus-2.0-10&lt;br /&gt;kuali-coeus-settings-dev-2.0-10&lt;br /&gt;kuali-coeus-kittdb-2.0-10&lt;/p&gt; or &lt;p class="code"&gt;% rpm -qi kuali-coeus-settings-dev-2.0-10&lt;br /&gt;Name        : kuali-coeus-settings-dev     Relocations: (not relocatable)&lt;br /&gt;Version     : 2.0                               Vendor: (none)&lt;br /&gt;Release     : 10                            Build Date: Tue 28 Dec 2010 07:19:51 AM MST&lt;br /&gt;Install Date: Tue 11 Jan 2011 01:23:25 AM MST      Build Host: uaz-kr-a02.mosaic.arizona.edu&lt;br /&gt;Group       : System/Base                   Source RPM: kuali-coeus-2.0-10.src.rpm&lt;br /&gt;Size        : 200685086                        License: EPL&lt;br /&gt;Signature   : (none)&lt;br /&gt;Packager    : leo [at] rsmart.com&lt;br /&gt;Summary     : External configuration settings for Kuali Coeus&lt;br /&gt;Description :&lt;br /&gt;Mosaic Kuali Coeus external configuration and settings. These files are located&lt;br /&gt;in /home/tomcat/app&lt;/p&gt;.  With dependencies management, they are able to make sure that all the necessary tools exist in the system before the software is installed/updated. Database upgrades are streamlined and integrated into the installation process.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Package Repositories&lt;/h4&gt;I mentioned that UA has a package management repository. Right now, it is a crude CIFS share. It is possible through YUM to be more elegant with updates. I plan to in the future create my own YUM repository for managing Kuali software upgrades. &lt;br /&gt;&lt;br /&gt;Another package management and build system is APT which uses DEB packages. A package maintainer can create a project on LaunchPad which is a portal for Ubuntu Package/Build management. These packages are then present on a package repository (PPA or Personal Package Archive) where a system administrator can then point a server to this PPA and gain updates to all the software on it. The software and repository are verified against a PGP (Pretty Good Privacy) key. See &lt;a href="https://launchpad.net/~r351574nc3"&gt;my launchpad&lt;/a&gt;. My goal is to eventually have a working packaged KFS distribution by Summer 2011.&lt;br /&gt;&lt;br /&gt;Between YUM and APT, it is possible to get automatic updates and patches to your Kuali Software. This is one of the biggest reasons to use software packaging for KFS.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1264494371080241712?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1264494371080241712/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/03/kis-me-kate-rpm-packaging-kfs-part-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1264494371080241712'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1264494371080241712'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/03/kis-me-kate-rpm-packaging-kfs-part-1.html' title='KIS Me Kate - RPM Packaging KFS Part 1'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3195864213768172341</id><published>2011-04-02T21:43:00.000-07:00</published><updated>2011-06-14T15:32:22.984-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coeus'/><category scheme='http://www.blogger.com/atom/ns#' term='overlay'/><category scheme='http://www.blogger.com/atom/ns#' term='kc'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><title type='text'>Implementing KC 2.0 as an Overlay</title><content type='html'>&lt;h2&gt;Overview&lt;/h2&gt;&lt;a href="http://maven.apache.org/plugins/maven-war-plugin/overlays.html"&gt;Maven overlays&lt;/a&gt; are a way to take 2 web application projects and combine them with one &lt;i&gt;overlaying&lt;/i&gt; the other. It sounds like it would be interesting if you want to use another project as the base for your own, right? Almost as if it is slightly wrong. This only makes sense when your project builds on that of another. Literally building upon another project as a foundation. You would only make another project the foundation when you are in absolute control of that other project. &lt;a href="http://www.kuali.org"&gt;Kuali&lt;/a&gt; is community source, so the community runs the &lt;i&gt;foundation&lt;/i&gt; project in this case. The other project is intended to be an &lt;i&gt;implementation&lt;/i&gt; of Kuali Coeus where the implementing institution is a member of the community. Then technically, an overlay makes sense. You wouldn't want to make an overlay of just any project you found that you thought was cool and you wanted to change. It would be much better to fork that project instead. If you overlay, you run the risk of the foundation changing from underneath you. When implementing &lt;a href="http://www.kuali.org"&gt;Kuali&lt;/a&gt; software, this is fine because you have the community supporting you and the software. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Problem with Enterprise Software Maintenance at a University&lt;/h3&gt;To truly understand why overlays are a good idea at institutions and particularly universities, you need to understand the problem universities have had implementing software pretty much throughout history.&lt;br /&gt;&lt;br /&gt;Universities are used to getting this software implemented, but as time goes business processes and practices change. Business is changing. Universities are changing. Why shouldn't their business practices change? It makes sense. The trouble is that they bankrupt their budgets on implementing the software and have nothing left to maintain it. All they budget for maintaining it is fixing leaks and bandaiding. Eventually, these institutions are left with failing systems on the brink of demise. It all comes down to maintenance. These systems can't just be good now. They need the potential to be good 10 years from now. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.kuali.org"&gt;Kuali&lt;/a&gt; is no different in that respect. Compared to other enterprise software, the source code is very extensive. Making bug fixes to it will have effects on upgrading. Every change an institution adds to the source code, that does not get back to the trunk will cause problems because the institution's code base differs that much from that which is upgraded. The mindset is change as little of the original codebase as possible. Only build upon it if possible. If you're going to change something, find a way to do it without modifying the original distribution. No matter what you do, keep a record between versions so we know what changed. Among all of these, the consistent idea is to modify the distribution as little as possible.&lt;br /&gt;&lt;br /&gt;That is what overlays allow. Modifying and customizing the distribution by overlaying it affords institutions the ability to &lt;ul&gt;&lt;li&gt;make changes without making patching or upgrading difficult in the future&lt;/li&gt;&lt;li&gt;track what changes you made&lt;/li&gt;&lt;li&gt;simplify your local distribution&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;The Screencast&lt;/h2&gt;&lt;br /&gt;This is a screencast based on the instructions laid out in &lt;a href="https://wiki.kuali.org/display/KRADOC/KC+2.0+Customization"&gt;KC 2.0 Customization&lt;/a&gt;.&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/teCJd9eIEOU?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;h2&gt;Instructions&lt;/h2&gt;Written instructions for following along with the screencast.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1 Checkout the KC Project&lt;/h3&gt;First, you need to download the full KC project. I created a path in my workspace to store all this.&lt;pre class="code"&gt;% mkdir -p .workspace/rsmart&lt;/pre&gt;Then checkout the source code from it. I used export because eventually, I want to import this into my own svn repository&lt;pre class="code"&gt;% cd .workspace/rsmart&lt;br /&gt;% svn export https://test.kuali.org/svn/kc_project/tags/kc-release-2_0-tag&lt;/pre&gt;&lt;br /&gt;The above creates a new &lt;b&gt;kc-release-2_0-tag&lt;/b&gt; directory.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2 Install KC WAR and JAR Files&lt;/h3&gt;To install the WAR file in our maven repository, we use &lt;pre class="code"&gt;% mvn -Dmaven.test.skip=true install&lt;/pre&gt;&lt;br /&gt;To create the JAR file, we use &lt;pre class="code"&gt;% mvn jar:jar&lt;/pre&gt;&lt;br /&gt;Installing the JAR is a little different. &lt;pre class="code"&gt;% mvn install:install-file -Dpackaging=jar -DgroupId=org.kuali.kra -DartifactId=kc_project -Dversion=2.0 -DgeneratePom=true -Dfile=target/kc_project-2.0.jar&lt;/pre&gt;&lt;br /&gt;That should be the end of our work with the &lt;b&gt;kc_project&lt;/b&gt;. &lt;br /&gt;&lt;h3&gt;3 Setup kc_custom&lt;/h3&gt;Create the directory structure and &lt;b&gt;pom.xml&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;3.1 Create Directory Structure&lt;/h4&gt;&lt;pre class="code"&gt;% mkdir kc&lt;br /&gt;% mkdir -p kc/src/main/java/com/rsmart/kuali/kc&lt;br /&gt;% mkdir -p kc/src/main/java/org/kuali/kra/infrastructure&lt;br /&gt;% mkdir -p kc/src/main/config&lt;br /&gt;% mkdir -p kc/src/main/resources/com/rsmart/kuali/kc&lt;br /&gt;% mkdir -p kc/src/main/webapp/WEB-INF/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;3.1 Copy Some Files Over&lt;/h4&gt;Might as well copy a couple files from the &lt;b&gt;kc_project&lt;/b&gt;. &lt;pre class="code"&gt;% cp kc-release-2_0-tag/src/main/webapp/WEB-INF/web.xml kc/src/main/webapp/WEB-INF/&lt;br /&gt;% cp kc-release-2_0-tag/src/main/java/org/kuali/kra/infrastructure/KraServiceLocator.java kc/src/main/java/org/kuali/kra/infrastructure&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;3.2 Create pom.xml&lt;/h4&gt;The new overlay project needs its own pom.xml&lt;pre class="brush: xml"&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&amp;gt;&lt;br /&gt;  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;groupId&amp;gt;com.rsmart.kuali.kc&amp;lt;/groupId&amp;gt;&lt;br /&gt;  &amp;lt;artifactId&amp;gt;kc&amp;lt;/artifactId&amp;gt;&lt;br /&gt;  &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;packaging&amp;gt;jar&amp;lt;/packaging&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;name&amp;gt;kc&amp;lt;/name&amp;gt;&lt;br /&gt;  &amp;lt;url&amp;gt;http://maven.apache.org&amp;lt;/url&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;properties&amp;gt;&lt;br /&gt;    &amp;lt;project.build.sourceEncoding&amp;gt;UTF-8&amp;lt;/project.build.sourceEncoding&amp;gt;&lt;br /&gt;  &amp;lt;/properties&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependencies&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;type&amp;gt;war&amp;lt;/type&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt;      &amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;          &amp;lt;groupId&amp;gt;org.kuali.rice&amp;lt;/groupId&amp;gt;&lt;br /&gt;          &amp;lt;artifactId&amp;gt;rice-kns&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;1.0.2.1&amp;lt;/version&amp;gt;                               &lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;3.8.1&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;  &amp;lt;/dependencies&amp;gt;&lt;br /&gt; &amp;lt;repositories&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;kuali&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;Kuali Repository&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;https://test.kuali.org/maven&amp;lt;/url&amp;gt;&lt;br /&gt;   &amp;lt;snapshots&amp;gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&amp;lt;/snapshots&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;codehaus&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;Codehaus&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;http://dist.codehaus.org&amp;lt;/url&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;apache&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;apache&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;http://people.apache.org/repo/m2-ibiblio-rsync-repository&amp;lt;/url&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;jboss&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;jboss&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;http://repository.jboss.com/maven2&amp;lt;/url&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;atlassian&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;atlassian&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;http://maven.atlassian.com/repository/public&amp;lt;/url&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;     &amp;lt;snapshots /&amp;gt;&lt;br /&gt;     &amp;lt;id&amp;gt;maven-repo1&amp;lt;/id&amp;gt;&lt;br /&gt;     &amp;lt;name&amp;gt;maven2 repo&amp;lt;/name&amp;gt;&lt;br /&gt;     &amp;lt;url&amp;gt;http://repo1.maven.org/maven2&amp;lt;/url&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt; &lt;br /&gt;&lt;br /&gt; &amp;lt;/repositories&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;build&amp;gt;&lt;br /&gt;    &amp;lt;plugins&amp;gt;&lt;br /&gt;      &amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-war-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;overlays&amp;gt;&lt;br /&gt;            &amp;lt;overlay&amp;gt;&lt;br /&gt;              &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;              &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;/overlay&amp;gt;&lt;br /&gt;          &amp;lt;/overlays&amp;gt;&lt;br /&gt;        &amp;lt;/configuration&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;    &amp;lt;/plugins&amp;gt;&lt;br /&gt;  &amp;lt;/build&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The important parts to notice are the &lt;ul&gt;&lt;li&gt;&lt;b&gt;dependencies&lt;/b&gt; - there are 2 &lt;b&gt;kc_project&lt;/b&gt; dependencies. One is for the JAR we installed and the other is for the WAR.&lt;pre class="brush: java"&gt;    &amp;lt;dependencies&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;type&amp;gt;war&amp;lt;/type&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;    &amp;lt;dependency&amp;gt;&lt;br /&gt;      &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;      &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;      &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt;      &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt;      &amp;lt;type&amp;gt;jar&amp;lt;/type&amp;gt;&lt;br /&gt;    &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;/pre&gt;Notice that the JAR dependency uses the &lt;b&gt;provided&lt;/b&gt; flag. This states how to use the jars in classpath when building. Here is an excerpt from &lt;a href="http://maven.apache.org/pom.html"&gt;Maven POM Reference&lt;/a&gt; &lt;blockquote&gt;provided - this is much like compile, but indicates you expect the JDK or a container to provide it at runtime. It is only available on the compilation and test classpath, and is not transitive.&lt;/blockquote&gt; &lt;/li&gt;&lt;li&gt;repositories - I added the rice repository to pick up all the Rice dependencies at build time &lt;pre class="brush: xml"&gt; &amp;lt;repositories&amp;gt;&lt;br /&gt;  &amp;lt;repository&amp;gt;&lt;br /&gt;   &amp;lt;id&amp;gt;kuali&amp;lt;/id&amp;gt;&lt;br /&gt;   &amp;lt;name&amp;gt;Kuali Repository&amp;lt;/name&amp;gt;&lt;br /&gt;   &amp;lt;url&amp;gt;https://test.kuali.org/maven&amp;lt;/url&amp;gt;&lt;br /&gt;   &amp;lt;snapshots&amp;gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&amp;lt;/snapshots&amp;gt;&lt;br /&gt;  &amp;lt;/repository&amp;gt;&lt;br /&gt;    &amp;lt;/repositories&amp;gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;plugins - here's what actually does the overlay&lt;pre class="brush: xml"&gt;  &amp;lt;build&amp;gt;&lt;br /&gt;    &amp;lt;plugins&amp;gt;&lt;br /&gt;      &amp;lt;plugin&amp;gt;&lt;br /&gt;        &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;        &amp;lt;artifactId&amp;gt;maven-war-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;        &amp;lt;version&amp;gt;2.1.1&amp;lt;/version&amp;gt;&lt;br /&gt;        &amp;lt;configuration&amp;gt;&lt;br /&gt;          &amp;lt;overlays&amp;gt;&lt;br /&gt;            &amp;lt;overlay&amp;gt;&lt;br /&gt;              &amp;lt;groupId&amp;gt;org.kuali.kra&amp;lt;/groupId&amp;gt;&lt;br /&gt;              &amp;lt;artifactId&amp;gt;kc_project&amp;lt;/artifactId&amp;gt;&lt;br /&gt;            &amp;lt;/overlay&amp;gt;&lt;br /&gt;          &amp;lt;/overlays&amp;gt;&lt;br /&gt;        &amp;lt;/configuration&amp;gt;&lt;br /&gt;      &amp;lt;/plugin&amp;gt;&lt;br /&gt;    &amp;lt;/plugins&amp;gt;&lt;br /&gt;  &amp;lt;/build&amp;gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;4 Add a Custom O/R Mapping File&lt;/h3&gt;Create a file in &lt;b&gt;src/main/resources/com/rsmart/kuali/kc&lt;/b&gt; which is my institution's module path. I call it &lt;b&gt;rsmart-repository.xml&lt;/b&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;descriptor-repository version="1.0"&amp;gt;&lt;br /&gt;&amp;lt;/descriptor-repository&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;5 Add a Custom Spring Beans File&lt;/h3&gt;To load our O/R mapping, we'll need to wire it up with Spring. Kuali has a facility to handle this. We just create a &lt;b&gt;CustomSpringBeans.xml&lt;/b&gt; file &lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt; Copyright 2005-2010 The Kuali Foundation.&lt;br /&gt;&lt;br /&gt; Licensed under the Educational Community License, Version 1.0 (the "License");&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt;&lt;br /&gt; http://www.opensource.org/licenses/ecl1.php&lt;br /&gt;&lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;--&amp;gt;&lt;br /&gt;&amp;lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt;       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" &lt;br /&gt;       xmlns:p="http://www.springframework.org/schema/p" &lt;br /&gt;       xmlns:aop="http://www.springframework.org/schema/aop"&lt;br /&gt;       xmlns:tx="http://www.springframework.org/schema/tx"&lt;br /&gt;       xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/tx&lt;br /&gt;                           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd&lt;br /&gt;                           http://www.springframework.org/schema/aop&lt;br /&gt;                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"&amp;gt; &lt;br /&gt;  &amp;lt;bean id="customModuleConfiguration-parentBean" class="org.kuali.rice.kns.bo.ModuleConfiguration" abstract="true"&amp;gt;&lt;br /&gt;    &amp;lt;property name="databaseRepositoryFilePaths"&amp;gt;&lt;br /&gt;      &amp;lt;list&amp;gt;&lt;br /&gt;        &amp;lt;value&amp;gt;com/rsmart/kuali/kc/rsmart-repository.xml&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/list&amp;gt;&lt;br /&gt;    &amp;lt;/property&amp;gt;&lt;br /&gt;  &amp;lt;/bean&amp;gt;&lt;br /&gt;&amp;lt;/beans&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;6 Add Custom Struts Config XML File&lt;/h3&gt;Struts has this concept of a context specific configuration where you can have more than one configuration. This is very helpful, but we need to list ours in the &lt;b&gt;web.xml&lt;/b&gt; and create it. This is a bare one fresh for putting new forms, actions, forwards, etc... into &lt;b&gt;src/main/webapp/WEB-INF/struts-custom-config.xml&lt;/b&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://struts.apache.org/dtds/struts-config_1_2.dtd" [&lt;br /&gt; &amp;lt;!ENTITY protocol_forwards SYSTEM "struts_protocol_forwards.xml"&amp;gt;&lt;br /&gt; ]&amp;gt;&lt;br /&gt;&amp;lt;struts-config&amp;gt;&lt;br /&gt;  &amp;lt;data-sources&amp;gt;&lt;br /&gt;  &amp;lt;/data-sources&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;form-beans&amp;gt;&lt;br /&gt;  &amp;lt;/form-beans&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;global-exceptions&amp;gt;&lt;br /&gt;  &amp;lt;/global-exceptions&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;global-forwards&amp;gt;&lt;br /&gt;  &amp;lt;/global-forwards&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;action-mappings&amp;gt;&lt;br /&gt;  &amp;lt;/action-mappings&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;controller processorClass="org.kuali.kra.web.struts.action.KraRequestProcessor" /&amp;gt;&lt;br /&gt;  &amp;lt;message-resources factory="org.kuali.rice.kns.web.struts.action.KualiPropertyMessageResourcesFactory" parameter="" /&amp;gt;&lt;br /&gt;  &amp;lt;plug-in className="org.kuali.kra.web.struts.action.GlobalFormatterRegistry" /&amp;gt;&lt;br /&gt;&amp;lt;/struts-config&amp;gt;&lt;/pre&gt;&lt;br /&gt;Of course, I change the &lt;b&gt;web.xml&lt;/b&gt; from &lt;pre class="brush: xml"&gt;&amp;gt;servlet&amp;gt;&lt;br /&gt;        &amp;gt;servlet-name&amp;gt;action&amp;gt;/servlet-name&amp;gt;&lt;br /&gt;        &amp;gt;servlet-class&amp;gt;org.kuali.rice.kns.web.struts.action.KualiActionServlet&amp;gt;/servlet-class&amp;gt;&lt;br /&gt;&amp;gt;init-param&amp;gt;&lt;br /&gt;  &amp;gt;param-name&amp;gt;config&amp;gt;/param-name&amp;gt;&lt;br /&gt;  &amp;gt;param-value&amp;gt;/WEB-INF/struts-config.xml&amp;gt;/param-value&amp;gt;&lt;br /&gt;&amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;init-param&amp;gt;&lt;br /&gt;                &amp;gt;param-name&amp;gt;debug&amp;gt;/param-name&amp;gt;&lt;br /&gt;                &amp;gt;param-value&amp;gt;3&amp;gt;/param-value&amp;gt;&lt;br /&gt;        &amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;init-param&amp;gt;&lt;br /&gt;                &amp;gt;param-name&amp;gt;detail&amp;gt;/param-name&amp;gt;&lt;br /&gt;                &amp;gt;param-value&amp;gt;3&amp;gt;/param-value&amp;gt;&lt;br /&gt;        &amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;load-on-startup&amp;gt;0&amp;gt;/load-on-startup&amp;gt;&lt;br /&gt;&amp;gt;/servlet&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;to &lt;pre class="brush: xml"&gt;&amp;gt;servlet&amp;gt;&lt;br /&gt;        &amp;gt;servlet-name&amp;gt;action&amp;gt;/servlet-name&amp;gt;&lt;br /&gt;        &amp;gt;servlet-class&amp;gt;org.kuali.rice.kns.web.struts.action.KualiActionServlet&amp;gt;/servlet-class&amp;gt;&lt;br /&gt;&amp;gt;init-param&amp;gt;&lt;br /&gt;  &amp;gt;param-name&amp;gt;config&amp;gt;/param-name&amp;gt;&lt;br /&gt;  &amp;gt;param-value&amp;gt;/WEB-INF/struts-config.xml,/WEB-INF/struts-custom-config.xml&amp;gt;/param-value&amp;gt;&lt;br /&gt;&amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;init-param&amp;gt;&lt;br /&gt;                &amp;gt;param-name&amp;gt;debug&amp;gt;/param-name&amp;gt;&lt;br /&gt;                &amp;gt;param-value&amp;gt;3&amp;gt;/param-value&amp;gt;&lt;br /&gt;        &amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;init-param&amp;gt;&lt;br /&gt;                &amp;gt;param-name&amp;gt;detail&amp;gt;/param-name&amp;gt;&lt;br /&gt;                &amp;gt;param-value&amp;gt;3&amp;gt;/param-value&amp;gt;&lt;br /&gt;        &amp;gt;/init-param&amp;gt;&lt;br /&gt;        &amp;gt;load-on-startup&amp;gt;0&amp;gt;/load-on-startup&amp;gt;&lt;br /&gt;&amp;gt;/servlet&amp;gt;&lt;/pre&gt;&lt;br /&gt;I just added &lt;b&gt;,/WEB-INF/struts-custom-config.xml&lt;/b&gt; to the parameter.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;7 Replace the KraServiceLocator.java&lt;/h3&gt;This is a little strange because you are not extending, but overriding the &lt;b&gt;KraServiceLocator&lt;/b&gt; class. That means that if there are any changes made to it, you will probably not pick those up unless you explicitly know. This is of course, a maintenance issue, but we're only making minor changes. Remaking them is not a hassle. &lt;br /&gt;&lt;br /&gt;&lt;h4&gt;7.1 Add the Custom Spring Beans as a Constant&lt;/h4&gt;First, we create a constant in &lt;b&gt;KraServiceLocator&lt;/b&gt; called &lt;b&gt;CUSTOM_SPRING_BEANS&lt;/b&gt;&lt;pre class="brush: java"&gt;private static final String CUSTOM_SPRING_BEANS = "com/rsmart/kuali/kc/CustomSpringBeans.xml";&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;7.2 Add the Constant to the springFiles Array&lt;/h4&gt;Now we make use of the constant.&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;    private static final class ContextHolder {&lt;br /&gt;        &lt;br /&gt;        static String[] springFiles = new String[] {COMMON_SPRING_BEANS,BUDGET_SPRING_BEANS, AWARD_SPRING_BEANS, IRB_SPRING_BEANS, COMMITTEE_SPRING_BEANS, &lt;br /&gt;                                                    INSTITUTIONAL_PROPOSAL_SPRING_BEANS, QUESTIONNAIRE_SPRING_BEANS, TIME_AND_MONEY_SPRING_BEANS, CUSTOM_SPRING_BEANS};&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;8 Love&lt;/h3&gt; Now you have your overlay project. Just run the following to create your war. &lt;pre class="code"&gt;% mvn -Dmaven.test.skip=true package&lt;/pre&gt; &lt;br /&gt;You will see a new WAR file in &lt;b&gt;target&lt;/b&gt;&lt;pre class="code"&gt;leo@behemoth~/.workspace/rsmart/kc&lt;br /&gt;(18:54:16) [48] ls target/&lt;br /&gt;classes   kc-1.0-SNAPSHOT.war war&lt;br /&gt;kc-1.0-SNAPSHOT  maven-archiver&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3195864213768172341?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3195864213768172341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/04/implementing-kc-20-as-overlay.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3195864213768172341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3195864213768172341'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/04/implementing-kc-20-as-overlay.html' title='Implementing KC 2.0 as an Overlay'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3335314192991044746</id><published>2011-04-02T12:29:00.000-07:00</published><updated>2011-04-02T12:50:34.220-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpm'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>KIS Me Kate - RPM Packaging KFS Part 4</title><content type='html'>&lt;h3&gt;KC Packaging&lt;/h3&gt;Packaging KC is much the same as KFS. The only difference is that when you build, you are using maven instead of ant. &lt;br /&gt;&lt;h4&gt;Setting up Hudson&lt;/h4&gt;Therefore, to get around adding my own hooks to the build, all I did was use &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt;. First, I setup a maven build. &lt;br /&gt;&lt;img src="http://www.u.arizona.edu/~przybyls/images/kc_mvn_build.png" /&gt;It is the equivalent to &lt;pre class="code"&gt;% mvn -Dmaven.test.skip=true package&lt;/pre&gt;&lt;br /&gt;Then, I created an &lt;b&gt;invoke shell&lt;/b&gt; command &lt;br /&gt;&lt;img src="http://www.u.arizona.edu/~przybyls/images/Screen shot 2011-04-02 at 10.59.49 AM.png" /&gt;&lt;br /&gt;&lt;pre class="code"&gt;TARFILE=kuali-coeus-2.0-$(cut -d= -f 2 kc-2.0/version.properties).tar.gz&lt;br /&gt;rm -rf kuali-coeus-2.0&lt;br /&gt;mkdir kuali-coeus-2.0&lt;br /&gt;cp -rf kc-2.0/target/kc_custom-2.0 kuali-coeus-2.0&lt;br /&gt;&lt;br /&gt;for x in $HOME/kuali/main/[a-z]*; do &lt;br /&gt;    mkdir -p kuali-coeus-2.0/kuali/main/$(basename $x)/&lt;br /&gt;    cp -rf $HOME/kuali/main/$(basename $x)/kc-config.xml kuali-coeus-2.0/kuali/main/$(basename $x)&lt;br /&gt;done&lt;br /&gt;cp $HOME/lib/ojdbc* kuali-coeus-2.0/kc_custom-2.0/WEB-INF/lib&lt;br /&gt;cp $HOME/*.properties kuali-coeus-2.0&lt;br /&gt;tar -czf $TARFILE kuali-coeus-2.0 kc-cfg-dbs/&lt;br /&gt;mv $TARFILE /mosaic/data/KITT/SOURCES&lt;/pre&gt;&lt;br /&gt;You can see that I am using a &lt;b&gt;version.properties&lt;/b&gt;. It looks like &lt;pre class="code"&gt;leo@behemoth~/.workspace/kc&lt;br /&gt;(11:02:12) [21] cat version.properties &lt;br /&gt;release=11&lt;/pre&gt;&lt;br /&gt;Finally, I create the package by calling the build-rpm.xml using ant.&lt;br /&gt;&lt;img src="http://www.u.arizona.edu/~przybyls/images/Screen shot 2011-04-02 at 11.00.01 AM.png" /&gt; The build-rpm.xml looks like&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;project             name="kc" &lt;br /&gt;                  default="build" &lt;br /&gt;         xmlns:kitt-tools="urn:com.rsmart.ant"&amp;gt;&lt;br /&gt;  &amp;lt;target name="build" depends="filter-spec"&amp;gt;&lt;br /&gt;    &amp;lt;exec executable="rpmbuild"&amp;gt;&lt;br /&gt;      &amp;lt;arg value="-bb" /&amp;gt;&lt;br /&gt;      &amp;lt;arg value="kc.spec" /&amp;gt;&lt;br /&gt;    &amp;lt;/exec&amp;gt;&lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="filter-spec"&amp;gt;&lt;br /&gt;    &amp;lt;property file="version.properties" /&amp;gt;&lt;br /&gt;    &amp;lt;kitt-tools:filter srcfile="kc.spec.template"&lt;br /&gt;                       filename="kc.spec" /&amp;gt;&lt;br /&gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;  &lt;br /&gt;  &amp;lt;macrodef uri="urn:com.rsmart.ant" name="filter"&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="srcfile" /&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="filename" /&amp;gt;&lt;br /&gt;    &amp;lt;sequential&amp;gt;&lt;br /&gt;      &amp;lt;loadfile property="buildroot.filter.template"&lt;br /&gt;                srcfile="@{srcfile}"&amp;gt;&lt;br /&gt;        &amp;lt;filterchain&amp;gt;&lt;br /&gt;          &amp;lt;expandproperties/&amp;gt;&lt;br /&gt;        &amp;lt;/filterchain&amp;gt;&lt;br /&gt;      &amp;lt;/loadfile&amp;gt;&lt;br /&gt;&lt;br /&gt;      &amp;lt;echo file="@{filename}"&amp;gt;${buildroot.filter.template}&amp;lt;/echo&amp;gt;&lt;br /&gt;    &amp;lt;/sequential&amp;gt;&lt;br /&gt;  &amp;lt;/macrodef&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A lot like our original build.xml for KFS. The differences here are &lt;ul&gt;&lt;li&gt;No vendor path, so we use build-rpm.xml instead of build.xml&lt;/li&gt;&lt;li&gt;no workflow&lt;/li&gt;&lt;li&gt;no changelogs&lt;/li&gt;&lt;li&gt;Building doesn't happen here since hudson did that for us&lt;/li&gt;&lt;li&gt;No importing the other build.xml&lt;/li&gt;&lt;/ul&gt; You can see we use a kc.spec.template and filter that just as we did with kfs.&lt;br /&gt;&lt;h4&gt;KC spec file&lt;/h4&gt;&lt;br /&gt;Here is the spec file I used&lt;pre class="code"&gt;&lt;br /&gt;%define __os_install_post %{nil}&lt;br /&gt;%define debug_package %{nil} &lt;br /&gt;&lt;br /&gt;Summary: Kuali Coeus&lt;br /&gt;Name: kuali-coeus&lt;br /&gt;Version: 2.0&lt;br /&gt;Release: %release&lt;br /&gt;Provides: kuali-coeus&lt;br /&gt;License: EPL&lt;br /&gt;BuildArch: noarch&lt;br /&gt;Source0: kuali-coeus-2.0-%release.tar.gz&lt;br /&gt;BuildRoot: /tmp/kc&lt;br /&gt;Requires: ant&lt;br /&gt;Group: Development/Tools&lt;br /&gt;Packager: przybyls@arizona.edu&lt;br /&gt;&lt;br /&gt;%package settings-${build.environment}&lt;br /&gt;Summary: External configuration settings for Kuali Coeus&lt;br /&gt;Group: System/Base&lt;br /&gt;Requires: kuali-coeus&lt;br /&gt;&lt;br /&gt;%package changelogs&lt;br /&gt;Summary: Kuali Coeus KITT Customization Schema&lt;br /&gt;Group: System/Base&lt;br /&gt;Requires: kc,liquibase,wget&lt;br /&gt;&lt;br /&gt;%description&lt;br /&gt;The Kuali Foundation research administration software&lt;br /&gt;&lt;br /&gt;%description changelogs&lt;br /&gt;Mosaic Kuali Coeus Environment Database Schema for KITT customizations based on KITT&lt;br /&gt;modification set %release&lt;br /&gt;&lt;br /&gt;%description settings-${build.environment}&lt;br /&gt;Mosaic Kuali Coeus external configuration and settings. These files are located&lt;br /&gt;in /home/tomcat/app&lt;br /&gt;&lt;br /&gt;%prep&lt;br /&gt;%setup -q&lt;br /&gt;&lt;br /&gt;%install&lt;br /&gt;TOOLSDIR=%{_builddir}/kuali-coeus-2.0/kitt-tools-1.0&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/bin&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/config&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/lib&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kuali/main/2.0-%release/changesets/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/classes/META-INF&lt;br /&gt;&lt;br /&gt;cp ${build.environment}.properties %{buildroot}/home/tomcat/kitt-tools&lt;br /&gt;cp $HOME/apache-ant/lib/ant.jar %{buildroot}/home/tomcat/kitt-tools/lib&lt;br /&gt;cp $HOME/apache-ant/lib/ant-launcher.jar %{buildroot}/home/tomcat/kitt-tools/lib&lt;br /&gt;&lt;br /&gt;cd kc_custom-2.0/WEB-INF/classes/META-INF/&lt;br /&gt;sed -e 's/\(.*build.environment.*"false"&amp;gt;\).*/\1${build.environment}&amp;lt;\/param&amp;gt;/' kc-config-defaults.xml | sed -e 's/\(.*build.version.*"false"&amp;gt;\).*/\12.0-%release&amp;lt;\/param&amp;gt;/' &amp;gt; /tmp/kc-config-defaults.xml&lt;br /&gt;mv /tmp/kc-config-defaults.xml %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/classes/META-INF&lt;br /&gt;cd -&lt;br /&gt;&lt;br /&gt;cp kc_custom-2.0/WEB-INF/web-${build.environment}.xml %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/web.xml&lt;br /&gt;cp -rf kuali/main/${build.environment} %{buildroot}/home/tomcat/kuali/main/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; %{buildroot}/home/tomcat/.rpmmacros&lt;br /&gt;%%_topdir /home/tomcat/.workspace/redhat&lt;br /&gt;%%_dbpath /home/tomcat/rpm&lt;br /&gt;EOF&lt;br /&gt;&lt;br /&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; %{buildroot}/home/tomcat/kitt-tools/.envrc&lt;br /&gt;${build.environment}&lt;br /&gt;EOF&lt;br /&gt;&lt;br /&gt;mv %{_builddir}/kuali-coeus-2.0/kc_custom-2.0 %{buildroot}/usr/share/tomcat5/webapps/kra/&lt;br /&gt;mv %{_builddir}/kc-cfg-dbs/update* %{buildroot}/home/tomcat/kuali/main/2.0-%release/changesets/&lt;br /&gt;&lt;br /&gt;set -x &lt;br /&gt;&lt;br /&gt;ant -f /dev/stdin -Dbuild.environment=${build.environment}&amp;lt;&amp;lt;EOF&lt;br /&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;project name="kitt-tools" default="run" basedir="." xmlns:kitt-tools="urn:com.rsmart.ant"&amp;gt;&lt;br /&gt;    &amp;lt;target name="run"&amp;gt;&lt;br /&gt;        &amp;lt;property file="$TOOLSDIR/\${build.environment}.properties" /&amp;gt;&lt;br /&gt;        &amp;lt;echo file="%{buildroot}/home/tomcat/kitt-tools/credentials.properties"&amp;gt;&lt;br /&gt;source.driver=\${dbcopy.default.driver}&lt;br /&gt;source.url=jdbc:oracle:thin:@uaz-kf-d02.mosaic.arizona.edu:1521:UAZKRDEV&lt;br /&gt;source.username=sandbox&lt;br /&gt;source.password=kulowner&lt;br /&gt;source.schema=SANDBOX&lt;br /&gt;target.driver=\${dbcopy.default.driver}&lt;br /&gt;target.url=\${oracle.datasource.url}&lt;br /&gt;target.username=\${datasource.username}&lt;br /&gt;target.schema=KULOWNER&lt;br /&gt;encrypted.password=\${encrypted.password}&lt;br /&gt;        &amp;lt;/echo&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;EOF&lt;br /&gt;&lt;br /&gt;%clean&lt;br /&gt;rm -rf %{buildroot}&lt;br /&gt;&lt;br /&gt;%files settings-${build.environment}&lt;br /&gt;%defattr(2770,tomcat,kuali)&lt;br /&gt;/home/tomcat/.rpmmacros&lt;br /&gt;/home/tomcat/kitt-tools&lt;br /&gt;/home/tomcat/kuali/main/${build.environment}/kc-config.xml&lt;br /&gt;/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/web.xml&lt;br /&gt;/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/classes/META-INF/kc-config-defaults.xml&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;%files changelogs&lt;br /&gt;%defattr(-,tomcat,kuali)&lt;br /&gt;%config /home/tomcat/kuali/main/2.0-%release/changesets/update.xml&lt;br /&gt;%config /home/tomcat/kuali/main/2.0-%release/changesets/update/&lt;br /&gt;&lt;br /&gt;%files&lt;br /&gt;%defattr(2770,tomcat,kuali)&lt;br /&gt;/usr/share/tomcat5/webapps/kra/&lt;br /&gt;&lt;br /&gt;%pre&lt;br /&gt;rm -rf /usr/share/tomcat5/webapps/kra-*&lt;br /&gt;&lt;br /&gt;%post&lt;br /&gt;set -x&lt;br /&gt;&lt;br /&gt;MYPWD=$PWD&lt;br /&gt;cd /usr/share/tomcat5/webapps&lt;br /&gt;mv kra kra-$(cat ~tomcat/kitt-tools/.envrc)&lt;br /&gt;cd $MYPWD&lt;br /&gt;&lt;br /&gt;%post changelogs &lt;br /&gt;set -x&lt;br /&gt;&lt;br /&gt;VERSION=%release&lt;br /&gt;CURRENT=$(ls -t ~tomcat/kuali/main/|grep -v %release|head -1|cut -d- -f 2)&lt;br /&gt;REPO_URL=&lt;br /&gt;KC_VERSION=2.0&lt;br /&gt;&lt;br /&gt;for x in $(seq $(expr $CURRENT + 1) $VERSION);&lt;br /&gt;do&lt;br /&gt;    if [ ! -e ~tomcat/kuali/main/$KC_VERSION-$x ];&lt;br /&gt;    then&lt;br /&gt;        cd ~tomcat/kuali/main&lt;br /&gt;        wget -r -l 2 --no-parent -nH --cut-dirs=5 $REPO_URL/$KC_VERSION-$x/&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;    cd ~tomcat/kuali/main/$KC_VERSION-$x/changesets&lt;br /&gt;    liquibase --changeLogFile=update.xml --logLevel=finest update&lt;br /&gt;    liquibase --logLevel=finest tag $KC_VERSION.$x&lt;br /&gt;    cd &lt;br /&gt;done&lt;br /&gt;&lt;br /&gt;cd -&lt;br /&gt;&lt;br /&gt;if [ -e ~tomcat/kuali/main/$KC_VERSION-$(expr %release + 1) ];&lt;br /&gt;then&lt;br /&gt;    echo '%release' &amp;gt; /tmp/lquninst&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;%postun changelogs&lt;br /&gt;if [ -e /tmp/lquninst ];&lt;br /&gt;then&lt;br /&gt;&lt;br /&gt;    START=%release&lt;br /&gt;    END=$(cat /tmp/lquninst)&lt;br /&gt;    for x in $(seq $START -1 $END); &lt;br /&gt;    do &lt;br /&gt;        cd /home/tomcat/kuali/main/2.0-$x/changesets&lt;br /&gt;        liquibase --changeLogFile=update.xml rollback 2.0.$(expr $x - 1)&lt;br /&gt;&lt;br /&gt;        cd -;&lt;br /&gt;    done&lt;br /&gt;    rm /tmp/lquninst&lt;br /&gt;fi &lt;br /&gt;exit&lt;br /&gt;&lt;/pre&gt;The script is pretty crazy looking, so I will take a jab at explaining it. It will help to explain the major differences in project organization and structure between KFS and KC.&lt;ul&gt;&lt;li&gt;New XML Configuration for KC/Rice in kuali/main/dev&lt;/li&gt;&lt;li&gt;no more security.properties (normally where database passwords are stored)&lt;/li&gt;&lt;li&gt;No build.properties since we're not using ant. Configuration is now in kc-config.xml in kuali/main/&amp;lt;environment&amp;gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Handle PreProcessing&lt;/h4&gt;Keeping that in mind, let's have a look at the &lt;b&gt;%install&lt;/b&gt; section which is where most of the work that is going on is. Since we can't bootstrap or hook into the regular maven packaging for the WAR, what I've done instead is to handle that preprocessing here.&lt;br /&gt;&lt;br /&gt;Keep in mind that since this is the &lt;b&gt;%install&lt;/b&gt; directive, this is not packaging yet. This is just creating the build sandbox we're going to package later. So first we need to prepare the structure&lt;pre class="code"&gt;TOOLSDIR=%{_builddir}/kuali-coeus-2.0/kitt-tools-1.0&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/bin&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/config&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kitt-tools/lib&lt;br /&gt;mkdir -p %{buildroot}/home/tomcat/kuali/main/2.0-%release/changesets/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/&lt;br /&gt;mkdir -p %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/classes/META-INF&lt;/pre&gt;&lt;br /&gt;You can see this is building out the path for the webapp to live. There is also this new concept of a &lt;b&gt;kitt-tools&lt;/b&gt; path. These are actually environment utilities for administrating KC. They are outside the scope of this explanation, but we do have to add to them a few things during installation. One of these is the &lt;b&gt;credential.properties&lt;/b&gt;&lt;pre class="code"&gt;ant -f /dev/stdin -Dbuild.environment=${build.environment}&amp;lt;&amp;lt;EOF&lt;br /&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;project name="kitt-tools" default="run" basedir="." xmlns:kitt-tools="urn:com.rsmart.ant"&amp;gt;&lt;br /&gt;    &amp;lt;target name="run"&amp;gt;&lt;br /&gt;        &amp;lt;property file="$TOOLSDIR/\${build.environment}.properties" /&amp;gt;&lt;br /&gt;        &amp;lt;echo file="%{buildroot}/home/tomcat/kitt-tools/credentials.properties"&amp;gt;&lt;br /&gt;source.driver=\${dbcopy.default.driver}&lt;br /&gt;source.url=jdbc:oracle:thin:@uaz-kf-d02.mosaic.arizona.edu:1521:UAZKRDEV&lt;br /&gt;source.username=sandbox&lt;br /&gt;source.password=kulowner&lt;br /&gt;source.schema=SANDBOX&lt;br /&gt;target.driver=\${dbcopy.default.driver}&lt;br /&gt;target.url=\${oracle.datasource.url}&lt;br /&gt;target.username=\${datasource.username}&lt;br /&gt;target.schema=KULOWNER&lt;br /&gt;encrypted.password=\${encrypted.password}&lt;br /&gt;        &amp;lt;/echo&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;EOF&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ant is used to populate this based on values set in the configuration.&lt;br /&gt;&lt;br /&gt;Each environment is going to get its own &lt;b&gt;kc-config.xml&lt;/b&gt;. For example, TST would be &lt;b&gt;kuali/main/tst/kc-config.xml&lt;/b&gt;. Usually, the there are very slight differences between each environment configuration. Usually, the only difference is the database password. For most of the common settings, we use a kc-config-defaults.xml. These files are maintained on the build system (where hudson is living). As a result our build version is usually clobbered by the default. I do a little hacking to get around that. The following takes care of that for us.&lt;pre class="code"&gt;cd kc_custom-2.0/WEB-INF/classes/META-INF/&lt;br /&gt;sed -e 's/\(.*build.environment.*"false"&amp;gt;\).*/\1${build.environment}&amp;lt;\/param&amp;gt;/' kc-config-defaults.xml | sed -e 's/\(.*build.version.*"false"&amp;gt;\).*/\12.0-%release&amp;lt;\/param&amp;gt;/' &amp;gt; /tmp/kc-config-defaults.xml&lt;br /&gt;mv /tmp/kc-config-defaults.xml %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/classes/META-INF&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We have environment specific web.xml files for any weird changes that are by environment &lt;pre class="code"&gt;cp kc_custom-2.0/WEB-INF/web-${build.environment}.xml %{buildroot}/usr/share/tomcat5/webapps/kra-${build.environment}/WEB-INF/web.xml&lt;br /&gt;cp -rf kuali/main/${build.environment} %{buildroot}/home/tomcat/kuali/main/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Once a package is installed, tools that we install take advantage of knowing what environment they're on. We handle this by creating an &lt;b&gt;.envrc&lt;/b&gt; file on the environment.&lt;pre class="code"&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; %{buildroot}/home/tomcat/kitt-tools/.envrc&lt;br /&gt;${build.environment}&lt;br /&gt;EOF&lt;/pre&gt;&lt;br /&gt;The last thing done is to move the changelogs and the webapp to it's environment agnostic locations. Remember from &lt;a href="http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html"&gt;Part 2&lt;/a&gt; we move these files into their appropriate locations during &lt;b&gt;post processing&lt;/b&gt;&lt;pre class="code"&gt;mv %{_builddir}/kuali-coeus-2.0/kc_custom-2.0 %{buildroot}/usr/share/tomcat5/webapps/kra/&lt;br /&gt;mv %{_builddir}/kc-cfg-dbs/update* %{buildroot}/home/tomcat/kuali/main/2.0-%release/changesets/&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;New packages&lt;/h4&gt;Aside from the &lt;b&gt;%install&lt;/b&gt;, the spec file and packages are very much the same or at least familiar to the KFS packages. For KC, it was decided that workflow would be handled manually instead of automatically, so the packages are:&lt;ul&gt;&lt;li&gt;kuali-coeus-2.0-1.noarch.rpm&lt;/li&gt;&lt;li&gt;kuali-coeus-settings-2.0-1.noarch.rpm&lt;/li&gt;&lt;li&gt;kuali-coeus-changelogs-2.0-1.noarch.rpm&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3335314192991044746?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3335314192991044746/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/04/kis-me-kate-rpm-packaging-kfs-part-4.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3335314192991044746'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3335314192991044746'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/04/kis-me-kate-rpm-packaging-kfs-part-4.html' title='KIS Me Kate - RPM Packaging KFS Part 4'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6107996498457762055</id><published>2011-04-02T11:35:00.000-07:00</published><updated>2011-04-02T11:35:00.225-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpm'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>KIS Me Kate - RPM Packaging KFS Part 3</title><content type='html'>&lt;h3&gt;Overview/Recap&lt;/h3&gt;&lt;br /&gt;This is the last part of a 3 part series on packaging KFS with &lt;a href="http://www.rpm.org"&gt;RPM&lt;/a&gt;. Just when you thought there wasn't anything left to say about the subject, there's more. What didn't we cover last time? &lt;ul&gt;&lt;li&gt;&lt;b&gt;Workflow packaging&lt;/b&gt; - this is actually useful to separate from the main package. Sometimes, you do not want to install/upgrade/reinstall your workflow definitions&lt;/li&gt;&lt;li&gt;&lt;b&gt;Database Upgrade/Installation&lt;/b&gt; - if you use liquibase, this is a very useful thing to split out from the main package. It is a useful thing to on-demand upgrade your database.&lt;/li&gt;&lt;li&gt;&lt;b&gt;KC Setup/Packaging (Part 4)&lt;/b&gt; - backtracking a little and it doesn't really have anything to do with KFS, but you have to admit that if you're interested in packaging KFS, you're also interested in packaging KC.&lt;/lI&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Workflow Packaging&lt;/h3&gt;This refers to workflow as in the workflow XML that one ingests (usually manually). Sometimes workflow changes are couple to java source code changes in &lt;a href="http://rice.kuali.org"&gt;Rice&lt;/a&gt;. As a project manager/release manager, you want your project to deploy with as little hiccups as possible. This includes &lt;b&gt;all&lt;/b&gt; of your changes that are interdependent to be deployed. If you deploy documents that require workflow changes, your application may not work unless you get those changes in somehow. I wouldn't trust a person to do it, so how do you get this done automatically?&lt;br /&gt;&lt;br /&gt;In &lt;a href="http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html"&gt;a previous post&lt;/a&gt;, I showed some configuration source code &lt;pre class="code"&gt;rice.dev.mode=false&lt;br /&gt;rice.standalone=false&lt;br /&gt;rice.kew.xml.pipeline.lifecycle.enabled=true&lt;/pre&gt;These are important. I'll explain:&lt;ul&gt;&lt;li&gt;&lt;b&gt;rice.kew.xml.pipeline.lifecycle.enabled&lt;/b&gt; - turns on a thread that runs periodically to ingest KEW xml (it does not use quartz, but an internal scheduler)&lt;/li&gt;&lt;li&gt;&lt;b&gt;rice.dev.mode&lt;/b&gt; - you want to set to false because unless, it is in dev, the xml pipeline will not run (regardless of the previous setting). There's a good reason for this. You generally do not want this running in any kind of production environment.&lt;/li&gt;&lt;li&gt;&lt;b&gt;rice.standalone&lt;/b&gt; - for now we are building KFS with rice running bundled/embedded. If this were set to false, then our rice would run separately, and we wouldn't be ingesting workflow through the KFS application.&lt;/lI&gt;&lt;/ul&gt;&lt;br /&gt;Now, let's move from theory to practice. It appears that things are mostly setup through our configuration. What we need next is&lt;ol&gt;&lt;li&gt;Copy workflow xml to the appropriate ingestion directory during the build process&lt;/li&gt;&lt;li&gt;Setup the spec file so that the files are included in their correct locations at packaging&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h4&gt;Modify Build to Move Workflow XML&lt;/h4&gt;We now need to modify our &lt;b&gt;build.xml&lt;/b&gt; in &lt;b&gt;vendor/&amp;lt;your institution&amp;gt;/&lt;/b&gt;. In &lt;a href="http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html"&gt;a previous post&lt;/a&gt;, there is a target called &lt;b&gt;dist-rpm&lt;/b&gt;. It looks like, &lt;pre class="brush: xml"&gt;&amp;lt;target        name="dist-war" &lt;br /&gt;          description="Kuali distribution plus post processing."&lt;br /&gt;              depends="init-classpath,dist"&amp;gt;&lt;br /&gt;    &amp;lt;fail unless="build.environment"&amp;gt;Need the build.environment to build&amp;lt;/fail&amp;gt;&lt;br /&gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="dist-rpm" depends="prepare-rpm,dist-war" /&amp;gt;&lt;/pre&gt;&lt;br /&gt;We add a new target &lt;b&gt;dist-workflow&lt;/b&gt; &lt;pre class="brush: xml"&gt; &amp;lt;target        name="dist-war" &lt;br /&gt;          description="Kuali distribution plus post processing."&lt;br /&gt;              depends="init-classpath,dist"&amp;gt;&lt;br /&gt;    &amp;lt;fail unless="build.environment"&amp;gt;Need the build.environment to build&amp;lt;/fail&amp;gt;&lt;br /&gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;target        name="dist-workflow" &lt;br /&gt;          description="Kuali post processing for KEW XML."&lt;br /&gt;              depends="init-classpath"&amp;gt;&lt;br /&gt;    &amp;lt;fail unless="build.environment"&amp;gt;Need the build.environment to build&amp;lt;/fail&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;deploy:workflow-sieve release="${build.version}" kfspath="${basedir}" /&amp;gt;&lt;br /&gt;   &lt;br /&gt;   &amp;lt;mkdir dir="${rpm.ingestion.directory}" /&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;copy todir="${rpm.ingestion.directory}" flatten="true"&amp;gt;&lt;br /&gt;      &amp;lt;fileset dir="${work.directory}/src/com" erroronmissingdir="false"&amp;gt;&lt;br /&gt;        &amp;lt;include name="**/workflow/*.xml" /&amp;gt;&lt;br /&gt;      &amp;lt;/fileset&amp;gt;&lt;br /&gt;      &amp;lt;fileset dir="${work.directory}/src/edu" erroronmissingdir="false"&amp;gt;&lt;br /&gt;        &amp;lt;include name="**/workflow/*.xml" /&amp;gt;&lt;br /&gt;      &amp;lt;/fileset&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;/copy&amp;gt;&lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="dist-rpm" depends="prepare-rpm,dist-war" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Obviously, we want to find the workflow xml and copy it to our desired location which is &lt;pre class="code"&gt;rpm.ingestion.directory=${rpm.external.work.directory}/staging/workflow/pending/&lt;/pre&gt; set in the &lt;b&gt;rpm.properties&lt;/b&gt; file mentioned in &lt;a href="http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html"&gt;KIS Me Kate - RPM Packaging KFS Part 2&lt;/a&gt;. There is a caveat though. In recent versions of &lt;a href="http://rice.kuali.org"&gt;Rice&lt;/a&gt;, a new ingestion of a workflow document type does &lt;b&gt;NOT&lt;/b&gt; replace the old document type. It creates a new one. The old type still exists. This means that with subsequent ingestion, new document types will be created regardless of their differences. If your institution fancies having daily building/packaging, you could find yourself with a rather large list of document types with very little different from each other. How do we get around this? What I did was create a &lt;b&gt;workflow-sieve&lt;/b&gt; task in the &lt;b&gt;macros-rpm.xml&lt;/b&gt; that determines whether the workflow XML had any changes. This is what it looks like:&lt;pre class="brush:xml"&gt;&amp;lt;project  xmlns:deploy="urn:com.rsmart.kuali"&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;   &amp;lt;macrodef uri="urn:edu.arizona.kitt" name="workflow-sieve"&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="release" /&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="kfsPath" /&amp;gt;&lt;br /&gt;    &amp;lt;sequential&amp;gt;&lt;br /&gt;      &amp;lt;echo file="/tmp/workflow-sieve.py"&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;&lt;br /&gt;import os.path&lt;br /&gt;import re&lt;br /&gt;import sys&lt;br /&gt;from subprocess import *&lt;br /&gt;&lt;br /&gt;svnpath = "https://subversion.uits.arizona.edu/kitt-anon/kitt/financial-system/kfs/branches"&lt;br /&gt;trunkpath = "https://subversion.uits.arizona.edu/kitt-anon/kitt/financial-system/kfs/trunk"&lt;br /&gt;&lt;br /&gt;def findWorkflowFiles(basedir):&lt;br /&gt;    retval = []&lt;br /&gt;    for root, dirs, files in os.walk(basedir):&lt;br /&gt;        for file in (files):&lt;br /&gt;            if (re.match(".*workflow$", root)):&lt;br /&gt;                newroot = root.split('/work/')[1]&lt;br /&gt;                retval.append('/'.join(['work', newroot, file]))&lt;br /&gt;&lt;br /&gt;    return retval&lt;br /&gt;&lt;br /&gt;def getLastReleaseRevision(release):&lt;br /&gt;    releaseLoc = svnpath + "/3.0-" + str(release)&lt;br /&gt;    return getRevisionFor(releaseLoc)&lt;br /&gt;&lt;br /&gt;def getRevisionFor(path):&lt;br /&gt;    retval = Popen(["svn", "info", path], stdout=PIPE).communicate()[0]&lt;br /&gt;    retval = int(retval.split("Last Changed Rev: ")[1].split("\n")[0])&lt;br /&gt;    return retval&lt;br /&gt;&lt;br /&gt;def command(command):&lt;br /&gt;    print 'Executing: ' + command&lt;br /&gt;    os.system(command)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;release = int("@{release}".split("-")[1]) - 1&lt;br /&gt;workflowFiles = findWorkflowFiles('@{kfsPath}/work/src/edu/')&lt;br /&gt;workflowFiles.extend(findWorkflowFiles('@{kfsPath}/work/src/com/'))&lt;br /&gt;revision = getLastReleaseRevision(release)&lt;br /&gt;&lt;br /&gt;for workflow in workflowFiles:&lt;br /&gt;    filename = trunkpath + "/" + workflow&lt;br /&gt;    print "Checking revision on " + filename&lt;br /&gt;    fileRev = getRevisionFor(filename)&lt;br /&gt;    if (revision &amp;gt; fileRev):&lt;br /&gt;        print "Removing " + workflow + " from package"&lt;br /&gt;        os.remove(workflow)&lt;br /&gt;      ]]&amp;gt;&lt;br /&gt;      &amp;lt;/echo&amp;gt;&lt;br /&gt;      &amp;lt;exec executable="${user.home}/python/bin/python"&amp;gt;&lt;br /&gt;        &amp;lt;arg value="/tmp/workflow-sieve.py" /&amp;gt;&lt;br /&gt;      &amp;lt;/exec&amp;gt;&lt;br /&gt;      &amp;lt;delete file="/tmp/workflow-sieve.py" /&amp;gt;&lt;br /&gt;    &amp;lt;/sequential&amp;gt;&lt;br /&gt;  &amp;lt;/macrodef&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Above is a simply python script that gets run as part of the &lt;b&gt;workflow-sieve&lt;/b&gt; task which occurs during the &lt;b&gt;dist-workflow&lt;/b&gt; target! This is great! Now our build is altered sufficiently to handle the workflow XML.&lt;br /&gt;&lt;h4&gt;Define Workflow Package&lt;/h4&gt;Now I will add workflow to the &lt;b&gt;kfs.spec.template&lt;/b&gt;&lt;pre class="code"&gt;%define __os_install_post %{nil}&lt;br /&gt;&lt;br /&gt;Summary: Kuali Financial System&lt;br /&gt;Name: kfs&lt;br /&gt;Version: ${version}&lt;br /&gt;Release: ${release}&lt;br /&gt;Provides: kfs&lt;br /&gt;License: EPL&lt;br /&gt;BuildArch: noarch&lt;br /&gt;Requires: tomcat5&lt;br /&gt;BuildRoot: /tmp/kfs/&lt;br /&gt;Source0: kfs-${build.version}.tar.gz&lt;br /&gt;Group: Development/Tools&lt;br /&gt;Packager: leo [at] rsmart.com&lt;br /&gt;&lt;br /&gt;%package workflow&lt;br /&gt;Summary: Kuali Financial System Workflow Document Types&lt;br /&gt;Group: System/Base&lt;br /&gt;Requires: kfs&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We set our requirement for KFS. this will ensure that all our KFS prerequisites exist before this is installed.&lt;br /&gt;After that, I add my description &lt;pre class="code"&gt;%description  workflow&lt;br /&gt;Workflow XML for %release&lt;/pre&gt;&lt;br /&gt;Notice that after &lt;b&gt;%description&lt;/b&gt;, I give the string "workflow". Normally, there is no qualifier for &lt;b&gt;%description&lt;/b&gt;. That indicates that it's going into the default package. We qualify with "workflow". This means that there will be a workflow qualifier added to the package name. The resulting package will be called &lt;b&gt;kfs-workflow-4.0-1.noarch.rpm&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;Next, we specify our &lt;b&gt;%files&lt;/b&gt; just like we did with the default package. &lt;pre class="code"&gt;%files workflow&lt;br /&gt;%defattr(-,tomcat,tomcat)&lt;br /&gt;/home/tomcat/app/work/kfs/staging/workflow/&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now when you rerun your packaging, you will have a workflow package that can be installed with your KFS application as well as all your workflow customizations for that release!&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Packaging Database Changes&lt;/h3&gt;In particular, packaging your liquibase changelogs and having them run on installation! This is actually, a really good idea. Just like workflow changes, your database changes are coupled to your java source code. This is especially the case thanks to ORM. KFS may not even start correctly if you do not have tables mentioned in your mappings. The best way to make sure they exist is to add your liquibase change logs to installation. As an added bonus, you can now have visibility on all changes made to your database from release to release.&lt;br /&gt;&lt;h4&gt;Update build.xml&lt;/h4&gt;Just like we did with our workflow packaging, we will need to change the build to include the liquibase change logs.&lt;pre class="brush: xml"&gt;&amp;lt;target name="dist-ddl" depends="init-classpath"&amp;gt;&lt;br /&gt;  &amp;lt;mkdir dir="${rpm.ddl.directory}" /&amp;gt;&lt;br /&gt;      &lt;br /&gt;  &amp;lt;copy todir="${rpm.ddl.directory}"&amp;gt;&lt;br /&gt;    &amp;lt;fileset dir="${work.directory}/db/"&amp;gt;&lt;br /&gt;     &amp;lt;include name="changesets/**/*" /&amp;gt;&lt;br /&gt;     &amp;lt;include name="scripts/**/*" /&amp;gt;&lt;br /&gt;    &amp;lt;/fileset&amp;gt;&lt;br /&gt;  &amp;lt;/copy&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;target name="dist-rpm" depends="prepare-rpm,dist-war,dist-ddl,dist-workflow" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;I have added now &lt;b&gt;dist-ddl&lt;/b&gt; to the &lt;b&gt;dist-rpm&lt;/b&gt; dependencies. I have also created the target for it. All it does is copy files out of &lt;b&gt;kfs-4.0/work/db/changets&lt;/b&gt; and &lt;b&gt;kfs-4.0/work/db/scripts&lt;/b&gt; into my build directory for packaging. &lt;br /&gt;&lt;h4&gt;Add Spec Information&lt;/h4&gt;Just like I did with workflow, I now add the &lt;b&gt;%package&lt;/b&gt; for changelogs &lt;pre class="code"&gt;%package changelogs&lt;br /&gt;Summary: Kuali Financial System KITT Customization Schema&lt;br /&gt;Group: System/Base&lt;br /&gt;Requires: liquibase,kfs,wget&lt;/pre&gt;&lt;br /&gt;This will create a new package with the name &lt;b&gt;kfs-changelogs-4.0-1.noarch.rpm&lt;/b&gt;. Also, notice that my dependency is liquibase. This means that liquibase will required as well as KFS before the changelogs can be run! A program called wget is required too. I will explain why later.&lt;br /&gt;&lt;br /&gt;Now I add &lt;b&gt;%description&lt;/b&gt; and &lt;b&gt;files&lt;/b&gt;.&lt;pre class="code"&gt;%description changelogs&lt;br /&gt;Liquibase change logs for %release&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;%files changelogs&lt;br /&gt;%defattr(-,tomcat,tomcat)&lt;br /&gt;/home/tomcat/app/ddl/${build.version}/changesets/latest&lt;br /&gt;/home/tomcat/app/ddl/${build.version}/changesets/install.xml&lt;br /&gt;/home/tomcat/app/ddl/${build.version}/changesets/constraints.xml&lt;br /&gt;%config /home/tomcat/app/ddl/${build.version}/changesets/update.xml&lt;br /&gt;%config /home/tomcat/app/ddl/${build.version}/changesets/update/&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above describes that a new changeset path is created for each &lt;b&gt;${build.version}&lt;/b&gt;. The reason for this is because we want to keep all the changelogs from previous builds. This allows us to quickly revert back in case we need to undo packages. One of the advantages of package management is being able to cleanly and systematically remove the software change without any evidence that it ever happened. The update.xml is listed as a configuration item because we never want this overwritten.&lt;br /&gt;&lt;h4&gt;Installation Post processing Script&lt;/h4&gt;Unlike with workflow, we now have some pre/post processing to do. Until now, just files are being dropped in for installation. We don't do anything with these files. That is, liquibase is required, but it never runs. The database isn't actually changed yet. We still need to run liquibase on the changelogs.&lt;pre class="code"&gt;%post changelogs &lt;br /&gt;set -x&lt;br /&gt;&lt;br /&gt;VERSION=%release&lt;br /&gt;CURRENT=$(ls -t ~tomcat/app/ddl/|grep -v %release|head -1|cut -d- -f 2)&lt;br /&gt;REPO_URL=&amp;lt;your institutions' SVN repo accessible via http&amp;gt;&lt;br /&gt;KFS_VERSION=%version&lt;br /&gt;&lt;br /&gt;for x in $(seq $(expr $CURRENT + 1) $VERSION);&lt;br /&gt;do&lt;br /&gt;    if [ ! -e ~tomcat/app/ddl/$KFS_VERSION-$x ];&lt;br /&gt;    then&lt;br /&gt;        cd ~tomcat/app/ddl&lt;br /&gt;        wget -r -l 2 --no-parent -nH --cut-dirs=5 $REPO_URL/$KFS_VERSION-$x/&lt;br /&gt;    fi&lt;br /&gt;&lt;br /&gt;    cd ~tomcat/app/ddl/$KFS_VERSION-$x/changesets&lt;br /&gt;    liquibase --changeLogFile=update.xml --logLevel=finest update&lt;br /&gt;    liquibase --logLevel=finest tag %version.$x&lt;br /&gt;    cd &lt;br /&gt;done&lt;br /&gt;&lt;br /&gt;if [ -e ~tomcat/app/ddl/%version-$(expr %release + 1) ];&lt;br /&gt;then&lt;br /&gt;    echo '%release' &gt; /tmp/lquninst&lt;br /&gt;fi&lt;/pre&gt;&lt;br /&gt;The above script is running liquibase on the current installation. This is pretty tricky which is why it's a shell script. When we upgrade/install our liquibase changelogs, we need to run all of the changes subsequently between the last and current version. For example, if we are upgrading from release 3 - release 15, we need to run all the scripts in between &lt;b&gt;in order&lt;/b&gt;. The caveat is that when we upgrade directly (going from package for release 3 to package for release 15), we're skipping packages that contain the changelogs. This means we cannot assume the system has the changelogs installed. This is where wget comes in. We actually setup an anonymous, read-only accessible SVN rep url in the script &lt;pre class="code"&gt;REPO_URL=&amp;lt;your institutions' SVN repo accessible via http&amp;gt;&lt;/pre&gt;. We now use wget to retrieve from SVN the missing changelogs and install them before proceeding to process them through liquibase.&lt;pre class="code"&gt;   liquibase --changeLogFile=update.xml --logLevel=finest update&lt;br /&gt;    liquibase --logLevel=finest tag %version.$x&lt;/pre&gt;it is important to note that liquibase is tagging a version after each update. We will use this in a moment.&lt;br /&gt;&lt;h4&gt;Uninstallation Post-processing Script&lt;/h4&gt;That handles installation/upgrades, but ... what about uninstallations. Well, an uninstallation is pretty much reverting liquibase to the state of the previous changelog. The way we know the state of the previous changelog is that the state was tagged on installation (mentioned earlier). Now we know where to downgrade to. To handle uninstallations, RPM has a directive called &lt;b&gt;%postun&lt;/b&gt;. We use that here&lt;pre class="code"&gt;%postun kittdb&lt;br /&gt;if [ -e /tmp/lquninst ];&lt;br /&gt;then&lt;br /&gt;&lt;br /&gt;    START=%release&lt;br /&gt;    END=$(cat /tmp/lquninst)&lt;br /&gt;    for x in $(seq $START -1 $END); &lt;br /&gt;    do &lt;br /&gt;        cd /home/tomcat/app/ddl/%version}-$x/changesets&lt;br /&gt;        liquibase --changeLogFile=update.xml rollback %version.$(expr $x - 1)&lt;br /&gt;&lt;br /&gt;        cd -;&lt;br /&gt;    done&lt;br /&gt;    rm /tmp/lquninst&lt;br /&gt;fi &lt;br /&gt;exit&lt;/pre&gt;&lt;br /&gt;Just as before, the uninstallation script determines what versions it needs to uninstall, then it loops through each one doing a "rollback" through liquibase.&lt;br /&gt;&lt;br /&gt;Part 4 will be packaging KC which is really interesting compared to KFS because it uses maven and the concept of xml configuration by environment instead of property-based configuration.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6107996498457762055?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6107996498457762055/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/04/kis-me-kate-rpm-packaging-kfs-part-3.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6107996498457762055'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6107996498457762055'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/04/kis-me-kate-rpm-packaging-kfs-part-3.html' title='KIS Me Kate - RPM Packaging KFS Part 3'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1928743306194852891</id><published>2011-04-01T23:41:00.000-07:00</published><updated>2011-04-02T09:01:02.603-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='antipattern'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Constants Done with Spring</title><content type='html'>&lt;h3&gt;Kuali's Antipattern&lt;/h3&gt;The &lt;a href="http://rice.kuali.org"&gt;Rice&lt;/a&gt; pattern for handling constants in &lt;a href="http://www.kuali.org"&gt;Kuali&lt;/a&gt; software is really an antipattern. It is basically an interface with &lt;b&gt;public static final&lt;/b&gt;'s. They chose an interface because it cannot be instantiated. This is an antipattern because it's not considered OO. An interface/class is created without the prospect of ever being instantiated or used for polymorphism. It's also inconsistent because as an interface is used at first, inner classes are used. Here's an example:&lt;pre class="brush: java"&gt;public class KFSConstants extends JSTLConstants implements ParameterKeyConstants {&lt;br /&gt;    private static final long serialVersionUID = 2882277719647128949L;&lt;br /&gt;&lt;br /&gt;    public static final String APPLICATION_NAMESPACE_CODE = "KFS";    &lt;br /&gt;    &lt;br /&gt;    public static class ParameterNamespaces {&lt;br /&gt;        public static final String KFS = "KFS-SYS";&lt;br /&gt;        public static final String CHART = "KFS-COA";&lt;br /&gt;        public static final String FINANCIAL = "KFS-FP";&lt;br /&gt;        public static final String GL = "KFS-GL";&lt;br /&gt;    ...&lt;br /&gt;    ...&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now look at &lt;pre class="brush: java"&gt;public interface DisbursementVoucherConstants extends ParameterKeyConstants {&lt;br /&gt;&lt;br /&gt;    // Text limits&lt;br /&gt;    public static final int MAX_NOTE_LINE_SIZE = 90;&lt;br /&gt;    &lt;br /&gt;    // payment methods&lt;br /&gt;    public static String PAYMENT_METHOD_CHECK = "P";&lt;br /&gt;    public static String PAYMENT_METHOD_WIRE = "W";&lt;br /&gt;    public static String PAYMENT_METHOD_DRAFT = "F";&lt;br /&gt;&lt;br /&gt;    // payee types&lt;br /&gt;    public static final String DV_PAYEE_TYPE_EMPLOYEE = "E";&lt;br /&gt;    public static final String DV_PAYEE_TYPE_VENDOR = "V";&lt;br /&gt;    public static final String DV_PAYEE_TYPE_CUSTOMER = "C";&lt;br /&gt;    public static final String DV_PAYEE_TYPE_SUBJECT_PAYMENT_VENDOR = "VSP";&lt;br /&gt;    public static final String DV_PAYEE_TYPE_REVOLVING_FUND_VENDOR = "VRF";&lt;br /&gt;    &lt;br /&gt;    public static final List&lt;String&gt; VENDOR_PAYEE_TYPE_CODES = Arrays.asList(DV_PAYEE_TYPE_VENDOR, DV_PAYEE_TYPE_SUBJECT_PAYMENT_VENDOR, DV_PAYEE_TYPE_REVOLVING_FUND_VENDOR);&lt;br /&gt;&lt;br /&gt;    // document location&lt;br /&gt;    public static final String NO_DOCUMENTATION_LOCATION = "N";&lt;br /&gt;&lt;br /&gt;    public static final String TAX_CONTROL_CODE_ALLOWS_EMPLOYEES = "A";&lt;br /&gt;    public static final String TAX_CONTROL_CODE_BEGIN_WITHHOLDING = "B";&lt;br /&gt;    public static final String TAX_CONTROL_CODE_HOLD_PAYMENT = "H";&lt;br /&gt;&lt;br /&gt;    public static class DocumentStatusCodes {&lt;br /&gt;        public static final String APPROVED = "A";&lt;br /&gt;        public static final String EXTRACTED = "E";&lt;br /&gt;    }&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;One is an class with another inner class. The other is an interface with an inner class. It's really inconsistent. Everyone does it differently.&lt;br /&gt;&lt;h3&gt;Use Spring for Constants&lt;/h3&gt;I propose getting constants from Spring. Here is an example I used in the KIM Ldap Integration:&lt;br /&gt;&lt;h4&gt;Create Constants Interface&lt;/h4&gt;&lt;pre class="brush:java"&gt;package org.kuali.rice.kim.util;&lt;br /&gt;&lt;br /&gt;import java.util.Collection;&lt;br /&gt;&lt;br /&gt;import org.kuali.rice.kim.bo.entity.dto.KimEntityInfo;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; *&lt;br /&gt; * @author Leo Przybylski (leo [at] rsmart.com)&lt;br /&gt; */ &lt;br /&gt;public interface Constants {    &lt;br /&gt;    Collection&lt;String&gt; getTestPrincipalNames();&lt;br /&gt;&lt;br /&gt;    String getDefaultChartCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityPrototype&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityPrototype&lt;br /&gt;     */&lt;br /&gt;    KimEntityInfo getEntityPrototype();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdTypeProperty&lt;br /&gt;     */&lt;br /&gt;    String getExternalIdTypeProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of taxExternalIdTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of taxExternalIdTypeCode&lt;br /&gt;     */&lt;br /&gt;    String getTaxExternalIdTypeCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdProperty&lt;br /&gt;     */&lt;br /&gt;    String getExternalIdProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeePhoneLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeePhoneLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getEmployeePhoneLdapProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeMailLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeMailLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getEmployeeMailLdapProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCountryCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCountryCode&lt;br /&gt;     */&lt;br /&gt;    String getDefaultCountryCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of personEntityTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of personEntityTypeCode&lt;br /&gt;     */&lt;br /&gt;    String getPersonEntityTypeCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of uaidLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of uaidLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getKimLdapIdProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of uidLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of uidLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getKimLdapNameProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of snLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of snLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getSnLdapProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of givenNameLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of givenNameLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getGivenNameLdapProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityIdKimProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityIdKimProperty&lt;br /&gt;     */&lt;br /&gt;    String getEntityIdKimProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterNamespaceCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterNamespaceCode&lt;br /&gt;     */&lt;br /&gt;    String getParameterNamespaceCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterDetailTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterDetailTypeCode&lt;br /&gt;     */&lt;br /&gt;    String getParameterDetailTypeCode();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedParameterName&lt;br /&gt;     */&lt;br /&gt;    String getMappedParameterName();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of unmappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of unmappedParameterName&lt;br /&gt;     */&lt;br /&gt;    String getUnmappedParameterName();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedValuesName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedValuesName&lt;br /&gt;     */&lt;br /&gt;    String getMappedValuesName();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeIdProperty&lt;br /&gt;     */&lt;br /&gt;    String getEmployeeIdProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of departmentLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of departmentLdapProperty&lt;br /&gt;     */&lt;br /&gt;    String getDepartmentLdapProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeTypeProperty&lt;br /&gt;     */&lt;br /&gt;    String getEmployeeTypeProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeTypeProperty Value to assign to this.employeeTypeProperty&lt;br /&gt;     */&lt;br /&gt;    void setEmployeeTypeProperty(String argEmployeeTypeProperty);&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeStatusProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeStatusProperty&lt;br /&gt;     */&lt;br /&gt;    String getEmployeeStatusProperty();&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeStatusProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeStatusProperty Value to assign to this.employeeStatusProperty&lt;br /&gt;     */&lt;br /&gt;    void setEmployeeStatusProperty(String argEmployeeStatusProperty);&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCampusCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCampusCode&lt;br /&gt;     */&lt;br /&gt;    String getDefaultCampusCode();&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of defaultCampusCode&lt;br /&gt;     *&lt;br /&gt;     * @param argDefaultCampusCode Value to assign to this.defaultCampusCode&lt;br /&gt;     */&lt;br /&gt;    void setDefaultCampusCode(String argDefaultCampusCode);&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of the employee affiliation code&lt;br /&gt;     * &lt;br /&gt;     * @return the value of employeeAffiliationCode&lt;br /&gt;     */&lt;br /&gt;    String getEmployeeAffiliationCodes();&lt;br /&gt;&lt;br /&gt;    /** &lt;br /&gt;     * Gets the mappings between LDAP and KIM affiliations&lt;br /&gt;     * @return mappings of the form "staff=STAFF,affiliate=AFLT"&lt;br /&gt;     */&lt;br /&gt;    String getAffiliationMappings();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice, there are only &lt;b&gt;get&lt;/b&gt; methods. You'll see easier in a second, but this is how we get Spring beans to be &lt;i&gt;read-only&lt;/i&gt; and constants. &lt;br /&gt;&lt;h4&gt;Create the Constants Implementation&lt;/h4&gt;It's just going to be a pojo. We can easily put this within the the original interface as an inner class since it's just spring that's going to use it.&lt;pre class="brush: java"&gt;/**&lt;br /&gt; * KIM Related Constants Implementation&lt;br /&gt; *&lt;br /&gt; * @author Leo Przybylski (leo [at] rsmart.com)&lt;br /&gt; */ &lt;br /&gt;static class ConstantsImpl implements Constants {    &lt;br /&gt;    private Collection&lt;String&gt; testPrincipalNames;&lt;br /&gt;    private KimEntityInfo entityPrototype;&lt;br /&gt;    private String externalIdTypeProperty;&lt;br /&gt;    private String taxExternalIdTypeCode;&lt;br /&gt;    private String externalIdProperty;&lt;br /&gt;    private String employeePhoneLdapProperty;&lt;br /&gt;    private String employeeMailLdapProperty;&lt;br /&gt;    private String defaultCountryCode;&lt;br /&gt;    private String personEntityTypeCode;&lt;br /&gt;    private String kimLdapIdProperty;&lt;br /&gt;    private String kimLdapNameProperty;&lt;br /&gt;    private String snLdapProperty;&lt;br /&gt;    private String givenNameLdapProperty;&lt;br /&gt;    private String entityIdKimProperty;&lt;br /&gt;    private String parameterNamespaceCode;&lt;br /&gt;    private String parameterDetailTypeCode;&lt;br /&gt;    private String mappedParameterName;&lt;br /&gt;    private String unmappedParameterName;&lt;br /&gt;    private String mappedValuesName;&lt;br /&gt;    private String employeeIdProperty;&lt;br /&gt;    private String departmentLdapProperty;&lt;br /&gt;    private String employeeTypeProperty;&lt;br /&gt;    private String employeeStatusProperty;&lt;br /&gt;    private String defaultCampusCode;&lt;br /&gt;    private String defaultChartCode;&lt;br /&gt;    private String employeeAffiliationCodes;&lt;br /&gt;    private String affiliationMappings;&lt;br /&gt;&lt;br /&gt;    public Collection&lt;String&gt; getTestPrincipalNames() {&lt;br /&gt;        return testPrincipalNames;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setTestPrincipalNames(Collection&lt;String&gt; testPrincipalNames) {&lt;br /&gt;        this.testPrincipalNames = testPrincipalNames;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityPrototype&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityPrototype&lt;br /&gt;     */&lt;br /&gt;    public KimEntityInfo getEntityPrototype() {&lt;br /&gt;        // return this.entityPrototype;&lt;br /&gt;        return (KimEntityInfo) getService("entityPrototype");&lt;br /&gt;        // return (KimEntityDefaultInfo) getService("entityPrototype");&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of entityPrototype&lt;br /&gt;     *&lt;br /&gt;     * @param argEntityPrototype Value to assign to this.entityPrototype&lt;br /&gt;     */&lt;br /&gt;    public void setEntityPrototype(KimEntityInfo argEntityPrototype) {&lt;br /&gt;        this.entityPrototype = argEntityPrototype;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public String getExternalIdTypeProperty() {&lt;br /&gt;        return this.externalIdTypeProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of externalIdTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argExternalIdTypeProperty Value to assign to this.externalIdTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public void setExternalIdTypeProperty(String argExternalIdTypeProperty) {&lt;br /&gt;        this.externalIdTypeProperty = argExternalIdTypeProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of taxExternalIdTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of taxExternalIdTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getTaxExternalIdTypeCode() {&lt;br /&gt;        return this.taxExternalIdTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of taxExternalIdTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @param argTaxExternalIdTypeCode Value to assign to this.taxExternalIdTypeCode&lt;br /&gt;     */&lt;br /&gt;    public void setTaxExternalIdTypeCode(String argTaxExternalIdTypeCode) {&lt;br /&gt;        this.taxExternalIdTypeCode = argTaxExternalIdTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of externalIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of externalIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getExternalIdProperty() {&lt;br /&gt;        return this.externalIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of externalIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argExternalIdProperty Value to assign to this.externalIdProperty&lt;br /&gt;     */&lt;br /&gt;    public void setExternalIdProperty(String argExternalIdProperty) {&lt;br /&gt;        this.externalIdProperty = argExternalIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeePhoneLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeePhoneLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeePhoneLdapProperty() {&lt;br /&gt;        return this.employeePhoneLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeePhoneLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeePhoneLdapProperty Value to assign to this.employeePhoneLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEmployeePhoneLdapProperty(String argEmployeePhoneLdapProperty) {&lt;br /&gt;        this.employeePhoneLdapProperty = argEmployeePhoneLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeMailLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeMailLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeMailLdapProperty() {&lt;br /&gt;        return this.employeeMailLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeMailLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeMailLdapProperty Value to assign to this.employeeMailLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEmployeeMailLdapProperty(String argEmployeeMailLdapProperty) {&lt;br /&gt;        this.employeeMailLdapProperty = argEmployeeMailLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCountryCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCountryCode&lt;br /&gt;     */&lt;br /&gt;    public String getDefaultCountryCode() {&lt;br /&gt;        return this.defaultCountryCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of defaultCountryCode&lt;br /&gt;     *&lt;br /&gt;     * @param argDefaultCountryCode Value to assign to this.defaultCountryCode&lt;br /&gt;     */&lt;br /&gt;    public void setDefaultCountryCode(String argDefaultCountryCode) {&lt;br /&gt;        this.defaultCountryCode = argDefaultCountryCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of personEntityTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of personEntityTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getPersonEntityTypeCode() {&lt;br /&gt;        return this.personEntityTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of personEntityTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @param argPersonEntityTypeCode Value to assign to this.personEntityTypeCode&lt;br /&gt;     */&lt;br /&gt;    public void setPersonEntityTypeCode(String argPersonEntityTypeCode) {&lt;br /&gt;        this.personEntityTypeCode = argPersonEntityTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of kimLdapIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @param kimLdapIdProperty value to set&lt;br /&gt;     */&lt;br /&gt;    public void setKimLdapIdProperty(String kimLdapIdProperty) {&lt;br /&gt;        this.kimLdapIdProperty = kimLdapIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of kimLdapIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of kimLdapIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getKimLdapIdProperty() {&lt;br /&gt;        return kimLdapIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of kimLdapNameProperty&lt;br /&gt;     *&lt;br /&gt;     * @param kimLdapNameProperty the value of kimLdapNameProperty&lt;br /&gt;     */&lt;br /&gt;    public void getKimLdapNameProperty(String kimLdapNameProperty) {&lt;br /&gt;        this.kimLdapNameProperty = kimLdapNameProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of kimLdapNameProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of kimLdapNameProperty&lt;br /&gt;     */&lt;br /&gt;    public String getKimLdapNameProperty() {&lt;br /&gt;        return kimLdapNameProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of snLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of snLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getSnLdapProperty() {&lt;br /&gt;        return this.snLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of snLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argSnLdapProperty Value to assign to this.snLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public void setSnLdapProperty(String argSnLdapProperty) {&lt;br /&gt;        this.snLdapProperty = argSnLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of givenNameLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of givenNameLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getGivenNameLdapProperty() {&lt;br /&gt;        return this.givenNameLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of givenNameLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argGivenNameLdapProperty Value to assign to this.givenNameLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public void setGivenNameLdapProperty(String argGivenNameLdapProperty) {&lt;br /&gt;        this.givenNameLdapProperty = argGivenNameLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of entityIdKimProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of entityIdKimProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEntityIdKimProperty() {&lt;br /&gt;        return this.entityIdKimProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of entityIdKimProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEntityIdKimProperty Value to assign to this.entityIdKimProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEntityIdKimProperty(String argEntityIdKimProperty) {&lt;br /&gt;        this.entityIdKimProperty = argEntityIdKimProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterNamespaceCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterNamespaceCode&lt;br /&gt;     */&lt;br /&gt;    public String getParameterNamespaceCode() {&lt;br /&gt;        return this.parameterNamespaceCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of parameterNamespaceCode&lt;br /&gt;     *&lt;br /&gt;     * @param argParameterNamespaceCode Value to assign to this.parameterNamespaceCode&lt;br /&gt;     */&lt;br /&gt;    public void setParameterNamespaceCode(String argParameterNamespaceCode) {&lt;br /&gt;        this.parameterNamespaceCode = argParameterNamespaceCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of parameterDetailTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of parameterDetailTypeCode&lt;br /&gt;     */&lt;br /&gt;    public String getParameterDetailTypeCode() {&lt;br /&gt;        return this.parameterDetailTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of parameterDetailTypeCode&lt;br /&gt;     *&lt;br /&gt;     * @param argParameterDetailTypeCode Value to assign to this.parameterDetailTypeCode&lt;br /&gt;     */&lt;br /&gt;    public void setParameterDetailTypeCode(String argParameterDetailTypeCode) {&lt;br /&gt;        this.parameterDetailTypeCode = argParameterDetailTypeCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public String getMappedParameterName() {&lt;br /&gt;        return this.mappedParameterName;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of mappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @param argMappedParameterName Value to assign to this.mappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public void setMappedParameterName(String argMappedParameterName) {&lt;br /&gt;        this.mappedParameterName = argMappedParameterName;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of unmappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of unmappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public String getUnmappedParameterName() {&lt;br /&gt;        return this.unmappedParameterName;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of unmappedParameterName&lt;br /&gt;     *&lt;br /&gt;     * @param argUnmappedParameterName Value to assign to this.unmappedParameterName&lt;br /&gt;     */&lt;br /&gt;    public void setUnmappedParameterName(String argUnmappedParameterName) {&lt;br /&gt;        this.unmappedParameterName = argUnmappedParameterName;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of mappedValuesName&lt;br /&gt;     *&lt;br /&gt;     * @return the value of mappedValuesName&lt;br /&gt;     */&lt;br /&gt;    public String getMappedValuesName() {&lt;br /&gt;        return this.mappedValuesName;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of mappedValuesName&lt;br /&gt;     *&lt;br /&gt;     * @param argMappedValuesName Value to assign to this.mappedValuesName&lt;br /&gt;     */&lt;br /&gt;    public void setMappedValuesName(String argMappedValuesName) {&lt;br /&gt;        this.mappedValuesName = argMappedValuesName;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeIdProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeIdProperty() {&lt;br /&gt;        return this.employeeIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeIdProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeIdProperty Value to assign to this.employeeIdProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEmployeeIdProperty(String argEmployeeIdProperty) {&lt;br /&gt;        this.employeeIdProperty = argEmployeeIdProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of departmentLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of departmentLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public String getDepartmentLdapProperty() {&lt;br /&gt;        return this.departmentLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of departmentLdapProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argDepartmentLdapProperty Value to assign to this.departmentLdapProperty&lt;br /&gt;     */&lt;br /&gt;    public void setDepartmentLdapProperty(String argDepartmentLdapProperty) {&lt;br /&gt;        this.departmentLdapProperty = argDepartmentLdapProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeTypeProperty() {&lt;br /&gt;        return this.employeeTypeProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeTypeProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeTypeProperty Value to assign to this.employeeTypeProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEmployeeTypeProperty(String argEmployeeTypeProperty) {&lt;br /&gt;        this.employeeTypeProperty = argEmployeeTypeProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of employeeStatusProperty&lt;br /&gt;     *&lt;br /&gt;     * @return the value of employeeStatusProperty&lt;br /&gt;     */&lt;br /&gt;    public String getEmployeeStatusProperty() {&lt;br /&gt;        return this.employeeStatusProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of employeeStatusProperty&lt;br /&gt;     *&lt;br /&gt;     * @param argEmployeeStatusProperty Value to assign to this.employeeStatusProperty&lt;br /&gt;     */&lt;br /&gt;    public void setEmployeeStatusProperty(String argEmployeeStatusProperty) {&lt;br /&gt;        this.employeeStatusProperty = argEmployeeStatusProperty;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Gets the value of defaultCampusCode&lt;br /&gt;     *&lt;br /&gt;     * @return the value of defaultCampusCode&lt;br /&gt;     */&lt;br /&gt;    public String getDefaultCampusCode() {&lt;br /&gt;        return this.defaultCampusCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Sets the value of defaultCampusCode&lt;br /&gt;     *&lt;br /&gt;     * @param argDefaultCampusCode Value to assign to this.defaultCampusCode&lt;br /&gt;     */&lt;br /&gt;    public void setDefaultCampusCode(String argDefaultCampusCode) {&lt;br /&gt;        this.defaultCampusCode = argDefaultCampusCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;    public void setDefaultChartCode(String chartCode) {&lt;br /&gt;        this.defaultChartCode = chartCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getDefaultChartCode() {&lt;br /&gt;        return defaultChartCode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getEmployeeAffiliationCodes() {&lt;br /&gt;        return employeeAffiliationCodes;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setEmployeeAffiliationCodes(String employeeAffiliationCodes) {&lt;br /&gt;        this.employeeAffiliationCodes = employeeAffiliationCodes;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getAffiliationMappings() {&lt;br /&gt;        return affiliationMappings;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public void setAffiliationMappings(String affiliationMappings) {&lt;br /&gt;        this.affiliationMappings = affiliationMappings;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;It is &lt;b&gt;static&lt;/b&gt;, so it can be instantiated, but it's also &lt;b&gt;package level&lt;/b&gt;. This is important. Now, normal classes cannot just instantiate a &lt;b&gt;Constants&lt;/b&gt; instance and override spring programmatically. Spring can still instantiate it and set it at startup though, so we are safe. Spring can instantiate and spring only. &lt;br /&gt;&lt;h4&gt;Define the Constants Bean in Spring&lt;/h4&gt;Now let us set our constants&lt;pre class="brush: xml"&gt;&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;br /&gt; &lt;!--&lt;br /&gt;  Copyright 2007 The Kuali Foundation. Licensed under the Educational&lt;br /&gt;  Community License, Version 1.0 (the "License"); you may not use this&lt;br /&gt;  file except in compliance with the License. You may obtain a copy of&lt;br /&gt;  the License at http://www.opensource.org/licenses/ecl1.php Unless&lt;br /&gt;  required by applicable law or agreed to in writing, software&lt;br /&gt;  distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or&lt;br /&gt;  implied. See the License for the specific language governing&lt;br /&gt;  permissions and limitations under the License.&lt;br /&gt; --&gt;&lt;br /&gt;&lt;beans xmlns="http://www.springframework.org/schema/beans"&lt;br /&gt; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"&lt;br /&gt; xsi:schemaLocation="http://www.springframework.org/schema/beans&lt;br /&gt;        http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;    &lt;!-- //////////////////////////////////////////////////////////////// --&gt;&lt;br /&gt; &lt;br /&gt;    &lt;bean id="kimConstants" class="org.kuali.rice.kim.util.ConstantsImpl"&gt;&lt;br /&gt;      &lt;property name="kimLdapIdProperty"         value="uaid" /&gt;&lt;br /&gt;      &lt;property name="kimLdapNameProperty"       value="uid" /&gt;&lt;br /&gt;      &lt;property name="snLdapProperty"            value="sn" /&gt;&lt;br /&gt;      &lt;property name="givenNameLdapProperty"     value="givenName" /&gt;&lt;br /&gt;      &lt;property name="entityIdKimProperty"       value="entityId" /&gt;&lt;br /&gt;      &lt;property name="employeeMailLdapProperty"  value="mail" /&gt;&lt;br /&gt;      &lt;property name="employeePhoneLdapProperty" value="employeePhone" /&gt;&lt;br /&gt;      &lt;property name="defaultCountryCode"        value="1" /&gt;&lt;br /&gt;      &lt;property name="mappedParameterName"       value="KIM_TO_LDAP_FIELD_MAPPINGS" /&gt;&lt;br /&gt;      &lt;property name="mappedValuesName"          value="KIM_TO_LDAP_VALUE_MAPPINGS" /&gt;&lt;br /&gt;      &lt;property name="parameterNamespaceCode"    value="KR-SYS" /&gt;&lt;br /&gt;      &lt;property name="parameterDetailTypeCode"   value="Config" /&gt;&lt;br /&gt;      &lt;property name="personEntityTypeCode"      value="PERSON" /&gt;&lt;br /&gt;      &lt;property name="employeeIdProperty"        value="emplId" /&gt;&lt;br /&gt;      &lt;property name="departmentLdapProperty"    value="employeePrimaryDept" /&gt;  &lt;br /&gt;      &lt;property name="employeeTypeProperty"      value="employeeType" /&gt;&lt;br /&gt;      &lt;property name="employeeStatusProperty"    value="employeeStatus" /&gt;&lt;br /&gt;      &lt;property name="defaultCampusCode"         value="MC" /&gt;&lt;br /&gt;      &lt;property name="defaultChartCode"          value="UA" /&gt;&lt;br /&gt;      &lt;property name="taxExternalIdTypeCode"     value="TAX" /&gt;&lt;br /&gt;      &lt;property name="externalIdProperty"        value="externalIdentifiers.externalId" /&gt;&lt;br /&gt;      &lt;property name="externalIdTypeProperty"    value="externalIdentifiers.externalIdentifierTypeCode" /&gt;&lt;br /&gt;      &lt;property name="affiliationMappings"       value="staff=STAFF,faculty=FCLTY,employee=STAFF,student=STDNT,affilate=AFLT"/&gt;&lt;br /&gt;      &lt;property name="employeeAffiliationCodes"  value="STAFF,FCLTY" /&gt;&lt;br /&gt;  &lt;/bean&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/beans&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Inject the Constants Bean into a Service&lt;/h4&gt;There, now the &lt;b&gt;Constants&lt;/b&gt; is instantiated and stored in Spring as a bean. We can now easily inject this into other classes that need it.&lt;pre class="brush: xml"&gt;    &lt;bean id="ldapPrincipalDao" class="org.kuali.rice.kim.dao.impl.LdapPrincipalDaoImpl"&gt;&lt;br /&gt;      &lt;property name="ldapTemplate"     ref="ldapTemplate" /&gt;&lt;br /&gt;      &lt;property name="parameterService" ref="parameterService" /&gt;&lt;br /&gt;      &lt;property name="kimConstants"     ref="kimConstants" /&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Now use it&lt;/h4&gt;Here is an example of the usage:&lt;pre class="brush: java"&gt;        String affiliationCode = getAffiliationTypeCodeForName(primaryAffiliation);&lt;br /&gt;        KimEntityAffiliationInfo aff1 = new KimEntityAffiliationInfo();&lt;br /&gt;        aff1.setAffiliationTypeCode(affiliationCode == null ? "AFLT" : affiliationCode);&lt;br /&gt;        aff1.setCampusCode(getConstants().getDefaultCampusCode());&lt;br /&gt;        aff1.setEntityAffiliationId("" + affiliationId++);&lt;br /&gt;        aff1.setDefault(true);&lt;br /&gt;        aff1.setActive(true);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Use Without Injection&lt;/h4&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;...&lt;br /&gt;        String affiliationCode = getAffiliationTypeCodeForName(primaryAffiliation);&lt;br /&gt;        KimEntityAffiliationInfo aff1 = new KimEntityAffiliationInfo();&lt;br /&gt;        aff1.setAffiliationTypeCode(affiliationCode == null ? "AFLT" : affiliationCode);&lt;br /&gt;        aff1.setCampusCode(getConstants().getDefaultCampusCode());&lt;br /&gt;        aff1.setEntityAffiliationId("" + affiliationId++);&lt;br /&gt;        aff1.setDefault(true);&lt;br /&gt;        aff1.setActive(true);&lt;br /&gt;...&lt;br /&gt;public Constants getConstants() {&lt;br /&gt;    return SpringContext.getService("kimConstants");&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;That's it! I found this very useful when needing to change constants or finding what the values of constants are. The &lt;b&gt;public static final&lt;/b&gt; boiler plate always makes it difficult to read, and the inner classes really make things a mess.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1928743306194852891?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1928743306194852891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/04/constants-done-with-spring.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1928743306194852891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1928743306194852891'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/04/constants-done-with-spring.html' title='Constants Done with Spring'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6145424463343965280</id><published>2011-03-24T12:59:00.001-07:00</published><updated>2011-04-02T09:37:05.982-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rpm'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='configuration management'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>KIS Me Kate - RPM Packaging KFS Part 2</title><content type='html'>Now that we know packaging is a pretty good idea, let's have a look at what needs to be done to actually create the package. I'm going to take a "city building" approach. I'll start small with a &lt;i&gt;no frills&lt;/i&gt; RPM distribution of KFS. I will slowly add more complexity until there is a fully robust package building system in place. &lt;br /&gt;&lt;h2&gt;Let's Get Started&lt;/h2&gt; What do we need for a bare-bones installation of KFS?&lt;ul&gt;&lt;li&gt;The source code (can't forget that).&lt;/li&gt;&lt;li&gt;Prebuilt local installation of KFS&lt;/li&gt;&lt;li&gt;RPM spec file&lt;/li&gt;&lt;li&gt;A repository to store packages in&lt;/li&gt;&lt;li&gt;rpm-build on the packaging machine&lt;/li&gt;&lt;li&gt;A machine setup for packaging&lt;/li&gt;&lt;li&gt;An environment ready to install to&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;1 Prebuilt local installation of KFS&lt;/h3&gt;Keep in mind that when you're packaging software, it is best to have the software built as much as possible. Compiling while installing is not the best thing. It requires more tools (development tools) on the machine you're installing on, it can complicate installation with further dependencies. Development dependencies are the worst to handle. You don't want those on your installation machine.&lt;br /&gt;&lt;br /&gt;You want to get this right. Let me show you what I did to create an installation ready for packaging. In the interest of time, I'm going to skip getting the source code and setting up your tools. I am going to assume that as a developer or configuration manager, you're already familiar with the tools and how to get the source code. Let's just pretend you just checked out the KFS source code from the Kuali Foundation as is.&lt;br /&gt;&lt;br /&gt;Once you checkout KFS, you should have something that looks like this:&lt;p class="code"&gt;build   cnv-build.properties reg-build.properties&lt;br /&gt;build-foundation.xml dev-build.properties test&lt;br /&gt;build.xml  ptd-build.properties work&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;1.1 Setup Pre-processing&lt;/h4&gt; Here we can add our pre-processing hooks. Create a directory called &lt;b&gt;vendor/&amp;lt;yourinstitution&amp;gt;/deployment&lt;/b&gt;. This is where all the processing code is going to go, so we do not disrupt the original KFS codebase. Within, I add a build.xm:&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;project          name="kfs" &lt;br /&gt;               default="dist-rpm" basedir="../../../"&lt;br /&gt;          xmlns:deploy="urn:com.rsmart.kuali"&lt;br /&gt;         xmlns:twitter="urn:com.rsmart.kuali.twitter"&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;property file="${tools.directory}/home/${ant.project.name}-build.properties" &lt;br /&gt;/&amp;gt;&lt;br /&gt;  &amp;lt;import file="${basedir}/build.xml"/&amp;gt;&lt;br /&gt;  &amp;lt;import file="${tools.directory}/macros-rpm.xml" /&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target      name="init-classpath" &lt;br /&gt;          description="Initializes classpath for normal execution. Classpath consists of the normal kuali classpath plus build dependencies."&lt;br /&gt;              depends="init-properties"&amp;gt;&lt;br /&gt;    &amp;lt;property  name="build.temp.directory" &lt;br /&gt;              value="${deploy.working.directory}/deploy-${build.environment}" /&amp;gt;&lt;br /&gt;    &amp;lt;property file="${basedir}/vendor/&amp;lt;yourinstitution&amp;gt;/deployment/rpm.properties" /&amp;gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="prepare-rpm" depends="init-classpath"&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.appserver.deploy.dir}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.external.config.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.appserver.localhost.dir}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.external.config.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.settings.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.security.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.logs.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.reports.directory}" /&amp;gt;&lt;br /&gt;    &amp;lt;mkdir dir="${rpm.external.work.directory}" /&amp;gt;&lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target        name="dist-war" &lt;br /&gt;          description="Kuali distribution plus post processing."&lt;br /&gt;              depends="init-classpath,dist"&amp;gt;&lt;br /&gt;    &amp;lt;fail unless="build.environment"&amp;gt;Need the build.environment to build&amp;lt;/fail&amp;gt;&lt;br /&gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="dist-rpm" depends="prepare-rpm,dist-war" /&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You probably noticed right away the following line:&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;project          name="kfs" &lt;br /&gt;               default="dist-rpm" basedir="../../../"&lt;br /&gt;          xmlns:deploy="urn:com.rsmart.kuali"&lt;br /&gt;         xmlns:twitter="urn:com.rsmart.kuali.twitter"&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;property file="${tools.directory}/home/${ant.project.name}-build.properties" /&amp;gt;&lt;br /&gt;  &amp;lt;import file="${basedir}/build.xml"/&amp;gt;&lt;br /&gt;  &amp;lt;import file="${tools.directory}/macros-rpm.xml" /&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The answer is, "Yes"! We're actually importing the original build.xml file. The reason for this is we want to execute the normal build and add hooks to it from within. This build.xml is executed via: &lt;p class="code"&gt;% ant -f vendor/&amp;lt;yourinstitution&amp;gt;/deployment/build.xml dist-war&lt;/p&gt;&lt;br /&gt;You can see from the &lt;b&gt;init-classpath&lt;/b&gt; ant target the above script uses the following rpm.properties file:&lt;pre class="code"&gt;&lt;br /&gt;rpm.appserver.deploy.dir=${rpm.build.root}${appserver.deploy.dir}/&lt;br /&gt;rpm.appserver.localhost.dir=${rpm.build.root}${appserver.localhost.dir}/&lt;br /&gt;rpm.external.config.directory=${rpm.build.root}${external.config.directory}/&lt;br /&gt;rpm.settings.directory=${rpm.build.root}${settings.directory}/&lt;br /&gt;rpm.security.directory=${rpm.build.root}${security.directory}/&lt;br /&gt;rpm.logs.directory=${rpm.build.root}${logs.directory}/&lt;br /&gt;rpm.reports.directory=${rpm.build.root}${reports.directory}/&lt;br /&gt;rpm.staging.directory=${rpm.build.root}${staging.directory}/&lt;br /&gt;rpm.external.work.directory=${rpm.build.root}${external.work.directory}/&lt;br /&gt;rpm.log4j.settings.file=${rpm.build.root}${log4j.settings.file}/&lt;br /&gt;rpm.security.property.file=${rpm.build.root}${security.property.file}/&lt;br /&gt;rpm.ingestion.directory=${rpm.external.work.directory}/staging/workflow/ingestion/&lt;br /&gt;rpm.ddl.directory=${rpm.build.root}${external.config.directory}/ddl/${build.version}/&lt;br /&gt;rpm.install.command.dev=&lt;br /&gt;rpm.install.command.tst=&lt;br /&gt;rpm.install.command.cnv=&lt;br /&gt;rpm.install.command.cfg=&lt;br /&gt;rpm.install.command.trn=&lt;br /&gt;rpm.install.command.dmo=touch work/db/changesets/configuration.sql; \&lt;br /&gt;touch work/db/changesets/conversion.sql;&lt;br /&gt;rpm.install.command.stg=&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1.2 Build Configuration&lt;/h3&gt;&lt;br /&gt;Notice that these are pretty much the original properties set in the &lt;b&gt;kfs-build.properties&lt;/b&gt; file. The new properties are:&lt;pre class="code"&gt;rpm.ingestion.directory=${rpm.external.work.directory}/staging/workflow/ingestion/&lt;br /&gt;rpm.ddl.directory=${rpm.build.root}${external.config.directory}/ddl/${build.version}/&lt;br /&gt;rpm.install.command.dev=&lt;br /&gt;rpm.install.command.tst=&lt;br /&gt;rpm.install.command.cnv=&lt;br /&gt;rpm.install.command.cfg=&lt;br /&gt;rpm.install.command.trn=&lt;br /&gt;rpm.install.command.dmo=touch work/db/changesets/configuration.sql; \&lt;br /&gt;touch work/db/changesets/conversion.sql;&lt;br /&gt;rpm.install.command.stg=&lt;/pre&gt;&lt;br /&gt;I will explain these later. This is how you configure what the filesystem layout of your package is going to look like. The above illustrated properties are all paths. Each one defines where files are going to go on the installed system. If you are unsure about how you want your system laid out, just use mine. I will provide a diagram of where everything ends up later.&lt;br /&gt;&lt;br /&gt;Now, let's have a look at the kfs-build.properties&lt;pre class="code"&gt;do.filter.project.help&lt;br /&gt;# TEMPORARILY CUSTOMIZE A GIVEN EXECUTION OF THE BUILD&lt;br /&gt;appserver.home=/Library/Tomcat/Home&lt;br /&gt;drivers.directory=${user.home}/kuali/drivers&lt;br /&gt;appserver.name=localhost:8080&lt;br /&gt;appserver.url=http://${appserver.name}/&lt;br /&gt;external.config.directory=${user.home}/kuali/main/dev/&lt;br /&gt;base.security.directory=${external.config.directory}/security&lt;br /&gt;base.settings.directory=${external.config.directory}/settings&lt;br /&gt;is.local.build=true&lt;br /&gt;rice.dev.mode=false&lt;br /&gt;# this property can be used to turn the batch schedule on and off&lt;br /&gt;use.quartz.scheduling=true&lt;br /&gt;periodic.thread.dump=false&lt;br /&gt;rice.standalone=false&lt;br /&gt;rice.kew.xml.pipeline.lifecycle.enabled=true&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see that I change the &lt;b&gt;appserver.home&lt;/b&gt; because I want it to point the RHEL (RedHat Enterprise Linux) default tomcat5 location.  I am going to run this build as the tomcat user on my packaging machien, so I also changed the &lt;b&gt;external.config.directory&lt;/b&gt; to be &lt;b&gt;/home/tomcat5/app&lt;/b&gt;. This will insure that on my installation machine, the &lt;b&gt;/home/tomcat/app&lt;/b&gt; will be the path consistently. I set &lt;b&gt;is.local.build&lt;/b&gt;, &lt;b&gt;rice.dev.mode&lt;/b&gt; as false , and &lt;b&gt;rice.kew.xml.pipeline.lifecycle.enabled&lt;/b&gt; to true because I want automatice XML ingestion on my development server. These properties in combination will enable automatic XML ingestion at the path &lt;b&gt;${external.config.directory}/settings/work/kfs/dev/staging/workflow/pending&lt;/b&gt;. Next, I set &lt;b&gt;periodic.thread.dump=false&lt;/b&gt; because I don't want the annoying log messages and &lt;b&gt;rice.standalone&lt;/b&gt; is set to &lt;b&gt;false&lt;/b&gt; as well. For simplicity, I am not going to complicate this with a Rice installation. We're going to run &lt;b&gt;bundled&lt;/b&gt; to keep it simple.&lt;br /&gt;&lt;br /&gt;That's it for communication. Now we will look at what happens during building and what the actual preprocessing is.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1.3 Build the WAR&lt;/h3&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;  &amp;lt;target        name="dist-war" &lt;br /&gt;          description="Kuali distribution plus post processing."&lt;br /&gt;              depends="init-classpath,dist"&amp;gt;&lt;br /&gt;    &amp;lt;fail unless="build.environment"&amp;gt;Need the build.environment to build&amp;lt;/fail&amp;gt;&lt;br /&gt;    &lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;target name="dist-rpm" depends="prepare-rpm,dist-war" /&amp;gt;&lt;/pre&gt;&lt;br /&gt;From the above, you can probably get a basic  understanding of what is happening. So far, there isn't much going on other than creating a war file. "We could have done that with the original KFS code", right? True. We've just setup for preprocessing. We'll do some actual preprocessing later. Now let's create the spec file.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;2 RPM Spec File&lt;/h2&gt;This is the part most people have been wondering about. How do we tie RPM in with building KFS? How does that work? Here we go. &lt;br /&gt;&lt;br /&gt;Before moving on, first check to make sure your packaging system has &lt;b&gt;rpm-build&lt;/b&gt; installed. Without this, the spec file is just a txt file. It's no more useful to building KFS than mindless scribbling on a bathroom stall.&lt;pre class="code"&gt;[tomcat@uaz-kf-a00 ~]$ rpm -qi rpm-build&lt;br /&gt;Name        : rpm-build                    Relocations: (not relocatable)&lt;br /&gt;Version     : 4.4.2.3                           Vendor: Red Hat, Inc.&lt;br /&gt;Release     : 18.el5                        Build Date: Thu 23 Jul 2009 10:58:22 PM MST&lt;br /&gt;Install Date: Mon 04 Jan 2010 08:57:28 AM MST      Build Host: hs20-bc1-7.build.redhat.com&lt;br /&gt;Group       : Development/Tools             Source RPM: rpm-4.4.2.3-18.el5.src.rpm&lt;br /&gt;Size        : 1582544                          License: GPLv2+&lt;br /&gt;Signature   : DSA/SHA1, Wed 29 Jul 2009 07:39:44 AM MST, Key ID 5326810137017186&lt;br /&gt;Packager    : Red Hat, Inc. &lt;http://bugzilla.redhat.com/bugzilla&gt;&lt;br /&gt;URL         : http://www.rpm.org/&lt;br /&gt;Summary     : Scripts and executable programs used to build packages.&lt;br /&gt;Description :&lt;br /&gt;The rpm-build package contains the scripts and executable programs&lt;br /&gt;that are used to build packages using the RPM Package Manager.&lt;br /&gt;[tomcat@uaz-kf-a00 ~]$ &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;2.1 Setup Package Home&lt;/h3&gt;Add this to the &lt;b&gt;.rpmmacros&lt;/b&gt; file &lt;pre class="code"&gt;%_topdir /home/tomcat/.workspace/redhat&lt;br /&gt;%_dbpath /home/tomcat/rpm&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above sets up the &lt;b&gt;tomcat&lt;/b&gt; user rpm environment so its database resides in &lt;b&gt;/home/tomcat/rpm&lt;/b&gt; and the rpm environment location to &lt;b&gt;/home/tomcat/.workspace/redhat&lt;/b&gt;. RHEL users will be familiar with the path for rpms&lt;pre class="code"&gt;/usr/src/redhat/&lt;br /&gt;/usr/src/redhat/BUILD&lt;br /&gt;/usr/src/redhat/RPMS&lt;br /&gt;/usr/src/redhat/SOURCES&lt;br /&gt;/usr/src/redhat/SPECS&lt;br /&gt;/usr/src/redhat/SRPMS&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2.2 Metadata&lt;/h3&gt;&lt;pre class="code"&gt;%define __os_install_post %{nil}&lt;br /&gt;&lt;br /&gt;Summary: Kuali Financial System&lt;br /&gt;Name: kfs&lt;br /&gt;Version: ${version}&lt;br /&gt;Release: ${release}&lt;br /&gt;Provides: kfs&lt;br /&gt;License: EPL&lt;br /&gt;BuildArch: noarch&lt;br /&gt;Requires: tomcat5&lt;br /&gt;BuildRoot: /tmp/kfs/&lt;br /&gt;Source0: kfs-${build.version}.tar.gz&lt;br /&gt;Group: Development/Tools&lt;br /&gt;Packager: leo [at] rsmart.com&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Given the above metadata, let's try to predict what the package will look like. The name is &lt;b&gt;kfs&lt;/b&gt;, so that means we can start with:&lt;pre class="code"&gt;kfs-&lt;/pre&gt;There's a version and release. The version is the official KFS version. In our case 4.0. The release is what I usually use to determine what my local release is by my institution's strategy. Let's just start with 1. That will make it look like: &lt;pre class="code"&gt;kfs-4.0-1&lt;/pre&gt;. Our buildarch is &lt;b&gt;noarch&lt;/b&gt;. Arch is for architecture. If there is a specific architecture (darwin, i386, i686, amd64, etc...), that gets declared here. KFS is platform independent and architecture free by virtue of Java; therefore, we choose &lt;b&gt;noarch&lt;/b&gt;. Now it is &lt;pre class="code"&gt;kfs-4.0-1.noarch.rpm&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Observe that &lt;b&gt;${version}&lt;/b&gt;, &lt;b&gt;${release}&lt;/b&gt;, and &lt;b&gt;${build.version}&lt;/b&gt; look like they're ant tokens. They are ant tokens. This file is intended to be filtered through ant. These values are passed in. That allows us to store this spec file in version control as a template, and repopulate it as needed. The tokens we are going to use are:&lt;pre class="code"&gt;version&lt;br /&gt;release&lt;br /&gt;build.version&lt;br /&gt;build.environment&lt;/pre&gt;&lt;br /&gt;Before getting into how the spec file is filtered, I want to show the rest of the spec file.&lt;pre class="code"&gt;%description&lt;br /&gt;Kuali Financial Sytem Environment based on rSmart Vendor build ${version}&lt;br /&gt;with release ${release}&lt;br /&gt;&lt;br /&gt;%prep&lt;br /&gt;%setup -q&lt;br /&gt;%build&lt;br /&gt;&lt;br /&gt;%install&lt;br /&gt;BUILDDIR=%{_builddir}/kfs-%{version}&lt;br /&gt;rm -rf %{buildroot}&lt;br /&gt;${rpm.install.command}&lt;br /&gt;ant -f vendor/kitt/deployment/build-rpm.xml -Dbasedir=$BUILDDIR -Drpm.build.root=%{buildroot} -Dbuild.environment=${build.environment} -Dbuild.version=${build.version} dist-rpm&lt;br /&gt;&lt;br /&gt;%files&lt;br /&gt;%defattr(2770,tomcat,tomcat)&lt;br /&gt;/usr/share/tomcat5/webapps/kfs/&lt;br /&gt;&lt;br /&gt;%def(-,tomcat,tomcat)&lt;br /&gt;/home/tomcat/app/work/kfs/staging/workflow/pending/&lt;br /&gt;&lt;br /&gt;%attr(2770,tomcat,tomcat)&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/WEB-INF/web.xml&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/WEB-INF/classes/configuration.properties&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/static/&lt;br /&gt;/home/tomcat/app/sa_forms/java/kfs/&lt;br /&gt;%config /home/tomcat/app/work/kfs/archive/&lt;br /&gt;%config /home/tomcat/app/work/kfs/attachments/&lt;br /&gt;%config /home/tomcat/app/work/kfs/reports/&lt;br /&gt;%config /home/tomcat/app/work/kfs/skel.zip&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/1099&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/ar&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/cm&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/cr&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/fp&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/gl&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/ld&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/pdp&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/purap&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/recon&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/sys&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/tax&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/WEB-INF&lt;br /&gt;%config /home/tomcat/app/work/kfs/temp/&lt;br /&gt;%config /home/tomcat/app/logs/kfs/WEB-INF/web.xml&lt;br /&gt;%config /home/tomcat/app/j2ee/kfs/log4j.properties&lt;br /&gt;&lt;br /&gt;%pre&lt;br /&gt;rm -rf /usr/share/tomcat5/webapps/kfs-*&lt;br /&gt;&lt;br /&gt;%post&lt;br /&gt;MYPWD=$PWD&lt;br /&gt;cd /usr/share/tomcat5/webapps&lt;br /&gt;mv kfs kfs-$(cat ~tomcat/kitt-tools/.envrc)&lt;br /&gt;cd $MYPWD&lt;br /&gt;&lt;br /&gt;setfacl -R -d -m g:kfs:rwx /home/tomcat/app/work/${build.environment}/kfs/ #modify directory defaults for new files &lt;br /&gt;setfacl -R -m g:kfs:rwx /home/tomcat/app/work/${build.environment}/kfs/ #modify existing files &amp; directories, x for directories &lt;br /&gt;&lt;br /&gt;%clean&lt;br /&gt;rm -rf %{buildroot}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I probably should have mentioned this earlier, but this is not an &lt;a href="http://www.rpm.org"&gt;RPM&lt;/a&gt; tutorial. I am not going to go over all of &lt;a href="http://www.rpm.org"&gt;RPM&lt;/a&gt; has to offer. There is just way too much to discuss. If you would like to study or know more about RPM, read the &lt;a href="http://www.rpm.org/max-rpm/"&gt;online book&lt;/a&gt; instead. I am simply going to review how it works for our purposes. Speaking of which! The spec file is describes two different phases. It is only used in one phase (packaging), but it describes both packaging and installation. That is, when the package is installed, it is still obeying instructions from the spec file. Keep that in mind or things can be pretty confusing. It's like working in two different universes that stare at each other.&lt;br /&gt;&lt;br /&gt;When the spec file is processed, the first thing it will try to do is build. That's what these directives are doing: &lt;pre class="code"&gt;%prep&lt;br /&gt;%setup -q&lt;br /&gt;%build&lt;br /&gt;&lt;br /&gt;%install&lt;br /&gt;BUILDDIR=%{_builddir}/kfs-%{version}&lt;br /&gt;rm -rf %{buildroot}&lt;br /&gt;${rpm.install.command}&lt;br /&gt;ant -f vendor/kitt/deployment/build.xml -Dbasedir=$BUILDDIR -Drpm.build.root=%{buildroot} -Dbuild.environment=${build.environment} -Dbuild.version=${build.version} dist-rpm&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The first few steps are merely uncompressing the source into the temporary build location. RPM likes to uncompress source files into &lt;b&gt;%_topdir/BUILD&lt;/b&gt; which is in our case &lt;b&gt;/home/tomcat/.workspace/redhat/BUILD&lt;/b&gt;. We should expect to find a &lt;b&gt;/home/tomcat/.workspace/redhat/BUILD/kfs-4.0&lt;/b&gt; directory after files have been uncompressed.&lt;br /&gt;&lt;br /&gt;Next, ant is called providing &lt;b&gt;basedir&lt;/b&gt;, &lt;b&gt;rpm.build.root&lt;/b&gt;, &lt;b&gt;build.environment&lt;/b&gt; and &lt;b&gt;build.version&lt;/b&gt;. The target is &lt;b&gt;dist-rpm&lt;/b&gt; which is what we looked at before. That means that by processing the spec file after the source code has been checked out, it will create the WAR file and necessary external configuration files. Thereby, it is essentially creating our application inside of &lt;b&gt;/home/tomcat/.workspace/redhat/BUILD/kfs-4.0&lt;/b&gt;. That's just what we need.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2.3 Spec File Re: Packaging&lt;/h3&gt;&lt;br /&gt;Now we need to tell RPM how the files are laid out and what the permissions are. RPM is very unforgiving when it comes to files not being where they are expected as well as finding unexpected files. Either case is considered a failure. Wouldn't want unexpected files getting into your package, right? Also wouldn't want to create a package without all the files you expect. It makes sense. This is how we do that:&lt;pre class="code"&gt;%files&lt;br /&gt;%defattr(2770,tomcat,tomcat)&lt;br /&gt;/usr/share/tomcat5/webapps/kfs/&lt;br /&gt;&lt;br /&gt;%attr(-,tomcat,tomcat)&lt;br /&gt;/home/tomcat/app/work/kfs/staging/workflow/pending/&lt;br /&gt;&lt;br /&gt;%attr(2770,tomcat,tomcat)&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/WEB-INF/web.xml&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/WEB-INF/classes/configuration.properties&lt;br /&gt;/usr/share/tomcat5/webapps/kfs-${build.environment}/static/&lt;br /&gt;/home/tomcat/app/sa_forms/java/kfs/&lt;br /&gt;%config /home/tomcat/app/work/kfs/archive/&lt;br /&gt;%config /home/tomcat/app/work/kfs/attachments/&lt;br /&gt;%config /home/tomcat/app/work/kfs/reports/&lt;br /&gt;%config /home/tomcat/app/work/kfs/skel.zip&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/1099&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/ar&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/cm&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/cr&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/fp&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/gl&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/ld&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/pdp&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/purap&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/recon&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/sys&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/tax&lt;br /&gt;%config /home/tomcat/app/work/kfs/staging/WEB-INF&lt;br /&gt;%config /home/tomcat/app/work/kfs/temp/&lt;br /&gt;%config /home/tomcat/app/logs/kfs/WEB-INF/web.xml&lt;br /&gt;%config /home/tomcat/app/j2ee/kfs/log4j.properties&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see it has both individual files and directories. In the case of directories, these are recursive. That means, that it assumes to include all files therein. The &lt;b&gt;%config&lt;/b&gt; directive denotes paths that are considered configuration paths. These paths are only created/updated on installation. They are not touched on upgrade. This is how we preserve any configuration customizations we have made. Once they have been installed, only system administrators may/can manually modify them.&lt;br /&gt;&lt;br /&gt;Permissions are set using &lt;pre class="code"&gt;%attr(2770,tomcat,tomcat)&lt;/pre&gt; which means the tomcat user and group have access with the mask 2770.&lt;br /&gt;&lt;br /&gt;But what is this &lt;b&gt;/usr/share/tomcat5/webapps/kfs/&lt;/b&gt;? That is the location of the webapp, but there is no environment designation. This is because when the application is built using the spec file, it is environment agnostic. It is built generally, so that environment specific information does not interfere. Only when the package is created and then installed does the environment actually matter. Not during building. During installation it will create a path &lt;b&gt;/usr/share/tomcat5/webapps/kfs/&lt;/b&gt;, but during post-processing, this will change to the appropriate environment. See below:&lt;pre class="code"&gt;%pre&lt;br /&gt;rm -rf /usr/share/tomcat5/webapps/kfs-*&lt;br /&gt;&lt;br /&gt;%post&lt;br /&gt;MYPWD=$PWD&lt;br /&gt;cd /usr/share/tomcat5/webapps&lt;br /&gt;mv kfs kfs-$(cat ~tomcat/.envrc)&lt;br /&gt;cd $MYPWD&lt;br /&gt;&lt;br /&gt;setfacl -R -d -m g:kfs:rwx /home/tomcat/app/work/${build.environment}/kfs/ #modify directory defaults for new files &lt;br /&gt;setfacl -R -m g:kfs:rwx /home/tomcat/app/work/${build.environment}/kfs/ #modify existing files &amp; directories, x for directories &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;During pre-processing of the package, cleanup is done to make sure any files that are intended to be overwritten are removed first. Then in post processing the path &lt;b&gt;kfs&lt;/b&gt; is moved to the environment of the machine.&lt;pre class="code"&gt;$(cat ~tomcat/.envrc)&lt;/pre&gt; is a file that is part of environment preparation that I will discuss prior to installation of the package. It is fair to assume though, that this contains the environment name of application.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2.4 Processing the Spec File&lt;/h3&gt;I explained earlier that the spec file we have been looking at so far is actually just a template (let's call it &lt;b&gt;kfs.spec.template&lt;/b&gt;) that is filtered through ant to create the real spec file. Here's how I handle it. Earlier, I showed from the &lt;b&gt;build.xml&lt;/b&gt;:&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;project          name="kfs" &lt;br /&gt;               default="dist-rpm" basedir="../../../"&lt;br /&gt;          xmlns:deploy="urn:com.rsmart.kuali"&lt;br /&gt;         xmlns:twitter="urn:com.rsmart.kuali.twitter"&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;property file="${tools.directory}/home/${ant.project.name}-build.properties" &lt;br /&gt;/&amp;gt;&lt;br /&gt;  &amp;lt;import file="${basedir}/build.xml"/&amp;gt;&lt;br /&gt;  &amp;lt;import file="${tools.directory}/macros-rpm.xml" /&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;/pre&gt;&lt;br /&gt;You can see that there is an import of &lt;b&gt;macros-rpm.xml&lt;/b&gt;. Within this macros file, I define:&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;project  xmlns:deploy="urn:com.rsmart.kuali"&amp;gt;&lt;br /&gt;  &amp;lt;macrodef uri="urn:com.rsmart.kuali" name="filter"&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="property" /&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="srcfile" /&amp;gt;&lt;br /&gt;    &amp;lt;attribute name="filename" /&amp;gt;&lt;br /&gt;    &amp;lt;sequential&amp;gt;&lt;br /&gt;      &amp;lt;loadfile property="@{property}"&lt;br /&gt;                srcfile="@{srcfile}"&amp;gt;&lt;br /&gt;        &amp;lt;filterchain&amp;gt;&lt;br /&gt;          &amp;lt;expandproperties/&amp;gt;&lt;br /&gt;        &amp;lt;/filterchain&amp;gt;&lt;br /&gt;      &amp;lt;/loadfile&amp;gt;&lt;br /&gt;&lt;br /&gt;      &amp;lt;echo file="@{filename}"&amp;gt;${@{property}}&amp;lt;/echo&amp;gt;&lt;br /&gt;    &amp;lt;/sequential&amp;gt;&lt;br /&gt;  &amp;lt;/macrodef&amp;gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The filter task processes a file with the currently loaded ant properties. This is where the the aforementioned properties will be populated in the spec file. In order to use this task, I add the following to the &lt;b&gt;build.xml&lt;/b&gt;&lt;pre class="brush: xml"&gt;  &amp;lt;target name="package" depends="init-classpath"&amp;gt;&lt;br /&gt;    &amp;lt;!-- create spec file --&amp;gt;&lt;br /&gt;    &amp;lt;deploy:filter   property="kfs.spec.template" &lt;br /&gt;                      srcfile="kfs.spec.template"&lt;br /&gt;                     filename="kfs.spec" /&amp;gt;&lt;br /&gt;&lt;br /&gt;    &amp;lt;exec executable="rpmbuild"&amp;gt;&lt;br /&gt;      &amp;lt;arg value="-bb" /&amp;gt;&lt;br /&gt;      &amp;lt;arg value="kfs.spec" /&amp;gt;&lt;br /&gt;    &amp;lt;/exec&amp;gt;&lt;br /&gt;  &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Two things are happening here. The first thing that happens is that the spec file is filtered and output to the current working directory. Then, &lt;b&gt;rpmbuild&lt;/b&gt; is run against it producing the rpm file in &lt;b&gt;/home/tomcat/.workspace/redhat/RPMS&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2.5 Execution&lt;/h3&gt;We run this simply by&lt;pre class="code"&gt;ant -Dversion=4.0 -Drelease=1 -Dbuild.environment=dev package&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;3 Installation&lt;/h2&gt;Now that we have a package, let's install it. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3.1 Setup the Environment First&lt;/h3&gt;Before we actually do an installation, there are a couple things that need to be setup on the environment. &lt;br /&gt;&lt;h4&gt;3.1.1 .envrc&lt;/h4&gt;First, if you recall, the &lt;b&gt;.envrc&lt;/b&gt; file contains the environment designation. We just need to create one for DEV &lt;pre class="code"&gt;% echo dev &gt; $HOME/.envrc&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;3.1.2 RPM Database&lt;/h4&gt;RPM normally requires root access to run because it requires root to be able to read/write the RPM database located at &lt;b&gt;/var/lib/rpm&lt;/b&gt;. What we're going to do is copy the database to retain all the information in it to &lt;b&gt;/home/tomcat/app/rpm&lt;/b&gt;. &lt;pre class="code"&gt;% mkdir $HOME/app&lt;br /&gt;% cp -rf /var/lib/rpm $HOME/app/&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;3.1.3 Setup .rpmmacros&lt;/h4&gt;Now that the database has been copied. RPM needs to be told which database to use. We do this by adding the following line to the &lt;b&gt;.rpmmacros&lt;/b&gt; file &lt;pre class="code"&gt;%_dbpath /home/tomcat/app/rpm&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;3.2 Installation&lt;/h3&gt;To finally install the new RPM package I use &lt;pre class="code"&gt;% rpm -Uvh --force $HOME/.workspace/redhat/RPMS/kfs-4.0-1.noarch.rpm&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That is part 2. Part 3 will add workflow, automatic database upgrades/downgrades, and multi-packages with dependency management.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6145424463343965280?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6145424463343965280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6145424463343965280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6145424463343965280'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/03/kis-me-kate-rpm-packaging-kfs-part-2.html' title='KIS Me Kate - RPM Packaging KFS Part 2'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3125986968935621523</id><published>2011-02-07T06:37:00.001-08:00</published><updated>2011-02-07T06:51:11.154-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blog'/><title type='text'>Please Leave Comments</title><content type='html'>&lt;p&gt;Again, more stuff related to the blog.&lt;/p&gt;&lt;br /&gt;&lt;h2&gt;Why Should I?&lt;/h2&gt;&lt;p&gt;Well, I couldn't understand why I never get comments. I thought to myself, why would people not want to comment? I checked out my settings and found that you must be a registered user. I thought to myself, "Why would something like this stop someone from commenting?" Answer: "Maybe people want to remain anonymous on my blog." Solution: I've allowed anonymous users to post. Now please, let me know what you think of the screencasts. I've been experimenting with &lt;a href="http://www.xtranormal.com"&gt;xtranormal&lt;/a&gt;, so I want to know what people think of it and if it's worth continuing to pursue.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3125986968935621523?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3125986968935621523/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/02/please-leave-comments.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3125986968935621523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3125986968935621523'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/02/please-leave-comments.html' title='Please Leave Comments'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-3969761597148862684</id><published>2011-02-07T06:29:00.000-08:00</published><updated>2011-02-07T06:37:10.786-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blog'/><title type='text'>Screencasts Coming Out Slowly</title><content type='html'>&lt;h2&gt;What's Goin' On?&lt;/h2&gt;&lt;p&gt;I know this isn't a kuali-related post, but it is related to the blog. I just started using Gnome 3 (with Gnome Shell) and KdenLive!!! This pair is incredible. Actually, KdenLive alone is incredible. It's available for macusers through macports, but it's hands down the best non-linear editor I've ever seen. This is the combintation I used for  my most recent screencasts; however, I do not think I will be able to continue the trend. I just switched to Mac because recent events with my linux system started to really hamper my development. I started to cry out for help and found a welcome tone with OS X. I don't want this to become a Linux v. OS X blog post, but I will say this: I finally got around some of the bugs I'd experienced in OS X that the team refuses to fix and millions continue to complain about. 2 things have always been my limiting factor for switching to OS X. No thanks to the team, I found my own solutions and am now comfortable with my decision. &lt;/p&gt;&lt;br /&gt;&lt;h2&gt;What's next?&lt;/h2&gt;&lt;p&gt;What am I doing then? Well, I downloaded a new application that seems to give me what I've been looking for with both KdenLive and Gnome-Shell. That's Voila. Wish me luck and feel free to comment on any of my work to let me know how the switch is turning out.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-3969761597148862684?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/3969761597148862684/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/02/screencasts-coming-out-slowly.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3969761597148862684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/3969761597148862684'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/02/screencasts-coming-out-slowly.html' title='Screencasts Coming Out Slowly'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1094330864107915262</id><published>2011-01-31T12:12:00.000-08:00</published><updated>2011-03-23T12:16:05.791-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='ldap'/><title type='text'>The KIS Identity - LDAP Development Setup</title><content type='html'>&lt;h3&gt;Abstract&lt;/h3&gt;Before you can use the LDAP/Entity integration into your Kuali Rice-based application, you have to first build it into Kuali Rice. To do that, you first need to setup a development environment for building and packaging rice.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Video Instructions&lt;/h3&gt;I've put together a video for instruction how to not just setup your rice development environment, but also modularize the LDAP Integration source code distribution with Kuali Rice to be packaged and distributed together. &lt;br /&gt;&lt;br /&gt;If you're an institution developing custom rice applications or just running rice-based kuali applications, you will most likely have your own rice release to add customizations too. LDAP integration in its current incarnation is pretty much that. It's like a third-party module/customization that is added to existing rice source.&lt;br /&gt;&lt;br /&gt;This screencast is also using the Eclipse IDE. Again, if you have another IDE&lt;br /&gt;or some other method. Please substitute whatever you would normally do.&lt;br /&gt;&lt;br /&gt;Here it is. Enjoy:&lt;br /&gt;&lt;!--&lt;br /&gt;&lt;object &gt;&lt;param name="movie" value="http://www.youtube-nocookie.com/v/ILD2EHJvjPg?fs=1&amp;amp;hl=en_US&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube-nocookie.com/v/ILD2EHJvjPg?fs=1&amp;amp;hl=en_US&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;--&gt;&lt;br /&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/7HdYJhvRzas?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Steps&lt;/h3&gt;These are written instructions for those that found the video to be difficult to follow.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;First, let's checkout the rice source code. Here, I am going to checkout the rice 1.0.3 branch from rsmart. Our trunk currently has an ldap integration. I created this branch specifically for this screencast. You should use whatever URL you have at your institution&lt;/li&gt;&lt;li&gt;Now that it's checked out, I am simply going to copy it to my Eclipse project. I&lt;br /&gt;then refresh my project.&lt;/li&gt;&lt;li&gt;The next thing we want to do is to add our ldap integration as a module or subproject within the rice project. What we need for this is to first create a path for it.&lt;/li&gt;&lt;li&gt;Now that the source code is checked out, we want to tell Eclipse that this is a Maven project. Go to your project context menu. Then, select the Maven submenu. Finally, Enable Dependencies Management.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;All that needs to be done is to update the rice pom.xml and the rice-web pom.xml. I now open the rice pom.xml. I add to the modules section that ldap is a module.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&lt;!--&lt;br /&gt; Copyright 2007-2010 The Kuali Foundation&lt;br /&gt; &lt;br /&gt; Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt; &lt;br /&gt; http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; &lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;--&gt;&lt;br /&gt;&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/&lt;br /&gt;2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;&lt;br /&gt; &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;&lt;br /&gt; &lt;groupId&gt;org.kuali.rice&lt;/groupId&gt;&lt;br /&gt; &lt;artifactId&gt;rice&lt;/artifactId&gt;&lt;br /&gt; &lt;packaging&gt;pom&lt;/packaging&gt;&lt;br /&gt; &lt;name&gt;Kuali Rice&lt;/name&gt;&lt;br /&gt; &lt;version&gt;1.0.3&lt;/version&gt;&lt;br /&gt; &lt;description&gt;The Kuali Rice effort provides an enterprise class&lt;br /&gt;  middleware suite of integrated products that allows both Kuali and&lt;br /&gt;  non-Kuali applications to be built in an agile fashion, such that&lt;br /&gt; &lt;/build&gt;&lt;br /&gt;&lt;br /&gt; &lt;modules&gt;&lt;br /&gt;  &lt;module&gt;api&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;impl&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;ldap&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;web&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;sampleapp&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;ksb&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;kcb&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;kns&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;kim&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;kew&lt;/module&gt;&lt;br /&gt;  &lt;module&gt;ken&lt;/module&gt;&lt;br /&gt; &lt;/modules&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/project&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Next, open the rice-web pom.xml. In here we set rice-ldap as a dependency. I will just copy the rice-impl dependency and change impl to ldap.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&lt;!--&lt;br /&gt; Copyright 2007-2010 The Kuali Foundation&lt;br /&gt; &lt;br /&gt; Licensed under the Educational Community License, Version 2.0 (the "License");&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt; &lt;br /&gt; http://www.opensource.org/licenses/ecl2.php&lt;br /&gt; &lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an "AS IS" BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;--&gt;&lt;br /&gt;&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/&lt;br /&gt;2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 h&lt;br /&gt;ttp://maven.apache.org/maven-v4_0_0.xsd"&gt;&lt;br /&gt; &lt;name&gt;Kuali Rice Web&lt;/name&gt;&lt;br /&gt; &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;&lt;br /&gt; &lt;parent&gt;&lt;br /&gt;  &lt;groupId&gt;org.kuali.rice&lt;/groupId&gt;&lt;br /&gt;  &lt;artifactId&gt;rice&lt;/artifactId&gt;&lt;br /&gt;  &lt;version&gt;1.0.3&lt;/version&gt;&lt;br /&gt; &lt;/parent&gt;&lt;br /&gt; &lt;artifactId&gt;rice-web&lt;/artifactId&gt;&lt;br /&gt; &lt;packaging&gt;war&lt;/packaging&gt;&lt;br /&gt;&lt;br /&gt; &lt;dependencies&gt;&lt;br /&gt;  &lt;dependency&gt;&lt;br /&gt;   &lt;groupId&gt;${project.groupId}&lt;/groupId&gt;&lt;br /&gt;   &lt;artifactId&gt;rice-impl&lt;/artifactId&gt;&lt;br /&gt;   &lt;version&gt;${project.version}&lt;/version&gt;&lt;br /&gt;  &lt;/dependency&gt;&lt;br /&gt;  &lt;dependency&gt;&lt;br /&gt;   &lt;groupId&gt;${project.groupId}&lt;/groupId&gt;&lt;br /&gt;   &lt;artifactId&gt;rice-ldap&lt;/artifactId&gt;&lt;br /&gt;   &lt;version&gt;${project.version}&lt;/version&gt;&lt;br /&gt;  &lt;/dependency&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;   &lt;/dependencies&gt;&lt;br /&gt;&lt;/project&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;There, it's that easy. Now, when rice is packaged, the rice-ldap jar will be created with it. Let's do that.&lt;br /&gt;&lt;p class="code"&gt;&lt;br /&gt;leo@leviathan~/.workspace/rice-1.0.3&lt;br /&gt;(02:15:03) [13] mvn -Dmaven.test.skip=true package&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;leo@leviathan~/.workspace/rice-1.0.3&lt;br /&gt;(02:20:06) [14] ls ldap/target/&lt;br /&gt;classes/  maven-archiver/  rice-ldap-1.0.3.jar&lt;br /&gt;leo@leviathan~/.workspace/rice-1.0.3&lt;br /&gt;(02:20:11) [15]&lt;br /&gt;&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1094330864107915262?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1094330864107915262/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-identity-ldap-development-setup.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1094330864107915262'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1094330864107915262'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-identity-ldap-development-setup.html' title='The KIS Identity - LDAP Development Setup'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-2949500549953990573</id><published>2011-01-20T16:13:00.000-08:00</published><updated>2011-01-31T16:40:03.205-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='rice'/><category scheme='http://www.blogger.com/atom/ns#' term='ldap'/><title type='text'>LDAP Development + Xtranormal</title><content type='html'>Video that I overlaid with an xtranormal as an experiment in instruction.&lt;br /&gt;&lt;br /&gt;&lt;embed width="640" height="385" allowfullscreen="false" quality="high" name="movie_player" id="movie_player" style="" src="http://www.youtube.com/v/7HdYJhvRzas?hd=1&amp;showinfo=0&amp;amp;enablejsapi=1&amp;amp;et=OEgsToPDskJni4UfFH3f0WbK6L_15ohf&amp;amp;hl=en_US&amp;amp;fs=1" type="application/x-shockwave-flash"&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-2949500549953990573?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/2949500549953990573/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2011/01/ldap-development-xtranormal.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2949500549953990573'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/2949500549953990573'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2011/01/ldap-development-xtranormal.html' title='LDAP Development + Xtranormal'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1146804094519491203</id><published>2010-06-05T07:53:00.000-07:00</published><updated>2010-06-05T08:44:42.476-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='hudson'/><title type='text'>Liquibase Testing with SVN Keywords</title><content type='html'>&lt;h3&gt;Abstract&lt;/h3&gt;In &lt;a href="http://kualigan.blogspot.com/2010/04/kis-of-dragon-testing-liquibase_27.html"&gt;KIS of the Dragon&lt;/a&gt;, I documented using the &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; rollback functionality for testing. Since then, I have found that &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; has a small bug with rollbacks. Some of you may have encountered it. It uses regex substitution when rolling back (not sure what exactly). Part of defining the group in regex substitution is the '$', so that makes '$' kind of a reserved character that isn't very well documented in liquibase. It doesn't effect SQL at all, but it does mean using '$' in changeSet attributes is forbidden. Just to give an example of what will happen, when you rollback in this scenario, you get an exception:&lt;br /&gt;&lt;p class="code"&gt;Jun 5, 2010 8:07:24 AM liquibase.commandline.Main main&lt;br /&gt;SEVERE: Illegal group reference&lt;br /&gt;java.lang.IllegalArgumentException: Illegal group reference&lt;br /&gt; at java.util.regex.Matcher.appendReplacement(Matcher.java:725)&lt;br /&gt; at java.util.regex.Matcher.replaceFirst(Matcher.java:872)&lt;br /&gt; at java.lang.String.replaceFirst(String.java:2158)&lt;br /&gt; at liquibase.database.AbstractDatabase.removeRanStatus(AbstractDatabase.java:1328)&lt;br /&gt; at liquibase.parser.visitor.RollbackVisitor.visit(RollbackVisitor.java:23)&lt;br /&gt; at liquibase.parser.ChangeLogIterator.run(ChangeLogIterator.java:41)&lt;br /&gt; at liquibase.Liquibase.rollback(Liquibase.java:273)&lt;br /&gt; at liquibase.Liquibase.rollback(Liquibase.java:248)&lt;br /&gt; at liquibase.commandline.Main.doMigration(Main.java:682)&lt;br /&gt; at liquibase.commandline.Main.main(Main.java:97)&lt;/p&gt;&lt;br /&gt;Below, we have the offending XML responsible for this.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;databaseChangeLog&lt;br /&gt;  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9&lt;br /&gt;         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gh;&lt;br /&gt; &lt;br /&gt;  &amp;lt;changeSet id="$Revision$" author="$Author$"&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Problem&lt;/h3&gt;You probably thought the above was the problem. No no no no no. That was just background on it. Such a thing is actually easily fixed. The real problem is testing in your IDE. The reason we have '$' is for the &lt;a href="http://subversion.tigris.org"&gt;Subversion&lt;/a&gt; keywords. We want these in our changelogs so we can rollback in our other environments. &lt;a href="http://subversion.tigris.org"&gt;Subversion&lt;/a&gt; doesn't actually add this information until after the &lt;i&gt;svn:keywords&lt;/i&gt; property and the file are committed to the VCS. When it does, you get something that looks like this:&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;databaseChangeLog&lt;br /&gt;  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9&lt;br /&gt;         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gh;&lt;br /&gt; &lt;br /&gt;  &amp;lt;changeSet id="$Revision: 1$" author="$Author: przybyls$"&amp;gt;&lt;/pre&gt;&lt;br /&gt;There are still '$' in there.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;What does this have to do with the IDE?&lt;/h4&gt;At UA, we use a &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; template, so all developers are consistent with their usage. It looks something like:&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;br /&gt; &lt;br /&gt;&amp;lt;databaseChangeLog&lt;br /&gt;  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9&lt;br /&gt;         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt; &lt;br /&gt;  &amp;lt;changeSet id="$Revision$" author="$Author$"&amp;gt;&lt;br /&gt;    &amp;lt;sql splitStatements="false" endDelimiter=""&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;    DECLARE&lt;br /&gt;    BEGIN&lt;br /&gt;    END;&lt;br /&gt;]]&amp;gt;&lt;br /&gt;    &amp;lt;/sql&amp;gt;&lt;br /&gt;    &amp;lt;rollback&amp;gt;&lt;br /&gt;      &amp;lt;sql&gt;&amp;lt;![CDATA[&lt;br /&gt;      DECLARE&lt;br /&gt;      BEGIN&lt;br /&gt;      END;&lt;br /&gt;]]&amp;gt;&lt;br /&gt;      &amp;lt;/sql&amp;gt;&lt;br /&gt;    &amp;lt;/rollback&amp;gt;&lt;br /&gt;  &amp;lt;/changeSet&amp;gt;&lt;br /&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/pre&gt;&lt;br /&gt;The first thing you should notice is the '$' in the id and author attributes. This actually makes the problem part of our IDE. Now developers have to remember to change this. That makes this template pretty much...useless.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Solution (it's not as cool as you think)&lt;/h3&gt;At UA, we decided to take &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; testing out of our IDE and instead move it into continuous integration with Ad-Hoc builds. In the end, developers still use the template. Only now, they don't need to install &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; at all! It's one of those, "Why didn't I think of this before!?" moments.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Instructions for &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt;&lt;/h3&gt;I hope you're using &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt;. If you're not, you have my sympathies. I am truly sorry that you will have to find your own way of doing this.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Step 1: Create an ad-hoc build&lt;/h4&gt;To do this, you simply setup a build parameter in your project/build configuration. When you do this, &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt; will want to know about the parameter you are adding. I used a file. This way developers simply upload their file to &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt;. I called mine &lt;i&gt;test.xml&lt;/i&gt; and you will see that come up in a second.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Step 2: Setup the Execution Script&lt;/h4&gt;I just used shell with the following contents:&lt;br /&gt;&lt;p class="code"&gt;&lt;br /&gt;/home/tomcat/liquibase/liquibase --defaultsFile=/home/tomcat/renwoluk.properties tag before&lt;br /&gt;sed -e 's/\$Revision\$/test/g' test.xml &gt; /tmp/test.xml;mv /tmp/test.xml test.xml&lt;br /&gt;sed -e 's/\$Author\$/test/g' test.xml &gt; /tmp/test.xml;mv /tmp/test.xml test.xml&lt;br /&gt;/home/tomcat/liquibase/liquibase --defaultsFile=/home/tomcat/renwoluk.properties --changeLogFile=test.xml updateSQL&lt;br /&gt;/home/tomcat/liquibase/liquibase --defaultsFile=/home/tomcat/renwoluk.properties --changeLogFile=test.xml update&lt;br /&gt;/home/tomcat/liquibase/liquibase --defaultsFile=/home/tomcat/renwoluk.properties --changeLogFile=test.xml rollbackSQL before&lt;br /&gt;/home/tomcat/liquibase/liquibase --defaultsFile=/home/tomcat/renwoluk.properties --changeLogFile=test.xml rollback before&lt;/p&gt;&lt;br /&gt;Notice the sed commands that do the replace on test.xml. It is then simply doing an update and rollback just like in &lt;a href="http://kualigan.blogspot.com/2010/04/kis-of-dragon-testing-liquibase_27.html"&gt;KIS of the Dragon&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;That's it. Now developers can all test their &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; changelogs in a consistent manner without any modification to their IDE. &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt; is also really effective here because changelog testing is really really fast. It takes a few seconds and won't hang up &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt;. &lt;a href="http://www.hudson-ci.org"&gt;Hudson&lt;/a&gt; also can facilitate multiple executors. I will include in another post on &lt;a href="http://www.liquibase.org"&gt;liquibase&lt;/a&gt; automation how to integrate this into configuration management and automated build processes.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Apologies&lt;/h3&gt;I am very sorry for the content in &lt;a href="http://kualigan.blogspot.com/2010/04/kis-of-dragon-testing-liquibase_27.html"&gt;KIS of the Dragon&lt;/a&gt; because it was somewhat incorrect. I'm glad to remedy it here. &lt;br /&gt;&lt;br /&gt;I also apologize for having not put out a screencast in awhile. My goal was one a week. I've had some family crisis to deal with lately and I made the mistake of working on a few at the same time. In the coming week, expect a torrent of posts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1146804094519491203?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1146804094519491203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/06/liquibase-testing-with-svn-keywords.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1146804094519491203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1146804094519491203'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/06/liquibase-testing-with-svn-keywords.html' title='Liquibase Testing with SVN Keywords'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6637709555491683519</id><published>2010-04-30T13:29:00.000-07:00</published><updated>2010-05-01T09:14:23.155-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='jetty'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><title type='text'>Setting up Eclipse to use Jetty for KFS Development</title><content type='html'>Sometimes, you just want to use a straight, no-nonsense appserver for development. You want it to be fast, simple, and integrate with Eclipse and its debugger. Sounds like a tall order, but it's not. It's possible to setup Jetty to run KFS within Eclipse and hook in the debugger.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Move Required Libraries into Your KFS Project&lt;/h3&gt;The following need to be copied to the &lt;i&gt;.classpath&lt;/i&gt; of your eclipse project&lt;ul&gt;&lt;li&gt;jetty-6.1.15.rc5.jar&lt;/li&gt;&lt;li&gt;jetty-util-6.1.15.rc5.jar&lt;/li&gt;&lt;li&gt;connector-1_5.jar&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Create a Startup Server Class in Your KFS Project&lt;/h3&gt;Here's ours&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;package edu.arizona.jetty;&lt;br /&gt;&lt;br /&gt;import org.mortbay.jetty.*;&lt;br /&gt;import org.mortbay.jetty.webapp.*;&lt;br /&gt;import org.mortbay.jetty.nio.*;&lt;br /&gt;&lt;br /&gt;import static java.lang.System.getProperty;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; *&lt;br /&gt; * @author Leo Przybylski (przybyls@arizona.edu)&lt;br /&gt; */&lt;br /&gt;public class HappyMeal {&lt;br /&gt;    public static void main(String[] args) {&lt;br /&gt;        final String WEBROOT        = getProperty("webroot.directory");&lt;br /&gt;        final int    SERVER_PORT    = Integer.parseInt(getProperty("jetty.port"));&lt;br /&gt;        final String CONTEXT_PATH   = "/kfs-dev";&lt;br /&gt;&lt;br /&gt;        Server server = new Server();&lt;br /&gt;        &lt;br /&gt;        Connector[] connectors = new Connector[1];&lt;br /&gt;        connectors[0] = new SelectChannelConnector();&lt;br /&gt;        connectors[0].setPort(SERVER_PORT);&lt;br /&gt;        server.setConnectors(connectors);&lt;br /&gt;        &lt;br /&gt;        WebAppContext webapp = new WebAppContext();&lt;br /&gt;        webapp.setContextPath(CONTEXT_PATH);&lt;br /&gt;        webapp.setWar(WEBROOT);&lt;br /&gt;        server.setHandler(webapp);&lt;br /&gt;        &lt;br /&gt;        try {&lt;br /&gt;            server.start();&lt;br /&gt;            server.join();&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            e.printStackTrace();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Create a .launch for Jetty Server&lt;/h3&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;&lt;br /&gt;&amp;lt;launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication"&amp;gt;&lt;br /&gt;&amp;lt;listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"&amp;gt;&lt;br /&gt;&amp;lt;listEntry value="/kfs"/&amp;gt;&lt;br /&gt;&amp;lt;/listAttribute&amp;gt;&lt;br /&gt;&amp;lt;listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"&amp;gt;&lt;br /&gt;&amp;lt;listEntry value="4"/&amp;gt;&lt;br /&gt;&amp;lt;/listAttribute&amp;gt;&lt;br /&gt;&amp;lt;booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/&amp;gt;&lt;br /&gt;&amp;lt;listAttribute key="org.eclipse.debug.ui.favoriteGroups"&amp;gt;&lt;br /&gt;&amp;lt;listEntry value="org.eclipse.debug.ui.launchGroup.debug"/&amp;gt;&lt;br /&gt;&amp;lt;listEntry value="org.eclipse.debug.ui.launchGroup.run"/&amp;gt;&lt;br /&gt;&amp;lt;/listAttribute&amp;gt;&lt;br /&gt;&amp;lt;stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="edu.arizona.jetty.HappyMeal"/&amp;gt;&lt;br /&gt;&amp;lt;stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="kfs"/&amp;gt;&lt;br /&gt;&amp;lt;stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dwebroot.directory=work/web-root/ -Djetty.port=8080 -Xmx2g -XX:MaxPermSize=256m -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -Denvironment=dev -DlogDefault=WARN"/&amp;gt;&lt;br /&gt;&amp;lt;/launchConfiguration&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The parts to pay attention to are the &lt;i&gt;org.eclipse.jdt.launching.MAIN_TYPE&lt;/i&gt;, &lt;i&gt;org.eclipse.jdt.launching.PROJECT_ATTR&lt;/i&gt;, and &lt;i&gt;org.eclipse.jdt.launching.VM_ARGUMENTS&lt;/i&gt; attributes. You'll need to adjust yours to your preferences.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Test it out&lt;/h3&gt;&lt;br /&gt;&lt;img src="http://farm4.static.flickr.com/3633/4566804437_df441d4fc1_o_d.png" /&gt;&lt;br /&gt;This will add jetty to the tools menu&lt;br /&gt;&lt;img src="http://farm4.static.flickr.com/3178/4566804519_2911ab60f7_o_d.png" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Hooking up the Debugger&lt;/h3&gt;The &lt;i&gt;jetty&lt;/i&gt; selection should now be available under the debug menu.&lt;br /&gt;&lt;img src="http://farm4.static.flickr.com/3297/4566804491_5751912585_o_d.png" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Love&lt;/h3&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6637709555491683519?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6637709555491683519/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/setting-up-eclipse-to-use-jetty-for-kfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6637709555491683519'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6637709555491683519'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/setting-up-eclipse-to-use-jetty-for-kfs.html' title='Setting up Eclipse to use Jetty for KFS Development'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-5268854703725158355</id><published>2010-04-30T13:12:00.001-07:00</published><updated>2010-05-01T08:54:06.268-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><title type='text'>REPOST KFS Inheritance/Composition with Private Methods</title><content type='html'>&lt;h3&gt;Why the Repost?&lt;/h3&gt;I got some feedback that I &lt;ul&gt;&lt;li&gt;Never resolved the namespace issue&lt;/li&gt;&lt;li&gt;Didn't show original code to explain the problem&lt;/li&gt;&lt;/ul&gt;Both are very good points, so I'm going to do that now.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Problem Scenario&lt;/h3&gt;Suppose an instance arrives where you need to change the functionality of KFS slightly, but the method you want to override is declared private. Need an example? I have one. Read on.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Digester and Namespace Validation&lt;/h3&gt;By default, Digester within KFS has namespace validation off. Suppose you want to turn it on. Now you run into exactly such issue. Below is the original code from the &lt;i&gt;XmlBatchInputFileTypeBase&lt;/i&gt;&lt;br /&gt;&lt;pre class="brush: java"&gt;...&lt;br /&gt;...&lt;br /&gt;public class XmlBatchInputFileTypeBase extends BatchInputFileTypeBase {&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.BatchInputFileType#parse(byte[])&lt;br /&gt;     */&lt;br /&gt;    public Object parse(byte[] fileByteContent) throws ParseException {&lt;br /&gt;        if (fileByteContent == null) {&lt;br /&gt;            LOG.error("an invalid(null) argument was given");&lt;br /&gt;            throw new IllegalArgumentException("an invalid(null) argument was given");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // handle zero byte contents, xml parsers don't deal with them well&lt;br /&gt;        if (fileByteContent.length == 0) {&lt;br /&gt;            LOG.error("an invalid argument was given, empty input stream");&lt;br /&gt;            throw new IllegalArgumentException("an invalid argument was given, empty input stream");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // validate contents against schema&lt;br /&gt;        ByteArrayInputStream validateFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;        validateContentsAgainstSchema(getSchemaLocation(), validateFileContents);&lt;br /&gt;&lt;br /&gt;        // setup digester for parsing the xml file&lt;br /&gt;        &lt;br /&gt;        Digester digester = buildDigester(getSchemaLocation());&lt;br /&gt;&lt;br /&gt;        Object parsedContents = null;&lt;br /&gt;        try {&lt;br /&gt;            ByteArrayInputStream parseFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;            parsedContents = digester.parse(validateFileContents);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            LOG.error("Error parsing xml contents", e);&lt;br /&gt;            throw new ParseException("Error parsing xml contents: " + e.getMessage(), e);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return parsedContents;    &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The problem is with &lt;i&gt;buildDigester(getSchemaLocation())&lt;/i&gt;. &lt;i&gt;XmlBatchInputFileTypeBase#buildDigester()&lt;/i&gt; is declared private. That is, if one tries to extend &lt;i&gt;XmlBatchInputFileTypeBase&lt;/i&gt;, you not only cannot override the &lt;i&gt;buildDigester()&lt;/i&gt; method, but you can't call it from an inheriting class either. &lt;br /&gt;&lt;br /&gt;We would love to do&lt;pre class="brush: java"&gt;        Digester digester = buildDigester(getSchemaLocation());&lt;/pre&gt;&lt;br /&gt;But we can't. The alternative is to use reflection and call the private &lt;i&gt;buildDigester&lt;/i&gt; method anyway.&lt;br /&gt;&lt;br /&gt;Here's an example:&lt;pre class="brush: java"&gt;package edu.arizona.kfs.sys.batch;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;public class XmlBatchInputFileTypeBase extends org.kuali.kfs.sys.batch.XmlBatchInputFileTypeBase {&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.BatchInputFileType#parse(byte[])&lt;br /&gt;     */&lt;br /&gt;    public Object parse(byte[] fileByteContent) throws ParseException {&lt;br /&gt;        if (fileByteContent == null) {&lt;br /&gt;            LOG.error("an invalid(null) argument was given");&lt;br /&gt;            throw new IllegalArgumentException("an invalid(null) argument was given");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // handle zero byte contents, xml parsers don't deal with them well&lt;br /&gt;        if (fileByteContent.length == 0) {&lt;br /&gt;            LOG.error("an invalid argument was given, empty input stream");&lt;br /&gt;            throw new IllegalArgumentException("an invalid argument was given, empty input stream");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // validate contents against schema&lt;br /&gt;        ByteArrayInputStream validateFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;        validateContentsAgainstSchema(getSchemaLocation(), validateFileContents);&lt;br /&gt;&lt;br /&gt;        // setup digester for parsing the xml file&lt;br /&gt;        &lt;br /&gt;        Digester digester = null;&lt;br /&gt;        try {&lt;br /&gt;            Method digesterMethod = getClass().getDeclaredMethod("buildDigester", String.class, String.class);&lt;br /&gt;            m.setAccessible(true); // Private? Who cares?! I sure don't.&lt;br /&gt;            m.invoke(this, getSchemaLocation(), getDigestorRulesFileName());&lt;br /&gt;        }&lt;br /&gt;        catch(IllegalAccessExption e) {&lt;br /&gt;            // Not since it's accessible now.&lt;br /&gt;        }&lt;br /&gt;        catch(InvocationTargetException e) {&lt;br /&gt;            // Something else naughty happened&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if (digester == null) {&lt;br /&gt;        // throw some exception because this is very bad&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        digester.setNamespaceAware(true); // Enabling the namespace checking! Yeay!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;        Object parsedContents = null;&lt;br /&gt;        try {&lt;br /&gt;            ByteArrayInputStream parseFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;            parsedContents = digester.parse(validateFileContents);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            LOG.error("Error parsing xml contents", e);&lt;br /&gt;            throw new ParseException("Error parsing xml contents: " + e.getMessage(), e);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return parsedContents;    &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The trick is in&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;        Digester digester = null;&lt;br /&gt;        try {&lt;br /&gt;            Method digesterMethod = getClass().getDeclaredMethod("buildDigester", String.class, String.class);&lt;br /&gt;            m.setAccessible(true); // Private? Who cares?! I sure don't.&lt;br /&gt;            m.invoke(this, getSchemaLocation(), getDigestorRulesFileName());&lt;br /&gt;        }&lt;br /&gt;        catch(IllegalAccessExption e) {&lt;br /&gt;            // Not since it's accessible now.&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By calling &lt;i&gt;m.setAccessible(true)&lt;/i&gt;, we can still call the private method. many call this a hack because it ignores the &lt;b&gt;private&lt;/b&gt; access. I think many people are confused in what that means. The control is rather just for managing scope and maintaining OO encapsulation. These directives are not intended for security. They are intended to enforce OO. If we wanted to restrict method access, a &lt;i&gt;SecurityManager&lt;/i&gt; implementation would be more appropriate. IMHO, this is an entirely acceptable thing to do &lt;i&gt;in this situation&lt;/i&gt;. That being that we are being restricted by a flaw in the API from doing something we should be allowed to do. This happens often, and is a much better alternative than duplicating code.&lt;br /&gt;&lt;br /&gt;Then later, we append our changes to the digester with...&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;        digester.setNamespaceAware(true); // Enabling the namespace checking! Yeay!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With this, we can get away with extending code and overriding behavior (setting namespace awareness).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-5268854703725158355?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/5268854703725158355/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/repost-kfs-inheritancecomposition-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5268854703725158355'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/5268854703725158355'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/repost-kfs-inheritancecomposition-with.html' title='REPOST KFS Inheritance/Composition with Private Methods'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-7530051151387665588</id><published>2010-04-27T23:15:00.000-07:00</published><updated>2010-04-29T21:44:24.736-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='implementation'/><title type='text'>KFS Inheritance/Composition with Private Methods</title><content type='html'>&lt;h3&gt;The Problem Scenario&lt;/h3&gt;Suppose an instance arrives where you need to change the functionality of KFS slightly, but the method you want to override is declared private. Need an example? I have one. Read on.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Digester and Namespace Validation&lt;/h3&gt;By default, Digester within KFS has namespace validation off. Suppose you want to turn it on. Now you run into exactly such issue.&lt;br /&gt;&lt;pre class="brush: java"&gt;package edu.arizona.kfs.sys.batch;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;public class XmlBatchInputFileTypeBase extends org.kuali.kfs.sys.batch.XmlBatchInputFileTypeBase {&lt;br /&gt;    /**&lt;br /&gt;     * @see org.kuali.kfs.sys.batch.BatchInputFileType#parse(byte[])&lt;br /&gt;     */&lt;br /&gt;    public Object parse(byte[] fileByteContent) throws ParseException {&lt;br /&gt;        if (fileByteContent == null) {&lt;br /&gt;            LOG.error("an invalid(null) argument was given");&lt;br /&gt;            throw new IllegalArgumentException("an invalid(null) argument was given");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // handle zero byte contents, xml parsers don't deal with them well&lt;br /&gt;        if (fileByteContent.length == 0) {&lt;br /&gt;            LOG.error("an invalid argument was given, empty input stream");&lt;br /&gt;            throw new IllegalArgumentException("an invalid argument was given, empty input stream");&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // validate contents against schema&lt;br /&gt;        ByteArrayInputStream validateFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;        validateContentsAgainstSchema(getSchemaLocation(), validateFileContents);&lt;br /&gt;&lt;br /&gt;        // setup digester for parsing the xml file&lt;br /&gt;        &lt;br /&gt;        Digester digester = null;&lt;br /&gt;        try {&lt;br /&gt;            Method digesterMethod = getClass().getDeclaredMethod("buildDigester", String.class, String.class);&lt;br /&gt;            m.setAccessible(true); // Private? Who cares?! I sure don't.&lt;br /&gt;            m.invoke(this, getSchemaLocation(), getDigestorRulesFileName());&lt;br /&gt;        }&lt;br /&gt;        catch(IllegalAccessExption e) {&lt;br /&gt;            // Not since it's accessible now.&lt;br /&gt;        }&lt;br /&gt;        catch(InvocationTargetException e) {&lt;br /&gt;            // Something else naughty happened&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;        Object parsedContents = null;&lt;br /&gt;        try {&lt;br /&gt;            ByteArrayInputStream parseFileContents = new ByteArrayInputStream(fileByteContent);&lt;br /&gt;            parsedContents = digester.parse(validateFileContents);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            LOG.error("Error parsing xml contents", e);&lt;br /&gt;            throw new ParseException("Error parsing xml contents: " + e.getMessage(), e);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return parsedContents;    &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The trick is in&lt;br /&gt;&lt;pre class="brush: java"&gt;&lt;br /&gt;        Digester digester = null;&lt;br /&gt;        try {&lt;br /&gt;            Method digesterMethod = getClass().getDeclaredMethod("buildDigester", String.class, String.class);&lt;br /&gt;            m.setAccessible(true); // Private? Who cares?! I sure don't.&lt;br /&gt;            m.invoke(this, getSchemaLocation(), getDigestorRulesFileName());&lt;br /&gt;        }&lt;br /&gt;        catch(IllegalAccessExption e) {&lt;br /&gt;            // Not since it's accessible now.&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By calling &lt;tt&gt;m.setAccessible(true)&lt;/tt&gt;, we can still call the private method. many call this a hack because it ignores the &lt;b&gt;private&lt;/b&gt; access. I think many people are confused in what that means. The control is rather just for managing scope and maintaining OO encapsulation. These directives are not intended for security. They are intended to enforce OO. If we wanted to restrict method access, a &lt;tt&gt;SecurityManager&lt;/tt&gt; implementation would be more appropriate. IMHO, this is an entirely acceptable thing to do &lt;i&gt;in this situation&lt;/i&gt;. That being that we are being restricted by a flaw in the API from doing something we should be allowed to do. This happens often, and is a much better alternative than duplicating code.&lt;br /&gt;&lt;br /&gt;With this, we can get away with extending code and overriding behavior.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-7530051151387665588?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/7530051151387665588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/screencast-kfs-inheritancecomposition.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7530051151387665588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7530051151387665588'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/screencast-kfs-inheritancecomposition.html' title='KFS Inheritance/Composition with Private Methods'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-8362758966423256920</id><published>2010-04-27T21:01:00.001-07:00</published><updated>2010-04-28T10:52:39.741-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>KIS of the Dragon - Testing Liquibase Changelogs</title><content type='html'>Here is a screencast on how to test changelogs with &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; tagging and rollback commands.&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/Rf2fCkhZBfM&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/Rf2fCkhZBfM&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Breakdown&lt;/h3&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Tag the database&lt;/b&gt;&lt;br /&gt;You want to tag before changes are made. This is for rolling back later.&lt;p class="code"&gt;% liquibase --defaultsFile=liquibase.properties tag &lt;your tag name&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Run your update&lt;/b&gt;&lt;br /&gt;&lt;p class="code"&gt;% liquibase --defaultsFile=liquibase.properties --changeLogFile=your changelog update &amp;lt;your tag name&amp;gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Rollback&lt;/b&gt;&lt;br /&gt;You can rollback if you make a mistake and run your update again, but to make sure you leave the place the same as when you came in, cleanup with rollback.&lt;p class="code"&gt;% liquibase --defaultsFile=liquibase.properties rollback &amp;lt;tag you used earlier&amp;gt;&lt;/p&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-8362758966423256920?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/8362758966423256920/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-of-dragon-testing-liquibase_27.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/8362758966423256920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/8362758966423256920'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-of-dragon-testing-liquibase_27.html' title='KIS of the Dragon - Testing Liquibase Changelogs'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-7781351564689328195</id><published>2010-04-26T01:26:00.000-07:00</published><updated>2010-04-28T10:52:17.002-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><title type='text'>KIS and Tell - Screencasts on the Horizon</title><content type='html'>I have started a screencast series on developing for Kuali. Entries that are part of the screencast series follow the naming convention where KIS stands out as a popular movie title that is used in the blog entry title. Here are names of upcoming screencasts.&lt;ul&gt;&lt;li&gt;KIS of the Dragon - Testing Liquibase Changelogs&lt;/li&gt;&lt;li&gt;KIS'ing Katie Holmes - Migrating an Oracle Database to Mysql with Liquibase and hsqldb&lt;/li&gt;&lt;li&gt;KIS KIS Bang Bang - Liquibase Automation&lt;/li&gt;&lt;li&gt;The Long KIS Goodnight - Automating Selenium Testing with Hudson CI and Amazon EC2&lt;/li&gt;&lt;li&gt;KIS Me Kate - RPM Packaging KFS&lt;/li&gt;&lt;li&gt;Pythagoras KIS - Exposing Quartz Scheduled Jobs via Web Service w/o KSB&lt;/li&gt;&lt;li&gt;The KIS Identity - University of Arizona LDAP Integration&lt;/li&gt;&lt;li&gt;The KIS Supremacy - Amazon EC2 and Launchpad for KFS&lt;/li&gt;&lt;li&gt;A KIS to Build a Dream On - Configuration Management of KFS at University of Arizona&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Stay tuned. It is my sincere hope that these screencasts empower implementing institutions to build strong sustainable implementations of Kuali Foundation projects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-7781351564689328195?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/7781351564689328195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-and-tell-screencasts-on-horizon.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7781351564689328195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7781351564689328195'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/kis-and-tell-screencasts-on-horizon.html' title='KIS and Tell - Screencasts on the Horizon'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-4913903659636008733</id><published>2010-04-26T00:53:00.000-07:00</published><updated>2010-04-28T22:35:48.328-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='howto'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>KISmet - Game Changer: Structuring the Project to Manage Database Changes with Liquibase</title><content type='html'>Structuring your project to handle configuration management of your project RDBMS can probably be the most difficult part of managing your project. If you plan to use &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; to manage your database migrations, then this is even more the case. At the &lt;a href="http://www.arizona.edu"&gt;University of Arizona&lt;/a&gt;, we first followed the instructions laid out by this &lt;a href="http://www.liquibase.org/tutorial-using-oracle"&gt;tutorial&lt;/a&gt; (&lt;a href="http://www.liquibase.org/tutorial-using-oracle"&gt;Tutorial Using Oracle&lt;/a&gt;) since we're using Oracle. &lt;br /&gt;&lt;br /&gt;We retain in our methodology much from that article, but rather than explain the differences, I'll just explain what we did.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The University of Arizona Liquibase Methodology in a Nutshell&lt;/h3&gt;The goals we wanted to solve with simplified data migration using &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; are:&lt;ul&gt;&lt;li&gt;Structure for isolating database changes.&lt;/li&gt;&lt;li&gt;Integrates with a process that versions a database from SVN, Jira, and Continuous Integration.&lt;/li&gt;&lt;li&gt;Coupled database version with application version.&lt;/li&gt;&lt;li&gt;Integrates with a process that facilitates rollback, update, and complete schema rebuilds.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Structure for Isolating Database Changes&lt;/h3&gt;A project was created at &lt;a href="http://www.arizona.edu"&gt;University of Arizona&lt;/a&gt; called &lt;i&gt;kfs-cfg-dbs&lt;/i&gt;. Within that project is where we created the structure to manage out database migrations. We followed the example outlined in &lt;a href="http://www.liquibase.org/tutorial-using-oracle"&gt;Tutorial Using Oracle&lt;/a&gt;. We found that we can create two paths. One path is for update (&lt;tt&gt;update/&lt;/tt&gt;), and the other path is for building the latest schema entirely (&lt;tt&gt;latest/&lt;/tt&gt;).&lt;br /&gt;&lt;h3&gt;latest/&lt;/h3&gt;&lt;p&gt;Here, we followed the convention of using 3-character paths according to the changelog content.&lt;/p&gt;&lt;br /&gt;&lt;table style="margin: 0 0 0 0; padding: 0 0 0 0;"&gt;  &lt;tr&gt;    &lt;th&gt;Pathname&lt;/th&gt;  &lt;th&gt;Content&lt;/th&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;cst&lt;/td&gt;    &lt;td&gt;constraint-related changelog&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;dat&lt;/td&gt;    &lt;td&gt;table-related changelog&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;idx&lt;/td&gt;    &lt;td&gt;index-related changelog&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;seq&lt;/td&gt;    &lt;td&gt;sequence-related changelog&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;tab&lt;/td&gt;    &lt;td&gt;table-related changelog&lt;/td&gt;  &lt;/tr&gt;  &lt;tr&gt;    &lt;td&gt;vw&lt;/td&gt;    &lt;td&gt;view-related changelog&lt;/td&gt;  &lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;h3&gt;constraints.xml&lt;/h3&gt;During our database migrations, we load schema changes and data changes. These data changes can sometimes effect constraints. For full schema rebuilds, we load constraints last to allow data loads to process faster. Therefore, we separate our constraint changelog information into its own file to run last.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/cst/LD_EXP_TRNFR_DOC_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/cst/FP_PMT_MTHD_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/cst/FP_PMT_MTHD_CHART_T.xml" /&amp;gt;&lt;br /&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice that entries are simply includes that point to files associated by table name in the &lt;tt&gt;cst/&lt;/tt&gt; directory within the &lt;tt&gt;latest/&lt;/tt&gt; path. For example, &lt;tt&gt;latest/cst/LD_EXP_TRNFR_DOC_T.xml&lt;/tt&gt; refers to constraints on the &lt;tt&gt;LD_EXP_TRNFR_DOC_T&lt;/tt&gt; table. Because &lt;tt&gt;cst/&lt;/tt&gt; is taken from &lt;tt&gt;latest/&lt;/tt&gt; we know that this file relates to new schema migrations. 100% of the time, includes in &lt;tt&gt;constraints.xml&lt;/tt&gt; will point to &lt;tt&gt;latest&lt;/tt&gt;. That is our convention.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;data.xml&lt;/h3&gt;Similarly to &lt;tt&gt;constraints.xml&lt;/tt&gt;, &lt;tt&gt;data.xml&lt;/tt&gt; has entries to data by table-name in &lt;tt&gt;latest/&lt;/tt&gt; for new schema migrations. Here is ours:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/FP_PMT_MTHD_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/FP_PMT_MTHD_CHART_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/KREW.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/KRIM.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/KRIM3.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/GL_OFFSET_DEFN_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/KRIM2.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/dat/KRNS_PARM_T.xml" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;install.xml&lt;/h3&gt;Anything that needs to be migrated before data and constraints is added to the &lt;tt&gt;install.xml&lt;/tt&gt;. It follows exactly the same convention as the previous two:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/FP_PRCRMNT_LVL3_ADD_ITEM_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/FP_PRCRMNT_LVL3_FUEL_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/FP_PRCRMNT_CARD_HLDR_DTL_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/FP_PRCRMNT_CARD_TRN_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/FP_PRCRMNT_CARD_HLDR_LD_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/tab/PDP_SHIPPING_INV_TRACKING_T.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/seq/ERROR_CERT_ID_SEQ.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/seq/CM_CPTLAST_AWARD_HIST_NBR_SEQ.xml" /&amp;gt;&lt;br /&gt;    &amp;lt;include file="latest/seq/PUR_PDF_LANG_ID.xml" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;update/&lt;/h3&gt;The update changelog is responsible for database migrations on existing schemas. It simply updates a schema already in use, so these are all changes.&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&amp;gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt;    &amp;lt;include file="update/KITT-958.xml" /&amp;gt;&lt;br /&gt;&amp;lt;/databaseChangeLog&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &lt;tt&gt;update.xml&lt;/tt&gt; refers to files in the &lt;tt&gt;update/&lt;/tt&gt; path which contains files associated by Jira Issue #. By this convention, we know that &lt;tt&gt;update/KITT-958.xml&lt;/tt&gt; contains a change in this version for KITT-958. This is how we get our Jira coupling. Using this convention, we can migrate perpetually and let Jira handle linkage with our data migration/issue management.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Integrates with a process that versions a database from SVN, Jira, and Continuous Integration.&lt;/h3&gt;I have already shown how this works with Jira, but how do we get exclusive database versions? We use SVN to handle changelog ids for us. Changelog ids are what &lt;a href="http://www.liquibase.org/"&gt;Liquibase&lt;/a&gt; uses to identify which changes are to be run. Each change gets its own id, to identify it apart from others. In order to lessen developer overhead, we simply use the &lt;tt&gt;$Revision$&lt;/tt&gt; keyword from SVN. &lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt;    &amp;lt;changeSet id="$Revision$" author="$Author" &amp;gt;&lt;br /&gt;        &amp;lt;comment&amp;gt;Adding a new kim type of patisserie and a new eclair baker role. Yum!!&amp;lt;/comment&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This will put a new revision id with each checkin. We also use &lt;tt&gt;$Author$&lt;/tt&gt; to identify the person that made the change. This convention falls apart when a person commits more than one changelog at a time. Then two changelogs have the same id. This will cause problems.&lt;br /&gt;&lt;br /&gt;Part of our methodology is the concept of &lt;i&gt;one-change-per-checkin&lt;/i&gt;. That is, when creating a changelog for &lt;tt&gt;update/&lt;/tt&gt;, we put all changes relating to KITT-958 for this release into a single changeset in our KITT-958.xml. If we have changes for another issue (KITT-959), then we put changes in KITT-959.xml. That is fine. It is crucial to understand that only one of these files gets checked in at a time though. That is, first, commit KITT-958.xml, then KITT-959.xml. The reason is that when one gets committed, it gets a revision number. Then, when the next one is committed, it gets a different revision number. This helps us keep a consistent set of changelog ids, and also prevent changelogs from stepping on each other.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Integrates with a process that facilitates rollback, update, and complete schema rebuilds.&lt;/h3&gt;I will explain this in more detail when I discuss testing changes to data migration. I will say though that &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; supports rollback of changes. Using our configuration management system, this allows us to couple data migrations with a release of KFS. We can suddenly move between versions very easily by undoing what we have done. In most cases, &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; can automatically rollback changes by analyzing different patterns based on the refactoring. Some cases, this is very difficult though (consult the &lt;a href="http://www.liquibase.org"&gt;Liquibase&lt;/a&gt; manual for more details on what does and doesn't auto-rollback). One example is data-related migrations. When inserting data, liquibase cannot understand how to undo an insert or an update of a record. For this, changelogs have a rollback directive to explicitly define a rollback pattern.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: xml"&gt;&lt;br /&gt;&amp;lt;databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"&lt;br /&gt;                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt;                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"&amp;gt;&lt;br /&gt;    &amp;lt;changeSet id="$Revision$" author="$Author" &amp;gt;&lt;br /&gt;        &amp;lt;comment&amp;gt;Adding a new kim type of patisserie and a new eclair baker role. Yum!!&amp;lt;/comment&amp;gt;&lt;br /&gt;    &amp;lt;sql splitStatements="false" endDelimiter=""&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;declare &lt;br /&gt;ktyp_id krim_typ_t.kim_typ_id%TYPE;&lt;br /&gt;BEGIN&lt;br /&gt;    INSERT INTO KRIM_TYP_T &lt;br /&gt;    (KIM_TYP_ID, OBJ_ID, VER_NBR, NM, SRVC_NM, ACTV_IND, NMSPC_CD) VALUES &lt;br /&gt;    (KRIM_TYP_ID_S.NEXTVAL,'SYS_GUID()', 1,'Patisserie',null,'Y','KUALI')&lt;br /&gt;    RETURNING kim_typ_id into ktyp_id;&lt;br /&gt;&lt;br /&gt;    INSERT INTO KRIM_ROLE_T &lt;br /&gt;    (ROLE_ID,OBJ_ID,VER_NBR,ROLE_NM,NMSPC_CD,DESC_TXT, KIM_TYP_ID, ACTV_IND) VALUES &lt;br /&gt;    (KRIM_ROLE_ID_S.NEXTVAL,SYS_GUID(),1,'Eclair Baker','KUALI',null,ktyp_id,'Y');&lt;br /&gt;END;&lt;br /&gt;]]&amp;gt;&lt;br /&gt;    &amp;lt;/sql&amp;gt;&lt;br /&gt;    &amp;lt;rollback&amp;gt;&lt;br /&gt;      &amp;lt;sql&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;delete from KRIM_TYP_T where NM = 'Patisserie';&lt;br /&gt;&lt;br /&gt;delete from KRIM_ROLE_T where ROLE_NM = 'Eclair Baker';&lt;br /&gt;]]&amp;gt;&lt;br /&gt;      &amp;lt;/sql&amp;gt;&lt;br /&gt;    &amp;lt;/rollback&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Case Study: Adding a Data-Only Change&lt;/h3&gt;Here is a screencast on how to use liquibase to migrate database changes using the University of Arizona's methodology for change management. Includes an example on making a data related change.&lt;br /&gt;&lt;br /&gt;&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/PvEhn8kzDww&amp;hl=en_US&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/PvEhn8kzDww&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Looking Ahead&lt;/h3&gt;Be sure to read my next blog entry which will describe how to test this change against a database using rollbacks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-4913903659636008733?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/4913903659636008733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/kismet-game-changer-structuring-project.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4913903659636008733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/4913903659636008733'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/kismet-game-changer-structuring-project.html' title='KISmet - Game Changer: Structuring the Project to Manage Database Changes with Liquibase'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1527197207559142838</id><published>2010-04-25T01:25:00.000-07:00</published><updated>2010-04-27T23:13:31.902-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='mylyn'/><title type='text'>Prelude to a KIS - Setting up and Getting Started with Mylyn</title><content type='html'>&lt;p&gt;This is my first screencast. I am going to discuss how to setup and get started using mylyn since both Jira and Eclipse are prominent Kuali development tools.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/r54jbydh7po&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/r54jbydh7po&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;My reasons for using Mylyn&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Enforces one-issue-at-a-time practice. Many times, complex check-ins from working on multiple issues at a time is the cause of bugs.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Organizes files to an issue. This helps you focus on the task by blocking out files that don't matter and helping you remember what you were working on.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Issue management integration connects what you're working on with ... what you're working on. Issue metadata only available in Jira is something that can be lost when developing. Mylyn keeps your work connected with your tools&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1527197207559142838?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1527197207559142838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/prelude-to-kis-setting-up-and-getting.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1527197207559142838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1527197207559142838'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/prelude-to-kis-setting-up-and-getting.html' title='Prelude to a KIS - Setting up and Getting Started with Mylyn'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-1476778352749342772</id><published>2010-04-23T18:29:00.001-07:00</published><updated>2010-04-28T11:00:55.261-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='kfs'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><title type='text'>Tuning the Garbage Collection in KFS</title><content type='html'>KFS does not ship with JVM configuration that optimizes the application. Implementing institutions are expected to do that themselves. At &lt;a href="http://www.arizona.edu"&gt;University of Arizona&lt;/a&gt;, we have spent some time tuning KFS garbage collection for what seems to be pretty decent performance. Our results came mostly from trial-and-error, research from literature, and &lt;a href="http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html"&gt;Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This is what we've come up with.&lt;/h3&gt;&lt;p class="code"&gt;-Xms2g -Xmx2g -XX:MaxPermSize=512m -Doracle.jdbc.Trace=true -Djava.util.logging.config.file=/usr/share/tomcat5/conf/ojdbclog.conf -server -XX:+UseParNewGC -XX:MaxNewSize=256m -XX:NewSize=256m -XX:SurvivorRatio=128 -XX:MaxTenuringThreshold=0 -XX:+UseTLAB -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Concurrent Low Pause Collector&lt;/h3&gt;What we decided to use is the &lt;b&gt;Concurrent Low Pause Collector&lt;/b&gt;. We run our application out of a 64-bit Redhat Enterprise Linux VM running from Vmware ESX Server with 2 processors and 4 gb of RAM. Now that's a mouthful. You may be wondering, "With 2 processors, why don't you use the parallel GC?" Well, we tried both parallel and concurrent low pause gc. Really, the only reason why you would use a parallel is not because you have an extra processor, but rather that sacrificing that processor during runtime is better than bringing the system to its knees. Well, there's got to be a way that you run the garbage collector without sacrificing processing power at what might be a crucial time. That's where the &lt;b&gt;concurrent low pause collector&lt;/b&gt; comes in. &lt;i&gt;Concurrent&lt;/i&gt; means it runs in a separate thread. You may sacrifice a processor here, yes. &lt;i&gt;Low Pause&lt;/i&gt; means that unlike a &lt;b&gt;Parallel Collector&lt;/b&gt;, this runs in a separate thread for a short amount of time. Win-win!! So what's this other stuff? Read on.&lt;br /&gt;&lt;h3&gt;Heap Memory Requirements&lt;/h3&gt;We set the min and max to be the same. This way the JVM doesn't have to reallocate memory. It's greedy and takes everything it can right away. This saves time. In a server environment, you don't want to be conservative. Greedy is a better way to go.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Optimizing for Caching&lt;/h3&gt;The &lt;tt&gt;-XX:MaxNewSize=256m -XX:NewSize=256m&lt;/tt&gt; properties grow the Eden gen space to 256 mb immediately. Just like with the heap, we don't want to reallocate on a server platform. Let's be as greedy as possible. We've optimized to 256mb because we expect to have a large amount of cache rewriting. &lt;a href="http://download-llnw.oracle.com/docs/cd/E13209_01/wlcp/wlss30/configwlss/jvmgc.html"&gt;Tuning JVM Garbage Collection for Production Deployments&lt;/a&gt; recommends setting it to 32mb for caching systems, but we've found that for larger retained caches like what we want, maybe 256mb is more about what we want to use. This is especially the case with the number and size of HashMap instances in use in KFS between Spring and Rice. We also optimize the survivor space for the young generation to be 128th the size of the eden space. &lt;tt&gt;MaxTenuringThreshold&lt;/tt&gt; is turned off so that a new &lt;tt&gt;NewSize&lt;/tt&gt; space becomes reusable with each collection. This is actually really really small, and works well for caching.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Optimizing for Concurrency&lt;/h3&gt;&lt;tt&gt;CMSClassUnloadingEnabled&lt;/tt&gt;, &lt;tt&gt;CMSPermGenSweepingEnabled&lt;/tt&gt;, and &lt;tt&gt;UseConcMarkSweepGC&lt;/tt&gt; give us our concurrentcy collection. Together, they force different algorithms for the GC that are optimal for multiprocessor system like forcing class unloading to prevent the GC from being intrusive on the application. &lt;tt&gt;UseTLAB&lt;/tt&gt; &amp;quot;uses thread-local object allocation blocks. This improves concurrency by reducing contention on the shared heap lock.&amp;quot; from &lt;a href="http://download-llnw.oracle.com/docs/cd/E13209_01/wlcp/wlss30/configwlss/jvmgc.html"&gt;Tuning JVM Garbage Collection for Production Deployments&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;We had tried numerous configurations, and this worked out the best for us. It gave us a 4x improvement when processing large batch jobs during the day. In most cases, we won't process large batch jobs while users are in the system; however, for testing we are limited on servers and sometimes we try to get more out of our systems than they can give.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-1476778352749342772?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/1476778352749342772/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/tuning-garbage-collection-in-kfs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1476778352749342772'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/1476778352749342772'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/tuning-garbage-collection-in-kfs.html' title='Tuning the Garbage Collection in KFS'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-6905207798532238603</id><published>2010-04-23T17:44:00.001-07:00</published><updated>2011-07-10T08:50:07.114-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='logging'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='rdbms'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Turning on Oracle Driver Tracing</title><content type='html'>You are probably asking right about now, "Why would I do this if I have p6spy?" Well, here are my reasons.&lt;ul&gt;&lt;li&gt;P6Spy is a proxy on the driver. It watches SQL that goes in and prints it.&lt;/li&gt;&lt;li&gt;OJDBC in debug mode prints what goes in (not just SQL).&lt;/li&gt;&lt;li&gt;OJDBC in debug mode reports what comes out (including exceptions!)&lt;/li&gt;&lt;/ul&gt;Basically, if there's an exception or any kind of warning handled by the driver, you would find out about it. This is useful for too many reasons to count. Further, it doesn't just spit out the SQL you send it. It spits out any requests to recover connections or API level communication happening in the backend. Really, anything over Net8 happens will get reported. That's huge if you're working with Oracle.&lt;br /&gt;&lt;br /&gt;Without further ado, here it is. Pass the following in when starting your JVM. &lt;br /&gt;&lt;br /&gt;&lt;p class="code"&gt;-Doracle.jdbc.Trace=true -Djava.util.logging.config.file=&amp;lt;location of your java logging configuration file&amp;gt;&lt;/p&gt;&lt;br /&gt;&lt;span style="color : navy;"&gt;&lt;br /&gt;&lt;b&gt;Note: You must use the ojbdcX_g.jar&lt;/b&gt;.&lt;/span&gt; &lt;span style="color: green"&gt;Oracle optimizes its driver library for each JVM. ojdbc14.jar is optimized for jdk 1.4. ojdbc5.jar is optimized for java 5. ojdbc6.jar is optimized for java 6. &lt;b&gt;_g&lt;/b&gt; is appended for the debugging enabled jar. For example, ojdbc14_g.jar is the jdk1.4 jar with debugging enabled.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ojdbc debug mode uses java.util.logging framework to define its logging. You simply configure it. Here is what our config looks like:&lt;br /&gt;&lt;br /&gt;&lt;p class="code"&gt;.level=SEVERE&lt;br /&gt;oracle.jdbc.level=WARNING&lt;br /&gt;oracle.jdbc.handlers=java.util.logging.FileHandler&lt;br /&gt;java.util.logging.FileHandler.level=WARNING&lt;br /&gt;java.util.logging.FileHandler.pattern=/usr/share/tomcat5/logs/jdbc.log&lt;br /&gt;java.util.logging.FileHandler.count=3&lt;br /&gt;java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;We use a java.util.logging.FileHandler, but you can use the ConsoleHandler which is useful for development; however, this merges all your logs to file which may not always be ideal&lt;br /&gt;&lt;br /&gt;That's it. If you're working with tomcat, just set this in your CATALINA_OPTS environment variable. You can also set this in your Eclipse JRE settings for your &lt;a href="http://kuali.org"&gt;Kuali&lt;/a&gt; project. If you're using eclipse, you may want to look at the &lt;a href="http://marketplace.eclipse.org/content/eclipse-log-viewer"&gt;Eclipse Log Viewer&lt;/a&gt; plugin which will allow you to follow external logs from within eclipse.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-6905207798532238603?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/6905207798532238603/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/turning-on-oracle-driver-tracing.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6905207798532238603'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/6905207798532238603'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/turning-on-oracle-driver-tracing.html' title='Turning on Oracle Driver Tracing'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2356890446425422475.post-7660372702009109027</id><published>2010-04-22T11:18:00.000-07:00</published><updated>2010-04-22T11:32:07.081-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='kuali'/><category scheme='http://www.blogger.com/atom/ns#' term='implementation'/><title type='text'>CSV Export in Internet Explorer</title><content type='html'>&lt;p&gt;At the &lt;a href="http://www.arizona.edu/"&gt;University of Arizona&lt;/a&gt;, we have had a problem for some time now when opening exported CSV using Internet Explorer. We knew the problem stemmed from security settings in Internet Explorer. Until now, we have not had a solution that did not involve weakening security settings in Internet Explorer. This was important to us because our institution has security policies that are enforced internally for applications on our domain like Internet Explorer. Weakening security settings just was not an option we could use.&lt;br /&gt;&lt;br /&gt;Just the other day, Andrew Hollamon, discovered a solution to this problem that had been plaguing us. He discovered that a setting in Internet Explorer would disable caching on pages rendered through HTTPS (details here &lt;a href="http://support.microsoft.com/kb/812935"&gt;http://support.microsoft.com/kb/812935&lt;/a&gt;). Basically, what happens is the security settings in Internet Explorer require it to submit a header to the server requesting a page without caching. Caching is necessary because the file is cached before it can be opened. If the page is not cached, then there is nothing to be opened. Andrew's solution was to add some custom code to the Rice WebUtils class that changes the headers only for requests of reports in CSV.&lt;br /&gt;&lt;br /&gt;We plan to contribute this back, but any institution can implement a solution easily enough by simply modifying the necessary headers in the servlet response according to this document &lt;a href="http://support.microsoft.com/kb/812935"&gt;http://support.microsoft.com/kb/812935&lt;/a&gt;)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2356890446425422475-7660372702009109027?l=kualigan.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://kualigan.blogspot.com/feeds/7660372702009109027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://kualigan.blogspot.com/2010/04/csv-export-in-internet-explorer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7660372702009109027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2356890446425422475/posts/default/7660372702009109027'/><link rel='alternate' type='text/html' href='http://kualigan.blogspot.com/2010/04/csv-export-in-internet-explorer.html' title='CSV Export in Internet Explorer'/><author><name>Leonidas Prime</name><uri>https://profiles.google.com/111951403234494220528</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-tXDtLXZ8Nzg/AAAAAAAAAAI/AAAAAAAAAas/gUuQGeKweOk/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
