azkaban-developers

Merge branch 'master' of github.com:azkaban/azkaban2 into

2/25/2014 7:19:17 PM
2.5.0-rc4

Details

.classpath 58(+29 -29)

diff --git a/.classpath b/.classpath
index f3617fb..8224458 100644
--- a/.classpath
+++ b/.classpath
@@ -3,35 +3,35 @@
 	<classpathentry kind="src" path="src/java"/>
 	<classpathentry kind="src" path="unit/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="lib" path="lib/commons-collections-3.2.1.jar"/>
-	<classpathentry kind="lib" path="lib/commons-fileupload-1.2.1.jar"/>
-	<classpathentry kind="lib" path="lib/commons-lang-2.6.jar"/>
-	<classpathentry kind="lib" path="lib/jackson-core-asl-1.9.5.jar"/>
-	<classpathentry kind="lib" path="lib/jackson-mapper-asl-1.9.5.jar"/>
-	<classpathentry kind="lib" path="lib/jetty-6.1.26.jar"/>
-	<classpathentry kind="lib" path="lib/jetty-util-6.1.26.jar"/>
-	<classpathentry kind="lib" path="lib/joda-time-2.0.jar"/>
-	<classpathentry kind="lib" path="lib/jopt-simple-4.3.jar"/>
-	<classpathentry kind="lib" path="lib/log4j-1.2.16.jar"/>
-	<classpathentry kind="lib" path="lib/servlet-api-2.5.jar"/>
-	<classpathentry kind="lib" path="lib/slf4j-api-1.6.1.jar"/>
-	<classpathentry kind="lib" path="lib/slf4j-log4j12-1.6.4.jar"/>
-	<classpathentry kind="lib" path="lib/velocity-1.7.jar"/>
-	<classpathentry kind="lib" path="lib/velocity-tools-2.0.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry kind="lib" path="lib/httpclient-4.2.1.jar"/>
-	<classpathentry kind="lib" path="lib/commons-io-2.4.jar"/>
-	<classpathentry kind="lib" path="lib/httpcore-4.2.1.jar"/>
-	<classpathentry kind="lib" path="lib/commons-logging-1.1.1.jar"/>
-	<classpathentry kind="lib" path="lib/guava-13.0.1.jar"/>
-	<classpathentry kind="lib" path="lib/commons-email-1.2.jar"/>
-	<classpathentry kind="lib" path="lib/mail-1.4.5.jar"/>
-	<classpathentry kind="lib" path="lib/commons-configuration-1.8.jar"/>
-	<classpathentry kind="lib" path="lib/commons-dbutils-1.5.jar"/>
-	<classpathentry kind="lib" path="lib/commons-dbcp-1.4.jar"/>
-	<classpathentry kind="lib" path="extlib/mysql-connector-java-5.1.16-bin.jar"/>
-	<classpathentry kind="lib" path="lib/commons-pool-1.6.jar"/>
-	<classpathentry kind="lib" path="lib/h2-1.3.170.jar"/>
-	<classpathentry kind="lib" path="lib/commons-jexl-2.1.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-collections-3.2.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-configuration-1.8.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-dbcp-1.4.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-dbutils-1.5.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-email-1.2.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-fileupload-1.2.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-io-2.4.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-jexl-2.1.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-lang-2.6.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-logging-1.1.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/commons-pool-1.6.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/guava-13.0.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/h2-1.3.170.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/httpclient-4.2.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/httpcore-4.2.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/jackson-core-asl-1.9.5.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/jackson-mapper-asl-1.9.5.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/jetty-6.1.26.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/jetty-util-6.1.26.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/joda-time-2.0.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/jopt-simple-4.3.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/log4j-1.2.16.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/mail-1.4.5.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/mysql-connector-java-5.1.28.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/servlet-api-2.5.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/slf4j-api-1.6.1.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/slf4j-log4j12-1.6.4.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/velocity-1.7.jar"/>
+	<classpathentry kind="lib" path="build/ivy/lib/velocity-tools-2.0.jar"/>
 	<classpathentry kind="output" path="dist/classes"/>
 </classpath>

.gitignore 6(+6 -0)

diff --git a/.gitignore b/.gitignore
index c0f64a8..1908b60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
 dist/
 build/
+.idea/
+*.iml
+*.log
+TestProcess_*
+_AzkabanTestDir_*
+reports/

build.xml 120(+69 -51)

diff --git a/build.xml b/build.xml
index 9b7f8f1..1435ccf 100644
--- a/build.xml
+++ b/build.xml
@@ -13,31 +13,31 @@
   <property name="dist.web.package.dir" value="${dist.packages.dir}/azkaban-web-server" />
   <property name="dist.exec.package.dir" value="${dist.packages.dir}/azkaban-exec-server" />
   <property name="dist.solo.package.dir" value="${dist.packages.dir}/azkaban-solo-server" />
-  <property name="dist.sql.package.dir" value="${dist.packages.dir}/sql" />  
+  <property name="dist.sql.package.dir" value="${dist.packages.dir}/sql" />
 
   <property name="conf.dir" value="${basedir}/conf" />
   <property name="web.package.dir" value="${basedir}/src/package/webserver" />
   <property name="exec.package.dir" value="${basedir}/src/package/execserver" />
   <property name="solo.package.dir" value="${basedir}/src/package/soloserver" />
-  
+
   <property name="bin.dir" value="${basedir}/bin" />
   <property name="java.src.dir" value="${basedir}/src/java" />
   <property name="dust.src.dir" value="${basedir}/src/tl" />
   <property name="less.src.dir" value="${basedir}/src/less" />
   <property name="web.src.dir" value="${basedir}/src/web" />
   <property name="sql.src.dir" value="${basedir}/src/sql" />
-  
+
   <property name="version.file" value="${dist.packages.dir}/azkaban.version" />
-  
+
   <!-- Ivy properties -->
   <property name="mvnrepo" value="http://repo2.maven.org/maven2" />
   <property name="build.dir" value="${base.dir}/build" />
   <property name="ivy.dir" location="ivy" />
   <loadproperties srcfile="${ivy.dir}/libraries.properties" />
-  
+
   <property name="loglevel" value="default" />
   <property name="ivy.jar" value="${ivy.dir}/ivy-${ivy.version}.jar" />
-  <property name="ivy.repo.url" 
+  <property name="ivy.repo.url"
       value="${mvnrepo}/org/apache/ivy/ivy/${ivy.version}/ivy-${ivy.version}.jar" />
   <property name="ivysettings.xml" value="${ivy.dir}/ivysettings.xml" />
   <property name="build.ivy.dir" location="${build.dir}/ivy" />
@@ -56,9 +56,9 @@
 
   <!-- set the build number based on environment variable, otherwise blank -->
   <property environment="env" description="System environment variables (including those set by Hudson)" />
-  
+
   <target name="all" depends="clean, package" description="Builds jars and packages." />
-  
+
   <target name="ivy-probe-antlib">
     <condition property="ivy.found">
       <typefound uri="antlib:org.apache.ivy.ant" name="cleancache" />
@@ -99,24 +99,33 @@
     <ivy:resolve settingsRef="${ant.project.name}.ivy.settings" log="${loglevel}" />
   </target>
 
+  <target name="ivy-resolve-test" depends="ivy-init" description="Resolve dependencies with Ivy">
+    <ivy:resolve settingsRef="${ant.project.name}.ivy.settings" conf="test" log="${loglevel}" />
+  </target>
+
   <target name="ivy-retrieve" depends="ivy-resolve" description="Retrieve Ivy-managed artifacts.">
     <ivy:retrieve settingsRef="${ant.project.name}.ivy.settings"
         pattern="${build.ivy.lib.dir}/${ivy.artifact.retrieve.pattern}" log="${loglevel}" />
   </target>
-  
+
+  <target name="ivy-retrieve-test" depends="ivy-resolve-test" description="Retrieve Ivy-managed artifacts for test conf.">
+    <ivy:retrieve settingsRef="${ant.project.name}.ivy.settings"
+        pattern="${build.ivy.lib.dir}/${ivy.artifact.retrieve.pattern}" conf="test" log="${loglevel}" />
+  </target>
+
   <available file=".git" type="dir" property="git.present"/>
   <target name="git.info" description="Store git info" if="git.present">
     <exec executable="git" outputproperty="git.commithash" failifexecutionfails="false" errorproperty="">
       <arg value="rev-parse"/>
       <arg value="HEAD"/>
     </exec>
-  
+
     <exec executable="git" outputproperty="git.repo" failifexecutionfails="false" errorproperty="">
       <arg value="config"/>
       <arg value="--get"/>
       <arg value="remote.origin.url"/>
     </exec>
-  
+
     <exec executable="git" outputproperty="git.tag" failifexecutionfails="false" errorproperty="">
       <arg value="describe"/>
       <arg value="--abbrev=0"/>
@@ -130,7 +139,7 @@
     </condition>
     <echo>Git tag found to be ${git.commithash} with tag ${git.tag} from repo ${git.repo}</echo>
   </target>
-  
+
   <target name="clean" description="Delete generated files.">
     <echo message="Deleting generated files in dist" />
     <delete dir="${dist.jar.dir}" />
@@ -143,8 +152,11 @@
     <exec dir="${less.src.dir}" executable="make" failonerror="true">
       <arg value="clean" />
     </exec>
+		<subant target="clean">
+			<fileset dir="unit" includes="build.xml" />
+		</subant>
   </target>
-  
+
   <target name="distclean" depends="clean" description="Delete all generated files.">
     <delete dir="${build.dir}" />
     <delete>
