About This Blog

This blog contains some information about Java programming strongly connected to making web pages, and JEE applications.

8 July 2008 - 21:27Wicket project nearly finished

There pass some time from my last writing here, there is no one, simple reason for that - maybe the most importat was that I went from theory to the practice with my Wicket skills. Previous posts were rather theroretical, books based news  - now my knowledge of the Wicket stuff is much more wide. In March company I work for started project which was related to car parts advertisements, now the project grows to quite nice car portal. There are advertisement section, questions and answers (or forum if you like the difference is subtle), picture gallery, user inbox stuff, full i18n.

All is powered by Wicket, Hibernate, Spring, search job are done by Hibernate Search (great collection indexing feature), lot of data is cached  by Ehcache. There are also plans of use Databinder, Terracotta and even Scala if my friend and great co-worker Tomek teach rest of the team this fabulous language.

Most important - link to the project http://www.auxto.pl/ - this is Polish version, but soon I think we launch others.

Thanks to the team Marcin, Tomek and Paweł - great work gentelmans :)

No Comments | Tags: Lucene, Wicket, hibernate, search, spring

18 February 2008 - 19:36Wicket and Hibernate Search

Hibernate Search is a full-text search based on Lucene which integrates near transparently with Hibernate ORM. Creating Lucene index on entities is really simple when you can use annotations in your code. First you need to mark your class as @Indexed, next assign @DocumentId to the entity primary key, last thing would be mark instance fields as indexable with @Field annotation - see the example below.

@Entity
@Indexed
public class Advertisement implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@DocumentId
	private Integer id;
 
	@Field(index=Index.TOKENIZED, store=Store.NO)
	private String title;
 
	private String body;
 
	@Field(index=Index.TOKENIZED, store=Store.YES)
	public String getRawBody() {
		Pattern pattern = Pattern.compile("<[^>]*>");
		Matcher matcher = pattern.matcher(this.body);
		return matcher.replaceAll("");
	}
 
	//getters / setters
}
 

You need to add some properties to Hibernate SessionFactory - e.g. in Spring Framework it would be something like this:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>
<prop key="hibernate.search.default.indexBase">/usr/home/ghost/workspace/AdvertisementSystem/target/search</prop>
	    	</props>
	</property>
<bean>
 

All of above is well described in Hibernate Search documentation, but some troubles could be with Wicket integration, in which we use DataView to show paginated data. This view needs to get DataProvider object which have to implements iterator() and size() methods, and it's quite obvious when using databases, but not when using Hibernate Search - why? Because it returns number of all results when querying even for part of them, so why you would ask search twice - my solution for this problem is:

class SearchDataProvider implements IDataProvider {
 
	private String query;
 
	private int resultsPerPage;
 
	private SearchDAO.SearchResults searchedAdverts;
 
	private Integer resultsCount; 
 
	public SearchDataProvider(String query, int resultsPerPage) {
		this.query = query;
		this.resultsPerPage = resultsPerPage;
	}
 
	public Iterator iterator(int first, int count) {
		if(searchedAdverts == null) {
			searchedAdverts = searchDAO.getSearchedAdverts(this.query, first, count);
		}
		return searchedAdverts.getResults().iterator();
	}
 
	public IModel model(Object object) {
		return new Model((Advertisement) object);
	}
 
	public int size() {
		if(resultsCount == null) {
			searchedAdverts = searchDAO.getSearchedAdverts(this.query, 0, this.resultsPerPage);
			resultsCount = searchedAdverts.getCount();
		}
		return resultsCount;
	}
 
	public void detach() {
		searchedAdverts = null;
	}
}
 

In DAO:

public SearchResults getSearchedAdverts(final String queryString, int first, int count) {
	Session session = getSession(true);
	FullTextSession fullTextSession = Search.createFullTextSession(session);
	SearchResults results = null;
	Transaction tx = fullTextSession.beginTransaction();
	MultiFieldQueryParser parser = new MultiFieldQueryParser( new String[]{"title", "rawBody"}, new StandardAnalyzer());
	Query query;
	try {
		query = parser.parse(queryString);
		FullTextQuery hibQuery = fullTextSession.createFullTextQuery(query, Advertisement.class)
			.setFirstResult(first)
			.setMaxResults(count);
		results = new SearchResults(hibQuery.list(), hibQuery.getResultSize());
	} catch (ParseException e) {
		e.printStackTrace();
	} finally {
		tx.commit();
		session.close();
	}
}
 

