Technical blog

April 29, 2009

[How to] Make custom search with Nutch(v 1.0)?

Filed under: nutch, search — Tags: , — paawak @ 20:41

What is Nutch?

Nutch is an open source web crawler + search engine based on Lucene. These are a few things that make it great:

  1. Open source
  2. Has a web-crawler which understands and indexes html, rtf and pdf format + all links that it might encounter
  3. Its search engine is based on Lucene
  4. Has a plugin based architecture, which means we can have our own plugins for indexing and searching, without a single line of code change to the core nutch.jar
  5. Uses Hadoop for storing indexes, so its pretty scalable

Use case

Suppose we want to search for the author of the website by his email id.

First things first: lets index our custom data

Before we can search for our custom data, we need to index it. Nutch has a plugin architecture very similar to that of Eclipse. We can write our own plugin for indexing. Here is the source code:

 
package com.swayam.nutch.plugins.indexfilter;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.nutch.crawl.CrawlDatum;
import org.apache.nutch.crawl.Inlinks;
import org.apache.nutch.indexer.IndexingException;
import org.apache.nutch.indexer.IndexingFilter;
import org.apache.nutch.indexer.NutchDocument;
import org.apache.nutch.indexer.lucene.LuceneWriter;
import org.apache.nutch.parse.Parse;
 
/**
 *@author paawak
 */
public class EmailIndexingFilter implements IndexingFilter {
 
    private static final Log LOG = LogFactory.getLog(EmailIndexingFilter.class);
 
    private static final String KEY_CREATOR_EMAIL = "email";
 
    private Configuration conf;
 
 
    public NutchDocument filter(NutchDocument doc, Parse parse, Text url,
            CrawlDatum datum, Inlinks inlinks) throws IndexingException {
 
        // look up email of the author based on the url of the site
        String creatorEmail = EmailLookup.getCreatorEmail(url.toString());
 
        LOG.info("######## creatorEmail = " + creatorEmail);
 
        if (creatorEmail != null) {
            doc.add(KEY_CREATOR_EMAIL, creatorEmail);
        }
 
        return doc;
    }
 
    public void addIndexBackendOptions(Configuration conf) {
 
        LuceneWriter.addFieldOptions(KEY_CREATOR_EMAIL, LuceneWriter.STORE.YES,
                LuceneWriter.INDEX.TOKENIZED, conf);
 
    }
 
    public Configuration getConf() {
        return conf;
    }
 
    public void setConf(Configuration conf) {
        this.conf = conf;
    }
 
}

Also, you need to create a plugin.xml:

 
<plugin id="index-email" name="Email Indexing Filter" version="1.0.0"
  provider-name="swayam">
 
  <runtime>
    <library name="EmailIndexingFilterPlugin.jar">
      <export name="*" />
    </library>
  </runtime>
 
  <requires>
    <import plugin="nutch-extensionpoints" />
  </requires>
 
  <extension id="com.swayam.nutch.plugins.indexfilter.EmailIndexingFilter"
    name="Email Indexing Filter"
    point="org.apache.nutch.indexer.IndexingFilter">
    <implementation id="index-email"
      class="com.swayam.nutch.plugins.indexfilter.EmailIndexingFilter" />
  </extension>
 
</plugin>

This done, create a new folder in the $NUTCH_HOME/plugins and put the jar and the plugin.xml there.

Now we have to activate this plugin. To do this, we have to edit the conf/nutch-site.xml.

<property>
  <name>plugin.includes</name>
  <value>nutch-extensionpoints|protocol-http|parse-(text|html)|index-(basic|email)|query-(basic|site|url)</value>
  <description>Regular expression naming plugin id names to
  include.  Any plugin not matching this expression is excluded.
  In any case you need at least include the nutch-extensionpoints plugin. By
  default Nutch includes crawling just HTML and plain text via HTTP,
  and basic indexing and search plugins.
  </description>