@@ -163,7 +175,7 @@
       <fileset dir="${dust.src.dir}/obj" includes="*.js" />
     </copy>
   </target>
-  
+
   <target name="less" description="Compile Less css files.">
     <!-- Compile LESS to CSS -->
     <delete dir="${dist.less.dir}" />
@@ -173,14 +185,14 @@
       <fileset dir="${less.src.dir}/obj" includes="*.css" />
     </copy>
   </target>
-  
+
   <target name="repo.file" depends="git.info" description="Create a file to reference the git commit">
     <delete file="azkaban.version" />
-    
+
     <tstamp>
       <format timezone="UTC" property="current.time" pattern="yyyy-MM-dd hh:mm zzz"/>
     </tstamp>
-    
+
     <!-- Need to remove indents of the echo contents, because echo into file includes the tabs -->
 <echo file="${version.file}">
 ${git.tag}
@@ -189,19 +201,19 @@ ${git.repo}
 ${current.time}
 </echo>
   </target>
-  
+
   <target name="build" depends="git.info, repo.file, ivy-retrieve" description="Compile main source tree java files">
     <echo>Building Classes</echo>
     <delete dir="${dist.classes.dir}" />
     <mkdir dir="${dist.classes.dir}" />
-    
+
     <!-- copy non-java files to classes dir to load from classpath -->
     <copy todir="${dist.classes.dir}">
       <fileset dir="${java.src.dir}">
         <exclude name="**/*.java" />
       </fileset>
     </copy>
-    
+
     <javac fork="true" destdir="${dist.classes.dir}"
       target="1.6" debug="true" deprecation="false" failonerror="true">
       <src path="${java.src.dir}" />
@@ -211,20 +223,20 @@ ${current.time}
     <antcall target="dust"></antcall>
     <antcall target="less"></antcall>
   </target>
-  
+
   <target name="webmin" description="Copies only the non compiled web resources to dist dir">
     <copy todir="${dist.web.dir}" overwrite="true">
       <fileset dir="${web.src.dir}" />
     </copy>
   </target>
-  
+
   <target name="web" description="Creates web resourses in a dir. Useful for development">
     <mkdir dir="${dist.web.dir}" />
-    
+
     <antcall target="webmin"></antcall>
     <antcall target="dust"></antcall>
     <antcall target="less"></antcall>
-    
+
     <!-- Copy compiled dust templates -->
     <copy todir="${dist.web.dir}/js">
       <fileset dir="${dist.dust.dir}" />
@@ -235,7 +247,7 @@ ${current.time}
       <fileset dir="${dist.less.dir}" />
     </copy>
   </target>
-  
+
   <target name="jars" depends="build" description="Create azkaban jar">
     <mkdir dir="${dist.jar.dir}" />
     <jar destfile="${dist.jar.dir}/azkaban-${git.tag}.jar">
@@ -244,7 +256,7 @@ ${current.time}
       </fileset>
     </jar>
   </target>
-  
+
   <target name="create-update-script" description="Prepare the creation of the Azkaban Scripts">
     <!-- Generic update table scripts -->
     <concat destfile="${dist.sql.package.dir}/update-all-sql-${updateVersion}.sql" fixlastline="yes">
@@ -253,7 +265,7 @@ ${current.time}
       </fileset>
     </concat>
   </target>
-  
+
   <target name="create-update-script-2.1" description="Prepare the creation of the Azkaban Scripts">
     <!-- 2.1 added the active_sla table -->
     <concat destfile="${dist.sql.package.dir}/update-all-sql-2.1.sql" fixlastline="yes">
@@ -263,7 +275,7 @@ ${current.time}
       </fileset>
     </concat>
   </target>
-  
+
   <target name="create-update-script-2.2" description="Prepare the creation of the Azkaban Scripts">
     <!-- 2.2 added the properties table -->
     <concat destfile="${dist.sql.package.dir}/update-all-sql-2.2.sql" fixlastline="yes">
@@ -273,7 +285,7 @@ ${current.time}
       </fileset>
     </concat>
   </target>
-  
+
   <target name="package-sql-scripts" description="Creates a package of sql">
     <delete dir="${dist.sql.package.dir}" />
     <mkdir dir="${dist.sql.package.dir}" />
@@ -284,13 +296,13 @@ ${current.time}
         <exclude name="database.properties"/>
       </fileset>
     </concat>
-    
+
     <!-- Collect various update scripts. -->
     <!-- Not sure how to do this better yet. -->
     <antcall target="create-update-script-2.1"></antcall>
     <antcall target="create-update-script-2.2"></antcall>
     <!-- End script collection-->
-    
+
     <copy todir="${dist.sql.package.dir}" >
       <fileset dir="${sql.src.dir}" />
     </copy>
@@ -310,7 +322,7 @@ ${current.time}
     <mkdir dir="${dist.web.package.dir}/web" />
     <mkdir dir="${dist.web.package.dir}/plugins" />
     <mkdir dir="${dist.web.package.dir}/extlib" />
-      
+
     <!-- Copy Azkaban jars and libs-->
     <copy file="${dist.jar.dir}/azkaban-${git.tag}.jar" todir="${dist.web.package.dir}/lib" />
     <copy todir="${dist.web.package.dir}/lib" >
@@ -318,12 +330,12 @@ ${current.time}
         <exclude name="hadoop-core*.jar"/>
       </fileset>
     </copy>
-    
+
     <!-- Copy bin files for web server only-->
     <copy todir="${dist.web.package.dir}/bin">
       <fileset dir="${web.package.dir}/bin"/>
     </copy>
-    
+
     <!-- Copy web files -->
     <copy todir="${dist.web.package.dir}/web">
       <fileset dir="${web.src.dir}" />
@@ -338,15 +350,15 @@ ${current.time}
     <copy todir="${dist.web.package.dir}/web/css">
       <fileset dir="${dist.less.dir}" />
     </copy>
-    
+
     <!-- Copy conf create table scripts -->
     <copy todir="${dist.web.package.dir}/conf">
       <fileset dir="${web.package.dir}/conf" />
     </copy>
-    
+
     <!-- Copy version file -->
     <copy file="${version.file}" todir="${dist.web.package.dir}" />
-    
+
     <!-- Tarball it -->
     <tar destfile="${dist.web.package.dir}/${name}-web-server-${git.tag}.tar.gz" compression="gzip" longfile="gnu">
       <tarfileset dir="${dist.web.package.dir}" prefix="azkaban-web-${git.tag}" filemode="755" includes="bin/*" />
@@ -356,7 +368,7 @@ ${current.time}
       </tarfileset>
     </tar>
   </target>
-  
+
   <target name="package-exec-server" depends="jars" description="Creates a package for the execserver">
     <delete dir="${dist.exec.package.dir}" />
     <mkdir dir="${dist.exec.package.dir}" />
@@ -365,7 +377,7 @@ ${current.time}
     <mkdir dir="${dist.exec.package.dir}/lib" />
     <mkdir dir="${dist.exec.package.dir}/plugins" />
     <mkdir dir="${dist.exec.package.dir}/extlib" />
-        
+
     <!-- Copy Azkaban jars and libs-->
     <copy file="${dist.jar.dir}/azkaban-${git.tag}.jar" todir="${dist.exec.package.dir}/lib" />
     <copy todir="${dist.exec.package.dir}/lib" >
@@ -373,7 +385,7 @@ ${current.time}
         <exclude name="hadoop-core*.jar"/>
       </fileset>
     </copy>
-    
+
     <!-- Copy bin files for exec server only-->
     <copy todir="${dist.exec.package.dir}/bin" >
       <fileset dir="${exec.package.dir}/bin"/>
@@ -383,10 +395,10 @@ ${current.time}
     <copy todir="${dist.exec.package.dir}/conf" >
       <fileset dir="${exec.package.dir}/conf" />
     </copy>
-    
+
     <!-- Copy version file -->
     <copy file="${version.file}" todir="${dist.exec.package.dir}" />
-    
+
     <!-- Tarball it -->
     <tar destfile="${dist.exec.package.dir}/${name}-executor-server-${git.tag}.tar.gz" compression="gzip" longfile="gnu">
       <tarfileset dir="${dist.exec.package.dir}" prefix="azkaban-executor-${git.tag}" filemode="755" includes="bin/*" />
@@ -396,7 +408,7 @@ ${current.time}
       </tarfileset>
     </tar>
   </target>
-  
+
   <target name="package-solo-server" depends="jars" description="Creates a package for the solo server">
     <delete dir="${dist.solo.package.dir}" />
     <mkdir dir="${dist.solo.package.dir}" />
@@ -406,7 +418,7 @@ ${current.time}
     <mkdir dir="${dist.solo.package.dir}/plugins" />
     <mkdir dir="${dist.solo.package.dir}/extlib" />
     <mkdir dir="${dist.solo.package.dir}/sql" />
-        
+
     <!-- Copy Azkaban jars and libs-->
     <copy file="${dist.jar.dir}/azkaban-${git.tag}.jar" todir="${dist.solo.package.dir}/lib" />
     <copy todir="${dist.solo.package.dir}/lib" >
@@ -414,7 +426,7 @@ ${current.time}
         <exclude name="hadoop-core*.jar"/>
       </fileset>
     </copy>
-    
+
     <!-- Copy bin files for exec server only-->
     <copy todir="${dist.solo.package.dir}/bin" >
       <fileset dir="${solo.package.dir}/bin"/>
@@ -424,28 +436,28 @@ ${current.time}
     <copy todir="${dist.solo.package.dir}/conf" >
       <fileset dir="${solo.package.dir}/conf" />
     </copy>