In my case SearchResults is private class which wraps list of results and ALL result counter, there is some problem in Spring with using HibernateTemplate() so I manually get new session. In DataProvider size() is asked only once when new DataView is instanced, and it takes first 20 results, store them as detached object, and set up number of results for a later use (when processing to next pages).

No Comments | Tags: Lucene, Wicket, hibernate, search

14 February 2008 - 19:19Wicket GMap2

One of the coolest Wicket component is GMap2, which is implementation of Google Maps API, using it is very simple, but some features still remains not so intuitive, and they need to be implemented by ourself. All communication are done via Ajax, which is fine, but what to do if you need to insert map values into existing form? Helpful could be very simple Javascript:

 
Form form = ...;
 
final HiddenField formGMapLat = (HiddenField) new HiddenField("gLatLng", new PropertyModel(this, "gLatLng")).setOutputMarkupId(true);
form.add(formGMapLat);
final HiddenField formGMapLng = (HiddenField) new HiddenField("gzoom", new PropertyModel(this, "gzoom")).setOutputMarkupId(true);
form.add(formGMapLng);
 
final GMap2 gmap = new GMap2("gmap", GOOGLE_KEY);
gmap.setOutputMarkupId(true);
gmap.setMapType(GMapType.G_NORMAL_MAP);
gmap.addControl(GControl.GSmallMapControl);
add(gmap);
 
add(new Link("addPlaceLink") {
	@Override
	public void onClick() {
	}
 
	@Override
	protected void onComponentTag(ComponentTag componentTag) {
		super.onComponentTag(componentTag);
		gmap.getJSinvoke("map.getCenter()");
		componentTag.addBehavior(new SimpleAttributeModifier("onclick", "document.getElementById('" + gMapLatLng.getMarkupId() + "').value=" + gmap.getJSinvoke("map.getCenter()") + ";document.getElementById('" + zoom.getMarkupId() + "').value=" + gmap.getJSinvoke("map.getZoom()") + "return false;"));
	}
});
 

It's all you need to implement to store map zoom and coordinates in HiddenForm fields when addPlaceLink is pressed. Map zoom is stored as int, map coordinates are stored in JS format so you need to use GLatLng latLng = GLatLng.parse(gLatLng); to get GLatLng object which contains Lat / Lng Doubles.

No Comments | Tags: Wicket

5 February 2008 - 17:12Solr and HttpClient

I use quite a lot HttpClient from Jakarta Commons, so when I found post.jar and post.sh script in Solr examples directory, I decided to write my own piece of code to demonstrate indexing with this package. It's very simple piece of code, but anyway I publish it here, maybe soon I will need to use more of it functionality, because Solr seems to be very mature now.

package solr;
 
import java.io.*;
import org.apache.commons.httpclient.*;
 
public class SolrTest {
 
	private static final String SOLR_HOST = "localhost";
	private static final int SOLR_PORT = 8983;
	private static final String SOLR_PATH = "/solr/update/";
 
	static String examplesDir = "/home/ghost/apache-solr-1.2.0/example/exampledocs/";
 
	public static void main(String[] args) throws IOException {
		File dir = new File(examplesDir);
		File[] files = dir.listFiles(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.indexOf(".xml") != -1;
			}
		});
		for(File file : files) {
			FileReader fr =  new FileReader(file);
			BufferedReader reader = new BufferedReader(fr);
			String line;
			StringBuilder sb = new StringBuilder();
			while((line = reader.readLine()) != null) {
				sb.append(line);
			}
			reader.close();
			fr.close();
			sendXML(sb.toString());
		}
		sendXML("<commit />");
	}
 
	public static void sendXML(String xml) throws HttpException, IOException {
		HttpClientParams httpClientParams = new HttpClientParams();
		HttpClient client = new HttpClient(httpClientParams);
 
		HostConfiguration hostConfiguration = new HostConfiguration();
		hostConfiguration.setHost(SOLR_HOST, SOLR_PORT);
		client.setHostConfiguration(hostConfiguration );
 
		PostMethod postMethod = new PostMethod();
		postMethod.setPath(SOLR_PATH);
		postMethod.addRequestHeader("Content-type", "text/xml; charset=utf-8");
 
		RequestEntity entity = new StringRequestEntity(xml);
		postMethod.setRequestEntity(entity);
 
		client.executeMethod(postMethod);
 
		postMethod.releaseConnection();
	}
}
 

No Comments | Tags: Solr