siark.com blog

November 24, 2010

The siark.com Gallery – Part 3

Part 3 of creating the siark.com Gallery involves adding the keywords. This requires a new domain object, Keyword and a many to many relationship between the gallery object and the keyword object with the gallery object being the ‘owner’ or the relationship.

The Keyword class.

package com.siark.igallery.domain;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "keyword")
public class Keyword extends DomainBase {
	
	private static final long serialVersionUID = 3747792588358317008L;
	private String text;
	private Set<Gallery> galleries = new HashSet<Gallery>();
	
	/**
	 * 
	 */
	public Keyword() {
	}
	
	/**
	 * Construct a new keyword with the keyword text. A keyword can include spaces.
	 * The keyword text is not case sensitive and blank spaces are removed.
	 * 
	 * @param text the keyword
	 */
	public Keyword(String text) {
		this.text = text.toLowerCase().trim();
	}

	/**
	 * Get the text of the keyword.
	 * 
	 * @return the keyword text
	 */
	@Column(name="text")
	public String getText() {
		return text;
	}

	/**
	 * Set the text of the keyword. A keyword can include spaces.
	 * The keyword text is not case sensitive and blank spaces at the end are removed.
	 * 
	 * @param text the keyword text
	 */
	public void setText(String text) {
		this.text = text;
	}
	
	/**
	 * Get the galleries associated with this keyword.
	 * 
	 * @return galleries associated with this keyword
	 */
	@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
			mappedBy = "keywords",
			targetEntity = Gallery.class)
	public Set<Gallery> getGalleries() {
		return galleries;
	}
	
	/**
	 * Set the galleries associated with this keyword.
	 * 
	 * @param galleries the galleries associated with this keyword.
	 */
	public void setGalleries(Set<Gallery> galleries) {
		this.galleries = galleries;
	}
	
	/**
	 *
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(super.toString());
		buffer.append("Text: " + text + ";");
		return buffer.toString();
	}
	
	/**
	 * 
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) return true;
		if (!(o instanceof Keyword)) return false;
		Keyword keyword = (Keyword) o;
		if ((this.id == keyword.getId())
			&& (this.text == null) ? (keyword.getText() == null) : keyword.getText().equals(this.text)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 
	 */
	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + this.id;
		result = 31 * result + (this.text == null ? 0 : this.text.hashCode());
		return result;
		
	}
}

The Gallery class must now be updated to include keywords. I am also including the notion of a gallery being a leaf. That is a leaf gallery may not be the parent of any other galleries.

package com.siark.igallery.domain;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.validator.constraints.NotEmpty;

@Entity
@Table(name = "gallery")
public class Gallery extends DomainBase {

	private static final long serialVersionUID = -3449742178300288206L;
	private String title;
	private Gallery parent;
	private List<Gallery> children = new ArrayList<Gallery>();
	private Set<Keyword> keywords = new HashSet<Keyword>();
	boolean leaf = false;

	/**
	 * Construct a new gallery with the display name of the gallery.
	 * Blank spaces at the end of the title are removed.
	 * 
	 * @param title the display name of the gallery
	 */
	public Gallery(String title) {
		this.title = title.trim();
	}

	/**
	 *
	 */
	public Gallery() {
	}

	/**
	 * Returns the display name of the gallery.
	 * 
	 * @return title the display name of the gallery
	 */
	@Column(name = "title")
	@NotEmpty
	public String getTitle() {
		return title;
	}

	/**
	 * Sets the display name of the gallery.
	 * Blank spaces at the end of the title are removed.
	 * 
	 * @param title the display name of the gallery
	 */
	public void setTitle(String title) {
		this.title = title.trim();
	}
	
	/**
	 * Returns the gallery that is the parent to this gallery.
	 * A gallery has only one parent.
	 * 
	 * @return the gallery that is this galleries parent
	 */
	@ManyToOne
	@JoinColumn(name="parent_fk")
	@ForeignKey(name="to_parent_fk") 
	public Gallery getParent() {
		return this.parent;
	}
	
	/**
	 * Sets this galleries parent.
	 * 
	 * @param the parent gallery
	 */
	public void setParent(Gallery parent) {
		this.parent = parent;
	}

	/**
	 * If this gallery is a non-leaf gallery it can have zero or more child galleries.
	 * 
	 * @return a list of this galleries children
	 */
	@OneToMany
	@JoinColumn(name="parent_fk")
	public List<Gallery> getChildren() {
		return children;
	}
	
	/**
	 * Child galleries can only be added to this gallery if this gallery is a non-leaf gallery.
	 * 
	 * @param the list of this galleries child galleries
	 */
	public void setChildren(List<Gallery> children) throws GalleryException {
		if (!this.leaf || children.isEmpty() || (children == null)) this.children = children;
		else throw new GalleryException();
		
	}
	