-    
+
     <!-- Copy web files -->
     <copy todir="${dist.solo.package.dir}/web" >
       <fileset dir="${web.src.dir}" />
     </copy>
-    
+
     <!-- Copy compiled dust templates -->
     <copy todir="${dist.solo.package.dir}/web/js">
       <fileset dir="${dist.dust.dir}" />
     </copy>
-    
+
     <!-- Copy compiled less CSS -->
     <copy todir="${dist.solo.package.dir}/web/css">
       <fileset dir="${dist.less.dir}" />
     </copy>
-    
+
     <!-- Copy sql files -->
     <copy todir="${dist.solo.package.dir}/sql" >
       <fileset dir="${sql.src.dir}" />
     </copy>
     <echo file="${dist.solo.package.dir}/sql/database.properties" append="true">version=${git.tag}</echo>
-    
+
     <!-- Copy version file -->
     <copy file="${version.file}" todir="${dist.solo.package.dir}" />
 
@@ -458,11 +470,17 @@ ${current.time}
       </tarfileset>
     </tar>
   </target>
-  
+
   <target name="package-all" depends="package-exec-server, package-web-server, package-solo-server, package-sql-scripts" description="Create all packages">
   </target>
 
   <target name="package" depends="package-all" description="Create all packages">
   </target>
 
+  <target name="test" depends="jars, ivy-retrieve-test" description="Build and run tests">
+		<subant target="all">
+			<fileset dir="unit" includes="build.xml" />
+		</subant>
+  </target>
+
 </project>

ivy.xml 111(+61 -50)

diff --git a/ivy.xml b/ivy.xml
index 100d96b..d550471 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -1,69 +1,80 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
-  <info 
+  <info
       organisation="com.linkedin.azkaban"
       module="azkaban"
       revision="2.5" status="release">
   </info>
 
+  <configurations defaultconfmapping="default">
+    <conf name="default" extends="master,runtime" />
+    <conf name="master" description="contains the artifact but no dependencies" />
+    <conf name="runtime" extends="compile" description="runtime but not the artifact" />
+
+    <conf name="compile" visibility="private" description="Build dependencies" />
+    <conf name="test" extends="compile" visibility="private" description="Test dependencies" />
+  </configurations>
+
   <dependencies>
     <dependency name="commons-collections" org="commons-collections"
-        rev="${commons-collections.version}" conf="default->master" />
+        rev="${commons-collections.version}" conf="compile->master" />
     <dependency name="commons-configuration" org="commons-configuration"
-        rev="${commons-configuration.version}" conf="default->master" />
-    <dependency name="commons-dbcp" org="commons-dbcp" 
-        rev="${commons-dbcp.version}" conf="default->master" />
-    <dependency name="commons-dbutils" org="commons-dbutils" 
-        rev="${commons-dbutils.version}" conf="default->master" />
+        rev="${commons-configuration.version}" conf="compile->master" />
+    <dependency name="commons-dbcp" org="commons-dbcp"
+        rev="${commons-dbcp.version}" conf="compile->master" />
+    <dependency name="commons-dbutils" org="commons-dbutils"
+        rev="${commons-dbutils.version}" conf="compile->master" />
     <dependency name="commons-email" org="org.apache.commons"
-        rev="${commons-email.version}" conf="default->master" />
+        rev="${commons-email.version}" conf="compile->master" />
     <dependency name="commons-fileupload" org="commons-fileupload"
-        rev="${commons-fileupload.version}" conf="default->master" />
+        rev="${commons-fileupload.version}" conf="compile->master" />
     <dependency name="commons-io" org="commons-io"
-        rev="${commons-io.version}" conf="default->master" />
+        rev="${commons-io.version}" conf="compile->master" />
     <dependency name="commons-jexl" org="org.apache.commons"
-        rev="${commons-jexl.version}" conf="default->master" />
+        rev="${commons-jexl.version}" conf="compile->master" />
     <dependency name="commons-lang" org="commons-lang"
-        rev="${commons-lang.version}" conf="default->master" />
+        rev="${commons-lang.version}" conf="compile->master" />
     <dependency name="commons-logging" org="commons-logging"
-        rev="${commons-logging.version}" conf="default->master" />
-    <dependency name="commons-pool" org="commons-pool" 
-        rev="${commons-pool.version}" conf="default->master" />
+        rev="${commons-logging.version}" conf="compile->master" />
+    <dependency name="commons-pool" org="commons-pool"
+        rev="${commons-pool.version}" conf="compile->master" />
     <dependency name="guava" org="com.google.guava"
-        rev="${guava.version}" conf="default->master" />
-    <dependency name="h2" org="com.h2database" 
-        rev="${h2.version}" conf="default->master" />
-    <dependency name="httpclient" org="org.apache.httpcomponents" 
-        rev="${httpclient.version}" conf="default->master" />
-    <dependency name="httpcore" org="org.apache.httpcomponents" 
-        rev="${httpcore.version}" conf="default->master" />
-    <dependency name="jackson-core-asl" org="org.codehaus.jackson" 
-        rev="${jackson-core.version}" conf="default->master" />
-    <dependency name="jackson-mapper-asl" org="org.codehaus.jackson" 
-        rev="${jackson-mapper.version}" conf="default->master" />
-    <dependency name="jetty" org="org.mortbay.jetty" 
-        rev="${jetty.version}" conf="default->master" />
-    <dependency name="jetty-util" org="org.mortbay.jetty" 
-        rev="${jetty-util.version}" conf="default->master" />
-    <dependency name="joda-time" org="joda-time" 
-        rev="${joda-time.version}" conf="default->master" />
-    <dependency name="jopt-simple" org="net.sf.jopt-simple" 
-        rev="${jopt-simple.version}" conf="default->master" />
-    <dependency name="log4j" org="log4j" 
-        rev="${log4j.version}" conf="default->master" />
-    <dependency name="mail" org="javax.mail" 
-        rev="${mail.version}" conf="default->master" />
-    <dependency name="mysql-connector-java" org="mysql" 
-        rev="${mysql-connector.version}" conf="default->master" />
-    <dependency name="servlet-api" org="javax.servlet" 
-        rev="${servlet-api.version}" conf="default->master" />
-    <dependency name="slf4j-api" org="org.slf4j" 
-        rev="${slf4j-api.version}" conf="default->master" />
-    <dependency name="slf4j-log4j12" org="org.slf4j" 
-        rev="${slf4j-log4j12.version}" conf="default->master" />
-    <dependency name="velocity" org="org.apache.velocity" 
-        rev="${velocity.version}" conf="default->master" />
-    <dependency name="velocity-tools" org="org.apache.velocity" 
-        rev="${velocity-tools.version}" conf="default->master" />
+        rev="${guava.version}" conf="compile->master" />
+    <dependency name="h2" org="com.h2database"
+        rev="${h2.version}" conf="compile->master" />
+    <dependency name="httpclient" org="org.apache.httpcomponents"
+        rev="${httpclient.version}" conf="compile->master" />
+    <dependency name="httpcore" org="org.apache.httpcomponents"
+        rev="${httpcore.version}" conf="compile->master" />
+    <dependency name="jackson-core-asl" org="org.codehaus.jackson"
+        rev="${jackson-core.version}" conf="compile->master" />
+    <dependency name="jackson-mapper-asl" org="org.codehaus.jackson"
+        rev="${jackson-mapper.version}" conf="compile->master" />
+    <dependency name="jetty" org="org.mortbay.jetty"
+        rev="${jetty.version}" conf="compile->master" />
+    <dependency name="jetty-util" org="org.mortbay.jetty"
+        rev="${jetty-util.version}" conf="compile->master" />
+    <dependency name="joda-time" org="joda-time"
+        rev="${joda-time.version}" conf="compile->master" />
+    <dependency name="jopt-simple" org="net.sf.jopt-simple"
+        rev="${jopt-simple.version}" conf="compile->master" />
+    <dependency name="junit" org="junit"
+        rev="${junit.version}" conf="test->default" />
+    <dependency name="log4j" org="log4j"
+        rev="${log4j.version}" conf="compile->master" />
+    <dependency name="mail" org="javax.mail"
+        rev="${mail.version}" conf="compile->master" />
+    <dependency name="mysql-connector-java" org="mysql"
+        rev="${mysql-connector.version}" conf="compile->master" />
+    <dependency name="servlet-api" org="javax.servlet"
+        rev="${servlet-api.version}" conf="compile->master" />
+    <dependency name="slf4j-api" org="org.slf4j"
+        rev="${slf4j-api.version}" conf="compile->master" />
+    <dependency name="slf4j-log4j12" org="org.slf4j"
+        rev="${slf4j-log4j12.version}" conf="compile->master" />
+    <dependency name="velocity" org="org.apache.velocity"
+        rev="${velocity.version}" conf="compile->master" />
+    <dependency name="velocity-tools" org="org.apache.velocity"
+        rev="${velocity-tools.version}" conf="compile->master" />
   </dependencies>
 </ivy-module>
diff --git a/ivy/libraries.properties b/ivy/libraries.properties
index 4beee65..ad9f00d 100644
--- a/ivy/libraries.properties
+++ b/ivy/libraries.properties
@@ -21,6 +21,7 @@ jetty.version=6.1.26
 jetty-util.version=6.1.26
 joda-time.version=2.0
 jopt-simple.version=4.3
+junit.version=4.8
 log4j.version=1.2.16
 mail.version=1.4.5
 mysql-connector.version=5.1.28
