%htmlDTD; %globalDTD; %feedDTD; ]>
import static org.easymock.EasyMock.createControl;In this test case I have not recorded any expectations. By default EasyMock mocks will then expect that no invocations can be made to it. This test case will fail with an error (entire stack trace not included)
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
private List vazips = new ArrayList();
private LookupDataServiceImpl svc;
private LookupDao mockdao;
public void setUp() {
vazips.add("20147");
vazips.add("20191");
// create the object to test
svc = new LookupDataServiceImpl();
// create the mock dao
mockdao = createControl().createMock(LookupDao.class);
svc.setLookupDao(mockdao);
}
public void test_NoExpectationsRecorded() {
// - no expectations are recorded
// - WILL FAIL the test since EasyMock requires you to
// - record the expectations
// - Switch to replay mode and run test
replay(mockdao);
// - invoke test
svc.getZipCodes("va");
// - verify WILL NOT GET CALLED...will fail in previous step
verify(mockdao);
}
java.lang.AssertionError:
Unexpected method call getZipCodes("va"):
public void test_CorrectExpectationIsRecorded() {This test case will pass since we have recorded one expectation and the test execution did invoke the expected method, which was verified in the call to verify.
// - One expectations are recorded
mockdao.addZipCode("va", "11122");
// - run test
replay(mockdao);
svc.addZipCode("va", "11122");
// - verify
verify(mockdao);
}
public void test_VerifyReturnData() {Variable vazips holds some hardcoded stub data. We would like that a call to dao.getZipCodes with an argument of statecode=VA return us this test data. The way we do this is in expectation
// - One expectations are recorded
expect(mockdao.getZipCodes("va")).andReturn(vazips);
// - run test
replay(mockdao);
List<String> zipcodes = svc.getZipCodes("va");
for (Iterator<String> iter = zipcodes.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
// - verify
verify(mockdao);
assertTrue(zipcodes.size() == 3);
}
expect(mockdao.getZipCodes("va")).andReturn(vazips);
expect(mockdao.getZipCodes("va")).andThrow(new RuntimeException("mock runtime exception"));
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.lib.legacy.ClassImposteriser;
public void test_VerifyReturnData() {
final String stateCode = "va";
// - record your expectations here
context.checking(new Expectations() {
{
oneOf(mockdao).getZipCodes(stateCode);
will(returnValue(vazips));
}
});
// - Execute test
List<String> zipcodes = svc.getZipCodes("va");
for (Iterator<String> iter = zipcodes.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
// - verify
context.assertIsSatisfied();
Assert.assertTrue(zipcodes.size() == 3);
}
context.checking(new Expectations() {Mockito
{
oneOf(mockdao).addZipCode("12121", "ca");
will(throwException(new RuntimeException("mock runtime exception")));
}
});
import static org.mockito.Mockito.mock;In this case you connect your class to the mock object as before, then simply run your test. After the test is executed you verify that expected methods were called. In this case a call to getZipCodes with an argument of "VA". Change the argument to "CA" and the verification will fail.
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public void setUp() {
vazips.add("20147");
vazips.add("20191");
// create the object to test
svc = new LookupDataServiceImpl();
// create the mock dao
mockdao = mock(LookupDao.class);
svc.setLookupDao(mockdao);
}
public void test_Mock() {
// NOTE: Mockito does not have concept of expectations
// you execute the test with the mock and after the test
// validate any method behaviors you want
// run test method
svc.getZipCodes("VA");
// verify
verify(mockdao).getZipCodes("VA");
}
public void test_VerifyReturnData() {Finally if you were to mock exceptions..
// stubs the return values on the mock dao
when(mockdao.getZipCodes("va")).thenReturn(vazips);
// run test method
List<String> zipcodes = svc.getZipCodes("va");
System.out.println(zipcodes.size());
for (Iterator<String> iter = zipcodes.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
// verify
verify(mockdao).getZipCodes("va");
assertTrue(zipcodes.size() > 0);
}
when(mockdao.getZipCodes("ca")).thenThrow(new RuntimeException("mock runtime exception"));Each has its own advantages but none so ground breaking that one rules over the other. Use what you are comfortable with.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.aver</groupId>
<artifactId>echo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>CXF Echo Service</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<contextPath>/echoservice</contextPath>
<connectors>
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
<port>9090</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/app-context.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-2.0.xsd
cxf.apache.org/jaxws
">cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxws:endpoint id="echoService" implementor="#echo"
address="/EchoService" />
<bean id="echo" class="com.aver.EchoServiceImpl" />
</beans
The wsdl2java command I executed was:./wsdl2java -p com.aver.client -client localhost:9090/echoservice/services/EchoService?wsdl
Run the project using maven: mvn clean package jetty:runInvoking echo...
echo.result=echo: 'uyy ' received on 10-07-2009 10:53:59 PM
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:comp="components.*" initialize="initApp()"> <mx:Script> <![CDATA[ import dto.Movie; import mx.controls.Alert; import mx.collections.ArrayCollection; [Bindable] public var movieList:ArrayCollection; public var moviexml:XML = <movies> <movie> <name>Angels & Demons</name> <rating>1</rating> </movie> <movie> <name>Ice Age 3</name> <rating>2</rating> </movie> <movie> <name>Transformers: Revenge of the Fallen</name> <rating>3</rating> </movie> </movies>; public function initApp():void { movieList = new ArrayCollection(); for each (var movie:XML in moviexml.movie) { var mv:Movie = new Movie(); mv.name = movie.name; mv.rating = movie.rating; movieList.addItem(mv); } movieGrid.initApp(); } ]]> </mx:Script> <mx:VBox width="50%"> <mx:Label color="blue" text="Movies I have seen..."/> <comp:MovieGrid id="movieGrid" movieList="{movieList}" width="100%"/> <comp:addmovieform movieList="{movieGrid.movieList}"/> </mx:VBox> </mx:Application>
package dto { public class Movie { public var name:String; public var rating:Number; public function Movie() { } } }
<?xmlversion="1.0"?>
<mx:Applicationxmlns:mx="http://www.adobe.com/2006/mxml"creationComplete="initApp()">
...
</mx:Application>
<mx:XMLList id="moviexml">
<movie>
<name>Angels&Demons</name>
<rating>1</rating>
</movie>
<movie>
<name>Ice Age3</name>
<rating>2</rating>
</movie>
<movie>
<name>Transformers:Revenge of theFallen</name>
<rating>3</rating>
</movie>
</mx:XMLList>
<mx: Panel title="Movie List" height="100%" width="100%" paddingTop="10" paddingLeft="10" paddingRight="10"> <mx:Label width="100%" color="blue" text="Movies I have seen..."/> <mx: DataGrid id="movieGrid" width="50%" height="50%" rowCount="4" resizableColumns="true" editable="false" > <mx:columns> <mx: DataGridColumn dataField="name" headerText="Name"/> <mx: DataGridColumn dataField="rating" headerText="Rating" /> <mx: DataGridColumn width="40" sortable="false" fontWeight="bold"> <mx:itemRenderer > <mx:Component> // refer to full listing for code here </mx:Component> </mx:itemRenderer> </mx: DataGridColumn> </mx:columns> </mx: DataGrid> <mx: Panel width="359" height="170" layout="absolute"> <mx:Label x="10" y="10" text="Movie Name:"/> <mx:Label x="42" y="36" text="Rating:"/> <mx:TextInput x="96" y="8" id="fldMovieName"/> <mx:TextInput x="96" y="34" width="25" id="fldMovieRating" maxChars="1"/> <mx:Button x="69" y="76" label="Add Movie" click="addMovie()"/> </mx: Panel> </mx: Panel>We define a label and then a data grid to display the XML data. The mxataGridColumn is used to define the columns in this table. Our 3rd column is a special non-data column which will display an X symbol to delete a row. The code for that can be seen in the full listing at the end. You can also see the text form to enter new movies. The mx:Button uses its click property to connect the click event to the addMovie function.
<mx:Script> <![CDATA[ import mx.collections.XMLListCollection; import mx.controls.Alert; [Bindable] private var movieList:XMLListCollection; public function initApp():void { movieList = new XMLListCollection(moviexml); movieGrid.dataProvider = movieList; } private function addMovie():void { if ( fldMovieName.text == "" || fldMovieRating.text == "") { Alert.show("Enter valid values for movie and rating.","Alert"); } else { movieList.addItem( {fldMovieName.text} {fldMovieRating.text} ); } } ]]> </mx:Script>The sample above is quite self-explanatory. The initApp creates a XMLListCollection and references that to the movieGrid.dataProvider.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <head> <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1" /> <title>Test Faceletswebapp</title> </head> <body> <h3><ui:insert name="title">DefaultTitle</ui:insert></h3> <hr /> <p> <ui:insert name="body">Default Body</ui:insert> </p> <br /> <br /> <hr /> </body> </html> |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:t="http://myfaces.apache.org/tomahawk"> <head> <title>Notes</title> </head> <body> <ui:composition template="/template.xhtml"> <ui:define name="title">Facelet works</ui:define> This text will also not be displayed. <ui:define name="body"> <h:form> <h:commandLink value="Display All Notes" action="toNotes"/> </h:form> </ui:define> </ui:composition> </body> </html> |
<?xml version="1.0"encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ">java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.xhtml</param-value> </context-param> <context-param> <param-name>facelets.DEVELOPMENT</param-name> <param-value>true</param-value> </context-param> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> </web-app> |
<?xml version="1.0"encoding="UTF-8"?> <faces-config version="1.2"xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaeejava.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" style="color:teal;">> <application> <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> </application> <navigation-rule> <from-view-id>/index.xhtml</from-view-id> <navigation-case> <from-outcome>toNotes</from-outcome> <to-view-id>/notes.xhtml</to-view-id> </navigation-case> </navigation-rule> </faces-config> |
<?xml version="1.0"encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ">java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>jaxrs</display-name> <servlet> <display-name>jaxrs tryout</display-name> <servlet-name>jaxrsservlet</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jaxrsservlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app> |
package com.tryout; import java.text.SimpleDateFormat; import java.util.Calendar; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @Path("/timeoftheday") public class TimeOfTheDayService { private static String PATTERN = "MM.dd.yyyy HH:mm:ss"; @GET @Produces("text/plain") @Path("/asplaintext/{name}") public String getTimeOfTheDay(@PathParam("name") String name) { SimpleDateFormat df = new SimpleDateFormat(PATTERN); return name + "-" + df.format(Calendar.getInstance().getTime()); } @GET @Produces("application/xml") @Path("/asxml/{name}/") public Time getTimeOfTheDayInXML(@PathParam("name") String name) { SimpleDateFormat df = new SimpleDateFormat(PATTERN); Time t = new Time(); t.setName(name); t.setTime(df.format(Calendar.getInstance().getTime())); return t; } @GET @Produces("application/json") @Path("/asjson/{name}/") public Time getTimeOfTheDayInJSON(@PathParam("name") String name) { SimpleDateFormat df = new SimpleDateFormat(PATTERN); Time t = new Time(); t.setName(name); t.setTime(df.format(Calendar.getInstance().getTime())); return t; } } |
package com.tryout; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "clock") public class Time { @XmlElement private String time; @XmlElement private String name; public void setTime(String time) { this.time = time; } public void setName(String name) { this.name = name; } } |
package com.tryout; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; public class JSONClient { public static void main(String[] args) throws Exception { Client c = Client.create(); // WebResource r = c .resource("http://localhost:8080/jaxrs/services/timeoftheday/asplaintext/mathew"); System.out.println("Plain Text=>> " +r.get(String.class)); // r = c .resource("http://localhost:8080/jaxrs/services/timeoftheday/asxml/mathew"); System.out.println("XML=>> " + r.get(String.class)); // r = c .resource("http://localhost:8080/jaxrs/services/timeoftheday/asjson/mathew"); r.accept("application/json"); System.out.println("JSON=>> " + r.get(String.class)); } } |
ID INT NOT NULL AUTO_INCREMENT,
rcv_dt date,
mbr_nm VARCHAR(100) not null,
chk_nbr VARCHAR(10) not null,
chk_dt date,
pymt_typ VARCHAR(50) not null,
dpst_amt double,
pymt_amt double,
comments VARCHAR(100),
PRIMARY KEY (ID)
<?xml version="1.0"encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
<!-- 1) USE ANNOTATIONS TO CONFIGURE SPRING BEANS --> <context:component-scan base-package="com.batch"/>
<!-- 2)DATASOURCE, TRANSACTION MANAGER AND JDBC TEMPLATE --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"value="com.mysql.jdbc.Driver" /> <property name="url"value="jdbc:mysql://localhost/seamdb" /> <property name="username"value="root" /> <property name="password"value="root" /> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource"ref="dataSource" /> </bean>
<!-- 3) JOBREPOSITORY - WE USE IN-MEMORY REPOSITORY FOR OUR EXAMPLE --> <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"> <property name="transactionManager"ref="transactionManager" /> </bean>
<!-- 4)LAUNCH JOBS FROM A REPOSITORY --> <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository"ref="jobRepository" /> </bean>
<!-- 5) Define the job and its steps. In our case I use one step. Configure its readers and writers --> <batch:job id="simpleJob"> <batch:step id="step1"> <batch:tasklet> <batch:chunk reader="cursorReader"writer="flatFileWriter" commit-interval="1000" /> </batch:tasklet> </batch:step> </batch:job>
<!--======================================================= --> <!-- 6) READER --> <!--======================================================= --> <bean id="cursorReader" class="org.springframework.batch.item.database.JdbcCursorItemReader"> <property name="dataSource"ref="dataSource" /> <property name="sql"value="select * from ledger" /> <property name="rowMapper"ref="ledgerRowMapper" /> </bean>
<!--======================================================= --> <!-- 7) WRITER --> <!--======================================================= --> <bean id="flatFileWriter"class="org.springframework.batch.item.file.FlatFileItemWriter"> <property name="resource"value="file:c:/temp/ledgers-output.txt" /> <property name="lineAggregator"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator"> <property name="delimiter"value="," /> <property name="fieldExtractor"> <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"> <property name="names"value="id,receiptDate,memberName" /> </bean> </property> </bean> </property> </bean> </beans>
|
|
ID INT NOT NULL AUTO_INCREMENT,)
rcv_dt date,
mbr_nm VARCHAR(100) not null,
chk_nbr VARCHAR(10) not null,
chk_dt date,
pymt_typ VARCHAR(50) not null,
dpst_amt double,
pymt_amt double,
comments VARCHAR(100),
PRIMARY KEY (ID)
<?xml version="1.0"encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:batch="http://www.springframework.org/schema/batch" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-2.0.xsd class="MsoNormal" style="margin-bottom: 0.0001pt; line-height: normal;"> http://www.springframework.org/schema/tx www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop www.springframework.org/schema/aop/spring-aop-2.0.xsd class="MsoNormal" style="margin-bottom: 0.0001pt; line-height: normal;"> http://www.springframework.org/schema/batch size="1">www.springframework.org/schema/batch/spring-batch-2.0.xsd http://www.springframework.org/schema/context href="http://www.springframework.org/schema/context/spring-context-2.5.xsd">www.springframework.org/schema/context/spring-context-2.5.xsd" >
<!-- 1) USE ANNOTATIONS TO CONFIGURE SPRING BEANS --> <context:component-scan base-package="com.batch"/>
<!-- 2)DATASOURCE, TRANSACTION MANAGER AND JDBC TEMPLATE --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"value="com.mysql.jdbc.Driver" /> <property name="url"value="jdbc:mysql://localhost/seamdb" /> <property name="username"value="root" /> <property name="password"value="root" /> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManage"> <property name="dataSource"ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource"ref="dataSource" /> </bean>
<!-- 3) JOBREPOSITORY - WE USE IN-MEMORY REPOSITORY FOR OUR EXAMPLE --> <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"> <property name="transactionManager"ref="transactionManager" /> </bean>
<!-- 4)LAUNCH JOBS FROM A REPOSITORY --> <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"> <property name="jobRepository"ref="jobRepository" /> </bean>
<!-- 5) Define the job and its steps. In our case I use onestep. Configure its readers and writers --> <batch:job id="simpleJob"> <batch:listeners> <batch:listener ref="appJobExecutionListener"/> </batch:listeners> <batch:step id="step1"> <batch:tasklet> <batch:listeners> <batch:listener ref="itemFailureLoggerListener"/> </batch:listeners> <batch:chunk reader="itemReader"writer="itemWriter" commit-interval="1000" /> </batch:tasklet> </batch:step> </batch:job>
<!--======================================================= --> <!-- 6) READER --> <!--======================================================= --> <bean id="itemReader"class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource"value="classpath:com/batch/todb/ledger.txt"/> <!--property name="linesToSkip" value="1" /--> <property name="lineMapper"> <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"> <property name="names" value="receiptDate,memberName,checkNumber,checkDate,paymentType,depositAmount,paymentAmount,comments"/> </bean> </property> <property name="fieldSetMapper"ref="ledgerMapper" /> </bean> </property> </bean>
<bean id="inputFile"class="org.springframework.core.io.ClassPathResource"> <constructor-arg value="com/batch/todb/ledger.txt"/> </bean> </beans> |
//======================================================== // Ledger BEAN - Bean representing a single ledger //========================================================
package com.batch.todb;
//======================================================== // Ledger DAO - Used to persist ledgers to the ledger table //======================================================== package com.batch.todb;
//======================================================== // Ledger WRITER - Performs db operations on a given list of ledger objects //======================================================== package com.batch.todb;
//======================================================== // Ledger MAPPER - Maps a set of fields for a single record to the Ledger bean //======================================================== package com.batch.todb;
|
package com.batch.todb; |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util" xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-2.0.xsd
www.springframework.org/schema/tx www.springframework.org/schema/tx/spring-tx-2.0.xsd
www.springframework.org/schema/aop www.springframework.org/schema/aop/spring-aop-2.0.xsd
www.springframework.org/schema/util www.springframework.org/schema/util/spring-util-2.0.xsd
www.springframework.org/schema/batch www.springframework.org/schema/batch/spring-batch-2.0.xsd
www.springframework.org/schema/context
">www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- 1) USE ANNOTATIONS TO CONFIGURE SPRING BEANS -->
<context:component-scan base-package="com.batch" />
<!-- 2) DATASOURCE, TRANSACTION MANAGER AND JDBC TEMPLATE -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/seamdb" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 3) JOB REPOSITORY - WE USE IN-MEMORY REPOSITORY FOR OUR EXAMPLE -->
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager" />
</bean>
<!-- 4) LAUNCH JOBS FROM A REPOSITORY -->
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<!--
5) Define the job and its steps. In our case I use one step. Configure
its readers and writers
-->
<batch:job id="simpleJob">
<batch:step id="step1">
<batch:tasklet>
<batch:chunk reader="cursorReader" writer="flatFileWriter"
commit-interval="1000" />
</batch:tasklet>
</batch:step>
</batch:job>
<!-- ======================================================= -->
<!-- 6) READER -->
<!-- ======================================================= -->
<bean id="cursorReader"
class="org.springframework.batch.item.database.JdbcCursorItemReader">
<property name="dataSource" ref="dataSource" />
<property name="sql" value="select * from ledger" />
<property name="rowMapper" ref="ledgerRowMapper" />
</bean>
<!-- ======================================================= -->
<!-- 7) WRITER -->
<!-- ======================================================= -->
<bean id="flatFileWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:/Users/mathew/temp/ledgers-output.txt" />
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value="," />
<property name="fieldExtractor">
<bean
class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names" value="id,receiptDate,memberName" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>
package com.batch.simpletask;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class HelloTask implements Tasklet {
private String taskStartMessage;
public void setTaskStartMessage(String taskStartMessage) {
this.taskStartMessage = taskStartMessage;
}
public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println(taskStartMessage);
return RepeatStatus.FINISHED;
}
}
package com.batch.simpletask;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class HelloTask implements Tasklet {
private String taskStartMessage;
public void setTaskStartMessage(String taskStartMessage) {
this.taskStartMessage = taskStartMessage;
}
public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
System.out.println(taskStartMessage);
return RepeatStatus.FINISHED;
}
}
package com.batch.simpletask;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StopWatch;
@ContextConfiguration(locations = "classpath:com/batch/simpletask/simpletaskletcontext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SimpleTaskletTestCase extends
AbstractDependencyInjectionSpringContextTests {
private final static Logger logger = Logger
.getLogger(SimpleTaskletTestCase.class);
@Autowired
private JobLauncher launcher;
@Autowired
private Job job;
private JobParameters jobParameters = new JobParameters();
@Before
public void setup() {
PropertyConfigurator
.configure("c:/mathew/springbatch2/src/com/batch/log4j.properties");
}
@Test
public void testLaunchJob() throws Exception {
StopWatch sw = new StopWatch();
sw.start();
launcher.run(job, jobParameters);
sw.stop();
logger.info(">>> TIME ELAPSED:" + sw.prettyPrint());
}
@Autowired
public void setLauncher(JobLauncher bootstrap) {
this.launcher = bootstrap;
}
@Autowired
public void setJob(Job job) {
this.job = job;
}
}
Hello World - the time is nowSpring Batch - Part II - Flat File To Database - Read from a comma separated file and insert 200k rows into a MYSQL database.
Thu Sep 24 21:36:03 EDT 2009
sudo update-alternatives --config javaI could not find a ready package to install jdk1.6 from Sun. After some tinkering around I executed the following (thanks to a great blog at http://fci-h.blogspot.com/2007/02/installing-jdk6-on-ubuntu.html). For your convinence I am repeating the steps (including an extra one to configure javac).
No alternatives for java
sudo update-alternatives --config javaOf course type in java and javac on the command line as a final test. We digressed. A lot of the commands so far and further down use sudo to execute them as root.
mathew@mathew-desktop:~$ sudo update-alternatives --config java
[sudo] password for mathew:
There is only 1 program which provides java
(/usr/lib/jvm/jdk1.6.0_10/jre/bin/java). Nothing to configure.
// setup timer to refresh list automatically
Timer refreshTimer = new Timer() {
public void run() {
refreshWatchList();
}
};
refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
private void refreshWatchList() {
// lazy initialization of service proxy
if (stockPriceSvc == null) {
stockPriceSvc = GWT.create(StockPriceService.class);
}
AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
public void onFailure(Throwable caught) {
// do something with errors
}
public void onSuccess(StockPrice[] result) {
updateTable(result);
}
};
// make the call to the stock price service
stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
}
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee java.sun.com/xml/ns/javaee/web-app_2_5.xsd" style="font-size: 10pt; font-family: "Courier New";"> id="WebApp_ID" version="2.5"> <display-name>springsecurity</display-name> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/application-security.xml /WEB-INF/application-service.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
<%@ include file="includes.jsp"%> <%@ page import="org.springframework.security.ui.AbstractProcessingFilter" %> <%@ page import="org.springframework.security.ui.webapp.AuthenticationProcessingFilter" %> <%@ page import="org.springframework.security.AuthenticationException" %>
<html> <head> <title>Login</title> </head> <body> <% if (session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY) != null) { %> <font color="red"> Your login attempt was not successful, please try again.<BR> <br/> Reason: <%=((AuthenticationException) session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage()%> </font> <% } %>
<form method="post" id="loginForm" action="<c:url value='j_spring_security_check'/>">Username: <input type="text" name="j_username" id="j_username" /> <br /> Password: <input type="password" name="j_password" id="j_password" /><br /> <input type="submit" value="Login" /></form> </body> </html> |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation=" http://www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-2.5.xsd style="font-size: 10pt; font-family: "Courier New";"> http://www.springframework.org/schema/security www.springframework.org/schema/security/spring-security-2.0.xsd" style="font-size: 10pt; font-family: "Courier New"; color: teal;">>
<security:authentication-manager alias="authenticationManager" />
<security:http auto-config="true" access-denied-page="/accessdenied.jsp"> <security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" /> <security:logout logout-success-url="/login.jsp" /> <security:intercept-url pattern="/index.jsp" access="ROLE_ADMIN,ROLE_USER" /> <security:intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> <security:intercept-url pattern="/**" access="ROLE_ANONYMOUS" /> </security:http>
<bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener" />
<security:authentication-provider> <security:password-encoder hash="md5"/> <security:user-service> <security:user password="5f4dcc3b5aa765d61d8327deb882cf99" name="thomasm" authorities="ROLE_USER,ROLE_ANONYMOUS" /> <security:user password="5f4dcc3b5aa765d61d8327deb882cf99" name="admin" authorities="ROLE_ADMIN,ROLE_USER,ROLE_ANONYMOUS" /> </security:user-service> </security:authentication-provider> </beans>
|
<%@ include file="includes.jsp"%> <html> <head> <title>Home</title> </head> <body> You are logged in. To log out click <a href='<c:url value="j_spring_security_logout"/>'>log out</a> <br /> <a href="admin/admin.jsp">admin</a> <br /> <authz:authorize ifAllGranted="ROLE_ADMIN"> <p style="font-weight: bold">This text is only visible to admin users.</p> </authz:authorize>
</body> </html> |
package com.test;
import org.springframework.dao.DataAccessException; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.userdetails.User; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException;
public class CustomUserService implements UserDetailsService { public UserDetails loadUserByUsername(String user) throws UsernameNotFoundException, DataAccessException { User ud = null; if ("admin".equals(user)) { GrantedAuthority[] auths = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMIN"), new GrantedAuthorityImpl("ROLE_USER"), new GrantedAuthorityImpl("ROLE_ANONYMOUS") }; ud = new User(user, "5f4dcc3b5aa765d61d8327deb882cf99", true, true, true, true, auths); } else if ("thomasm1".equals(user)) { GrantedAuthority[] auths = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_USER"), new GrantedAuthorityImpl("ROLE_ANONYMOUS") }; ud = new User(user, "5f4dcc3b5aa765d61d8327deb882cf99", true, true, true, true, auths); } return ud; } } |
<security:authentication-provider user-service-ref="customUserService"> <security:password-encoder hash="md5" /> </security:authentication-provider> |
<global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>Next add the annotations @Secured( {"ROLE_SECRET_AGENT"} ) to your service methods.
package com.test;
import isbnservice.ISBNService_Stub;
import java.rmi.RemoteException;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class ISBNValidator extends MIDlet implements CommandListener {
private EnterISBNNumberForm isbnForm;
public ISBNValidator() {
isbnForm = new EnterISBNNumberForm("ISBN Validator", this);
}
protected void destroyApp(boolean arg0) {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(isbnForm);
}
public void commandAction(Command cmd, Displayable disp) {
if (cmd.getCommandType() == Command.EXIT) {
destroyApp(false);
notifyDestroyed();
} else if (cmd.getLabel().equalsIgnoreCase("Check ISBN")) {
final MIDlet parent = this;
new Thread() {
public void run() {
String result =
validateISBN
(isbnForm.getIsbnNumber());
String msg = "ISBN Validd => " + isbnForm.getIsbnNumber() + ", is ";
ISBNValidatorResultForm resultForm = new ISBNValidatorResultForm(msg, result, (CommandListener) parent);
Display.getDisplay(parent).setCurrent(resultForm);
}
}.start();
} else if (cmd.getLabel().equalsIgnoreCase("Main")) {
Display.getDisplay(this).setCurrent(isbnForm);
}
}
private String validateISBN(String isbn) {
ISBNService_Stub stub = new ISBNService_Stub();
String result = "bad isbn";
if (isbn == null || (isbn.trim().length() != 10 && isbn.trim().length() != 13)) {
return result;
}
try {
if (isbn.trim().length() == 10 && stub.IsValidISBN10(isbn)) {
result = "good isbn";
} else if (isbn.trim().length() == 13 && stub.IsValidISBN13(isbn)) {
result = "good isbn";
}
} catch (RemoteException e) {
e.printStackTrace();
}
return result;
}
}
In the method validateISBN
you can see I do the web service
call. Now you must be guessing how I got the stubs created. Netbeans
has made that easy for us. Right click on the project and select "New
JavaME Web Service Client". Provide the WSDL URL webservices.daehosting.com/services/isbnservice.wso?WSDL and you are done.Android
is a complete open source stack that allows one to build mobile
applications. The stack includes an operating system, middleware and
common applications. It also provides us with a Java API to develop our
own custom mobile applications. It does not discriminate between common
applications vs custom applications. Everything that the common
applications can do so can yours (making calls, sending SMS, etc.).
What gets me excited is the (Linux kernel based) OS. There is no open
source OS on the mobile platform. This is a major plus when it comes to
mobile environments.
Before going into an Android sample, it is obvious to ask...what about Java ME (previously called J2ME). I do not see any reason why there cannot be a Java ME runtime created for Android OS. With that we could continue to use Java ME. Also this would open up use of JavaFX on this platform. As of today I could not locate Java ME implementations. If the reader knows of one please do let me know.
The one concern I had was regarding the Optional API's
in Android. It is a known fact that not all mobile devices are created
equal (in hardware and other related device capabilities). Some devices
will not support a certain feature therefore those API's will not work
on them. This is exactly the reason why Java ME created configuration
and profiles. So now the Android optional API is going to end up in the
same situation as Java ME. Whatever they decide to call it eventually,
there has to be some basic API sets such as Java ME's configuration and
profile. So I do not see any big need to jump ship from Java ME to
Android (other than the new car smell). I do believe that eventually
there will be a Java ME implementation on the Android.
First get yourself a copy of Android from http://code.google.com/android/intro/installing.html. Follow the instructions there or just do the following:
Now create a New Android project in eclipse. It will pop up
Click finish and you should now have your project ready. The wizard has created the following basic application class named HelloApplication.java
package com.hello;
import android.app.Activity;
import android.os.Bundle;
public class HelloApplication extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
}
}
package com.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloApplication extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
TextView tv = new TextView(this);
tv.setText("The ever so happy Hello World program.");
setContentView(tv);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, Now loaded from XML." />
</LinearLayout>
package trial; import
java.util.List; @Autowired @Override |
<?xml version="1.0" encoding="UTF-8"?> <beans
xmlns="http://www.springframework.org/schema/beans" <context:annotation-config
/> <bean
id="ldapContextSource" <bean
id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate"> |
package trial; import
org.junit.Test; @RunWith(SpringJUnit4ClassRunner.class) @Autowired |
<assembly> |
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <configuration> <outputDirectory>target</outputDirectory> <finalName></finalName> <attach>false</attach> </configuration> <executions> <execution> <id>make-source-jar</id> <phase>package</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <configuration> <outputDirectory>target</outputDirectory> </configuration> <executions> <execution> <id>make-javadoc</id> <phase>package</phase> <goals> <goal>javadoc</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <descriptor> src/main/assembly/src.xml </descriptor> </descriptors> </configuration> </plugin> </plugins> </build> |
package
trial; public interface Greeter { public String getGreeting(); } |
package
trial; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class GreeterImpl implements Greeter { @Autowired private Clock clock; public String getGreeting() { return "Good day. The time is now " + clock.getTime(); } } |
package
trial; public interface Clock { public String getTime(); } |
package
trial; import java.util.Calendar; import org.springframework.stereotype.Service; @Service public class ClockImpl implements Clock { @Override public String getTime() { return Calendar.getInstance().getTime().toString(); } } |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-2.5.xsd www.springframework.org/schema/aop www.springframework.org/schema/aop/spring-aop-2.5.xsd www.springframework.org/schema/context ">www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="trial" /> </beans> |
package
trial; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:context.xml" }) public class GreeterTestCase { @Autowired private Greeter greeter; @Test public void testGreeting() { System.out.println(greeter.getGreeting()); } } |
<tx:annotation-driven transaction-manager="txManager"/>
#just print out hello world puts "Hello World" # create a method to print hello world def echo(name) puts "Hello #{name}" end # invoke it echo("Mathew") # define a user info class class User attr_accessor :userName attr_accessor :fullName attr_accessor :otherNames def initialize(userName = "none", fullName = "",otherNames="") @userName = userName @fullName = fullName @otherNames = otherNames greetings = ["hello", "namaste", "gutantag"] for i in 0...greetings.length puts">>" + greetings[i] end end def printOtherNames if @otherNames.nil? puts "[no othernames]" elsif @otherNames.respond_to?("each") @otherNames.eachdo |i| puts #{i} end end end def toString puts "UserName->" + @userName +", FullName->" + @fullName end end # create an instance and call echo e = User.new("mthomas", "mathew thomas", ["guru", "ggg"]) e.toString() # print the attr value puts e.userName puts e.printOtherNames |
HelloWorld Hello Mathew >>hello >>namaste >>gutan tag UserName->mthomas, FullName->mathew thomas mthomas guru ggg |
> svnadmin create \svnrepository
svnrepository
. If you browse to this folder you will see folders/files that subversion uses internally to manage itself.> svn mkdir -m "Creating initial project root folder" file:///svnrepository/myapp1
> svn mkdir -m "Creating initial project root folder" file:///svnrepository/myapp2
myapp1
and myapp2
.
Subversion tracks all of your folders and files, so how you decide to
organize your folder structure is up to you. Recommended structure is:project\
\trunk
\branches
\tags
myapp1\
\trunk
\dev
\web
\service
\test
build.xml
\docs
\branches
\tags
> svn import myapp1 file:///svnrepository/myapp1 -m"initial load of myapp1 code base"
> svn checkout file:///svnrepository/myapp1
Once again you will see a bunch of log messages indicating the files are being checked out.file:///svnrepository/myapp1
.What
if others need to connect to the same repository. One option is toshare
your folder. The better option is to run subversion as a service.You
have various options to do this, but the simplest way is toregister the svnserve executable as a windows service.>
sc create svnserve binpath=
"\"c:\ProgramFiles\Subversion\bin\svnserve.exe\" --service --root
c:\\svnrepository"displayname= "Subversion" depend= tcpip start= auto
file:///svnrepository/myapp1or svn://localhost
. Use svn: only if you have the svnserve executable running./svnrepository/conf/svbserve.conf.
This
file is only valid if you use svnserve executable, as is clearly
mentioned in the comments inside this file. Create the users and their
passwords in the /svnrepository/conf/passwd
file.svn copy svn://hostname/svnrepository/myapp1/trunk \Creating Branches
svn://hostname/svnrepository/myapp1/tags/R1-S1 \
-m "Release1-Sprint1 tag"
svn copy svn://hostname/svnrepository/myapp1/trunk \You will notice that the commands to create a tag is the same as that for creating a branch. Subversion shows no difference in the two. Just remember to use tags to create point-in-time snapshots and branches to track and work on multiple streams of work.
svn://hostname/svnrepository/myapp1/branches/release1.1 \
-m "Creating branch for 1.1."
<html><head> <script type="text/javascript"src="http://jqueryjs.googlecode.com/files/jquery-1.2.2.min.js"></script> <link rel="stylesheet"href="http://dev.jquery.com/view/trunk/themes/flora/flora.all.css"type="text/css" media="screen" title="Flora (Default)"> <script type="text/javascript"src="http://dev.jquery.com/view/trunk/ui/current/ui.tabs.js"></script> <script type="text/javascript"> $(document).ready(function(){ // draw the tabs $("#example > ul").tabs(); // make the ajax call to load the atom xml $.ajax({ url: 'atom.xml', type: 'GET', dataType: 'xml', timeout: 1000, error: function(){ alert('Error loading XML document'); }, success: function(xml){ $(xml).find('entry').find('title').each(function(){ var item_text = $(this).text(); $('<li></li>') .html(item_text) .appendTo('ol').appendTo("#titles"); }); } }); // attach a event handler to ALL links on the page $("a").click(function(){ // change the style of the links to bold $("a").addClass("test"); // effects example...hide the clickme link $("#clickme").hide("slow"); // change the li items to blue color $("#orderedlist > li").addClass("blue"); // print the number of paras and the current time $("#numparas").text("Number of paras " + $("#mycontainer p").size() +". Time is "+ new Date()); return false; }); }); </script> <style type="text/css"> a.test { font-weight: bold; } .blue {color:blue} </style> </head> <body> <a id="clickme"href="http://blogs.averconsulting.com/">Click here top hide meand make the li items blue!!</a> <ul id="orderedlist"> <li>test1</li> <li>test2</li> </ul> <div id="titles"> <b><u>Some of my bloggedtopics...</u></b><br/> </div> <div id="mycontainer"> <p>para1</p> <p>para2</p> <p>para3</p> </div> <div id="numparas"> </div> <div id="example" class="flora"> <ul> <li><ahref="#fragment-1"><span>Tab-1</span></a></li> <li><ahref="#fragment-2"><span>Tab-2</span></a></li> <li><ahref="#fragment-3"><span>Tab-3</span></a></li> </ul> <div id="fragment-1"> Tab-1 content here... </div> <div id="fragment-2"> Tab-2 content here... </div> <div id="fragment-3"> Tab-3 content here... </div> </div> </body> </html> |
Lets say you have a web application that uses some YUI widgets and also some other commercial AJAX widgets. Now these frameworks contains many .js and .css files that need to come down to the browser. To add to this mix you have your own javascript files. Caching these files in the browsers' cache will help performance and give the user a better experience. Also since some of these files are quite large we can consider gzipping them on the server to reduce the payload size.
To get to this analysis I used Yslow Firefox plugin (http://developer.yahoo.com/yslow/). It analyzes the current page and gives it a grade. The application I tested did not get grade 'A' but I cannot control the remaining items (such as CDN). I would strongly recommend folks to use Yslow to at least see where things stand. Also check out http://developer.yahoo.com/performance/rules.html.
First things, I need to cache .js and .css files in the browser cache. I was using Weblogic 8.1 and could not find a configurable way to set expires headers for .js files that are included like:
<script
type="text/javascript"
src="<%=context%>/js/yui/<%=yuiVrsn%>/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="<%=context%>/js/myapp.js?<%=bldVrsn%>"></script>
I put in a version as part of the URL since I do not want to be at the mercy of the users browser settings. If I change the YUI version on the server then it will force the browser to pull down the new file and NOT use any old cached version with the same file name. I follow the same approach for 3rd party files as well as my own (with my own files I use a build version).
If you are using Apache Web Server then there are configuration parameters you can setup to control cache headers and gzipping (using mod_gzip). I could not find a configurable approach in Weblogic 8.1. Either it is hidden deep inside or it just does not exist.
So instead I put in a couple of Servlet filters to do the job for me. First one is a custom servlet filter that is mapped to all .js and .css files. This one simply adds an Expires header with 30 days into the future. I can do 30 days since I know I have control of the URL via version numbers. The second filter is for gzip. I was about to create my own filter when I noticed a gzip filter inside ehcache (net.sf.ehcache.constructs.web.filter.GzipFilter). I looked at the source and it did what I needed so I used it. The main thing that this filter does is to check the 'Accept-Encoding' header in the request to make sure that the client can support gzip and then if that’s good uses the java gzip libraries to compress the contents.
One other thing Yslow reported was to reduce the number of .js files. With framework files such as YUI I cannot control that, but with my own libraries I can. I did merge some utility scripts into a single file. Not a big difference in the end but that’s what tuning is. Getting a little here and there and the grand total adds up fast.
If there are other ways folks have implemented this then please let me know. Remember I am own Weblogic 8.1.
I
am using YUI (version 2.4.1) datatable for tabular data rendering. I
needed to allow the users to have the ability to select one or more rows
using checkboxes and then submit them to the server for further action.
Up to this point you can find sample code on YUI website or other sites
if you search around.
For
a better user experience I also needed to give them links to 'Select
All' and 'Unselect All' which did exactly that. This I was not able to
find on the web (I am sure it is there somewhere). Since I finally
figured it out, I thought it only fair to blog-it so others can find.
Code is quite simple.
In
the sample below I will only highlight the code necessary to make this
happen. For more details on YUI datatable refer to Yahoo site.
To start off lets add a check box to the table. In the sample below the first column is the check box column.
myColumnDefs = [ {key:"checked",label:"", width:"30", formatter:YAHOO.widget.DataTable.formatCheckbox}, {key:"id", label:"ID",sortable:true, resizeable:true, width:"50"}, {key:"name", label:"Name",sortable:true, resizeable:true, width:"250"}, {key:"netamount", label:"Amount",sortable:true,resizeable:true,width:"100", formatter:YAHOO.widget.DataTable.formatCurrency} ]; |
In my case the data is coming via. JSON and I need the default case to be select all. Thus my JSON result set will have checked = true for all rows.
Next here is the code for 'select all' and 'unselect all'. You can be smarter about this and use one function if you care. function selectAll() { records = dataTable.getRecordSet().getRecords(); for (i=0; i < records.length; i++) { dataTable.getRecordSet().updateKey(records[i], "checked", "true"); } dataTable.refreshView(); return false; } function unselectAll() { records = dataTable.getRecordSet().getRecords(); for (i=0; i < records.length; i++) { dataTable.getRecordSet().updateKey(records[i], "checked", ""); } dataTable.refreshView(); return false; } |
<a id="selectall" href="#" onclick="return selectAll();">Select All</a> | <a id="unselectall" href="#" onclick="return unselectAll();">Unselect All</a> |
admin=admin,ROLE_ADMIN testuser=testpassword,ROLE_USER |
<authz:authorizeifAnyGranted="ROLE_ADMIN"> some role specificcontent here </authz:authorize> |
{ "phone":{ "areaCode":703, "number":777000 }, "age":5, "name":"mathew" } |
<root> <phone> <areaCode>703</areaCode> <number>777000</number> </phone> <age>5</age> <name>mathew</name> </root> |
<project name="myproject"
buildafterfailed="true"> <plugin name="clearcase" classname="net.sourceforge.cruisecontrol.sourcecontrols.ClearCase"/> <listeners> <currentbuildstatuslistener file="logs/myproject/status.txt"/> </listeners> <bootstrappers> </bootstrappers> <!-- Defines where cruise looks for changes, to decide whether to run the build --> <modificationset quietperiod="10"> <!--ucm stream="dev" viewpath="C:\projects\dev\myproject" contributors="true"/--> <clearcase branch="dev" viewpath="C:\projects\dev\myproject" recursive="true"/> </modificationset> <!-- Configures the actual build loop, how often and which build file/target --> <schedule interval="1800"> <maven2 mvnscript="C:\tools\maven-2.0.7\bin\mvn.bat" pomfile="C:\projects\dev\myproject\pom.xml" goal="scm:update | clean test"> <property name="VIEW_HOME" value="C:\projects\dev"/> .... other properties to pass to maven ... </maven2> </schedule> <log dir="logs/myproject" encoding="UTF-8"> </log> <publishers> <currentbuildstatuspublisher file="logs/myproject/buildstatus.txt"/> <artifactspublisher dir="checkout/myproject/report" dest="artifacts/myproject"/> <htmlemail mailhost="mailserver.yourcompany.com" returnaddress="buildmanager@yourcompany.com" reportsuccess="fixes" subjectprefix="myproject Build Results" buildresultsurl="http://yourcompany.com:12000/cruisecontrol/buildresults/myproject" skipusers="false" spamwhilebroken="false" css="webapps/cruisecontrol/css/cruisecontrol.css" xsldir="webapps/cruisecontrol/xsl" logdir="logs/myproject"> <success address="devmailinglist@yourcompany.com"/> <failure address="devmailinglist@yourcompany.com"/> </htmlemail> </publishers> </project> |
<scm> <connection>scm:clearcase:load c:/projects/dev</connection> </scm> |
package com.aver; public interface EchoService { public String printback(java.lang.String name); } |
package com.aver; import java.text.SimpleDateFormat; import java.util.Calendar; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; @WebService(name = "EchoService", targetNamespace ="http://www.averconsulting.com/services/EchoService") public class EchoServiceImpl implements EchoService { @WebMethod(operationName = "echo",action = "urn:echo") @WebResult(name = "EchoResult") public String printback(@WebParam(name ="text") String text) { if (text== null || text.trim().length() == 0) { return "echo: -please provide a name-"; } SimpleDateFormat dtfmt = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a"); return "echo: '" + text + "' received on " +dtfmt.format(Calendar.getInstance().getTime()); } } |
<?xmlversion="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTDWeb Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:org/codehaus/xfire/spring/xfire.xml /WEB-INF/xfire-servlet.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>XFireServlet</servlet-name> <servlet-class> org.codehaus.xfire.spring.XFireSpringServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>XFireServlet</servlet-name> <url-pattern>/servlet/XFireServlet/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>XFireServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app> |
<?xmlversion="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans ">www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="webAnnotations" class="org.codehaus.xfire.annotations.jsr181.Jsr181WebAnnotations"/> <bean id="jsr181HandlerMapping"xfire_echo.jar class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping"> <property name="typeMappingRegistry"> <refbean="xfire.typeMappingRegistry" /> </property> <property name="xfire" ref="xfire" /> <property name="webAnnotations" ref="webAnnotations" /> </bean> <bean id="echo"class="com.aver.EchoServiceImpl" /> </beans> |
<ec:EchoRequest>
<ec::Echo>
<ec:Name>Mathew</ec:Name>
</ec:Echo>
</ec:EchoRequest>
<ec:EchoResponse>
<ec:EchoResponse>
<ec:Message>echoback: name Mathew received on 05-06-2007 06:42:08PM</ec:Message>
</ec:EchoResponse>
</ec:EchoResponse>
package echo.service;
public interface EchoService {
public String echo(java.lang.String name);
}
package echo.service;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class EchoServiceImpl implements EchoService {
public String echo(String name) {
if (name == null || name.trim().length() == 0) {
return "echo back: -please provide a name-";
}
SimpleDateFormat dtfmt = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a");
return "echo back: name " + name + " received on "
+ dtfmt.format(Calendar.getInstance().getTime());
}
}
<display-name>Echo Web Service Application</display-name>
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<bean id="echoEndpoint" class="echo.endpoint.EchoEndpoint">
<property name="echoService"><ref bean="echoService"/></property>
</bean>
<bean id="echoService" class="echo.service.EchoServiceImpl"/>
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
<prop key="{http://www.averconsulting.com/echo/schemas}EchoRequest"
>echoEndpoint</prop>
</props>
</property>
<property name="interceptors">
<bean
class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"
/>
</property>
</bean>
<bean id="echo" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
<property name="builder">
<bean
class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
<property name="schema" value="/WEB-INF/echo.xsd"/>
<property name="portTypeName" value="Echo"/>
<property name="locationUri" value="http://localhost:9090/echoservice/"/>
</bean>
</property>
</bean>
package echo.endpoint;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint;
import echo.service.EchoService;
public class EchoEndpoint extends AbstractJDomPayloadEndpoint {
private EchoService echoService;
public void setEchoService(EchoService echoService) {
this.echoService = echoService;
}
protected Element invokeInternal(Element request) throws Exception {
// ok now we have the XML document from the web service request
// lets system.out the XML so we can see it on the console (log4j
// latter)
System.out.println("XML Doc >> ");
XMLOutputter xmlOutputter = new XMLOutputter();
xmlOutputter.output(request, System.out);
// I am using JDOM for my example....feel free to process the XML in
// whatever way you best deem right (jaxb, castor, sax, etc.)
// some jdom stuff to read the document
Namespace namespace = Namespace.getNamespace("ec",
"http://www.averconsulting.com/echo/schemas");
XPath nameExpression = XPath.newInstance("//ec:Name");
nameExpression.addNamespace(namespace);
// lets call a backend service to process the contents of the XML
// document
String name = nameExpression.valueOf(request);
String msg = echoService.echo(name);
// build the response XML with JDOM
Namespace echoNamespace = Namespace.getNamespace("ec",
"http://www.averconsulting.com/echo/schemas");
Element root = new Element("EchoResponse", echoNamespace);
Element message = new Element("Message", echoNamespace);
root.addContent(message);
message.setText(msg);
Document doc = new Document(root);
// return response XML
System.out.println();
System.out.println("XML Response Doc >> ");
xmlOutputter.output(doc, System.out);
return doc.getRootElement();
}
}
This is a simple class. Important point to note is that it extends 'AbstractJDomPayloadEndpoint'. The 'AbstractJDomPayloadEndpoint'
class is a helper that gives you the XML payload as a JDom object.
There are similar classes built for SAX, Stax and others. Most of the
code above is reading the request XML using JDOM API and parsing the
data out so that we may provide it to our echo service for consumption.
MyBigApp |
package com.aver.jmx; |
<target name="compileattr"> |
<bean id="httpConnector" class="com.sun.jdmk.comm.HtmlAdaptorServer" init-method="start"> |
org.apache.log4j.PropertyConfigurator.configureAndWatch( |
org.apache.log4j.PropertyConfigurator.configureAndWatch |
package com.aver.logging; |
<?xml version="1.0" encoding="UTF-8"?> |
cc |
<!DOCTYPE cruisecontrol [ |
<project name="myproject" buildafterfailed="true"> |
<project name="myproject" default="all" basedir="checkout/myproject"> |
somepath/cruisecontrol-bin-2.5/cruisecontrol.sh -cchome somepath/cruisecontrol-bin-2.5 |
package com.aver.service.timer;
|
package com.aver.service.timer; |
package com.aver.web; |
package com.aver.service.timer; |
package com.aver.service.timer; |
package com.aver.web; |
ackage com.aver.web.annotation; |
package com.aver.web; |
.. other stuff ... |
import static java.lang.System.out; |
function Greeter() |
Hello from inline exec! |
package com.aver.web; |
<%@ taglib prefix="ww" uri="/webwork" %> |
<%@ taglib prefix="ww" uri="/webwork" %> |
package com.aver.web; |
<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
|
webwork.objectFactory=spring |
package com.aver.service.timer; |
<?xml version="1.0"?> |
While doing some reading (or catching up) on new features in XSLT 2.0, I came upon a very useful addition. It is possible to implement this in XSLT 1.0, though rather painfully, but it's a lot more intuitive in 2.0. Let's say we have the following XML data with employee bonus amounts:
|
I would like to display an HTML page to summarize each employee's bonus amounts grouped by quarters (q1, q2, etc).
<xsl:for-each-group select="company/employee" group-by="@name"> |
The display (once you fill in the blanks around the XSLT) is:
The java code to transform and print the HTML to the console:
Source xsltSource = new StreamSource(new File("transform.xsl")); |
Last but not the least you will need a XSLT parser that supports XSLT 2.0 and XPATH 2.0. Neither of the specs are final releases yet. But Saxon has an implementation of the pre-release version out for use.
Before I stop I do want to make sure that the reader is 'aware' of how the java runtime 'knows' which XSLT engine to use. Remember JAXP. Well it was created to insulate the developers from the gory implementation details of different vendor implementations. So in our case we may want to use Apache Xalan as our XSLT processor or like in my case I used Saxon's implementation. I used Saxon cause their current release supports XSLT 2.0 on which this blog is based on.
Ok so how do we tell Java to use our choice of XSLT processor? One of three ways in order:
Tapestry
Are there too many web frameworks out there? Well for those in the know the answer will be a resounding yes. Which one to pick up has become really a painful decision! Once you take one path you cannot just switch the framework mid-way. There is always the good old Struts framework. But that seems to be “oh not so fashionable nowadays”. Ah that ‘Ruby on Rails’ … and then you can sift through the web to find its java inspired half-brother. Or should we Seam with JBoss Seam? Though in all fairness JBoss Seam cannot be called just a web framework. It is a complete framework for front-end and back-end development.
Then of course there is the macho-man approach. Roll up your sleeves and write your own web framework. Being an independent consultant I am not too interested in creating my own web framework and leaving the client with an unsupported framework when I leave. So I will leave that option out.
On a brand new project what do you use? In my quest to find that answer through experimentation I decided to give Tapestry a try. I liked what I saw initially; though I got very tired of the .page files. It was possible, in most cases, to reduce them to a bare minimum. And if you are lucky to use JDK 1.5 then annotations come to the rescue. Right at the onset let me tell you one thing; there is a sharp learning curve with Tapestry. But once you get the feel of the framework things become easier and actually fun.
I am going to go through a simple example in this article on how to get up and running with Tapestry. Here are the use cases we will implement:
1. Display Home Page
2. Display current list of products in the Catalog.
3. Add new Product (go back to 2 after successful add).
Lets start with the general project setup. I am using Eclipse 3.2 with JDK 5. My project structure is:
Catalog |-src |-catalog.pages (page classes here) |-catalog.service (backend mock service here) |-META-INF |-WEB-INF |-lib |-web.xml |-*.page files |-*.html files |-catalog.application |
I am using Jetty as my web container. Download Jetty (http://www.mortbay.org) and also install Jetty Launcher (http://jettylauncher.sourceforge.net). Jetty Launcher is an eclipse plug-in that allows you to run (and deploy) your application with Jetty. I leave it up to the reader to do this required setup before proceeding.
Here is the web.xml so you know what it is.
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/TR/xmlschema-1/"
</web-app> |
Now lets get to Tapestry…finally! First forget about all of the other web frameworks and how they do stuff. Forget JSTL, JSF, Struts and all.
Now lets start thinking of our application structure based on our requirements. We need to display a home page with some welcome stuff. Ok lets then create a Home.html page with following contents.
1. <html> 2. <head> 3. <title>Catalog Mania</title> 4. </head> 5. <body> 6. Welcome to <span jwcid="@Insert" value="ognl:message">(some message here)</span>! 8. <br/> 9. <br/> 10. <a href="#" jwcid="@PageLink" page="Catalog">Enter Catalog 11. Mania</a> 12. </body> 13. </html> |
What’s this jwcid thing on line 6? Tapestry calls the above html as a template. It contains both your static and dynamic content. You use special Tapestry decorations, on standard HTML tags, for the dynamic behavior. JWCID stands for Java Web Component ID. Those items in your template that are dynamic are decorated with jwcid notations like in the above example. Here we have made a component out of the span tag by specifying the component type @Insert. Tapestry has many of these component types built-in like @TextField, @TextArea, @For (for loop), etc.
So anything dynamic should be thought of as a component (like the span tag above). Next give it the appropriate component type (@Insert). It is very important you understand how the component paradigm works here. So let me try to summarize it again. The component you attach to the standard HTML tag takes over the responsibility of evaluating what its contents should be at runtime. It is that content which is sent out to the browser.
I need to step a little ahead before explaining the ‘ognl’ stuff. Thus far we have a Home.html. If you open a browser and point to it you will see that it displays correctly. And this is the other power of Tapestry. Pages are pure HTML so the web designer and the java developer can both view the pages in their own working domains. The web designer in his designer tool and the java developer via his servlet container.
Now we need something that will process on the server side events and requests from this Home.html page. Lets write a Home.java.
package catalog.pages;
import org.apache.tapestry.html.BasePage;
public class Home extends BasePage { public String getMessage() { return "Catalog Mania"; } } |
The class extends the Tapestry class BasePage and provides one method getMessage. Now lets jump back to line 6 in the Home.html. The string “value=ognl:message” will at runtime be evaluated to the getMessage class in Home.java. OGNL stands for ‘Object Graph Navigation Language’ and is an open source expression language (like EL in JSTL). Please google-it to get more info.
So to summarize, Home.html is attached to Home.java. Home.html uses ognl to express the desire to call getMessage for the above span-based insert component.
Lastly how does Tapestry know Home.html is connected to Home.java. Nah there is no special default naming convention here. This is done in a Home.page file. I do not like the .page concept one bit. With a large application it will be a pain to maintain so many files but to be fair it serves a purpose and is not a showstopper.
<?xml version="1.0"?> <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <page-specification class="catalog.pages.Home"> </page-specification> |
If you have followed the instructions carefully (including the project structure in eclipse) you should be able to deploy and run this application. Using Jetty that would be.
The app should be available at http://localhost:9090/catalog/app. Note that Tapestry by default will resolve to Home.html if no page is requested. You could also request the same page via http://localhost:9090/catalog/app?service=page&page=Home. But you are better off by not hardcoding such links.
If you look at line 10 of the Home.html we use a built-in Tapestry component @PageLink to link to another Tapestry page, in this case ‘Catalog’. We have not coded that yet. Tapestry will generate the correct links and also do session encoding when necessary.
So we are now passed some basic Tapestry stuff and have displayed the home page as per our requirements. Now the next requirement is to display the list of products in the catalog. We already put a link on line 10 of Home.html to invoke the Catalog page.
So now here is the Catalog.html and Catalog.java.
Catalog.html |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> It is: <strong><span jwcid="@Insert" value="ognl:new java.util.Date()">June 26 2005</span></strong> <br> <a href="#" jwcid="@PageLink" page="Catalog">refresh</a> <br> <hr/> Current Product Catalog<br/> <hr/> <table border="1" BGCOLOR="#FFCC00"> <tr> <th>Name</th> <th>Desc</th> <th>Product release date</th> </tr> <tr jwcid="@For" source="ognl:products" value="ognl:product" element="tr"> <td><span jwcid="@Insert" value="ognl:product.name">name here</span></td> <td><span jwcid="@Insert" value="ognl:product.description">desc</span></td> <td><span jwcid="@Insert" value="ognl:product.releaseDate">1/1/1111</span></td> </tr> <tr jwcid="$remove$"> <td>Books</td> <td>book description</td> <td>1/1/1111</td> </tr> <tr jwcid="$remove$"> <td>Toys</td> <td>toy description</td> <td>1/1/2222</td> </tr> </table> <hr/> <p> <a href="#" jwcid="@PageLink" page="AddProduct">Add New Product</a> </p> </body> </html> |
Two things worth mentioning in Catalog.html. First the use of ‘jwcid="$remove$"’. Like I mentioned previously Tapestry pages can be viewed in a regular browser without a servlet container. In this case obviously none of the Tapestry components are evaluated at runtime but the page being standard HTML will be displayed. In the case of the table above, it will be displayed with two rows (Books and Toys). The ‘remove’ jwcid tells Tapestry to ignore those rows at runtime. Thus the page works fine for the web designer and the java developer. This is not possible with a JSP+JSTL approach.
The other thing worth mentioning is
<tr jwcid="@For" source="ognl:products" value="ognl:product" element="tr">
We use the loop component, @for, to display all of the products returned from Catalog java class. This connection to the java class is denoted by source="ognl:products". The value parameter gives a name to a temporary variable that will hold the current product, as the loop is evaluated. Thus we are able to do the following:
<span jwcid="@Insert" value="ognl:product.description">desc</span>
‘product.description’ will resolve to Catalog.getProduct().getDescription().
Here is Catalog.java.
Catalog.java |
package catalog.pages;
import org.apache.tapestry.html.BasePage; import catalog.service.Product; import catalog.service.ProductService; import catalog.service.ProductServiceImpl;
1. public abstract class Catalog extends BasePage { 2. public abstract Product getProduct(); public abstract void setProduct(Product p);
// hard coded the backend service for now // hmmmm ?? need to see if we can inject this from Spring ?? 3. ProductService service = new ProductServiceImpl();
4. return service.getProducts(); } } |
Line 1: Class is now abstract. Tapestry pools the page classes for reuse. This being the case we have to avoid putting instance variables in the class. That would require us to do cleanup every time the page class is reused. Rather than us doing this we can avoid instance variables and provide abstract getters/setters for interested properties. Tapestry will now take care cleaning up the instance before handing it out for use in a fresh request invocation.
Line 2: We need it so our for loop will work correctly. The value=’ognl:product” uses this instance variable on the page class to store the contents each time it goes through the loop. Why I have no idea? Shows I have still things to learn.
Line 3: Our mock backend product service.
Line 4: source="ognl:products" connects to getProducts on the class.
That’s it. Remember the Catalog.page. Once you have that you can navigate to the following two pages successfully.
Clicking on the link takes you to.
Thus far you may have realized that Tapestry is indeed very different from other frameworks. But it does take some learning effort. But its well worth it.
Our final requirement is to add a new product and redisplay the catalog page (the new product should show up).
Here is AddProduct.html
AddProduct.html |
<html jwcid="@Shell" title="Add Product">
<body jwcid="@Body"> <h1>Add New Product</h1> <form jwcid="form@Form" success="listener:doAddProduct"> <table> <tr> <td> <label jwcid="@FieldLabel" field="component:name">Name</label>: <input jwcid="name@TextField" value="ognl:product.name" validators="validators:required" displayName="User Name" size="30"/> </td> </tr> <tr> <td> Description: <textarea jwcid="description@TextArea" value="ognl:product.description" rows="5" cols="30"/> </td> </tr> <tr> <td> Release Date: <input jwcid="releaseDate@DatePicker" value="ognl:product.releaseDate"/> </td> </tr> </table> <input type="submit" value="Add Project"/> </form>
</body> </html> |
Some of the new components we used here:
Refer to the online documentation at http://jakarta.apache.org/tapestry/tapestry/ComponentReference/Shell.html for more details on shell and also all of the other built-in components.
Here is AddProduct.java
Catalog.java |
package catalog.pages;
import java.util.Date; import org.apache.tapestry.IPage; import org.apache.tapestry.annotations.InjectPage; import org.apache.tapestry.event.PageBeginRenderListener; import org.apache.tapestry.event.PageEvent; import org.apache.tapestry.html.BasePage; import catalog.service.Product; import catalog.service.ProductService; import catalog.service.ProductServiceImpl;
public abstract class AddProduct extends BasePage implements PageBeginRenderListener { ProductService service = new ProductServiceImpl();
@InjectPage("Catalog") public abstract Catalog getCatalogPage();
public abstract Product getProduct();
public abstract void setProduct(Product p);
// from PageBeginRenderListener public void pageBeginRender(PageEvent event) { Product project = new Product(); project.setReleaseDate(new Date()); setProduct(project); }
public IPage doAddProduct() { service.addProduct(getProduct()); return getCatalogPage(); } } |
The method pageBeginRender is from the interface PageBeginRenderListener. Tapestry invokes this method, as the name suggest, before rendering the page. Here we can do apply some default behaviour. For example when the AddProduct.html is displayed we want to provide a default values for the release date field. This is another good example of how Tapestry forces us think of web development from a Object Oriented point of view using these page classes.
Another very important part of the code is:
@InjectPage("Catalog")
public abstract Catalog getCatalogPage();
Remember a page in Tapestry is represented by three files; the .HTML file with the display template, the .java file with the processing logic and the .page file being the glue between the .HTML and .java code. So whenever I say go to another page I meant this logical page represented by the three things mentioned here.
After the doAddProduct method is finished doing its business we would like to return to the Catalog page and display the list of products once again. This is done by injecting the Catalog page into the AddProduct action.
Note: All of what we have talked thus far can be done using JDK 1.4. But wherever we use annotations we would have to enter XML into the .page file instead. |
Once again do not forget the AddProduct.page. Compile and redeploy and you should be able to get to the add product page which when done takes you back to the catalog list page. You should see the new product you just added in the list.
Final Notes:
Did I mention my dislike for the .page files. Maybe it’s just me, but I just think it’s redundant. I reduced my Home.page file to
<page-specification>
</page-specification>
Note I removed the class name attribute. I made sure WEB-INF\catalog.application had the following:
<application>
<meta key="org.apache.tapestry.page-class-packages" value="catalog.pages"/>
</application>
This tells Tapestry where to look for the page classes. Having done this I thought I could get rid of my empty Home.page file above. No luck. As soon as I did that tapestry blew up with an exception.
Using Tapestry involves a steep learning curve and a shift in mindset on how you develop web applications. I personally feel like I have only scratched the surface thus far. In the weeks to come I hope to have a follow-up article on Tapestry using some more of its features and built-in components. And maybe we can even write our own component. Yes that is entirely possible.
Let’s create a java bean class to represent our record with space character separated columns.
… import org.aver.fft.annotations.Column; import org.aver.fft.annotations.Transform; @Transform (spaceEscapeCharacter="_", recordIdValue="88") public class DelimitedBean { ..... @Column(position = 1, required = true) @Column(position = 2, required = true) @Column(position = 3, required = true) @Column(position = 4, required = true) @Column(position = 5, required = true) @Column(position = 6, required = true) @Column(position = 7, required = true) public @Column(position = 8, required = true, format = "MMddyyyy") ... other methods here ... } |
As you can see we use Java 5.0 annotations to mark our record format. By default the parser sets itself up to parse character separated columns and the delimiter is space.
@Transform (spaceEscapeCharacter="_", recordIdValue="88") |
By default the parser is setup to parse character-separated columns. The attribute spaceEscapeCharacter indicates the character used to represent spaces within column data. The parser can replace that with space before loading it into your java object. The recordIdValue identifies the value of the key column. The transformer keeps an internal mapping of the key value to the java bean class that represents it. By default the first column is the key column. You can change that by passing in parameter recordIdColumn for character separated columns or using recordStartIdColumn / recordEndIdColumn for fixed length columns. By default the column separator is space for character. You can change that using columnSeparator.
That’s enough on defining the file format. Now here is how to actually read it.
Transformer spec = TransformerFactory.getTransformer(new Class[] { DelimitedBean.class }); String line = "88 Mathew_Thomas 4111111111111111 02 2008 12.89 222 10212005"; DelimitedBean bean = (DelimitedBean) spec.loadRecord(line); |
You get a transformer instance as shown above. Pass it an array of all classes that represent your various records and that uses annotations as defined above. Now you have a fully loaded bean from which to read your data. That’s all.
Now lets see how you define the same for a fixed column record format. The parsing code above stays the same. The difference is in how you annotate your result bean class.
… import org.aver.fft.annotations.Column; import org.aver.fft.annotations.Transform; @Transform(spaceEscapeCharacter
= "_", columnSeparatorType = Transformer.ColumnSeparator.FIXLENGTH,
recordIdStartColumn = 1, recordIdEndColumn = 2, recordIdValue=”88”) @Column(position= 1, start = 1, end = 2, required = true) @Column(position= 2, start = 3, end = 15, required = true) @Column(position= 3, start = 16, end = 31, required = true) @Column(position= 4, start = 32, end = 33, required = true) @Column(position= 5, start = 34, end = 37, required = true) @Column(position= 6, start = 38, end = 43, required = true) @Column(position= 7, start = 44, end = 46, required = true) public String getCardSecurityCode() { @Column(position= 8, start = 47, end = 54, required = true, format = "MMddyyyy") … other methods here … } |
The parsing logic stays the same. Just give it the correct line of data.
Now I will show you the SAX-like parsing approach.
package org.aver.fft; public class DelimitedFullFileReaderTestCase extends TestCase { class Listener implements RecordListener { public void unresolvableRecord(String rec) { } |
Unit Testing Woes
While unit testing is expected to be an integral part of every
development effort, it is often not given its due importance during
planning stages. When you derive the LOE (level of effort) for
development tasks do you make sure to include unit testing as part of
that calculation.
Let’s start with the question “what is unit testing”?
It is the effort of selecting a unit of code and writing an independent set of code that exercises one or more features of that unit.
You can define the unit as either a class or a set of classes that work together to deliver a certain feature. I would not waste time writing unit test for every java class you create. Select a logical unit of work that suites your needs (and project schedule) and write tests against that. Make sure the test exercises your code and not integration with other developers’ units. It is a good idea to write integration test cases but start with your own unit first. If you have dependencies with other components you can choose the strategy of mocking those components. This way you are free to test your code and do not get bogged down by other components. Let’s say you are testing a backend component that accepts credit card information from users, does some validations on the user data, stores it in the database and then submits the transaction to a credit card authorization provider. Obviously you are not interested in testing the authorization provider’s code and you may not even have access to that provider during development. You can write a few classes and mock out the provider or write a simple emulator to emulate the actions of the provider. Later on, as part of your developer integration testing, you can swap in the real providers test environment for more thorough testing.
Often the importance of unit testing is not understood. During a recent project status meeting, for a release that I was not actively involved in, the build manager asked “should we run the automated unit tests that used to run every night during the previous release?” Everyone stared at each other with a silence and then there were some quiet shaking of the heads to say “no”. I was shocked to see this reaction. The team for the current release was not writing a single new test case and on top of that had the boldness to choose not to run any of the tests that already existed. The unit test execution was completely automated. You add your unit test case class name to an XML file and that’s it. It would be included in the nightly build and test cycle. Come next morning and you have a neat HTML page with the complete test results. Finally it was decided that once development finishes everyone can spend a week and write test cases. I disagree with this approach too, since it comes a little too late. It was disappointing to say the least.
When experienced developers make this choice, how do we convince everyone else about the importance of unit testing? Let’s start with some questions you can ask yourself. How do you know if the “thing” you coded works? How do you test this “thing” everyday to make sure it still works? How does someone else make sure that the “thing” continues to work long after you, the original developer, are off the project? The answer is obvious. Write unit tests.
Some of the challenges often encountered in projects are:
Remember that in spite of unit tests, defects will show up during testing. On a recent project the QA lead went on to make this an issue. Often these kinds of statements come from immature managers/leads that really have very little software development process background. Or they are just plain ignorant. Getting total coverage is impossible given the schedules for many fast-paced projects.
So what are some of the options for us Java developers to write unit tests? There are many options but I will cover the following in brief:
JUnit
JUnit has been around for a while now. It is a simple framework that allows one to write Java test classes. Your classes follow a certain convention in naming the test methods.
import junit.framework.*;
public class MyTestCase extends TestCase {
protected void setUp() {
.. set up test data ...
}
protected void tearDown() {
}
public void testAddVisaTransaction() {
}
public void testAddMasterCardTransaction() {
}
}
You can easily integrate the execution of the tests in an Ant script. You can use the optional JUnit and JUnitReport ant tasks to execute the unit tests and produce a nice HTML report of the test results. Setting this up should not take you more than a day. Plug this into your nightly build cycle and you have a simple yet immensely powerful automated unit testing execution strategy. Time spent on this is time well spent.
TestNG
TestNG is a nice little framework that takes a slightly different approach in the way you write your test classes. Suffice it to say it’s less intrusive (that is if you can call JUnit intrusive). With TestNG you do not extend any framework classes nor do you have to name your tests methods in any particular format. You use annotations (either JDK 5.0 annotations or javadoc style annotations if you are using JDK 1.4.x). Let’s see an example with JDK 5.0 annotations.
import org.testng.annotations.*;
public class MyTestCase {
@Configuration(beforeTestClass = true)
public void setUp() {
.. set up test data ...
}
@Test(groups = { "mygroup" })
public void testAddVisaTransaction(){
}
@Test(groups = { "mygroup" })
public void testAddMasterCardTransaction() {
}
}
Personally I like this approach better. With the introduction of annotations in JDK 5.0 this style is definitely going to become the preferred approach. Note you can name the above methods with any name you choose. I simply ported the previous JUnit test to TestNG.
Custom framework using JDK 5.0 Annotations
I would suggest you stick with TestNG, but if you want to create your own framework its not too hard now. Annotations are probably the most important new feature in JDK 5. While I have seen some examples of code with annotations which almost drove me up the wall, in most cases it is a much calmer experience.
Let’s create a set of custom annotations to create a simple test framework. We will create the following annotations
1. @TestCase – will be used to mark a class as a test case. Will allow the tester to give a description to the test case.
2. @Setup – Used to mark one or more methods as set up methods.
3. @Test – Used to designate a method as a test method to execute.
First we will create the @TestCase annotation. This will be used to mark the class as a test case and provide some useful description of the test class.
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface TestCase { String description(); } |
· Import the annotation java classes from java.lang.annotation.
· @interface – keyword is used to mark the, otherwise regular, java class as an annotation definition class.
· The annotation we are creating is itself annotated with meta-annotations.
· @Retention(RetentionPolicy.RUNTIME) – these annotations can be read at run-time.
· @Target(ElementType.METHOD) - this annotation only applies to methods.
Next we will create the @Setup annotation. This one is used to mark the methods that are set up in nature. These will run before any of the tests are executed.
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Setup { } |
Finally we will create our @Test annotation that marks individual methods as test methods which are to be executed.
Import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { } |
Now let’s use these annotations in a sample test case class.
import com.unittest.*;
@TestCase (description="My test case description.") public class MyTestCase {
} |
I am sure you will agree that the entire exercise so far is not complicated in any way. I am using the latest Eclipse 3.1 to write this code. Eclipse 3.1 supports building custom annotations and it will invoke the annotation compiler for you. Now to create the test harness that will execute the tests. The class java.lang.Class has been updated in JDK 5.0 to support annotations. You will see that in the next sample code.
import java.lang.reflect.*; import com.unittest.*;
public class TestRunner { public static void main(String[] args) throws Exception {
} } |
That’s it we are done. Pass the test case class name to the TestRunner and you will see your test executing. To keep this simple I have not included any exception handling to any of the code above.
Conclusion
There are many options to build your unit testing strategy. Pick any one that suites your needs but just use something.
One
project I worked on management told us to build “a zero defect system”.
I laughed because knowing the schedule and chaos on the project this
was as good as finding a real superman and then asking him for a ride to
the moon.
So what is
Code Quality? Is it code which produces very low number of defects or is
it code that is well documented or code that has a good design behind
it or is it a measure of how well you have unit tested it or is the
pretty/well-formatted looking code.
Quality code can be achieved by following a few basic principles:
Quality Code is code that has a well thought of reason for its very existence, is backed by a solid design, is testable, has repeatable unit tests, is self-documenting and has extensibility built into its very core.
To achieve good code quality everyone has to play their part.
The
next time you check in some code without design or unit testing realize
that you have just checked in code whose quality is suspect.
Your value is not just in getting the work done but it’s also in how well you approach your work. And quality will follow.
The goal of this article is to introduce the reader to the concepts and theory behind the knowledge management process and how intelligent software agents can help to manage the knowledge management process.
Before we get into the crux of this article it is important to understand why we even discuss this topic. Various types of Decision support systems (DSS) are widely used in enterprises today. Organizations might need an EIS (enterprise information system) that not only caters to the needs of management but also serves the needs of the rest of the enterprise. Needs are obviously defined by what role each person plays. For example an EIS can be used to check the demand and match it closely to the supply or perform forecasting based on trends or patterns in the data. The validity of the results from this process is dependent on the knowledge that was used to come to this result.
The key to the success of a DSS system is to have access to a reliable, valid and growing knowledge base. Human actors will often enter knowledge into the knowledge base directly. But that may not be feasible or many times not possible. Here is where intelligent software agents can help.
Intelligent Software agents, in the context of knowledge management, are automated software modules that act on behalf of the knowledge management system to automatically collect knowledge, validate it, organize it and then add it to the knowledge base.
Typical production databases are transactional in nature. They can be seen as the database of operations, where all business transactions take place. Orders are placed, inventory is tracked, and customers are managed among other activities. Here the onus is on managing data and the challenge is to have efficient and reliable access to this data. Data is often organized into tables to form meaningful information.
But there is a parallel requirement in many large enterprises to have a different view of the data. A view that is used by upper management (and others) to track sales, to forecast trends (like demand and supply), to trouble shoot specific performance problems, etc. Here the onus is not on pure data. Instead what is needed is to consolidate the information from the many databases spread across the organization and bring them together to provide what we term knowledge. Knowledge is giving meaning or more substance to data, so that decisions can be made using this knowledge.
Knowledge is typically collected and organized into a knowledge base (similar to how data is stored in databases). Typically these knowledge bases could also be located as data warehouses and data marts and they are separate from the operational databases. In fact the latter should be requirement for your knowledge base. Some of the activities that are run on a knowledge base can be very intense and could slow down your already fully loaded operational database.
Typically the knowledge base is another DBMS that caters purely to the knowledge management subsystem. This could be an RDBMS (like Oracle, DB2) or you can even use XML enabled databases.
Knowledge management is the process of collecting, rearranging and validating data to produce knowledge. Knowledge can be gathered from various sources like
Knowledge can be gathered from any of the above sources (maybe from all too). Here is a simple checklist to keep in mind when gathering knowledge. Assume the system is getting a new feed of data, which is to be entered into the knowledge base. In a good knowledge management process…
An important function of a knowledge management process is to continuously grow its knowledge base (following the process we outlined above). An inference engine will use this knowledge to provide value to a client. An inference engine is only as good as its backend knowledge base.
Very often it is not feasible to leave it to human agents to enter data into the knowledge base. Sometimes the data may be so large that this is not possible. Or the information may arrive periodically at predefined times or maybe it follows no time schedules. Here is where we can use intelligent software agents.
Intelligent Software agents are independent autonomous software programs that gather knowledge by following the process we outlined earlier. In doing so they require no help from human agents. The process is completely automated. They are termed as intelligent because they possess all process information on how to intelligently read incoming information and convert it to knowledge to be stored into the knowledge base. These agents can be written in various programming languages such as C, C++, Java, etc. They can also be implemented using newer technologies such as Web Services.
Agents can be one of two types; static agents or mobile agents. Lets discuss each in some detail and also how they can be used in the knowledge management process. There are other classifications of software agents. But for the purposes of this paper we will concentrate on static and mobile classifications only.
Static Agents
Static agents are called so based on the fact that they do not move or relocate themselves from the computer that started them. If a particular computer starts a static agent then the agent will continue to run on that very computer throughout its lifecycle.
The life cycle of a static agent can be better understood using the diagram below.
Initially there are no agents in the knowledge management system. The agents come to life either when the first knowledge gathering task is initiated or a pre-configured number of agents can be set up to be in a pool of free agents. When a new knowledge task arrives either an available agent from the pool is allocated or a new one is created.
When the agent is running, it is at that point that the knowledge process we outlined earlier is applied. When the agent finishes execution it is added back to the pool of available agents. Keeping a pool such as this can be useful in improving the reliability and scalability of the knowledge management system.
Next question we need to ask is how are these agents called by clients. There are many ways that this can be done. But lets discuss one very innovative method. Today Web Services is the “in-thing” in the tech world. Beyond the hype, this technology is extremely viable for implementing static intelligent agents. Web Services allow us to expose interfaces on existing or new business objects as available to our business partners.
Our static agent could be designed as a J2EE (or .NET) object running on a remote server. This object though private to the server exposes some of its interfaces using the web services suite of protocols (SOAP, WSDL, UDDI). Clients will typically call one of the interfaces on our web services. Clients who need to feed in data can do so using appropriate web service interfaces. They would provide the data as an XML document that has a predefined XML Schema. Due to the use of XML Schema there is naturally a strict adherence to data formats and some amount of data validation is applied right here at this step. This can save an enormous amount of computing space on the server and can better facilitate the knowledge validation process.
Static agents can also be of a reactive kind. Wherein they react to new data that is added into a database or new data that an application server receives. In such cases the application server or database can spawn a reactive static agent and delegate to it the task knowledge processing. Or these agents can run in the background always waiting for new data to come in. Once they detect new data they read it and run the knowledge gathering process to move it into a separate knowledge base. Very often though knowledge creation is done at predefined times, maybe once a day. During that time slot the agent will read the operational database and process the new or updated data. For large amounts of highly volatile data this can be very beneficial approach.
Static agents are especially well suited for data-mining tasks. Typically data mining involves wading through large amounts of data in a data warehouse or data mart to find patterns in existing knowledge. Agents can perform these background tasks based on either predefined time intervals or maybe it is triggered by the arrival of certain data or simply when a request for knowledge access comes in.
This is a very innovative field of research. Ironically what makes it so innovative is also a reason why this technology is not in common use.
Mobile agents are software modules that do not necessarily stay on the server that initiated them. Simply put these agents travel. Say we start an agent on one computer (the parent). To perform its work the agent needs to communicate to a remote server. The agent might start performing some of its duties on the parent computer and then decide to move from the parent towards the remote server. In doing so it might decide to travel the network and move ever so close to the remote server. Finally once it finishes its task it will notify the remote server or it may even destroy itself. The parent can at all times send messages to the agents, such as control messages.
Some agents might interact with other static or mobile agents to perform its task. Some may even spawn additional sub-agents to delegate some tasks. At all times the agent maintains a reference to the parent server.
One would ask how could this be of any use in a knowledge management system. The answer is simple. It depends on what type of information your knowledge base is tracking. Lets say we have an enterprise with a large globally distributed computing facility. The network is so complex that it has become difficult to track what is happening on this network. It has become difficult to collect performance and security related information. And we need to periodically have this knowledge added to a knowledge base, so that we can later analyze and maybe even predict network performance.
We can create a mobile agent that will roam our network, moving from one node to the other and always collecting network performance statistics as it roams. Periodically the mobile agent can send the information back to the parent, which can then add it to the knowledge base. The agent can communicate with the network elements using SNMP. Based on this simple yet realistic example you can see the power behind mobile agents.
Due to the mobility of these agents they may be limited to gathering data only and maybe performing some initial validations on it. Once they gather data they can call a static agent on the parent to perform the remaining tasks. It is important that the mobile agent keep doing its main task, which is to keep moving and gathering new data.
Limitations of Mobile Agents
Mobile agents face many challenges among them are security concerns, what if someone tampers with the agent runtime code, how does the agent find a suitable platform from which to execute as it moves, how does the parent know that the data it is receiving is from the agent and not from some other malicious agent, how does the parent know if a child agent is still alive, what if the agent looses ability to communicate to the parent, etc.
Conclusion
Automated knowledge management using intelligent software agents is very much a reality. The advances in newer technologies such as J2EE, Web Services, .NET allow us to create more reliable, scalable and secure intelligent agents. Static agents are more common compared to mobile agents. But as was discussed earlier mobile agents are definitely useful in certain types of applications.
Resources
For information on XML Schema refer to http://www.w3.org/XML/Schema
ADK for mobile agent development http://develop.tryllian.com/
IBM's Aglet mobile agent development
Voyager from http://www.recursionsw.com/
Software Agents on Distributed Knowledge Management Systems (DKMS Brief No. Three, July 30, 1998