	/**
	 * Get the keywords associated with this gallery.
	 * 
	 * @return a set of keywords
	 */
	@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
			targetEntity = Keyword.class)
	@JoinTable(name = "gallery_keyword", 
			joinColumns = @JoinColumn(name = "gallery_fk"), 
			inverseJoinColumns = @JoinColumn(name="keyword_fk"))
	@ForeignKey(name = "to_gallery_fk", inverseName = "to_keyword_fk")
	@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
	public Set<Keyword> getKeywords() {
		return keywords;
	}
	
	/**
	 * Set the keywords associated with this gallery. A gallery may have zero or more keywords,
	 * but no duplicate keywords.
	 * 
	 * @param keywords a set of keywords
	 */
	public void setKeywords(Set<Keyword> keywords) {
		this.keywords = keywords;
	}

	/**
	 * A leaf gallery may not be the parent of any other galleries.
	 * 
	 * @return true if this gallery is a leaf otherwise false
	 */
	@Column(name="leaf")
	public boolean isLeaf() {
		return leaf;
	}

	/**
	 * The value of leaf can only be set to true if there are no children.
	 * 
	 * @param leaf
	 */
	public void setLeaf(boolean leaf) {
		this.leaf = leaf;
	}

	/**
	 *
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(super.toString());
		buffer.append("Title: " + this.title + "; ");
		buffer.append(this.parent == null ? "Parent: null ; " : "Parent: " + this.parent.getId() + "; ");
		buffer.append("Children: ");
		for (Iterator<Gallery> i = this.children.iterator(); i.hasNext();) {
			buffer.append(i.next().getTitle());
			buffer.append(i.hasNext() ? ", " : "; ");
		}
		buffer.append("Keywords: ");
		for (Iterator<Keyword> i = this.keywords.iterator(); i.hasNext();) {
			buffer.append(i.next());
			buffer.append(i.hasNext() ? ", " : "; ");
		}
		buffer.append("isLeaf: " + this.leaf + ";");
		return buffer.toString();
	}
	
	/**
	 * 
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) return true;
		if (!(o instanceof Gallery)) return false;
		if (getClass() != o.getClass()) return false;
		Gallery gallery = (Gallery) o;
		if ((this.id == gallery.getId())
			&& (this.title == null) ? (gallery.getTitle() == null) : gallery.getTitle().equals(this.title)
			&& (this.parent == null) ? (gallery.getParent() == null) : gallery.getParent().equals(this.parent)
			&& this.children.equals(gallery.getChildren())
			&& this.keywords.equals(gallery.getKeywords())
			&& this.leaf == gallery.leaf) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 
	 */
	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + this.id;
		result = 31 * result + (this.title == null ? 0 : this.title.hashCode());
		result = 31 * result + (this.parent == null ? 0 : this.parent.hashCode());
		result = 31 * result + this.children.hashCode();
		result = 31 * result + this.keywords.hashCode();
		result = 31 * result + (this.leaf ? 1 : 0);
		return result;
	}
}

I now need to create a dao for the keyword class.

package com.siark.igallery.repository;

import com.siark.igallery.domain.Keyword;

public interface KeywordDao {

	public void persistKeyword(Keyword keyword);
	public Keyword findKeywordByKeyword(String keyword);
	public Keyword findKeywordById(int id);
}
package com.siark.igallery.repository;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;

import com.siark.igallery.domain.Keyword;

@Named("KeywordDao")
public class HibernateKeywordDao implements KeywordDao {
	
	protected final Log logger = LogFactory.getLog(getClass());
	private SessionFactory sessionFactory;

	/**
	 * Save a new keyword or update an existing keyword.
	 * 
	 * @param keyword a new or existing keyword
	 */
	public void persistKeyword(Keyword keyword) {
		this.sessionFactory.getCurrentSession()
			.saveOrUpdate(keyword);
	}

	/**
	 * Find the keyword with a specific id or null if a keyword with that id does not exist.
	 * 
	 * @param id the id of the keyword to find
	 * @return a keyword with the id specified or null
	 */
	public Keyword findKeywordById(int id) {
		return (Keyword) this.sessionFactory.getCurrentSession()
			.createQuery("from Keyword as keyword where keyword.id = :id")
			.setInteger("id", id)
			.uniqueResult();
	}

	/**
	 * Find the keyword with the specified text.
	 * 
	 * @param text the text the keyword must contain
	 * @return the keyword containing the specified text
	 */
	public Keyword findKeywordByKeyword(String text) {
		return (Keyword) this.sessionFactory.getCurrentSession()
			.createQuery("from Keyword as keyword where text = :text")
			.setString("text", text)
			.uniqueResult();
	}
	
	/**
	 *
	 */
	@Inject
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
}

Now I need a service.

package com.siark.igallery.service;

import java.util.Set;

import com.siark.igallery.domain.Keyword;

public interface KeywordService {
	
	public void persistKeyword(Keyword keyword);
	public Keyword getKeyword(String Keyword);
	public Set<Keyword> getKeywords(Set<String> Keywords);
	public Keyword findKeywordById(int id);
	public Keyword findKeywordByKeyword(String keyword);
}
package com.siark.igallery.service;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.siark.igallery.domain.Keyword;
import com.siark.igallery.repository.KeywordDao;

@Named("KeywordService")
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public class KeywordServiceImpl implements KeywordService {
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	@Inject
	@Named("KeywordDao")
	private KeywordDao keywordDao;

	/**
	 * 
	 * 
	 * @param keyword
	 */
	@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
	public void persistKeyword(Keyword keyword) {
		keywordDao.persistKeyword(keyword);
	}

	/**
	 * 
	 * 
	 * @param id
	 * @return
	 */
	public Keyword findKeywordById(int id) {
		return keywordDao.findKeywordById(id);
	}
	
	/**
	 * 
	 * 
	 * @param keyword
	 * @return
	 */
	public Keyword findKeywordByKeyword(String keyword) {
		return keywordDao.findKeywordByKeyword(keyword);
	}

	/**
	 * 
	 * 
	 * @param keywordStr
	 * @return
	 */
	public Keyword getKeyword(String keywordStr) {
		Keyword keyword = keywordDao.findKeywordByKeyword(keywordStr);
		if (keyword == null) {
			keyword = new Keyword(keywordStr);
			keywordDao.persistKeyword(keyword);
			return keywordDao.findKeywordByKeyword(keywordStr);
		}
		return keyword;
	}

	/**
	 * 
	 * 
	 * @param keywordsStrs
	 * @return
	 */
	public Set<Keyword> getKeywords(Set<String> keywordStrs) {
		Set<Keyword> keywords = new HashSet<Keyword>();
		for (Iterator<String> i = keywordStrs.iterator(); i.hasNext();) {
			String keywordStr = i.next();
			keywords.add(getKeyword(keywordStr));
		}
		return keywords;
	}
	
	/**
	 * 
	 * 
	 * @param keywordDao
	 */
	public void setKeywordDao(KeywordDao keywordDao) {
		this.keywordDao = keywordDao;
	}
	
	/**
	 * 
	 * 
	 * @return
	 */
	public KeywordDao getKeywordDao() {
		return keywordDao;
	}
}

I’ve added Spring transactions to the service classes so I have to add transactional support to the db-config.xml file.

