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>

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 22, 2010

Home Page Design with CSS

Filed under: Uncategorized — Tags: , , — Mark Gould @ 10:23 am

Home Page Design with CSS.

The page design requirements of the siark.com website are different from many websites. In contrast to websites that contain quite a lot of content on their pages that is arranged in columns, the pages of the siark.com website contain minimal content that is arranged centrally.

The pages of the siark.com website have two menus, one at the top which is nearly always displayed, and one at the bottom which is not always displayed. The home page also has an image wich is positioned centrally on the page.

The page layout is achieved using CSS. There are two significant problems to overcome on the home page; the first is to make the bottom menu stick to the bottom of the browser window when the content does not fill the browser window and the second is to centralise both horizontally and vertically the image.

There is a base CSS file called base.css which is in the webapp/styles directory.

This CSS file contains general design styles and is organised by typography & colours, structure, images and menus.

Each page on the siark.com website has some or all of the components, a header, a top menu, content, a bottom menu and a footer.

/* Typography & Colors
------------------------------------ */
body { font-size:100%; }
.container {
	background-color:rgb(245,245,245);
	font-family: Helvetica, sans-serif;
	
}
#logo {
	font-variant: small-caps;
	font-size: 1.5em; 
}
#copyright {
	font-variant: small-caps;
	font-size: 0.8em;
}
.caption {
	font-variant: small-caps;
	font-size: 0.8em;
}
.menu { font-variant: small-caps; }

/* Structure
------------------------------------ */

html, body { 
	height: 100%;
	margin: 0;
	padding: 0;
}
.container {
	width: 960px;
	margin: 0 auto;
	min-height: 100%;
}
#header { 
	height: 2em;
	margin: 0 0.5em;
	line-height: 2em;
}
#top-menu { 
	height: 1em;
	line-height: 1em;
}
#content { 
	margin-top: 0;
	padding-bottom: 2em;
}
#bottom-menu {
	position: absolute;
	bottom: 2em; /* the same height as the footer */
	height: 1em;
	line-height: 1em;
	padding-bottom: 1px;
}
#footer {
	position: absolute;
	bottom: 0;
	height: 2em;
	width: 950px;
	margin: 0 0.5em;
	line-height: 2em;
}

/* Images
------------------------------------ */

img.border {
	border-style: solid;
	border-color: black;
	border-width: 1px;
	padding: 3px;
	background-color: rgb(255, 255, 255);
}

/* Menus
------------------------------------ */

.menu ul {
	margin: 0;
	padding: 0;
	list-style: none;
}
.menu ul li {
	float: left;
	position: relative;
	padding: 0 1em;
}
.menu ul li.right { float: right; }
.menu li a:hover { text-decoration:underline; }
.menu li ul { 
	display: none;
	position: absolute;
	top: 1em;
	left: 0;
}
.menu li:hover ul {
	display: block;
}
.menu li li {
	list-style: none;
	display: list-item;
}
a { 
	text-decoration: none;
	color: black;
}
.bottom-line {
	border-bottom-style: solid;
	border-bottom-color: black;
	border-bottom-width: 1px;
}

.top-line {
	border-top-style: solid;
	border-top-color: black;
	border-top-width: 1px;
}

The bottom menu and footer have absolute positioning so that they always appear at the bottom of the page.

Each page also has it’s own CSS file and the file for the home page is home.css.

#home-image {
	position: absolute;
 	top: 45%;
 	left: 50%;
 	margin-top: -241px;
 	margin-left: -241px;
 	height: 482px;
 	width: 482px;
}

The home page image is also positioned absolutely. This strategy is from the CSS Cookbook by Christopher Schmitt.

The home.jsp page.

<?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" />
			<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>
									<li><a href="#"><fmt:message key="menu.gallery1" /></a></li>
									<li><a href="#"><fmt:message key="menu.gallery2" /></a></li>
									<li><a href="#"><fmt:message key="menu.gallery3" /></a></li>
								</ul>
							</li>
	  						<li><a href="#"><fmt:message key="menu.about" /></a></li>
	  						<li><a href="#"><fmt:message key="menu.contact" /></a></li>
	  						<li class="right"><a href="#"><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.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>

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 11, 2010

What is siark.com?

Filed under: Uncategorized — Tags: , , , — Mark Gould @ 1:42 pm

siark.com is two things. Firstly it’s my photography website. Secondly it’s a photo oriented CMS (Content Management System) written by me in Java.

This blog is where I’ll post articles about photography and Java and web technologies that are related to siark.com.

Blog at WordPress.com.