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.

Create a free website or blog at WordPress.com.