diff --git a/src/java/azkaban/database/DataSourceUtils.java b/src/java/azkaban/database/DataSourceUtils.java
index ff40296..a18f1cf 100644
--- a/src/java/azkaban/database/DataSourceUtils.java
+++ b/src/java/azkaban/database/DataSourceUtils.java
@@ -125,6 +125,8 @@ public class DataSourceUtils {
 			super();
 			
 			String url = "jdbc:mysql://" + (host + ":" + port + "/" + dbName);
+			addConnectionProperty("useUnicode","yes");
+			addConnectionProperty("characterEncoding","UTF-8");
 			setDriverClassName("com.mysql.jdbc.Driver");
 			setUsername(user);
 			setPassword(password);
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index c849910..44a3f16 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -1,12 +1,12 @@
 #*
  * Copyright 2012 LinkedIn Corp.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -61,13 +61,15 @@
 									<div id="notification-panel" class="side-panel">
 										<h4>Notify on failure</h4>
 										<p>On a job failure, notify on either the first failure, and/or when the failed flow finishes.</p>
-										<hr>
-										<label class="checkbox-inline">
-											<input id="notify-failure-first" type="checkbox" name="notify" value="first">First failure
-										</label>
-										<label class="checkbox-inline">
-											<input id="notify-failure-last" type="checkbox" name="notify" value="last">Flow finished
-										</label>
+                    <hr>
+                    <div class="btn-group" data-toggle="buttons">
+                      <label class="btn btn-default">
+                        <input id="notify-failure-first" type="radio" name="notify" value="first">First failure
+                      </label>
+                      <label class="btn btn-default">
+                        <input id="notify-failure-last" type="radio" name="notify" value="last">Flow finished
+                      </label>
+                    </div>
 
 										<h4>Failure Emails</h4>
 										<div class="checkbox">
@@ -82,7 +84,7 @@
 										<h4>Success Emails</h4>
 										<div class="checkbox">
 											<label>
-												<input type="checkbox" name="overrideSuccessEmails" value="overrideSuccessEmails">
+												<input type="checkbox" id="override-success-emails" name="overrideSuccessEmails" value="overrideSuccessEmails">
 												Override flow email settings.
 											</label>
 										</div>
@@ -101,7 +103,7 @@
 											<li><strong>Cancel All</strong> immediately kills all jobs and fails the flow.</li>
 											<li><strong>Finish All Possible</strong> will keep executing jobs as long as its dependencies are met.</li>
 										</ul>
-										<select id="failure-action" name="failureAction">
+										<select id="failure-action" name="failureAction" class="form-control form-control-auto">
 											<option value="finishCurrent">Finish Current Running</option>
 											<option value="cancelImmediately">Cancel All</option>
 											<option value="finishPossible">Finish All Possible</option>
@@ -135,7 +137,7 @@
 												<input type="radio" id="pipeline" name="concurrent" value="pipeline">
 												Pipeline
 											</label>
-											<select id="pipelineLevel" name="pipelineLevel">
+											<select id="pipeline-level" name="pipelineLevel" class="form-control form-control-auto input-sm">
 												<option value="1">Level 1</option>
 												<option value="2">Level 2</option>
 											</select>
@@ -178,7 +180,7 @@
 						</div><!-- /modal-body -->
 
 						<div class="modal-footer">
-#if (!$show_schedule || $show_schedule == 'true') 
+#if (!$show_schedule || $show_schedule == 'true')
               <div class="pull-left">
                 <button type="button" class="btn btn-success" id="schedule-btn">Schedule</button>
               </div>
@@ -198,7 +200,7 @@
 				</div><!-- /modal-dialog -->
 			</div><!-- /modal -->
 
-#if (!$show_schedule || $show_schedule == 'true') 
+#if (!$show_schedule || $show_schedule == 'true')
 	#parse ("azkaban/webapp/servlet/velocity/schedulepanel.vm")
 #end
 
diff --git a/src/less/base.less b/src/less/base.less
index 4c7445d..62861ca 100644
--- a/src/less/base.less
+++ b/src/less/base.less
@@ -154,3 +154,7 @@
     margin-bottom: 0;
   }
 }
+
+.form-control-auto {
+  width: auto;
+}
diff --git a/src/package/execserver/bin/azkaban-executor-start.sh b/src/package/execserver/bin/azkaban-executor-start.sh
index b60a13e..9c706f9 100755
--- a/src/package/execserver/bin/azkaban-executor-start.sh
+++ b/src/package/execserver/bin/azkaban-executor-start.sh
@@ -48,5 +48,5 @@ AZKABAN_OPTS="$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmp
 
 java $AZKABAN_OPTS $JAVA_LIB_PATH -cp $CLASSPATH azkaban.execapp.AzkabanExecutorServer -conf $azkaban_dir/conf $@ &
 
-echo $! > currentpid
+echo $! > $azkaban_dir/currentpid
 
diff --git a/src/package/soloserver/bin/azkaban-solo-start.sh b/src/package/soloserver/bin/azkaban-solo-start.sh
index 6beb4ba..5e5a0a3 100755
--- a/src/package/soloserver/bin/azkaban-solo-start.sh
+++ b/src/package/soloserver/bin/azkaban-solo-start.sh
@@ -47,5 +47,5 @@ AZKABAN_OPTS="$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmp
 
 java $AZKABAN_OPTS -cp $CLASSPATH azkaban.webapp.AzkabanSingleServer -conf $azkaban_dir/conf $@ &
 
-echo $! > currentpid
+echo $! > $azkaban_dir/currentpid
 
diff --git a/src/package/webserver/bin/azkaban-web-start.sh b/src/package/webserver/bin/azkaban-web-start.sh
index c2cdd5d..30e8056 100755
--- a/src/package/webserver/bin/azkaban-web-start.sh
+++ b/src/package/webserver/bin/azkaban-web-start.sh
@@ -47,5 +47,5 @@ AZKABAN_OPTS="$AZKABAN_OPTS -server -Dcom.sun.management.jmxremote -Djava.io.tmp
 
 java $AZKABAN_OPTS $JAVA_LIB_PATH -cp $CLASSPATH azkaban.webapp.AzkabanWebServer -conf $azkaban_dir/conf $@ &
 
-echo $! > currentpid
+echo $! > $azkaban_dir/currentpid
 