</property>

Now, how do I search my indexed data?

Option 1 [cumbersome]:

Add my own query plugin:

package com.swayam.nutch.plugins.queryfilter;
 
import org.apache.nutch.searcher.FieldQueryFilter;
 
/**
 *@author paawak
 */
public class MyEmailQueryFilter extends FieldQueryFilter {
 
    public MyEmailQueryFilter() {
        super("email");
    }
 
}

Do not forget to edit the plugin.xml.

<plugin
   id="query-email"
   name="Email Query Filter"
   version="1.0.0"
   provider-name="swayam">
 
   <runtime>
      <library name="EmailQueryFilterPlugin.jar">
         <export name="*"/>
      </library>
   </runtime>
 
   <requires>
      <import plugin="nutch-extensionpoints"/>
   </requires>
 
   <extension id="com.swayam.nutch.plugins.queryfilter.MyEmailQueryFilter"
              name="Email Query Filter"
              point="org.apache.nutch.searcher.QueryFilter">
      <implementation id="query-email"
                      class="com.swayam.nutch.plugins.queryfilter.MyEmailQueryFilter">
        <parameter name="fields" value="email"/>
      </implementation>
 
   </extension>
 
</plugin>

This line is particularly important:

<parameter name=”fields” value=”email”/>

If you skip this line, you will never be able to see this in search results.

The only catch here is you have to prepend the keyword email: to the search key. For example, if you want to search for jsmith@mydomain.com, you have to search for email:jsmith@mydomain.com or email:jsmith.

There is an easier and more elegant way :), read on…

Option 2 [smart]

Use the existing query-basic plugin.

This involves editing just one file: conf/nutch-default.xml.

In the default distribution, you can see some commented lines like this:

<!--
<property>
  <name>query.basic.description.boost</name>
  <value>1.0</value>
  <description> Declares a custom field and its boost to be added to the default fields of the Lucene query.
  </description>
</property>
-->

All you have to do is un-comment them and put your custom field, email, in our case in place of description. The resulting fragment will look like:

<property>
  <name>query.basic.email.boost</name>
  <value>1.0</value>
  <description> Queries the author of the site by his email-id
  </description>
</property>

With this while looking for jsmith@mydomain.com, you can simply enter jsmith@mydomain.com or a part the name like jsmit.

Building a Nutch plugin

The preferred way is by ant, but I have used maven with the following dependencies:

<project>
...
	<dependencies>
		...
		<!-- nutch -->
		<dependency>
			<groupId>org.apache.lucene</groupId>
      		<artifactId>lucene-core</artifactId>     
      		<version>2.4.0</version>
      		<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.lucene</groupId>
      		<artifactId>lucene-misc</artifactId>     
      		<version>2.4.0</version>
      		<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.nutch</groupId>
      		<artifactId>nutch</artifactId>     
      		<version>1.0</version>
      		<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
      		<artifactId>taglibs-i18n</artifactId>     
      		<version>1.0.N20030822</version>
      		<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.tika</groupId>
			<artifactId>tika</artifactId>
			<version>0.1-incubating</version>	
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>xerces</groupId>
			<artifactId>xerces</artifactId>
			<version>2.6.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>xerces</groupId>
			<artifactId>xerces-apis</artifactId>
			<version>2.6.2</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.jets3t.service</groupId>
			<artifactId>jets3t</artifactId>
			<version>0.6.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>oro</groupId>
  			<artifactId>oro</artifactId>
  			<version>2.0.8</version>
  			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.ibm.icu</groupId>
  			<artifactId>icu4j</artifactId>
  			<version>4.0.1</version>
  			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
  			<artifactId>hadoop-core</artifactId>
  			<version>0.19.1</version>
  			<scope>provided</scope>
		</dependency>			
		<dependency>
			<groupId>org.apache.solr</groupId>
  			<artifactId>solr-common</artifactId>
  			<version>1.3.0</version>
  			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.solr</groupId>
  			<artifactId>solrj</artifactId>
  			<version>1.3.0</version>
  			<scope>provided</scope>
		</dependency>
		<!-- end nutch -->
	    ...
	</dependencies>