<?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:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:jdbc.properties</value>
				<value>classpath:igallery.properties</value>
			</list>
		</property>
	</bean>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
	
	<!-- Hibernate SessionFactory -->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan" value="com.siark.igallery.domain" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
			</props>
		</property>
	</bean>
	
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	
	<tx:annotation-driven />
</beans>

I have also removed the flushModeName property from the mvc-config.xml file.

November 12, 2010

The siark.com Gallery – Part 2

Part two of creating the siark.com gallery involves implementing the leaf and non-leaf types of gallery. To do this it’s necessary to add the concept of a parent and child relationship within the gallery.

The gallery domain class.

package com.siark.igallery.domain;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "gallery")
public class Gallery extends DomainBase {
	
	private static final long serialVersionUID = -7236265616573470929L;
	private String title;
	private Gallery parent;
	private List<Gallery> children = new ArrayList<Gallery>();
	boolean leaf = false;

	/**
	 *
	 */
	public Gallery(String title) {
		this.title = title;
	}

	/**
	 *
	 */
	public Gallery() {
	}

	/**
	 * Returns the display name of the gallery.
	 * 
	 * @return title the display name of the gallery
	 */
	@Column(name = "title")
	public String getTitle() {
		return title;
	}

	/**
	 * Sets the display name of the gallery.
	 * 
	 * @param title the display name of the gallery
	 */
	public void setTitle(String title) {
		this.title = title;
	}
	
	/**
	 * Returns the gallery that is the parent to this gallery.
	 * A gallery has only one parent.
	 * 
	 * @return the gallery that is this galleries parent
	 */
	@ManyToOne
	@JoinColumn(name="parent_fk")
	public Gallery getParent() {
		return this.parent;
	}
	
	/**
	 * Sets this galleries parent.
	 * 
	 * @param the parent gallery
	 */
	public void setParent(Gallery parent) {
		this.parent = parent;
	}
	
	/**
	 * If this gallery is a non-leaf gallery it can have zero or more child galleries.
	 * 
	 * @return a list of this galleries children
	 */
	@OneToMany
	@JoinColumn(name="parent_fk")
	public List<Gallery> getChildren() {
		return children;
	}
	
	/**
	 * Child galleries can only be added to this gallery if this gallery is a non-leaf gallery.
	 * 
	 * @param the list of this galleries child galleries
	 */
	public void setChildren(List<Gallery> children) throws GalleryException {
		if (!this.leaf || children.isEmpty() || (children == null)) this.children = children;
		else throw new GalleryException();
	}
	
	/**
	 * 
	 */
	@Column(name="leaf")
	public boolean isLeaf() {
		return leaf;
	}

	/**
	 * The value of leaf can only be set to true if there are no children.
	 * @param leaf
	 */
	public void setLeaf(boolean leaf) {
		this.leaf = leaf;
	}

	/**
	 *
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(super.toString());
		buffer.append("Title: " + this.title + "; ");
		return buffer.toString();
	}
	
	/**
	 * 
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) return true;
		if (!(o instanceof Gallery)) return false;
		if (getClass() != o.getClass()) return false;
		Gallery gallery = (Gallery) o;
		if ((this.id == gallery.getId())
			&& (this.title == null) ? (gallery.getTitle() == null) : gallery.getTitle().equals(this.title)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 
	 */
	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + this.id;
		result = 31 * result + (this.title == null ? 0 : this.title.hashCode());
		return result;
	}
}

The dao interface is modified to include some methods to access the parent and the children.

package com.siark.igallery.repository;

import java.util.List;
import com.siark.igallery.domain.Gallery;

public interface GalleryDao {

	public void persistGallery(Gallery gallery);
	public List<Gallery> getAllNonLeafGalleries();
	public List<Gallery> getAllLeafGalleries();
	public List<Gallery> getAllGalleries();
	public List <Gallery> getGalleriesByParent(Gallery parent);
	public Gallery getGalleryById(int id);
	public void delete(int id);
}
package com.siark.igallery.repository;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;

import com.siark.igallery.domain.Gallery;

@Named("GalleryDao")
public class HibernateGalleryDao implements GalleryDao {

	protected final Log logger = LogFactory.getLog(getClass());
	private SessionFactory sessionFactory;
	
	/**
	 * Save a new gallery or update an existing gallery.
	 * 
	 * @param gallery a new or existing gallery
	 */
	public void persistGallery(Gallery gallery) {
		this.sessionFactory.getCurrentSession()
			.saveOrUpdate(gallery);
	}
	
	/**
	 * Return all the non-leaf galleries.
	 * 
	 * @return a list of the non-leaf galleries
	 */
	@SuppressWarnings("unchecked")
	public List<Gallery> getAllNonLeafGalleries() {
		return this.sessionFactory.getCurrentSession()
			.createQuery("from Gallery as gallery where leaf = false")
			.list();
	}
	
	/**
	 * Return all the leaf galleries.
	 * 
	 * @return a list of the leaf galleries
	 */
	@SuppressWarnings("unchecked")
	public List<Gallery> getAllLeafGalleries() {
		return this.sessionFactory.getCurrentSession()
		.createQuery("from Gallery as gallery where leaf = true")
		.list();
	}
	
	/**
	 * Return all of the galleries including the root gallery.
	 * 
	 * @return a list of all the galleries
	 */
	@SuppressWarnings("unchecked")
	public List<Gallery> getAllGalleries() {
		return this.sessionFactory.getCurrentSession()
			.createQuery("from Gallery")
			.list();
	}
	
	/**
	 * Return all the child galleries of a parent gallery.
	 * 
	 * @return a list of child galleries
	 */
	@SuppressWarnings("unchecked")
	public List<Gallery> getGalleriesByParent(Gallery parent) {
		return this.sessionFactory.getCurrentSession()
		.createQuery("from Gallery as gallery where parent = :parent")
		.setEntity("parent", parent)
		.list();
	}
	