diff --git a/src/web/js/azkaban/view/flow-execute-dialog.js b/src/web/js/azkaban/view/flow-execute-dialog.js
index ce590ca..ad39ac4 100644
--- a/src/web/js/azkaban/view/flow-execute-dialog.js
+++ b/src/web/js/azkaban/view/flow-execute-dialog.js
@@ -1,12 +1,12 @@
 /*
  * Copyright 2012 LinkedIn Corp.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,40 +23,40 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		"click #schedule-btn": "scheduleClick",
 		"click #execute-btn": "handleExecuteFlow"
 	},
-	
+
 	initialize: function(settings) {
 		this.model.bind('change:flowinfo', this.changeFlowInfo, this);
-		$("#overrideSuccessEmails").click(function(evt) {
+		$("#override-success-emails").click(function(evt) {
 			if ($(this).is(':checked')) {
-				$('#successEmails').attr('disabled', null);
+				$('#success-emails').attr('disabled', null);
 			}
 			else {
-				$('#successEmails').attr('disabled', "disabled");
+				$('#success-emails').attr('disabled', "disabled");
 			}
 		});
-				
-		$("#overrideFailureEmails").click(function(evt) {
+
+		$("#override-failure-emails").click(function(evt) {
 			if ($(this).is(':checked')) {
-				$('#failureEmails').attr('disabled', null);
+				$('#failure-emails').attr('disabled', null);
 			}
 			else {
-				$('#failureEmails').attr('disabled', "disabled");
+				$('#failure-emails').attr('disabled', "disabled");
 			}
 		});
 	},
-	
+
 	render: function() {
 	},
-	
+
 	getExecutionOptionData: function() {
-		var failureAction = $('#failureAction').val();
-		var failureEmails = $('#failureEmails').val();
-		var successEmails = $('#successEmails').val();
-		var notifyFailureFirst = $('#notifyFailureFirst').is(':checked');
-		var notifyFailureLast = $('#notifyFailureLast').is(':checked');
-		var failureEmailsOverride = $("#overrideFailureEmails").is(':checked');
-		var successEmailsOverride = $("#overrideSuccessEmails").is(':checked');
-		
+		var failureAction = $('#failure-action').val();
+		var failureEmails = $('#failure-emails').val();
+		var successEmails = $('#success-emails').val();
+		var notifyFailureFirst = $('#notify-failure-first').is(':checked');
+		var notifyFailureLast = $('#notify-failure-last').is(':checked');
+		var failureEmailsOverride = $("#override-failure-emails").is(':checked');
+		var successEmailsOverride = $("#override-success-emails").is(':checked');
+
 		var flowOverride = {};
 		var editRows = $(".editRow");
 		for (var i = 0; i < editRows.length; ++i) {
@@ -69,18 +69,18 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 				flowOverride[key] = val;
 			}
 		}
-		
+
 		var data = this.model.get("data");
 		var disabledList = gatherDisabledNodes(data);
-		
+
 		var executingData = {
 			projectId: projectId,
 			project: this.projectName,
 			ajax: "executeFlow",
 			flow: this.flowId,
 			disabled: JSON.stringify(disabledList),
-			failureEmailsOverride:failureEmailsOverride,
-			successEmailsOverride:successEmailsOverride,
+			failureEmailsOverride: failureEmailsOverride,
+			successEmailsOverride: successEmailsOverride,
 			failureAction: failureAction,
 			failureEmails: failureEmails,
 			successEmails: successEmails,
@@ -88,19 +88,19 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 			notifyFailureLast: notifyFailureLast,
 			flowOverride: flowOverride
 		};
-		
+
 		// Set concurrency option, default is skip
 
 		var concurrentOption = $('input[name=concurrent]:checked').val();
 		executingData.concurrentOption = concurrentOption;
 		if (concurrentOption == "pipeline") {
-			var pipelineLevel = $("#pipelineLevel").val();
+			var pipelineLevel = $("#pipeline-level").val();
 			executingData.pipelineLevel = pipelineLevel;
 		}
 		else if (concurrentOption == "queue") {
 			executingData.queueLevel = $("#queueLevel").val();
 		}
-		
+
 		return executingData;
 	},
 
@@ -116,44 +116,46 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		var pipelineExecutionId = this.model.get("pipelineExecution");
 		var queueLevel = this.model.get("queueLevel");
 		var nodeStatus = this.model.get("nodeStatus");
-		var overrideSuccessEmails = this.model.get("overrideSuccessEmails");
-		var overrideFailureEmails = this.model.get("overrideFailureEmails");
-		
+		var overrideSuccessEmails = this.model.get("failureEmailsOverride");
+		var overrideFailureEmails = this.model.get("successEmailsOverride");
+
 		if (overrideSuccessEmails) {
-			$('#overrideSuccessEmails').attr('checked', true);
+			$('#override-success-emails').attr('checked', true);
 		}
 		else {
-			$('#successEmails').attr('disabled','disabled');
+			$('#success-emails').attr('disabled','disabled');
 		}
 		if (overrideFailureEmails) {
-			$('#overrideFailureEmails').attr('checked', true);
+			$('#override-failure-emails').attr('checked', true);
 		}
 		else {
-			$('#failureEmails').attr('disabled','disabled');
+			$('#failure-emails').attr('disabled','disabled');
 		}
-		
+
 		if (successEmails) {
-			$('#successEmails').val(successEmails.join());
+			$('#success-emails').val(successEmails.join());
 		}
 		if (failureEmails) {
-			$('#failureEmails').val(failureEmails.join());
+			$('#failure-emails').val(failureEmails.join());
 		}
 		if (failureActions) {
-		$('#failureAction').val(failureActions);
+			$('#failure-action').val(failureActions);
 		}
-		
+
 		if (notifyFailure.first) {
-		$('#notifyFailureFirst').attr('checked', true);
+			$('#notify-failure-first').attr('checked', true);
+			$('#notify-failure-first').parent('.btn').addClass('active');
 		}
 		if (notifyFailure.last) {
-			$('#notifyFailureLast').attr('checked', true);
+			$('#notify-failure-last').attr('checked', true);
+			$('#notify-failure-last').parent('.btn').addClass('active');
 		}
-		
+
 		if (concurrentOption) {
 			$('input[value='+concurrentOption+'][name="concurrent"]').attr('checked', true);
 		}
 		if (pipelineLevel) {
-			$('#pipelineLevel').val(pipelineLevel);
+			$('#pipeline-level').val(pipelineLevel);
 		}
 		if (queueLevel) {
 			$('#queueLevel').val(queueLevel);
@@ -162,25 +164,25 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		if (flowParams) {
 			for (var key in flowParams) {
 				editTableView.handleAddRow({
-					paramkey: key, 
+					paramkey: key,
 					paramvalue: flowParams[key]
 				});
 			}
 		}
 	},
-	
+
 	show: function(data) {
 		var projectName = data.project;
 		var flowId = data.flow;
 		var jobId = data.job;
-		
+
 		// ExecId is optional
 		var execId = data.execid;
 		var exgraph = data.exgraph;
-		
+
 		this.projectName = projectName;
 		this.flowId = flowId;
-		
+
 		var self = this;
 		var loadCallback = function() {
 			if (jobId) {
@@ -190,13 +192,12 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 				self.showExecuteFlow(projectName, flowId);
 			}
 		}
-		
+
 		var loadedId = executableGraphModel.get("flowId");
 		this.loadGraph(projectName, flowId, exgraph, loadCallback);
 		this.loadFlowInfo(projectName, flowId, execId);
-
 	},
-	
+
 	showExecuteFlow: function(projectName, flowId) {
 		$("#execute-flow-panel-title").text("Execute Flow " + flowId);
 		this.showExecutionOptionPanel();
@@ -204,20 +205,20 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		// Triggers a render
 		this.model.trigger("change:graph");
 	},
-	
+
 	showExecuteJob: function(projectName, flowId, jobId, withDep) {
 		sideMenuDialogView.menuSelect($("#flow-option"));
 		$("#execute-flow-panel-title").text("Execute Flow " + flowId);
-		
+
 		var data = this.model.get("data");
 		var disabled = this.model.get("disabled");
-		
+
 		// Disable all, then re-enable those you want.
 		disableAll();
-		
+
 		var jobNode = data.nodeMap[jobId];
 		touchNode(jobNode, false);
-		
+
 		if (withDep) {
 			recurseAllAncestors(jobNode, false);
 		}
@@ -225,61 +226,61 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		this.showExecutionOptionPanel();
 		this.model.trigger("change:graph");
 	},
-	
+
 	showExecutionOptionPanel: function() {
 		sideMenuDialogView.menuSelect($("#flow-option"));
 		$('#execute-flow-panel').modal();
 	},
-	
+
 	hideExecutionOptionPanel: function() {
 		$('#execute-flow-panel').modal("hide");
 	},
-	
+
 	scheduleClick: function() {
 		console.log("click schedule button.");
 		this.hideExecutionOptionPanel();
 		schedulePanelView.showSchedulePanel();
 	},
-	
+
 	loadFlowInfo: function(projectName, flowId, execId) {
 		console.log("Loading flow " + flowId);
 		fetchFlowInfo(this.model, projectName, flowId, execId);
 	},
-	
+
 	loadGraph: function(projectName, flowId, exgraph, callback) {
 		console.log("Loading flow " + flowId);
 		var requestURL = contextURL + "/manager";
-		
+
 		var graphModel = executableGraphModel;
 		// fetchFlow(this.model, projectName, flowId, true);
 		var requestData = {
-				"project": projectName, 
-				"ajax": "fetchflowgraph", 
+				"project": projectName,
+				"ajax": "fetchflowgraph",
 				"flow": flowId
 			};
 		var self = this;
 		var successHandler = function(data) {
 			console.log("data fetched");
 			graphModel.addFlow(data);
-			
+
 			if (exgraph) {
 				self.assignInitialStatus(data, exgraph);
 			}
-			
+
 			// Auto disable jobs that are finished.
 			disableFinishedJobs(data);
 			executingSvgGraphView = new azkaban.SvgGraphView({
-				el: $('#flow-executing-graph'), 
+				el: $('#flow-executing-graph'),
 				model: graphModel,
 				render: false,
-				rightClick: { 
+				rightClick: {
 					"node": expanelNodeClickCallback,
-					"edge": expanelEdgeClickCallback, 
-					"graph": expanelGraphClickCallback 
+					"edge": expanelEdgeClickCallback,
+					"graph": expanelGraphClickCallback
 				},
 				tooltipcontainer: "#svg-div-custom"
 			});
-			
+
 			if (callback) {
 				callback.call(this);
 			}
@@ -291,7 +292,7 @@ azkaban.FlowExecuteDialogView = Backbone.View.extend({
 		// Copies statuses over from the previous execution if it exists.
 		var statusNodeMap = statusData.nodeMap;
 		var nodes = data.nodes;
-		for(var i=0; i<nodes.length; ++i) {
+		for (var i = 0; i<nodes.length; ++i) {
 			var node = nodes[i];
 			var statusNode = statusNodeMap[node.id];
 			if (statusNode) {
@@ -321,23 +322,23 @@ azkaban.EditTableView = Backbone.View.extend({
 
 	initialize: function(setting) {
 	},
-	
+
 	handleAddRow: function(data) {
 		var name = "";
 		if (data.paramkey) {
 			name = data.paramkey;
 		}
-		
+
 		var value = "";
 		if (data.paramvalue) {
 			value = data.paramvalue;
 		}
-	
+
 		var tr = document.createElement("tr");
 		var tdName = document.createElement("td");
 		$(tdName).addClass('property-key');
 		var tdValue = document.createElement("td");
-		
+
 		var remove = document.createElement("div");
 		$(remove).addClass("pull-right").addClass('remove-btn');
 		var removeBtn = document.createElement("button");
@@ -352,28 +353,28 @@ azkaban.EditTableView = Backbone.View.extend({
 		var valueData = document.createElement("span");
 		$(valueData).addClass("spanValue");
 		$(valueData).text(value);
-						
+
 		$(tdName).append(nameData);
 		$(tdName).addClass("editable");
-		
+
 		$(tdValue).append(valueData);
 		$(tdValue).append(remove);
 		$(tdValue).addClass("editable").addClass('value');
-		
+
 		$(tr).addClass("editRow");
 		$(tr).append(tdName);
 		$(tr).append(tdValue);
-	 
+
 		$(tr).insertBefore(".addRow");
 		return tr;
 	},
-	
+
 	handleEditColumn: function(evt) {
 		var curTarget = evt.currentTarget;
-	
+
 		var text = $(curTarget).children(".spanValue").text();
 		$(curTarget).empty();
-					
+
 		var input = document.createElement("input");
 		$(input).attr("type", "text");
 		$(input).addClass('form-control').addClass('input-sm');
@@ -382,12 +383,12 @@ azkaban.EditTableView = Backbone.View.extend({
 		$(curTarget).addClass("editing");
 		$(curTarget).append(input);
 		$(input).focus();
-		
+
 		var obj = this;
 		$(input).focusout(function(evt) {
 			obj.closeEditingTarget(evt);
 		});
-		
+
 		$(input).keypress(function(evt) {
 			if (evt.which == 13) {
 				obj.closeEditingTarget(evt);
@@ -401,7 +402,7 @@ azkaban.EditTableView = Backbone.View.extend({
 		var row = curTarget.parentElement.parentElement;
 		$(row).remove();
 	},
-	
+
 	closeEditingTarget: function(evt) {
 		var input = evt.currentTarget;
 		var text = $(input).val();
@@ -422,7 +423,7 @@ azkaban.EditTableView = Backbone.View.extend({
 			$(remove).append(removeBtn);
 			$(parent).append(remove);
 		}
-		
+
 		$(parent).removeClass("editing");
 		$(parent).append(valueData);
 	}
@@ -433,7 +434,7 @@ azkaban.SideMenuDialogView = Backbone.View.extend({
 	events: {
 		"click .menu-header": "menuClick"
 	},
-	
+
 	initialize: function(settings) {
 		var children = $(this.el).children();
 		for (var i = 0; i < children.length; ++i ) {
@@ -444,25 +445,25 @@ azkaban.SideMenuDialogView = Backbone.View.extend({
 		}
 		this.menuSelect($("#flow-option"));
 	},
-	
+
 	menuClick: function(evt) {
 		this.menuSelect(evt.currentTarget);
 	},
-	
+
 	menuSelect: function(target) {
 		if ($(target).hasClass("active")) {
 			return;
 		}
-		
+
 		$(".side-panel").each(function() {
 			$(this).hide();
 		});
-		
+
 		$(".menu-header").each(function() {
 			$(this).find(".menu-caption").slideUp("fast");
 			$(this).removeClass("active");
 		});
-		
+
 		$(target).addClass("active");
 		$(target).find(".menu-caption").slideDown("fast");
 		var panelName = $(target).attr("viewpanel");
@@ -472,7 +473,7 @@ azkaban.SideMenuDialogView = Backbone.View.extend({
 
 var handleJobMenuClick = function(action, el, pos) {
 	var jobid = el[0].jobid;
-	
+
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowName + "&job=" + jobid;
 	if (action == "open") {
 		window.location.href = requestURL;
@@ -490,7 +491,7 @@ var executableGraphModel;
 var disableFinishedJobs = function(data) {
 	for (var i = 0; i < data.nodes.length; ++i) {
 		var node = data.nodes[i];
-		
+
 		if (node.status == "DISABLED" || node.status == "SKIPPED") {
 			node.status = "READY";
 			node.disabled = true;
@@ -528,7 +529,7 @@ var recurseTree = function(data, disabled, recurse) {
 	for (var i = 0; i < data.nodes.length; ++i) {
 		var node = data.nodes[i];
 		node.disabled = disabled;
-		
+
 		if (node.type == "flow" && recurse) {
 			recurseTree(node, disabled);
 		}
@@ -560,26 +561,26 @@ var touchChildren = function(node, disable) {
 			outNodes[key].disabled = disable;
 		}
 	}
-	
+
 	executableGraphModel.trigger("change:disabled");
 }
 
 var touchAncestors = function(node, disable) {
 	recurseAllAncestors(node, disable);
-	
+
 	executableGraphModel.trigger("change:disabled");
 }
 
 var touchDescendents = function(node, disable) {
 	recurseAllDescendents(node, disable);
-	
+
 	executableGraphModel.trigger("change:disabled");
 }
 
 var gatherDisabledNodes = function(data) {
 	var nodes = data.nodes;
 	var disabled = [];
-	
+
 	for (var i = 0; i < nodes.length; ++i) {
 		var node = nodes[i];
 		if (node.disabled) {
@@ -594,7 +595,7 @@ var gatherDisabledNodes = function(data) {
 			}
 		}
 	}
-	
+
 	return disabled;
 }
 
@@ -623,7 +624,7 @@ var expanelNodeClickCallback = function(event, model, node) {
 	var jobId = node.id;
 	var flowId = executableGraphModel.get("flowId");
 	var type = node.type;
-	
+
 	var menu;
 	if (type == "flow") {
 		var flowRequestURL = contextURL + "/manager?project=" + projectName + "&flow=" + node.flowId;
@@ -632,7 +633,7 @@ var expanelNodeClickCallback = function(event, model, node) {
 				{title: "Collapse Flow...", callback: function() {model.trigger("collapseFlow", node);}},
 				{title: "Open Flow in New Window...", callback: function() {window.open(flowRequestURL);}}
 			];
-	
+
 		}
 		else {
 			menu = [
@@ -679,7 +680,7 @@ var expanelGraphClickCallback = function(event) {
 	console.log("Graph clicked callback");
 	var flowId = executableGraphModel.get("flowId");
 	var requestURL = contextURL + "/manager?project=" + projectName + "&flow=" + flowId;
-	
+
 	var menu = [
 		{title: "Open Flow in New Window...", callback: function() {window.open(requestURL);}},
 		{break: 1},
@@ -688,7 +689,7 @@ var expanelGraphClickCallback = function(event) {
 		{break: 1},
 		{title: "Center Graph", callback: function() {executableGraphModel.trigger("resetPanZoom");}}
 	];
-	
+
 	contextMenuView.show(event, menu);
 }
 
@@ -696,10 +697,10 @@ var contextMenuView;
 $(function() {
 	executableGraphModel = new azkaban.GraphModel();
 	flowExecuteDialogView = new azkaban.FlowExecuteDialogView({
-		el: $('#execute-flow-panel'), 
+		el: $('#execute-flow-panel'),
 		model: executableGraphModel
 	});
-	
+
 	sideMenuDialogView = new azkaban.SideMenuDialogView({
 		el: $('#graph-options')
 	});

unit/build.xml 239(+143 -96)

diff --git a/unit/build.xml b/unit/build.xml
index a13fb6f..76f80bc 100644
--- a/unit/build.xml
+++ b/unit/build.xml
@@ -1,114 +1,161 @@
 <?xml version="1.0"?>
 
 <project name="azkaban-testjob" basedir="." default="all">
-	<property name="base.dir" value="${basedir}/.." />
-	<property name="dist.jar.dir" value="${base.dir}/dist/unit/jars" />
-	<property name="dist.classes.dir" value="${base.dir}/dist/unit/classes" />
-	<property name="dist.packages.dir" value="${base.dir}/dist/unit/packages" />
-	
-	<property name="java.src.dir" value="${base.dir}/unit/java" />
-	<property name="job.conf.dir" value="${base.dir}/unit/executions" />
-  <property name="build.ivy.lib.dir" location="../build/ivy/lib" />
-		
-	<property environment="env" />
-
-	<path id="main.classpath">
+  <property name="base.dir" value="${basedir}/.." />
+  <property name="dist.jar.dir" value="${base.dir}/dist/unit/jars" />
+  <property name="dist.classes.dir" value="${base.dir}/dist/unit/classes" />
+  <property name="dist.packages.dir" value="${base.dir}/dist/unit/packages" />
+
+  <property name="java.src.dir" value="${base.dir}/unit/java" />
+  <property name="job.conf.dir" value="${base.dir}/unit/executions" />
+  <property name="build.ivy.lib.dir" location="${base.dir}/build/ivy/lib" />
+  <property name="build.jar.dir" location="${base.dir}/dist/jars" />
+  <property name="test.output.dir" location="${base.dir}/reports" />
+
+  <property environment="env" />
+
+  <path id="main.classpath">
     <fileset dir="${build.ivy.lib.dir}">
-			<include name="*.jar" />
-		</fileset>
-
-		<pathelement path="${dist.classes.dir}" />
-	</path>
-	
-	<!-- set the build number based on environment variable, otherwise blank -->
-	<property environment="env" description="System environment variables (including those set by Hudson)" />
-
-	<target name="all" depends="clean, package" description="Builds and packages" />
-
-	<target name="clean" description="Delete generated files.">
-		<echo message="Deleting generated files in dist" />
-		<delete dir="${dist.jar.dir}" />
-		<delete dir="${dist.classes.dir}" />
-		<delete dir="${dist.packages.dir}" />
-	</target>
-
-	<target name="build" description="Compile main source tree java files">
-		<delete dir="${dist.classes.dir}" />
-		<mkdir dir="${dist.classes.dir}" />
-		<mkdir dir="${dist.packages.dir}" />
-		
-		<javac fork="true" destdir="${dist.classes.dir}"
-			target="1.6" debug="true" deprecation="false" failonerror="true" srcdir="${java.src.dir}/azkaban/test/executor" includes="SleepJavaJob.java">
-			<classpath refid="main.classpath" />
-		</javac>
-	</target>
-	
-	<target name="jars" depends="build" description="Create azkaban jar">
-		<delete dir="${dist.jar.dir}" />
-		<mkdir dir="${dist.jar.dir}" />
-		<jar destfile="${dist.jar.dir}/test.jar">
-			<fileset dir="${dist.classes.dir}">
-				<include name="**/*.*" />
-			</fileset>
-		</jar>
-	</target>
-	
-	<target name="package-exectest1" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/exectest1.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+      <include name="*.jar" />
+    </fileset>
+    <fileset dir="${build.jar.dir}">
+      <include name="*.jar" />
+    </fileset>
+  </path>
+
+  <!-- set the build number based on environment variable, otherwise blank -->
+  <property environment="env" description="System environment variables (including those set by Hudson)" />
+
+  <target name="all" depends="clean, test" description="Builds and packages" />
+
+  <target name="clean" description="Delete generated files.">
+    <echo message="Deleting generated files in dist" />
+    <delete dir="${dist.jar.dir}" />
+    <delete dir="${dist.classes.dir}" />
+    <delete dir="${dist.packages.dir}" />
+    <delete dir="${test.output.dir}" />
+    <delete dir="${base.dir}/h2dbtest" />
+    <delete dir="${base.dir}/temp" />
+    <delete>
+      <fileset dir="${base.dir}">
+        <include name="azkaban-*.log" />
+        <include name="TestProcess_*" />
+      </fileset>
+    </delete>
+  </target>
+
+  <target name="build" description="Compile main source tree java files">
+    <delete dir="${dist.classes.dir}" />
+    <mkdir dir="${dist.classes.dir}" />
+    <mkdir dir="${dist.packages.dir}" />
+
+    <javac fork="true" destdir="${dist.classes.dir}"
+      target="1.6" debug="true" deprecation="false" failonerror="true">
+      <src path="${java.src.dir}" />
+      <classpath refid="main.classpath" />
+    </javac>
+  </target>
+
+  <target name="jars" depends="build" description="Create azkaban jar">
+    <delete dir="${dist.jar.dir}" />
+    <mkdir dir="${dist.jar.dir}" />
+    <jar destfile="${dist.jar.dir}/test.jar">
+      <fileset dir="${dist.classes.dir}">
+        <include name="**/*.*" />
+      </fileset>
+    </jar>
+  </target>
+
+  <target name="package-exectest1" depends="jars" description="Creates a test zip">
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/exectest1.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/exectest1" />
-		</zip>
-	</target>
+    </zip>
+  </target>
 