...
</project>

Useful links

Be warned that these are a bit out dated, so they may not be correct verbatim.

April 18, 2009

Jar Browser

Filed under: java — Tags: — paawak @ 18:42

This is a Swing app to look for a class, package or any resource in a set of given jar/zip files.

Use case

I have got an ugly NoClassDef exception and I suspect the class to be present in some 5 or so odd jars present in a certain folder. I Enter the class name in the text box and point the probable jars form AddJars… and click Search. The matches are shown in the tree view.

How to run?

You need to have JDK1.6 or above. If you want to run it with 1.5, compile the sources with 1.5.

On Linux: java -jar JarBrowser.jar

On Windows: just double click on the JarBrowser.jar or use the same command.

Find the binaries here and the source here.

JarBrowser

April 17, 2009

A simple wait notify example

Filed under: thread — Tags: , — paawak @ 16:21

At times we often need to fetch an object which might take a long time. Our preferred way of doing that, especially when we are on a UI thread, is to spawn a different thread so as to keep the UI responsive (this is just one of the many use cases that I can think of now). But since we need that object to proceed further in the current execution, we have to resort to some sort of wait/notify mechanism. The following code demoes a very simplistic approach using the regular wait()/notify().

package com.swayam.thread.test;
 
/**
 *
 * @author paawak
 *
 */
public class WaitNotifyExample {
 
    private Object lock = new Object();
 
    public WaitNotifyExample() {
 
    }
 
    public void runLongTask() {
 
        Thread job = new Thread(new Runnable() {
 
            public void run() {
 
                System.out.println("Long task started");
 
                try {
 
                    int maxCount = 1000;
                    // int maxCount = Integer.MAX_VALUE;
 
                    // do some long task
                    for (int i = 0; i &lt; maxCount; i++) {
                        System.out.println(i);
                    }
 
                    System.out.println("Long task done.");
 
                } finally {
 
                    System.out.println("About to notify lock...");
 
                    synchronized (lock) {
                        lock.notifyAll();
                    }
 
                    System.out.println("Lock notified");
 
                }
 
            }
        });
 
        // job.setPriority(Thread.MAX_PRIORITY - 2);
 
        System.out.println("Starting a long task...");
        job.start();
 
        System.out
                .println("Pausing normal execution and waiting for long task to finish...");
 
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
 
        System.out.println("Resuming normal execution as long task is done.");
 
    }
 
    public static void main(String[] a) {
 
        new WaitNotifyExample().runLongTask();
 
    }
 
}

Note: This is far from fool proof. One case where it will fail is if the long task is over before that lock.wait() is called.

April 4, 2009

Why Teneo won’t work with Ehcache?

Filed under: teneo — Tags: , — paawak @ 02:18

The first problem is that by default EMF classes are not Serializable. The very first stack trace that you will get in two parts will look something like:

Stack Trace I

java.lang.ClassCastException: xxx.impl.SomeEmfClassImpl
at org.hibernate.type.AbstractType.disassemble(AbstractType.java:78)
at org.hibernate.type.TypeFactory.disassemble(TypeFactory.java:474)
at org.hibernate.cache.entry.CacheEntry.<init>(CacheEntry.java:69)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:164)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:877)
at org.hibernate.loader.Loader.doQuery(Loader.java:752)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)

Stack Trace II

**Serious** 04/04/09 12:08:15 PM    org.hibernate.HibernateException: A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance: Properties.trail
at org.hibernate.engine.Collections.processDereferencedCollection(Collections.java:119)
at org.hibernate.engine.Collections.processUnreachableCollection(Collections.java:62)
at org.hibernate.event.def.AbstractFlushingEventListener.flushCollections(AbstractFlushingEventListener.java:241)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:100)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:49)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)