	/**
	 * Return the gallery with a specific id or null if a gallery with that id does not exist.
	 * 
	 * @param id the id of the gallery to find
	 * @return a gallery with the id specified or null
	 */
	public Gallery getGalleryById(int id) {
		return (Gallery) this.sessionFactory.getCurrentSession()
			.createQuery("from Gallery as gallery where gallery.id = :id")
			.setInteger("id", id)
			.uniqueResult();
	}
	
	/**
	 * Delete the gallery with a specific id.
	 * 
	 * @param id the id of the gallery to delete
	 */
	public void delete(int id) {
		this.sessionFactory.getCurrentSession()
			.createQuery("delete from Gallery as gallery where gallery.id = :id")
			.setInteger("id", id);
	}
	
	@Inject
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
}

The service is also modified to include the methods to access the parent and children.

package com.siark.igallery.service;

import java.util.List;

import com.siark.igallery.domain.Gallery;

public interface GalleryService {
	
	public void persistGallery(Gallery gallery);
	public List<Gallery> getAllNonLeafGalleries();
	public List<Gallery> getAllLeafGalleries();
	public List<Gallery> getGalleriesByParent(Gallery parent);
	public List<Gallery> getAllGalleries();
	public Gallery getRootGallery();
	public Gallery getGalleryById(int id);
	public void delete(int id);
}
package com.siark.igallery.service;

import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.siark.igallery.domain.Gallery;
import com.siark.igallery.repository.GalleryDao;

@Named("GalleryService")
public class GalleryServiceImpl implements GalleryService {
	
	protected final Log logger = LogFactory.getLog(getClass());

	@Inject
	@Named("GalleryDao")
	private GalleryDao galleryDao;
	
	/**
	 * Save a new gallery or update an existing gallery.
	 * 
	 * @param gallery a new or existing gallery
	 */
	public void persistGallery(Gallery gallery) {
		galleryDao.persistGallery(gallery);
	}
	
	/**
	 * Return all the non-leaf galleries.
	 * 
	 * @return a list of the non-leaf galleries
	 */
	public List<Gallery> getAllNonLeafGalleries() {
		return galleryDao.getAllNonLeafGalleries();
	}
	
	/**
	 * Return all the leaf galleries.
	 * 
	 * @return a list of the leaf galleries
	 */
	public List<Gallery> getAllLeafGalleries() {
		return galleryDao.getAllLeafGalleries();
	}
	
	/**
	 * Return all of the galleries including the root gallery.
	 * 
	 * @return a list of all the galleries
	 */
	public List<Gallery> getAllGalleries() {
		return galleryDao.getAllGalleries();
	}
	
	/**
	 * Return all the child galleries of a parent gallery.
	 * 
	 * @return a list of child galleries
	 */
	public List<Gallery> getGalleriesByParent(Gallery parent) {
		return galleryDao.getGalleriesByParent(parent);
	}
	
	/**
	 * Return the root gallery.
	 * 
	 * @return the root gallery
	 */
	public Gallery getRootGallery() {
		return galleryDao.getGalleryById(1);
	}
	
	/**
	 * Return the gallery with a specific id or null if a gallery with that id does not exist.
	 * 
	 * @param id the id of the gallery to find
	 * @return a gallery with the id specified or null
	 */
	public Gallery getGalleryById(int id) {
		return galleryDao.getGalleryById(id);
	}
	
	/**
	 * Delete the gallery with a specific id. The root gallery can not be deleted.
	 * 
	 * @param id the id of the gallery to delete
	 */
	public void delete(int id) {
		if (id > 1) galleryDao.delete(id);
	}
	
	public void setGalleryDao(GalleryDao galleryDao) {
		this.galleryDao = galleryDao;
	}
	
	public GalleryDao getGalleryDao() {
		return galleryDao;
	}
}

The gallery controller remains unchanged, but the home page menu is now populated using the children of the root gallery.

<?xml version="1.0" encoding="ISO-8859-1" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
		xmlns:c="http://java.sun.com/jsp/jstl/core"
		xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
		version="2.0">
	<fmt:setBundle basename="Messages" />
    <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" />
    <jsp:text>
        <![CDATA[ <?xml version="1.0" encoding="ISO-8859-1" ?> ]]>
    </jsp:text>
    <jsp:text>
        <![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ]]>
    </jsp:text>
	<html xmlns="http://www.w3.org/1999/xhtml">
		<head>
			<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
			<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/styles/base.css" />
			<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/styles/home.css" />
			<title><fmt:message key="home.title" /></title>
		</head>
		<body>
			<div class="container">
				<div id="header" class="bottom-line">
					<div id="logo">
						<a href="#">siark.com</a>
					</div>
				</div>
				<div id="top-menu">
					<div class="menu">
						<ul>
							<li>
								<a href="#"><fmt:message key="menu.gallery" /></a>
								<ul>
									<c:forEach items="${gallery.children}" var="item">
										<li>
											<jsp:element name="a">
												<jsp:attribute name="href">${pageContext.request.contextPath}/gallery/view.html?id=<c:out value="${item.id}" /></jsp:attribute>
												<jsp:body><c:out value="${item.title}" /></jsp:body>
											</jsp:element>
										</li>
									</c:forEach>
								</ul>
							</li>
	  						<li><a href="${pageContext.request.contextPath}/about.html"><fmt:message key="menu.about" /></a></li>
	  						<li><a href="#"><fmt:message key="menu.contact" /></a></li>
	  						<li class="right"><a href="${pageContext.request.contextPath}/gallery/admin/list.html"><fmt:message key="menu.admin" /></a></li>
	  					</ul>
	  				</div>
				</div>
				<div id="content">
					<div id="home-image">
						<div style="position:relative; top:79px; left:0px;">
							<img class="border" src="images/MG-100228-30-Edit.jpg" width="474" height="316" />
						</div>
					</div>
				</div>
				<div id="bottom-menu">
					<div class="menu">
						<ul>
							<li><a href="#"><fmt:message key="menu.news" /></a></li>
	  						<li><a href="#"><fmt:message key="menu.reviews" /></a></li>
	  						<li><a href="#"><fmt:message key="menu.tutorials" /></a></li>
	  						<li><a href="#"><fmt:message key="menu.videos" /></a></li>
	  					</ul>
	  				</div>
				</div>
				<div id="footer" class="top-line">
					<div id="copyright">all content © siark ltd</div>
				</div>
			</div>
		</body>
	</html>
