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 – Part 1

Part 1 of creating the siark.com Gallery involves creating a simple gallery (not one that includes keywords or leaf/non-leaf galleries) with create, read, update and delete functionality.

The gallery domain class.

Before creating a gallery domain class, I will create a base domain class that includes an id as this will be common to all of the persistent domain classes. I have included a toString() implementation, but not hashCode() or equals() implementations as these would be implemented in the subclasses.

package com.siark.igallery.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@SuppressWarnings("serial")
@MappedSuperclass
public abstract class DomainBase implements Serializable {

	protected int id;

	/**
	 *
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	public int getId() {
		return id;
	}

	/**
	 *
	 */
	public void setId(int id) {
		this.id = id;
	}
	
	/**
	 * 
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("Id: " + this.id + "; ");
		return buffer.toString();
	}
}

The gallery domain class. The first implementation of this class simply contains a title. The equals() and hashCode() methods are based on those described by Joshua Bloch in Effective Java 2nd Edition.

package com.siark.igallery.domain;

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

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

	private static final long serialVersionUID = -7236265616573470929L;
	private String title;

	/**
	 *
	 */
	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;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(super.toString());
		buffer.append("Title: " + this.title + "; ");
		return buffer.toString();
	}
	
	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@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;
		}
	}
	
	/**
	 * @see java.lang.Object#hashCode()
	 */
	@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 gallery data access object.

The dao has an interface and an implemetation using hibernate. The dao implementation uses JSR330 annotations for dependency injection as Spring 3 is able to use these annotations instead of it’s own annotations.

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> getAllGalleries();
	public Gallery getGalleryById(int id);
	public void delete(int id);
}
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 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 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 gallery service.

Again the gallery service has an interface and an implementation.

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> getAllGalleries();
	public Gallery getRootGallery();
	public Gallery getGalleryById(int id);
	public void delete(int id);
}
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 of the galleries including the root gallery.
	 * 
	 * @return a list of all the galleries
	 */
	public List<Gallery> getAllGalleries() {
		return galleryDao.getAllGalleries();
	}
	
	/**
	 * 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.

package com.siark.igallery.web;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.siark.igallery.domain.Gallery;
import com.siark.igallery.service.GalleryService;

public class GalleryConroller extends MenuController {
	
	protected final Log logger = LogFactory.getLog(getClass());
	@Inject
	private GalleryService galleryService;
	
	/**
	 * Add a list of all galleries to the model.
	 * 
	 * @return the model with the galleries added.
	 */
	@RequestMapping(value="/gallery/admin/list", method=RequestMethod.GET)
	public ModelMap list() {
		logger.info("Returning gallery list for admin.");
		ModelMap model = new ModelMap();
		model.addAttribute("galleries", galleryService.getAllGalleries());
		return model;
	}
	
	/**
	 * 
	 * @param id
	 * @return
	 */
	@RequestMapping(value="/gallery/admin/form", method=RequestMethod.GET)
	public Gallery get(@RequestParam(required=false) Integer id, ModelMap model) {
		Gallery gallery = new Gallery();
		if (id != null) {
			gallery = galleryService.getGalleryById(id.intValue());
		}
		return gallery;
	}
	
	/**
	 * 
	 * @param gallery
	 * @return
	 */
	@RequestMapping(value="/gallery/admin/form", method=RequestMethod.POST)
	public String post(Gallery gallery) {
		logger.info("Persisting gallery: " + gallery.getId());
		galleryManager.persistGallery(gallery);
		return "redirect:/admin/gallery/list.html";
	}
	
	/**
	 * 
	 * @return
	 */
	@RequestMapping(value="/gallery/admin/delete", method=RequestMethod.GET)
	public String delete(@RequestParam Integer id) {
		logger.info("Deleting gallery id: " + id);
		galleryService.delete(id);
		return "redirect:/admin/gallery/list.html";
	}
}

The JSP pages that are handled by the controller are pages in the admin part of the siark.com website. One page displays a list of galleries and allows galleries to be deleted and the other page displays a gallery and allows a new gallery to be created or an existing gallery to be edited.

The page that lists the galleries.

<?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/admin.css" />
			<title><fmt:message key="gallery.admin.title" /></title>
		</head>
		<body>
			<div class="container">
				<div id="header" class="bottom-line">
					<div id="logo">
						<a href="${pageContext.request.contextPath}">siark.com</a>
					</div>
				</div>
				<div id="top-menu">
					<div class="menu">
						<ul>
							<li><fmt:message key="menu.admin.galleries" /></li>
						</ul>
					</div>
				</div>
				<div id="content">
					<fieldset>   
						<legend><span><fmt:message key="gallery.admin.form.galleries" /></span></legend>
						<c:forEach items="${galleries}" var="item">
							<c:out value="${item.title}" />
							<!-- Edit button. -->
							<jsp:element name="a">
								<jsp:attribute name="href">${pageContext.request.contextPath}/gallery/admin/form.html?id=${item.id}</jsp:attribute>
								<jsp:body><span><fmt:message key="edit" /></span></jsp:body>
							</jsp:element>
							<!-- Delete button. -->
							<jsp:element name="a">
								<jsp:attribute name="href">${pageContext.request.contextPath}/gallery/admin/delete.html?id=${item.id}</jsp:attribute>
								<jsp:body><span><fmt:message key="delete" /></span></jsp:body>
							</jsp:element>
						</c:forEach>
					</fieldset>
					<fieldset>
						<a href="${pageContext.request.contextPath}/gallery/admin/form.html">
							<span><fmt:message key="create" /></span>
						</a>
					</fieldset>
				</div>
			</div>
		</body>
	</html>
</jsp:root>

The page that creates/edits a 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"
		xmlns:form="http://www.springframework.org/tags/form"
		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/admin.css" />
			<title><fmt:message key="gallery.admin.title" /></title>
		</head>
		<body>
			<div class="container">
				<div id="header" class="bottom-line">
					<div id="logo">
						<a href="${pageContext.request.contextPath}">siark.com</a>
					</div>
				</div>
				<div id="top-menu">
					<div class="menu">
						<ul>
							<li><a href="${pageContext.request.contextPath}/gallery/admin/list.html"><fmt:message key="menu.admin.galleries" /></a></li>
						</ul>
					</div>
				</div>
				<div id="content">
					<form:form method="post" commandName="gallery">
						<fieldset>   
							<legend><span><fmt:message key="gallery.admin.form.gallery" /></span></legend>
							<!-- Title. -->
							<label for="title"><fmt:message key="gallery.admin.form.title" /></label>
							<form:input path="title" />
						</fieldset>
						<fieldset class="submit">
							<input type="submit" value="Execute" />
						</fieldset>
					</form:form>
				</div>
			</div>
		</body>
	</html>
</jsp:root>

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

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>

Create a free website or blog at WordPress.com.