Stack Trace II is most confusing. If you trust Stack Trace II, it will lead you off on a false scent. You will think that there is a problem with the way EMF handles collections. You will constatntly rant and spit fire at them, thinking there is nothing to be done except selectively disabling caching for those queries.

But friends, here is the killer: Stack Trace II is caused by Stack Trace I. So let us explore that first. If you look at the very first line: ClassCastException. What has caused this? Let us see.

at org.hibernate.type.AbstractType.disassemble(AbstractType.java:78)

If you look at the source code AbstractType.disassemble() looks like:

	public Serializable disassemble(Object value, SessionImplementor session, Object owner)
	throws HibernateException {
 
		if (value==null) {
			return null;
		}
		else {
			return (Serializable) deepCopy( value, session.getEntityMode(), session.getFactory() );
		}
	}

So in line 78, the EMF model is casted into Serializable, and hence the cause of exception.

Solution

There is no other go, but to make the EMF models Serializable. How do we do that? We have to edit the default JET Templates so that all generated interfaces by EMF are Serializable. Here is an excellent article which will guide you. The template to edit is templates/model/Class.javajet.

<%if (isImplementation) {%>
public<%if (genClass.isAbstract()) {%> abstract<%}%> class <%=genClass.getClassName()%><%=genClass.getTypeParameters().trim()%><%=genClass.getClassExtends()%><%=genClass.getClassImplements()%>
<%} else {%>
public interface <%=genClass.getInterfaceName()%><%=genClass.getTypeParameters().trim()%><%=genClass.getInterfaceExtends()%>
<%}%>, java.io.Serializable
{

Now re-generate the model code from genmodel and try again.

With this, some operations, of course will work. But if you are using Teneo lesser than 1.0.4 (1.0.3 is the latest release as of now ), for most of the operations you will get a trace which looks something like:

Stack Trace III

04/04/09 01:05:47.164 PM  131221 WARN  org.apache.struts.chain.commands.AbstractExceptionHandler - Unhandled exception
java.lang.NullPointerException
at xxx.MyInterceptor.getEntityName(MyInterceptor.java:41)
at org.hibernate.impl.SessionImpl.guessEntityName(SessionImpl.java:1792)
at org.hibernate.impl.SessionImpl.bestGuessEntityName(SessionImpl.java:1759)
at org.eclipse.emf.teneo.hibernate.mapping.econtainer.EContainerUserType.assemble(EContainerUserType.java:300)
at org.hibernate.type.TypeFactory.assemble(TypeFactory.java:443)
at org.hibernate.cache.entry.CacheEntry.assemble(CacheEntry.java:119)

Before you get lost in the trace, lets take a quick look at what actually happens. Hibernate encounters a cache-entry and tries to make sense of it and construct the actual object queried. In the process, it is delegated to the TypeFactory.assemble(), which determines the type of the entry and delegates it to an implementation of org.hibernate.type.Type. So far so good. So what goes wrong?

at org.eclipse.emf.teneo.hibernate.mapping.econtainer.EContainerUserType.assemble(EContainerUserType.java:300)

If you have a look at the source code and then its super class, org.hibernate.type.AbstractType, at the assemble() method, you will notice that one significant difference is, in AbstractType.assemble(), if the cached parameter is null, it returns a null, assuming that Hibernate will fetch it from the Database instead.

	public Object assemble(Serializable cached, SessionImplementor session, Object owner)
	throws HibernateException {
		if ( cached==null ) {
			return null;
		}
		else {
			return deepCopy( cached, session.getEntityMode(), session.getFactory() );
		}
	}

While in EContainerUserType.assemble(), there is no null check that is what causes the big trace. Grab the Teneo sources, apply the fix, re-compile and bingo!

	public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
 
	    // palash: fix for ALL our teneo/ehcache woes!!
        // if cache is null, just return null, without guessing; hibernate is smart enough to fetch it from the db
        if (cached == null) {
            return null;
        }
 
		// already correct
		if (!(cached instanceof ContainerPointer)) {
			final String entityName = session.bestGuessEntityName(cached);
			final Serializable idObject = getID(entityName, cached, session);
			return session.internalLoad(entityName, idObject, false, false);
		} else {
			final ContainerPointer cp = (ContainerPointer) cached;
			return cp.getObject(session);
		}
	}