</jsp:root>

In order for the JSP page to be able to access the children of the root gallery without incurring a lazy loading exception, the Open Session in View pattern is used. Spring makes it easy to use this pattern and all that is required is to add an interceptor to the mvc-config.xml file.

<?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:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

	<!-- Configures the @Controller programming model -->
	<mvc:annotation-driven />

	<!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
	<mvc:interceptors>
		<bean class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
			<property name="sessionFactory">
				<ref bean="sessionFactory" />
			</property>
			<property name="flushModeName">
				<value>FLUSH_AUTO</value>
			</property>
		</bean>
	</mvc:interceptors>
</beans>

Note: The flush mode is set to FLUSH_AUTO as there is no transaction management at the moment.

November 11, 2010

The siark.com Gallery – Introduction

Filed under: Java, Web Application — Tags: , — Mark Gould @ 10:34 am

The next few posts are going to build the gallery component of the siark.com website.

There will be two kinds of gallery, a ‘leaf’ gallery which is associated with images and a ‘non-leaf’ gallery which will contain other galleries. I have used the words associated and contain deliberately to describe the relationship between images and galleries and galleries and galleries. The contains relationship is straightforward as a non-leaf gallery simply has-a number of child galleries which means that there is a gallery hierarchy that can be viewed as a tree. The root of the tree is implemented as an invisible (to the viewer of the website) non-leaf gallery called ‘root’. The associated relationship is implemented using keywords. The concept of keywording an image in the photographic industry is now a common one. Keywords are words that describe an image. A gallery will also have keywords and therefore the images that would appear in a leaf gallery would be those that have keywords that match the keywords of the gallery.

There are three domain objects in this specification, a gallery, an image and a keyword. In the next few posts I will implement the gallery ‘stack’ and keyword ‘stack’. The stack is comprised of a domain layer, data access layer, service layer and MVC controller.

Tomcat 6, Securing the Admin Pages in web.xml

Filed under: Eclipse, Java, Web Application — Tags: , , , , , , — Mark Gould @ 7:38 am