-	<target name="package-exectest2" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/exectest2.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+  <target name="package-exectest2" depends="jars" description="Creates a test zip">
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/exectest2.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/exectest2" />
-		</zip>
-	</target>
-	
-	<target name="package-animal" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/animal.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+    </zip>
+  </target>
+
+  <target name="package-animal" depends="jars" description="Creates a test zip">
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/animal.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/animal" />
-		</zip>
-	</target>
-	
-	<target name="package-embedded" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/embedded.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+    </zip>
+  </target>
+
+  <target name="package-embedded" depends="jars" description="Creates a test zip">
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/embedded.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/embedded" />
-		</zip>
-	</target>
+    </zip>
+  </target>
 
-	<target name="package-embedded2" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/embedded2.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+  <target name="package-embedded2" depends="jars" description="Creates a test zip">
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/embedded2.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/embedded2" />
-		</zip>
-  </target>	
-  
+    </zip>
+  </target>
+
   <target name="package-embedded3" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/embedded3.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/embedded3.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/embedded3" />
-		</zip>
-  </target>	
-  
+    </zip>
+  </target>
+
   <target name="package-embeddedBad" depends="jars" description="Creates a test zip">
-		<!-- Tarball it -->
-		<zip destfile="${dist.packages.dir}/embeddedBad.zip">
-			<zipfileset dir="${dist.jar.dir}" />
+    <!-- Tarball it -->
+    <zip destfile="${dist.packages.dir}/embeddedBad.zip">
+      <zipfileset dir="${dist.jar.dir}" />
       <zipfileset dir="${job.conf.dir}/embeddedBad" />