This fix will be available in Teneo 1.0.4 release. I have raised a bug on this and Martin has been kind enough to check it in.

For this article, I am using EMF 2.3.0_v_200706262000, Teneo 0.8_v_200708101732,  Hibernate 3.3.1.GA and Ehcache 1.5.0. You will find the Teneo sources in the public CVS repository here

:pserver:anonymous@dev.eclipse.org:/cvsroot/modeling.

April 2, 2009

Configuring connection pooling with Teneo

Filed under: database, hibernate, teneo — Tags: , , — paawak @ 12:38

Connection pooling is one thing that needs to be done on a production server. Spring is becoming an extremely popular choice with Hibernate for its seamless integartion. But with Teneo, its a different ball-game altogether. Fortunately for us, Hibernate provides one such hook via the property in the Environment.CONNECTION_PROVIDER configuration. It takes in the name of a class implementing the interface ConnectionProvider.

I prefer the Apache Commons DBCP. You will find the example here. When configuring Teneo, you need to set:

Properties props = new Properties();
 
//this line is the killer: delegates the ConnectionProvider to our custom implementation
//look at org.hibernate.connection.ConnectionProviderFactory.newConnectionProvider()
props.put(Environment.CONNECTION_PROVIDER, MyConnectionProvider.class
.getName());

MyConnectionProvider.java looks like:

 
package com.swayam.teneo.config;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
 
import javax.sql.DataSource;
 
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.connection.ConnectionProvider;
 
/**
 * 
 * @author paawak
 */
public class MyConnectionProvider implements ConnectionProvider {
 
    private DataSource ds;
 
    public MyConnectionProvider() {
 
    }
 
    public void close() throws HibernateException {
        // TODO Auto-generated method stub
 
    }
 
    public void closeConnection(Connection conn) throws SQLException {
        conn.close();
    }
 
    public void configure(Properties props) throws HibernateException {
 
        String jdbcDriver = (String) props.get(Environment.DRIVER);
 
        if (jdbcDriver == null) {
            throw new HibernateException(
                    "Please specify the JDBC driver with the key `Environment.DRIVER`");
        }
 
        // load the driver
        try {
            Class.forName(jdbcDriver);
        } catch (ClassNotFoundException e) {
            throw new HibernateException(e);
        }
 
        String connectURI = (String) props.get(Environment.URL);
 
        if (connectURI == null) {
            throw new HibernateException(
                    "Please specify the connection URL with the key `Environment.URL`");
        }
 
        ds = setupDataSource(connectURI);
    }
 
    public Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
 
    public boolean supportsAggressiveRelease() {
        // TODO Auto-generated method stub
        return false;
    }
 
    /**
     * refer to DBCP ManualPoolingDataSourceExample:
     * 
     * 
     * http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/doc/ManualPoolingDataSourceExample.java?view=markup
     * 
     * 
     * This is made static for de-coupling
     * 
     */
    private static DataSource setupDataSource(String connectURI) {
        //
        // First, we'll need a ObjectPool that serves as the
        // actual pool of connections.
        //
        // We'll use a GenericObjectPool instance, although
        // any ObjectPool implementation will suffice.
        //
        ObjectPool connectionPool = new GenericObjectPool(null);
 
        //
        // Next, we'll create a ConnectionFactory that the
        // pool will use to create Connections.
        // We'll use the DriverManagerConnectionFactory,
        // using the connect string passed in the command line
        // arguments.
        //
        ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
                connectURI, null);
 