To the right of the top menu on the siark.com website is the ‘admin’ link that accesses the administration (create, update and delete) pages. The hierarchy of the pages are such that the admin pages are in a series of admin directories (the admin pages for the galleries are in /gallery/admin, the admin pages for the keywords are in /keyword/admin and so on). Therefore it is necessary to restrict access to any pages to all of the admin subdirectories. This restriction is done by specifying security settings in the web-xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_2_5.xsd" 
	version="2.5">
	<display-name>igallery</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	<servlet>
		<servlet-name>igallery</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				/WEB-INF/igallery-servlet.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>igallery</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>Admin Security</web-resource-name>
			<url-pattern>/gallery/admin/*</url-pattern>
		</web-resource-collection>
		<auth-constraint>
			<role-name>myrole</role-name>
		</auth-constraint>
	</security-constraint>
	<login-config>
		<auth-method>BASIC</auth-method>
	</login-config>
	<security-role>
		<role-name>myrole</role-name>
	</security-role>
</web-app>

As I am using Tomcat 6, I can simply specify user and role information in the tomcat-users.xml file. To do this from the server instance in Eclipse, expand the ‘Servers’ directory in the ‘Project Explorer’, then expand the appropriate server, and you’ll see the file. There are sample roles and users in the file already. Create a role to match the role used in the web.xml file (in this case myrole).

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
	<role rolename="myrole" />
	<user username="myuser" password="mypassword" roles="myrole" />
</tomcat-users>

Note: It’s possible to specify the HTTP methods in the web.xml web-resource-collection, however this will mean that *only* these methods to the url-pattern specified will be secured and other methods will be unsecured (see http://www.aspectsecurity.com/documents/Bypassing_VBAAC_with_HTTP_Verb_Tampering.pdf).

October 14, 2010

Hibernate 3, Spring Framework 3, MySQL and Maven 2

To add database connectivity to the siark.com webapp I’m using a MySQL database To add database connectivity to the siark.com webapp I’m using a MySQL database and the Hibernate ORM framework. I need to add some dependencies to my pom.xml, some of which (Hibernate) are not in any of the repositories supported by Nexus in it’s out-of-the-box state. The Hibernate jar files are available from the JBoss repository.

To add a new proxy repository to Nexus.

1 Log in to Nexus as an admin.

2 Select ‘Proxy Repository’ from the ‘Add’… menu.

3 Enter the ‘Repository ID’ ‘jboss’, ‘Repository Name’ ‘JBoss Repository’, ‘Remote Storage Location’ ‘http://repository.jboss.org/nexus/content/groups/public&#8217; and press the ‘Save’ button.

4 Select the ‘Public Repositories’ from the repository list. Select the ‘JBoss Repository’ from the ‘Available Repositories’ list and add it to the ‘Ordered Group Repositories’ list. Press the ‘Save’ button.

I can now add Hibernate to my pom.xml.

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-core</artifactId>
	<version>3.5.6-Final</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-annotations</artifactId>
	<version>3.5.6-Final</version>
</dependency>

For some reason it’s also necessary to add javassist to the pom.xml (A Hibernate dependency that isn’t included in the dependencies!)

<dependency>
	<groupId>javassist</groupId>
	<artifactId>javassist</artifactId>
	<version>3.11.0.GA</version>
</dependency>

and slf4j (See http://www.slf4j.org/codes.html#StaticLoggerBinder for more options with slf4j.)

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.5.8</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-simple</artifactId>
	<version>1.5.8</version>
</dependency>

As many of the annotations used in the Hibernate POJO’s are java persistence annotations, it’s necessary to add persistence-api to the pom.xml.

<dependency>
	<groupId>javax.persistence</groupId>
	<artifactId>persistence-api</artifactId>
	<version>1.0</version>
</dependency>

As I’m using Spring I also need to add spring-orm.

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-orm</artifactId>
	<version>${org.springframework.version}</version>
</dependency>

Now I need to configure Spring to use MySQL and Hibernate.

I’ve decided to move my Spring MVC configuration into a file called mvc-config.xml and my Spring database configuraion in db-config.xml. Both of these files are referenced from the igallery-servlet.xml file (see https://siark.wordpress.com/2010/09/29/web-mvc-project-using-spring-framework-3-eclipse-helios-and-maven-2-on-os-x-snow-leopard/). All three of these files are located in the WEB-INF directory.

igallery-servlet.xml

<?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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Scans the classpath of this application for @Components (@Service, @Controller, etc...) annotations to deploy as beans -->
	<context:component-scan base-package="com.siark.igallery" />
	
	<!-- Configures Spring MVC -->
	<import resource="mvc-config.xml" />
	
	<!-- Configures Hibernate - Database Config -->
	<import resource="db-config.xml" />
	
</beans>

mvc-config.xml

<?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:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

	<!-- Configures the @Controller programming model -->
	<mvc:annotation-driven />

	<!-- Resolves view names to protected .jsp resources within the /WEB-INF/jsp directory -->
	<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
</beans>

db-config.xml

<?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:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:jdbc.properties</value>
				<value>classpath:igallery.properties</value>
			</list>
		</property>
	</bean>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>${jdbc.driverClassName}</value>
		</property>
		<property name="url">
			<value>${jdbc.url}</value>
		</property>
		<property name="username">
			<value>${jdbc.username}</value>
		</property>
		<property name="password">
			<value>${jdbc.password}</value>
		</property>
	</bean>
	
	<!-- Hibernate SessionFactory -->
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource"><ref local="dataSource" /></property>
		<property name="packagesToScan" value="com.siark.igallery.domain" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.show_sql">false</prop>
			</props>
		</property>
	</bean>

	<!-- Transaction manager for a Hibernate SessionFactory -->
	<tx:annotation-driven />
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory" />
		</property>
	</bean>
</beans>

Finally my database properties are in a file called jdbc.properties which is in the src/main/resources directory but will end up in the WEB-INF/classes directory when the jar is packaged.

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/siark
jdbc.username=username
jdbc.password=password

October 5, 2010

Java Message Resources

I want the siark.com website to have support internationalisation. The first step in achieving this is to use message resources. I have a default resource file called Messages.properties that is stored in the src/main/resources directory. When maven packages the war file, this resource file will be located on the classpath of the war file in WEB-INF/classes (by default).

I also have another message resource file called Messages_en_US.properties. The Messages.properties file is the default and includes all of the website text in English, but as some words have a differnt spelling in the United States (internationalisation is a good example), the alternative spelling is included in the Messages_en_US.properties file. The Messages_en_US.properties file is used if the locale of the browser is set to the United States. The file has the same name as the default messages resource file, but with the language code (en) and country or dialect code (US) appended.

The Messages.properties file.

home.title=siark.com home
home.greeting=Welcome to the internationalised siark.com website.

The Messages_en_US.properties file.

home.greeting=Welcome to the internationalized siark.com website.

The message resource file is used in JSP page by setting the default resource using . An entry in the resource file is accesed using

<?xml version="1.0" encoding="ISO-8859-1" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
		xmlns:c="http://java.sun.com/jsp/jstl/core"
		xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
		version="2.0">
	<fmt:setBundle basename="Messages" />
    <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" />
    <!-- According to the W3C XHTML 1.0 Recommendation, an XML declaration is not required, but authors are strongly encouraged to use XML declarations in documents. -->
    <jsp:text>
        <![CDATA[ <?xml version="1.0" encoding="ISO-8859-1" ?> ]]>
    </jsp:text>
    <!-- According to the W3C XHTML 1.0 Recommendation, there must be a DOCTYPE declaration prior to the root element. -->
    <jsp:text>
        <![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ]]>
    </jsp:text>
	<html xmlns="http://www.w3.org/1999/xhtml">
		<head>
			<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
			<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/styles/base.css" />
			<title><fmt:message key="home.title" /></title>
		</head>
		<body>
			<fmt:message key="home.greeting" />
		</body>
	</html>
</jsp:root>

October 1, 2010

Behaviour Driven Development With JBehave Web 3, Selenium and Maven 2 on OS X Snow Leopard

For integration testing on the siark.com webapp I’m using JBehave 3 and Selenium. As an initial test all I’m going to do is test that the home page displays the message “Welcome to the siark.com website.” when a user navigates to /igallery, the root of the siark.com website.I’m using the Page Object pattern to construct the tests.

1 Write a scenario and save it in a file called home.story in src/test/resources/com/siark/igallery/stories

Scenario: User opens home page

Given the user opens the home page
Then the home page should be displayed

2 Create a generic page class that I call IGalleryPage. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery.pages;

import org.jbehave.web.selenium.SeleniumPage;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class IGalleryPage extends SeleniumPage {
	
	public IGalleryPage(Selenium selenium, ConditionRunner conditionRunner) {
		super(selenium, conditionRunner);
	}
}

3 Create a HomePage class that deals with the home page specifically. The java file is created in src/test/java/com/siark/igallery/pages.

package com.siark.igallery.pages;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class HomePage extends IGalleryPage {
	
	public HomePage(Selenium selenium, ConditionRunner conditionRunner) {
		super(selenium, conditionRunner);
	}
	
	public void open() {
		open("/igallery/");
	}
	
	public void verifyPage() {
		textIsVisible("Welcome to the siark.com website.");
	}
}

4 Now create a class called IGallerySteps that defines the annotations used in the story. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;

import com.siark.igallery.pages.FindSteps;
import com.siark.igallery.pages.HomePage;
import com.siark.igallery.pages.PageFactory;

public class IGallerySteps {
	private final PageFactory pageFactory;
	private HomePage home;
	
	public IGallerySteps(PageFactory pageFactory) {
		this.pageFactory = pageFactory;
	}
	
	@Given("the user opens the home page")
	public void theUserOpensTheHomePage() {        
		home = pageFactory.home();
		home.open();        
	}
	
	@Then("the home page should be displayed")
	public void theHomePageShouldBeDisplayed(){
		home.verifyPage();
	}
}

5 A factory class called PageFactory is used to get page classes. The java class is created in src/test/java/com/siark/igallery/pages.

package com.siark.igallery.pages;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class PageFactory {
	private final Selenium selenium;
	private final ConditionRunner conditionRunner;
	
	public PageFactory(Selenium selenium, ConditionRunner conditionRunner) {
		this.selenium = selenium;
		this.conditionRunner = conditionRunner;
	}
	
	public HomePage home() {
		return new HomePage(selenium, conditionRunner);
	}
}

6 To configure and run the tests, an embedable runnable class is created, which I have called IGalleryStories. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import static java.util.Arrays.asList;
import static org.jbehave.core.io.CodeLocations.codeLocationFromClass;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.CONSOLE;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.HTML;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.TXT;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.XML;

import java.util.List;

import org.jbehave.core.Embeddable;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.io.CodeLocations;
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.io.StoryFinder;
import org.jbehave.core.junit.JUnitStories;
import org.jbehave.core.reporters.ConsoleOutput;
import org.jbehave.core.reporters.StoryReporter;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.steps.CandidateSteps;
import org.jbehave.core.steps.InstanceStepsFactory;
import org.jbehave.core.steps.SilentStepMonitor;
import org.jbehave.web.selenium.SeleniumConfiguration;
import org.jbehave.web.selenium.SeleniumContext;
import org.jbehave.web.selenium.SeleniumStepMonitor;

import com.siark.igallery.pages.PageFactory;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class IGalleryStories extends JUnitStories {
	
	private Selenium selenium = SeleniumConfiguration.defaultSelenium();
	private ConditionRunner conditionRunner = SeleniumConfiguration.defaultConditionRunner(selenium);
	private PageFactory pageFactory = new PageFactory(selenium, conditionRunner);
	private SeleniumContext seleniumContext = new SeleniumContext();

	@Override
	public Configuration configuration() {
		Class<? extends Embeddable> embeddableClass = this.getClass();
		return new SeleniumConfiguration()
			.useSelenium(selenium)
			.useSeleniumContext(seleniumContext)
			.useStepMonitor(new SeleniumStepMonitor(selenium, seleniumContext, new SilentStepMonitor()))
			.useStoryLoader(new LoadFromClasspath(embeddableClass))
			.useStoryReporterBuilder(new StoryReporterBuilder() {
			
				@Override
				public StoryReporter reporterFor(String storyPath, Format format) {
					if (format == CONSOLE) {
						return new ConsoleOutput() {
							@Override
							public void beforeScenario(String title) {
								seleniumContext.setCurrentScenario(title);
								super.beforeScenario(title);
							}
						};
					} else {
						return super.reporterFor(storyPath, format);
					}
				}
			}
			.withCodeLocation(CodeLocations.codeLocationFromClass(embeddableClass))
			.withDefaultFormats()
			.withFormats(CONSOLE, TXT, HTML, XML));
	}
	
	@Override
	public List<CandidateSteps> candidateSteps() {
		return new InstanceStepsFactory(configuration(), new IGallerySteps(pageFactory), new FailingScenarioScreenshotCapture(selenium))
		.createCandidateSteps();
	}
	
	@Override
	protected List<String> storyPaths() {
		return new StoryFinder().findPaths(codeLocationFromClass(this.getClass()).getFile(), asList("**/*.story"), null);
	}
}