-		</zip>
-  </target>	
-  
+    </zip>
+  </target>
+
   <target name="package" depends="package-exectest1, package-exectest2, package-animal, package-embedded, package-embedded2, package-embedded3, package-embeddedBad" description="Creates all packages">
   </target>
+
+  <target name="test-junit" depends="package" description="Runs JUnit tests.">
+    <mkdir dir="${test.output.dir}" />
+    <junit failureProperty="test.failure" fork="yes" forkmode="once">
+      <classpath>
+        <fileset dir="${build.ivy.lib.dir}">
+          <include name="*.jar" />
+        </fileset>
+        <fileset dir="${build.jar.dir}">
+          <include name="*.jar" />
+        </fileset>
+        <fileset dir="${dist.jar.dir}">
+          <include name="*.jar" />
+        </fileset>
+      </classpath>
+      <formatter type="plain" usefile="false" />
+      <formatter type="xml" />
+      <batchtest todir="${test.output.dir}">
+        <fileset dir="${java.src.dir}" includes="**/*Test*.java" />
+      </batchtest>
+    </junit>
+
+    <junitreport todir="${test.output.dir}">
+      <fileset dir="${test.output.dir}">
+        <include name="TEST-*.xml" />
+      </fileset>
+      <report todir="${test.output.dir}" />
+    </junitreport>
+
+    <fail message="Unit tests failed. See ${test.output.dir} for a summary of test results." if="test.failure" />
+  </target>
+
+  <target name="test" depends="test-junit" description="Runs tests">
+  </target>
 </project>