        //
        // Now we'll create the PoolableConnectionFactory, which wraps
        // the "real" Connections created by the ConnectionFactory with
        // the classes that implement the pooling functionality.
        //
        @SuppressWarnings("unused")
        PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(
                connectionFactory, connectionPool, null, null, false, true);
 
        //
        // Finally, we create the PoolingDriver itself,
        // passing in the object pool we created.
        //
        PoolingDataSource dataSource = new PoolingDataSource(connectionPool);
 
        return dataSource;
    }
 
}

July 30, 2008

Replacing all occurances of a given pattern using Regex

Filed under: java — Tags: — paawak @ 11:43

    /**

     * Replaces all occurances of the pattern regex with the String

     * replacement

     * 

     * @param hayStack 

     */

    private String replaceAllWithRegex(String hayStack) {

        String replacement = "replacement";

        String regex = "ns\w*:";

        Pattern pattern = Pattern.compile(regex);

        Matcher matcher = pattern.matcher(hayStack);

        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {

            matcher.appendReplacement(sb, replacement);

        }

        matcher.appendTail(sb);

        return sb.toString();

    }

July 21, 2008

Using Log4j with Maven Jetty plugin

Filed under: java — Tags: , — paawak @ 11:42

No extra configuration is needed. If log4j.xml or log4j.properties is present in the classpath, it is picked up and logs are directed to the specified appender(s). I found http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin#MavenJettyPlugin-Logging overtly complicated and unnecessary. The logs can be redirected to a file by using a suitable appender in log4j.xml:

    <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
        <param name="File" value="./target/dmsweb_log_file.log" />
        <param name="Append" value="false" >
        <param name="MaxBackupIndex" value="1" >
        <param name="MaxFileSize" value="500KB" >
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                value="%d [%t] %-5p %c - %m%n">
        </layout>
    </appender>

Running Jetty from a custom port through Maven

Filed under: java — Tags: , — paawak @ 11:40

There are two options:

  • pom.xml:
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.10</version>
                <configuration>                  
                    ...
                    </scanTargetPatterns>
                    <systemProperties>                      
                        <systemProperty>
                            <name>jetty.port</name>
                            <value>8080</value>
                        </systemProperty>                      
                    </systemProperties>
                </configuration>
            </plugin>
  • command prompt: mvn -Djetty.port=8090 jetty:run

The thing to remember is the command line value will override the value in pom.xml.

Connecting to MsSql through JDBC

Filed under: java — Tags: — paawak @ 11:39

I was not able to connect to MsSql using JDBC. I was using the host with the port no. like:

jdbc:jtds:sqlserver//localhost:3683/TestDB

I would always get “Connection refused” exception.

Then I chucked out the port no. from the host name, and it works fine now.

jdbc:jtds:sqlserver://localhost/TestDB

July 13, 2008

How to pass an object from the controller to the view in SpringMVC?

Filed under: java, spring — Tags: , — paawak @ 11:38

The scenario is: I want to show a list of items on my view from the database. My controller picks up the data, passes it to the view which displays it. I am using a sub class of org.springframework.web.servlet.mvc.SimpleFormController:

@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {

ModelAndView view = new ModelAndView(”complaintListings”);
List<String> strings = new ArrayList<String>();
strings.add(”AAAAAAAA”);
strings.add(”BBBBBBB”);
strings.add(”CCCCCCCC”);
strings.add(”EEEEEEEE”);
strings.add(”FFFFFFFFF”);

//set the object to view
view.addObject(”testStrings”, strings);

return view;

}

The view looks like this:

<c:forEach var=”string” items=”${testStrings}”>
<c:out value=”${string}”/>
<br />
</c:forEach>

Its that simple!

« Newer PostsOlder Posts »

Powered by WordPress