This class is actually the same class provided on the JBehave website 🙂

7 The last class is also from the JBehave website and is the FailingScenarioScreenshotCapture used in IGalleryStories. It is also created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import org.jbehave.core.annotations.AfterScenario;
import org.jbehave.core.annotations.AfterScenario.Outcome;
import org.jbehave.web.selenium.PerScenarioSeleniumSteps;

import com.thoughtworks.selenium.Selenium;

public class FailingScenarioScreenshotCapture extends PerScenarioSeleniumSteps {
	public FailingScenarioScreenshotCapture(Selenium selenium) {
		super(selenium);
	}
	
	@AfterScenario(uponOutcome = Outcome.FAILURE)
	public void afterFailingScenario() throws Exception {
		String home = System.getenv("HOME");
		selenium.captureScreenshot(home+"/failedScenario.png");
	}
}

8 Finally I need to add a dependency for jbehave-web-selenium, the maven-jetty-plugin plugin so that the Jetty application server can be started and stopped and the igallery.war file can be deployed to it, the selenium-maven-plugin plugin to start and stop the Selenium server and lastly the jbehave-maven-plugin plugin to run the integration tests. The Jetty server will listen on port 8080 for HTTP requests, and the Selenium server will listen on port 4444.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.siark.igallery</groupId>
	<artifactId>igallery</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>Siark iGallery Webapp</name>
	<url>http://www.siark.com</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.jbehave.web</groupId>
			<artifactId>jbehave-web-selenium</artifactId>
			<version>3.0</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>igallery</finalName>
		<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.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<configuration>
					<webApp>${project.build.directory}/igallery.war</webApp>
					<webAppConfig>
						<contextPath>/igallery</contextPath>
					</webAppConfig>
				</configuration>
				<executions>
					<execution>
						<id>start-jetty</id>
						<phase>pre-integration-test</phase>
						<goals>
							<goal>run-war</goal>
						</goals>
						<configuration>
							<scanIntervalSeconds>0</scanIntervalSeconds>
							<daemon>true</daemon>
						</configuration>
					</execution>
					<execution>
						<id>stop-jetty</id>
						<phase>post-integration-test</phase>
						<goals>
							<goal>stop</goal>
						</goals>
						<configuration>
							<stopKey>stopJetty</stopKey>
							<stopPort>9966</stopPort>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>selenium-maven-plugin</artifactId>
				<version>1.0.1</version>
				<executions>
					<execution>
						<id>start-selenium</id>
						<phase>pre-integration-test</phase>
						<goals>
							<goal>start-server</goal>
						</goals>
						<configuration>
							<background>true</background>
							<debug>false</debug>
							<logOutput>true</logOutput>
						</configuration>
					</execution>
					<execution>
						<id>stop-selenium</id>
						<phase>post-integration-test</phase>
						<goals>
							<goal>stop-server</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.jbehave</groupId>
				<artifactId>jbehave-maven-plugin</artifactId>
				<version>3.0</version>
				<executions>
					<execution>
						<id>run-stories</id>
						<phase>integration-test</phase>
						<configuration>
							<includes>
								<include>**/*Stories.java</include>
							</includes>
							<scope>test</scope>
						</configuration>
						<goals>
							<goal>run-stories-as-embeddables</goal>
						</goals>
					</execution>
				</executions>
				<dependencies>
					<dependency>
						<groupId>org.jbehave.web</groupId>
						<artifactId>jbehave-web-selenium</artifactId>
						<version>3.0</version>
					</dependency>
				</dependencies>
			</plugin>
		</plugins>
	</build>
	<properties>
		<org.springframework.version>3.0.4.RELEASE</org.springframework.version>
	</properties>
</project>

September 29, 2010

Web MVC Project using Spring Framework 3 Eclipse Helios and Maven 2 on OS X Snow Leopard

I want to create the simplest of webapps using Spring Framework 3 and JSP documents. The version of JSP is 2.1 as that is the version used by Tomcat 6.

1 Update the pom.xml with the Spring Framework dependencies.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.siark.igallery</groupId>
	<artifactId>igallery</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>Siark iGallery Webapp</name>
	<url>http://www.siark.com</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>igallery</finalName>
		<plugins>
			<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>
	<properties>
		<org.springframework.version>3.0.4.RELEASE</org.springframework.version>
	</properties>
</project>

2 Update the web.xml file to include the Spring Framework Dispatcher Servlet.

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_2_5.xsd" 
	version="2.5">
	<display-name>igallery</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	<servlet>
		<servlet-name>igallery</servlet-name>
			<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>igallery</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
</web-app>

The Dispatcher Servlet handles requests to all resources that have the extension html. The DispatcherServlet class is in the spring-webmvc artifact.

3 I want the JSP pages to be in the WEB-INF/jsp directory. As this directory is not part of the public hierarchy, the welcome page (index.jsp) must redirect to the home page (home.html) which is then intercepted by the Spring Framework Dispatcher servlet.

<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" info="siark.com home page" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!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" xml:lang="en" lang="en">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
		<title>siark.com home</title>
	</head>
	<body>
		<%-- Redirected because we can't set the welcome page to a virtual URL. --%>
		<c:redirect url="home.html" />
	</body>
</html>

4 The Spring Framework Web MVC uses a special file to configure it’s web application context and is based on the servlet name assigned to the dispatcher servlet (igallery). This file then is igallery-servlet.xml and by default is in the WEB-INF directory.

<?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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	
	<context:component-scan base-package="com.siark.igallery" />
	
	<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jspx"/>
	</bean>
</beans>

This minimal web application context configuration file includes an entry to enable auto-detection of annotated controllers and an entry to configure the view resolver.

5 The controller for the home page is as simple as possible and just handles an HTTP GET request.

package com.siark.igallery.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@Controller
@RequestMapping("/home")
public class HomeController {
	
	protected final Log logger = LogFactory.getLog(getClass());
	
	/**
	 *
	 */
	@RequestMapping(method=RequestMethod.GET)
	public void get() {
		logger.info("Returning the home view.");
	}
}