diff --git a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
index 0de4121..7474c27 100644
--- a/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
+++ b/unit/java/azkaban/test/project/JdbcProjectLoaderTest.java
@@ -35,19 +35,18 @@ import azkaban.utils.PropsUtils;
 
 public class JdbcProjectLoaderTest {
 	private static boolean testDBExists;
-	//@TODO remove this and turn into local host.
-	private static final String host = "rpark-ld.linkedin.biz";
+	private static final String host = "localhost";
 	private static final int port = 3306;
 	private static final String database = "test";
 	private static final String user = "azkaban";
 	private static final String password = "azkaban";
 	private static final int numConnections = 10;
-	
+
 	@BeforeClass
 	public static void setupDB() {
 		DataSource dataSource = DataSourceUtils.getMySQLDataSource(host, port, database, user, password, numConnections);
 		testDBExists = true;
-		
+
 		Connection connection = null;
 		try {
 			connection = dataSource.getConnection();
@@ -86,7 +85,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.query(connection, "SELECT COUNT(1) FROM project_files", countHandler);
 		} catch (SQLException e) {
@@ -95,7 +94,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.query(connection, "SELECT COUNT(1) FROM project_flows", countHandler);
 		} catch (SQLException e) {
@@ -104,7 +103,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.query(connection, "SELECT COUNT(1) FROM project_properties", countHandler);
 		} catch (SQLException e) {
@@ -113,12 +112,12 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		DbUtils.closeQuietly(connection);
-		
+
 		clearDB();
 	}
-	
+
 	private static void clearDB() {
 		if (!testDBExists) {
 			return;
@@ -138,7 +137,7 @@ public class JdbcProjectLoaderTest {
 		QueryRunner runner = new QueryRunner();
 		try {
 			runner.update(connection, "DELETE FROM projects");
-			
+
 		} catch (SQLException e) {
 			e.printStackTrace();
 			testDBExists = false;
@@ -154,7 +153,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.update(connection, "DELETE FROM project_permissions");
 		} catch (SQLException e) {
@@ -172,7 +171,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.update(connection, "DELETE FROM project_flows");
 		} catch (SQLException e) {
@@ -181,7 +180,7 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
+
 		try {
 			runner.update(connection, "DELETE FROM project_properties");
 		} catch (SQLException e) {
@@ -190,11 +189,11 @@ public class JdbcProjectLoaderTest {
 			DbUtils.closeQuietly(connection);
 			return;
 		}
-		
-		
+
+
 		DbUtils.closeQuietly(connection);
 	}
-	
+
 	@Test
 	public void testCreateProject() throws ProjectManagerException {
 		if (!isTestSetup()) {
@@ -205,7 +204,7 @@ public class JdbcProjectLoaderTest {
 		String projectName = "mytestProject";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		Assert.assertTrue("Project Id set", project.getId() > -1);
 		Assert.assertEquals("Project name", projectName, project.getName());
@@ -215,7 +214,7 @@ public class JdbcProjectLoaderTest {
 		Project project2 = loader.fetchProjectById(project.getId());
 		assertProjectMemberEquals(project, project2);
 	}
-	
+
 	@Test
 	public void testRemoveProject() throws ProjectManagerException {
 		if (!isTestSetup()) {
@@ -226,25 +225,25 @@ public class JdbcProjectLoaderTest {
 		String projectName = "testRemoveProject";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		Assert.assertTrue("Project Id set", project.getId() > -1);
 		Assert.assertEquals("Project name", projectName, project.getName());
 		Assert.assertEquals("Project description", projectDescription, project.getDescription());
-		
+
 		Project project2 = loader.fetchProjectById(project.getId());
 		assertProjectMemberEquals(project, project2);
 		loader.removeProject(project, user.getUserId());
-		
+
 		Project project3 = loader.fetchProjectById(project.getId());
 		Assert.assertFalse(project3.isActive());
-		
+
 		List<Project> projList = loader.fetchAllActiveProjects();
 		for (Project proj: projList) {
 			Assert.assertTrue(proj.getId() != project.getId());
 		}
 	}
-	
+
 	@Test
 	public void testAddRemovePermissions() throws ProjectManagerException {
 		if (!isTestSetup()) {
@@ -255,7 +254,7 @@ public class JdbcProjectLoaderTest {
 		String projectName = "mytestProject1";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		Assert.assertTrue("Project Id set", project.getId() > -1);
 		Assert.assertEquals("Project name", projectName, project.getName());
@@ -265,42 +264,42 @@ public class JdbcProjectLoaderTest {
 		loader.updatePermission(project, user.getUserId(), new Permission(0x2), false);
 		loader.updatePermission(project, "group1", new Permission(0x2), true);
 		Assert.assertEquals(perm, project.getUserPermission(user.getUserId()));
-		
+
 		Permission permOverride = new Permission(0x6);
 		loader.updatePermission(project, user.getUserId(), permOverride, false);
 		Assert.assertEquals(permOverride, project.getUserPermission(user.getUserId()));
-		
+
 		Project project2 = loader.fetchProjectById(project.getId());
 		assertProjectMemberEquals(project, project2);
 		Assert.assertEquals(permOverride, project2.getUserPermission(user.getUserId()));
 	}
-	
+
 	@Test
 	public void testProjectEventLogs() throws ProjectManagerException {
 		if (!isTestSetup()) {
 			return;
 		}
-		
+
 		ProjectLoader loader = createLoader();
 		String projectName = "testProjectEventLogs";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		String message = "My message";
 		EventType type = EventType.USER_PERMISSION;
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		loader.postEvent(project, type, user.getUserId(), message);
-		
+
 		List<ProjectLogEvent> events = loader.getProjectEvents(project, 10, 0);
 		Assert.assertTrue(events.size() == 1);
-		
+
 		ProjectLogEvent event = events.get(0);
 		Assert.assertEquals(event.getProjectId(), project.getId());
 		Assert.assertEquals(event.getUser(), user.getUserId());
 		Assert.assertEquals(event.getMessage(), message);
 		Assert.assertEquals(event.getType(), type);
 	}
-	
+
 	@Test
 	public void testFlowUpload() throws ProjectManagerException {
 		ProjectLoader loader = createLoader();
@@ -308,23 +307,23 @@ public class JdbcProjectLoaderTest {
 		String projectName = "mytestFlowUpload1";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
-		
+
 		Flow flow = new Flow("MyNewFlow");
 
 		flow.addNode(new Node("A"));
 		flow.addNode(new Node("B"));
 		flow.addNode(new Node("C"));
 		flow.addNode(new Node("D"));
-		
+
 		flow.addEdge(new Edge("A", "B"));
 		flow.addEdge(new Edge("A", "C"));
 		flow.addEdge(new Edge("B", "D"));
 		flow.addEdge(new Edge("C", "D"));
-		
+
 		flow.initialize();
-		
+
 		loader.uploadFlow(project, 4, flow);
 		project.setVersion(4);
 		Flow newFlow = loader.fetchFlow(project, flow.getId());
@@ -333,7 +332,7 @@ public class JdbcProjectLoaderTest {
 		Assert.assertEquals(flow.getEdges().size(), newFlow.getEdges().size());
 		Assert.assertEquals(flow.getNodes().size(), newFlow.getNodes().size());
 	}
-	
+
 	@Test
 	public void testFlowUploadPlain() throws ProjectManagerException {
 		ProjectLoader loader = createLoader();
@@ -341,23 +340,23 @@ public class JdbcProjectLoaderTest {
 		String projectName = "mytestFlowUpload2";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
-		
+
 		Flow flow = new Flow("MyNewFlow2");
 
 		flow.addNode(new Node("A1"));
 		flow.addNode(new Node("B1"));
 		flow.addNode(new Node("C1"));
 		flow.addNode(new Node("D1"));
-		
+
 		flow.addEdge(new Edge("A1", "B1"));
 		flow.addEdge(new Edge("A1", "C1"));
 		flow.addEdge(new Edge("B1", "D1"));
 		flow.addEdge(new Edge("C1", "D1"));
-		
+
 		flow.initialize();
-		
+
 		loader.uploadFlow(project, 4, flow);
 		project.setVersion(4);
 		Flow newFlow = loader.fetchFlow(project, flow.getId());
@@ -365,11 +364,11 @@ public class JdbcProjectLoaderTest {
 		Assert.assertEquals(flow.getId(), newFlow.getId());
 		Assert.assertEquals(flow.getEdges().size(), newFlow.getEdges().size());
 		Assert.assertEquals(flow.getNodes().size(), newFlow.getNodes().size());
-		
+
 		List<Flow> flows = loader.fetchAllProjectFlows(project);
 		Assert.assertTrue(flows.size() == 1);
 	}
-	
+
 	@Test
 	public void testProjectProperties() throws ProjectManagerException {
 		ProjectLoader loader = createLoader();
@@ -377,7 +376,7 @@ public class JdbcProjectLoaderTest {
 		String projectName = "testProjectProperties";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		project.setVersion(5);
 		Props props = new Props();
@@ -386,14 +385,14 @@ public class JdbcProjectLoaderTest {
 		props.put("c", "cde");
 		props.setSource("mysource");
 		loader.uploadProjectProperty(project, props);
-		
+
 		Props retProps = loader.fetchProjectProperty(project, "mysource");
 
 		Assert.assertEquals(retProps.getSource(), props.getSource());
 		Assert.assertEquals(retProps.getKeySet(), props.getKeySet());
 		Assert.assertEquals(PropsUtils.toStringMap(retProps, true), PropsUtils.toStringMap(props, true));
 	}
-	
+
 	@Test
 	public void testProjectFilesUpload() throws ProjectManagerException {
 		if (!isTestSetup()) {
@@ -404,7 +403,7 @@ public class JdbcProjectLoaderTest {
 		String projectName = "testProjectFilesUpload1";
 		String projectDescription = "This is my new project";
 		User user = new User("testUser");
-		
+
 		Project project = loader.createNewProject(projectName, projectDescription, user);
 		Assert.assertTrue("Project Id set", project.getId() > -1);
 		Assert.assertEquals("Project name", projectName, project.getName());
@@ -413,7 +412,7 @@ public class JdbcProjectLoaderTest {
 		File testDir = new File("unit/project/testjob/testjob.zip");
 
 		loader.uploadProjectFile(project, 1, "zip", "testjob.zip", testDir, user.getUserId());
-		
+
 		ProjectFileHandler handler = loader.getUploadedFile(project, 1);
 		Assert.assertEquals(handler.getProjectId(), project.getId());
 		Assert.assertEquals(handler.getFileName(), "testjob.zip");
@@ -428,7 +427,7 @@ public class JdbcProjectLoaderTest {
 		Assert.assertTrue(handler.getLocalFile() == null);
 		Assert.assertFalse(file.exists());
 	}
-	
+
 	// Custom equals for what I think is important
 	private void assertProjectMemberEquals(Project p1, Project p2) {
 		Assert.assertEquals(p1.getId(), p2.getId());
@@ -439,17 +438,17 @@ public class JdbcProjectLoaderTest {
 		Assert.assertEquals(p1.getVersion(), p2.getVersion());
 		Assert.assertEquals(p1.isActive(), p2.isActive());
 		Assert.assertEquals(p1.getLastModifiedUser(), p2.getLastModifiedUser());
-		
+
 		assertUserPermissionsEqual(p1, p2);
 		assertGroupPermissionsEqual(p1, p2);
 	}
-	
+
 	private void assertUserPermissionsEqual(Project p1, Project p2) {
 		List<Pair<String, Permission>> perm1 = p1.getUserPermissions();
 		List<Pair<String, Permission>> perm2 = p2.getUserPermissions();
-		
+
 		Assert.assertEquals(perm1.size(), perm2.size());
-		
+
 		{
 			HashMap<String, Permission> perm1Map = new HashMap<String, Permission>();
 			for (Pair<String, Permission> p: perm1) {
@@ -461,7 +460,7 @@ public class JdbcProjectLoaderTest {
 				Assert.assertEquals(perm, p.getSecond());
 			}
 		}
-		
+
 		{
 			HashMap<String, Permission> perm2Map = new HashMap<String, Permission>();
 			for (Pair<String, Permission> p: perm2) {
@@ -474,13 +473,13 @@ public class JdbcProjectLoaderTest {
 			}
 		}
 	}
-	
+
 	private void assertGroupPermissionsEqual(Project p1, Project p2) {
 		List<Pair<String, Permission>> perm1 = p1.getGroupPermissions();
 		List<Pair<String, Permission>> perm2 = p2.getGroupPermissions();
-		
+
 		Assert.assertEquals(perm1.size(), perm2.size());
-		
+
 		{
 			HashMap<String, Permission> perm1Map = new HashMap<String, Permission>();
 			for (Pair<String, Permission> p: perm1) {
@@ -492,7 +491,7 @@ public class JdbcProjectLoaderTest {
 				Assert.assertEquals(perm, p.getSecond());
 			}
 		}
-		
+
 		{
 			HashMap<String, Permission> perm2Map = new HashMap<String, Permission>();
 			for (Pair<String, Permission> p: perm2) {
@@ -505,31 +504,31 @@ public class JdbcProjectLoaderTest {
 			}
 		}
 	}
-	
+
 	private ProjectLoader createLoader() {
 		Props props = new Props();
 		props.put("database.type", "mysql");
-		
-		props.put("mysql.host", host);		
+
+		props.put("mysql.host", host);
 		props.put("mysql.port", port);
 		props.put("mysql.user", user);
 		props.put("mysql.database", database);
 		props.put("mysql.password", password);
 		props.put("mysql.numconnections", numConnections);
-		
+
 		return new JdbcProjectLoader(props);
 	}
-	
+
 	private boolean isTestSetup() {
 		if (!testDBExists) {
 			System.err.println("Skipping DB test because Db not setup.");
 			return false;
 		}
-		
+
 		System.out.println("Running DB test because Db setup.");
 		return true;
 	}
-	
+
 	public static class CountHandler implements ResultSetHandler<Integer> {
 		@Override
 		public Integer handle(ResultSet rs) throws SQLException {
@@ -537,8 +536,8 @@ public class JdbcProjectLoaderTest {
 			while (rs.next()) {
 				val++;
 			}
-			
+
 			return val;
 		}
 	}
-}
\ No newline at end of file
+}