The Controller annotation is in the spring-context artifact and the RequestMapping and RequestMethod annotations are in the spring-web artifact.

6 The home page itself is home.jspx and is in the WEB-INF/jsp directory. It is a JSP document.

<?xml version="1.0" encoding="ISO-8859-1" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
		xmlns:c="http://java.sun.com/jsp/jstl/core"
		xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
		version="2.0">
    <jsp:directive.page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" />
    <!-- According to the W3C XHTML 1.0 Recommendation, an XML declaration is not required, but authors are strongly encouraged to use XML declarations in documents. -->
    <jsp:text>
        <![CDATA[ <?xml version="1.0" encoding="ISO-8859-1" ?> ]]>
    </jsp:text>
    <!-- According to the W3C XHTML 1.0 Recommendation, there must be a DOCTYPE declaration prior to the root element. -->
    <jsp:text>
        <![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ]]>
    </jsp:text>
	<html xmlns="http://www.w3.org/1999/xhtml">
		<head>
			<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
		</head>
		<body>
			<p>Welcome to the siark.com website.</p>
		</body>
	</html>
</jsp:root>

September 17, 2010

A Simple Web Application in Eclipse

A web application contains a structured hierarchy of directories. The root of the directory hierarchy is known as the document root. The WEB-INF diretory is special directory within the directory hierarchy that is not part of the public hierarchy of directories. The contents of the WEB-INF directory include the web.xml deployment descriptor file, a classes directory and a lib directory.

When a web application is packaged as a WAR file, a META-INF directory is included in the root of the directory hierarchy.

Tomcat 6 supports the Servlet 2.5 specification. The web.xml file for a web application deployed to Tomcat 6 must contain as a minimum a web-app element. However, to create the first page of the siark.com website I want to include an index.jsp page that will be used as a welcome file.

1 Create the web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_2_5.xsd" 
	version="2.5">
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

2 Create the index.jsp file in the root of the directory hierarchy (src/main/webapp).

<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" info="siark.com home page" %>
<!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" xml:lang="en" lang="en">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
		<title>siark.com home</title>
	</head>
	<body>
		<p>Welcome to the siark.com website.</p>
	</body>
</html>

To deploy and run the project, right click on the igallery project in the ‘Project Explorer’ view. Select ‘Run on Server’ from the ‘Run As’ menu item. Select the server created. Eclipse will deploy the igallery web app to the server and a browser tab will open in the Eclipse workbench displaying the landing page of the web app.

Create a free website or blog at WordPress.com.