Monday, February 11, 2013

Sample Apps: Spring data MongoDB and JSF Integration tutorial (PART 5)


Table of Contents:
1. Introduction to sample application (MongoShop Product Catalog)
2. MongoDB schema design and data preparation
3. JSF (PrimeFaces) and Spring data MongoDB Integration
4. Enquriy data with spring data repository and mongotemplate
5. Create, Edit and delete data


Create, Edit and Delete data with Spring data repository


In the last part of this tutorial, we will add create, edit and delete function to the MongoShop Product Catalog application.

The search page is modified. A modal confirm dialogue box is added before the product is physically deleted
updated search.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:p="http://primefaces.org/ui">

 <ui:composition template="/template/common.xhtml">
 
  <ui:define name="pageTitle">
   <h:outputText value="Product Search" />
  </ui:define>
 
  <ui:define name="content">
      <h:form id="searchForm">
       <p:growl id="mainGrowl" sticky="true"  />
       <p:panelGrid style="width:1024px">  
        <f:facet name="header">
         <p:row>  
                <p:column colspan="4">  
              Product Search
             </p:column>
            </p:row>
        </f:facet>  
       <p:row> 
        <p:column>
          <h:outputLabel for="sku" value="sku: " />
         </p:column>
         <p:column>  
          <p:inputText id="sku" value="#{productSearchBean.criteria.sku}" />
         </p:column>  
         <p:column>  
          <h:outputLabel for="productType" value="Product Type: " />
         </p:column>
            <p:column>  
          <p:selectOneMenu id="productType"   label="Type"   value="#{productSearchBean.criteria.productType}"  >  
                 <f:selectItem itemLabel="Select One" itemValue="" />  
                 <f:selectItem itemLabel="Audio Album" itemValue="Audio Album" />  
                 <f:selectItem itemLabel="Book" itemValue="Book" /> 
             </p:selectOneMenu>
         </p:column>
       </p:row>
       <p:row> 
        <p:column>
          <h:outputLabel for="title" value="Title: " />
         </p:column>
         <p:column>  
          <p:inputText id="title" value="#{productSearchBean.criteria.title}" />
         </p:column>  
         <p:column>  
          <h:outputLabel for="description" value="Description: " />
         </p:column>
            <p:column>  
          <p:inputText id="description" value="#{productSearchBean.criteria.description}" />
         </p:column>
       </p:row>      
  
       <p:row> 
        <p:column>
          <h:outputLabel for="track" value="Track: " />
         </p:column>
         <p:column>  
          <p:inputText id="track" value="#{productSearchBean.criteria.track}" />
         </p:column>  
         <p:column>  
          <h:outputLabel for="chapter" value="Chapter: " />
         </p:column>
            <p:column>  
          <p:inputText id="chapter" value="#{productSearchBean.criteria.chapter}" />
         </p:column>
       </p:row> 
       
     </p:panelGrid>
     <p:commandButton value="search" icon="ui-icon-search"  actionListener="#{productSearchBean.doSearch}" update="dataTable"/>
     <hr/>
 
     <p:dataTable id="dataTable" var="prod" value="#{productSearchBean.productList}"  
                  paginator="true" rows="10">  
      
            <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Sku" />  
                </f:facet>  
                <h:outputText value="#{prod.sku}" />  
            </p:column>  
      
        <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Type" />  
                </f:facet>  
                <h:outputText value="#{prod.type}" />  
            </p:column> 
      
            <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Title" />  
                </f:facet>  
                <h:outputText value="#{prod.title}" />  
            </p:column>  
      
            <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Publisher" />  
                </f:facet>  
                <h:outputText value="#{prod.publisher}" />  
            </p:column>  
      
             <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Artist" />  
                </f:facet>  
                <h:outputText value="#{prod.details.artist}" />  
            </p:column>  
       
       <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Author" />  
                </f:facet>  
                <h:outputText value="#{prod.details.author}" />  
            </p:column>  
       
        <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Edit" />  
                </f:facet>  
                 <p:commandButton value="Edit"  action="#{productSearchBean.doEditDetail}" ajax="false">
                  <f:setPropertyActionListener target="#{productSearchBean.selectedProduct}" value="#{prod}" />
                 </p:commandButton>
            </p:column>  
  
       <p:column>  
                <f:facet name="header">  
                    <h:outputText value="Delete" />  
                </f:facet>
                  
                <p:commandButton id="showDialogButton" value="Delete" oncomplete="confirmation.show()" ajax="true" update=":searchForm:confirmDialog">  
                  <f:setPropertyActionListener target="#{productSearchBean.selectedProduct}" value="#{prod}" />
                </p:commandButton>
                
            </p:column>
         
  
        </p:dataTable>
      
      <p:confirmDialog id="confirmDialog" message="Are you sure to delete this product (#{productSearchBean.selectedProduct.sku})?"  
                    header="Delete Product" severity="alert" widgetVar="confirmation">  
                      
            <p:commandButton id="confirm" value="Yes" update="mainGrowl" oncomplete="confirmation.hide()"  
                        actionListener="#{productSearchBean.doDelete}" />  
            <p:commandButton id="decline" value="No" onclick="confirmation.hide()" type="button" />   
                      
        </p:confirmDialog>
      
      </h:form>
  </ui:define>
 </ui:composition>
</html>

updated ProductSearchBean.java
package com.borislam.view;

import java.util.List;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
import com.borislam.domain.Product;
import com.borislam.service.ProductService;

@Component
@Scope("session")
public class ProductSearchBean {
 
 private Product selectedProduct;
 
 private ProductSearchCriteria criteria = new ProductSearchCriteria();
 
 private List<Product> productList;
 
 
 public Product getSelectedProduct() {
  return selectedProduct;
 }

 public void setSelectedProduct(Product selectedProduct) {
  this.selectedProduct = selectedProduct;
 }

 public List<Product> getProductList() {
  return productList;
 }

 public void setProductList(List<Product> productList) {
  this.productList = productList;
 }

 public ProductSearchCriteria getCriteria() {
  return criteria;
 }

 public void setCriteria(ProductSearchCriteria criteria) {
  this.criteria = criteria;
 }
 @Autowired
 private ProductService productService;
 
 public void doSearch(ActionEvent event){
  productList= productService.searchByCriteria(criteria);
 }
 
 
 public String doEditDetail() {
  (FacesContext.getCurrentInstance().getExternalContext().getFlash()).put("selected", selectedProduct);
  return "detail.xhtml";
 }
 

 public void doDelete(ActionEvent event){
  try {   
   productService.deleteProduct(selectedProduct);
   
   FacesContext context = FacesContext.getCurrentInstance();  
         context.addMessage(null, new FacesMessage("Delete Successfully!"));
  }
  catch (DataAccessException e ) {
   FacesContext context = FacesContext.getCurrentInstance();  
         context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,"Error when deleting product!",null));
  }
  
 }
}


A product detail page is added to view the produc details. Creation and edition of product is done in the product detail page.
detail.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:p="http://primefaces.org/ui">

 <ui:composition template="/template/common.xhtml">
 
  <ui:define name="pageTitle">
   <h:outputText value="Product Search" />
  </ui:define>
 
  <ui:define name="content">
     <f:event listener="#{productDetailBean.initProduct}" type="preRenderView" />
     
      <h:form id="mainForm">      
       <p:growl id="mainGrowl" sticky="true"  />
       <p:panelGrid style="width:1024px">  
        <f:facet name="header">
         <p:row>  
                <p:column colspan="2">  
              Product Details
             </p:column>
            </p:row>
        </f:facet>  
       <p:row> 
        <p:column>
          <h:outputLabel for="sku" value="sku: *" />
         </p:column>
         <p:column>  
          <p:inputText id="sku" required="true" value="#{productDetailBean.product.sku}" label="Sku"  rendered="#{productDetailBean.newProduct}"/>
          <h:outputText  value="#{productDetailBean.product.sku}" label="Sku" rendered="#{not productDetailBean.newProduct}"/>
         </p:column>  
       </p:row>
       <p:row> 
        <p:column>
          <h:outputLabel for="type" value="Type *" />
         </p:column>
         <p:column>  
          <p:selectOneMenu id="type" required="true"  label="Type"  valueChangeListener="#{productDetailBean.clearDetails}" value="#{productDetailBean.product.type}"  >  
                 <f:selectItem itemLabel="Select One" itemValue="" />  
                 <f:selectItem itemLabel="Audio Album" itemValue="Audio Album" />  
                 <f:selectItem itemLabel="Book" itemValue="Book" /> 
                 <f:ajax  render="buttonPanel trackPanel chapterPanel"/>
             </p:selectOneMenu>
         </p:column>  
       </p:row> 
       <p:row> 
        <p:column>
          <h:outputLabel for="title" value="Title: *" />
         </p:column>
         <p:column>  
          <p:inputText id="title" required="true" value="#{productDetailBean.product.title}" label="Title" />
         </p:column>  
       </p:row> 
       <p:row> 
        <p:column>
          <h:outputLabel for="description" value="Description: *" />
         </p:column>
         <p:column>  
          <p:inputText id="description" required="true" value="#{productDetailBean.product.description}" label="Description" />
         </p:column>  
       </p:row> 
       <p:row> 
        <p:column>
          <h:outputLabel for="publisher" value="Publisher: *" />
         </p:column>
         <p:column>  
          <p:inputText id="publisher" required="true" value="#{productDetailBean.product.publisher}" label="Publisher" />
         </p:column>  
       </p:row> 
        
        <p:row> 
        <p:column>
          <h:outputLabel for="artist" value="Artist: " />
         </p:column>
         <p:column>  
          <p:inputText id="artist"  value="#{productDetailBean.product.details.artist}" label="Artist" />
         </p:column>  
       </p:row>  
 
        <p:row> 
        <p:column>
          <h:outputLabel for="listPrice" value="List Price: " />
         </p:column>
         <p:column>  
          <p:inputText id="listPrice"  required="true" value="#{productDetailBean.product.pricing.list}" label="List Price" />
         </p:column>  
       </p:row>  
  
         <p:row> 
        <p:column>
          <h:outputLabel for="retailPrice" value="Retail Price: " />
         </p:column>
         <p:column>  
          <p:inputText id="retailPrice"  required="true" value="#{productDetailBean.product.pricing.retail}" label="REtail Price" />
         </p:column>  
       </p:row>  
  
        <p:row> 
        <p:column>
          <h:outputLabel for="author" value="Author: " />
         </p:column>
         <p:column>  
          <p:inputText id="author"  value="#{productDetailBean.product.details.author}" label="Author" />
         </p:column>  
       </p:row> 
  
        <p:row> 
        <p:column>
          <h:outputLabel for="genre" value="Genre: *" />
         </p:column>
         <p:column>  
          <p:inputText id="genre" required="true" value="#{productDetailBean.product.details.genre}" label="Genre" />
         </p:column>  
       </p:row> 
 
      <p:row>  
            <p:column colspan="2" styleClass="ui-widget-header">  
             <p:outputPanel id="buttonPanel">            
                 <p:commandButton value="Add Tracks"   onclick="addTrackDlg.show();" type="button" rendered="#{productDetailBean.product.type == 'Audio Album'}"/>               
                 <p:commandButton value="Add Chapters"   onclick="addChapterDlg.show();" type="button" rendered="#{productDetailBean.product.type == 'Book'}"/>
                </p:outputPanel>
            </p:column>  
        </p:row>
 
        <p:row>  
            <p:column colspan="2" > 
             <p:outputPanel id="trackPanel" >
                 <p:dataList value="#{productDetailBean.product.details.tracks}" var="track" type="ordered" rendered="#{productDetailBean.product.details.tracks.size() > 0}">  
            #{track} 
        </p:dataList> 
       </p:outputPanel>
       <p:outputPanel id="chapterPanel" >
        <p:dataList value="#{productDetailBean.product.details.chapters}" var="chapter" type="ordered" rendered="#{productDetailBean.product.details.chapters.size() > 0}">  
            #{chapter} 
        </p:dataList> 
       </p:outputPanel> 
       
            </p:column>  
        </p:row> 
  
        <f:facet name="footer"> 
         <p:row>
          <p:column colspan="2">  
           <p:commandButton value="Save" icon="ui-icon-disk"  actionListener="#{productDetailBean.doSave}" update="mainGrowl" />
           <p:button value="Back to Search" icon="ui-icon-back"  outcome="search.xhtml" />
          </p:column>
         </p:row>          
        </f:facet>  
    </p:panelGrid>   
   
   </h:form>
   
   
   <h:form>   
    <p:growl id="trackGrowl" sticky="true"  />
    <p:dialog id="addTrackDlg" header="Adding Tracks for the product" widgetVar="addTrackDlg" modal="true" height="100" width="450" resizable="false">  
        <h:outputLabel for="track" value="Track: " /> 
        <p:inputText id="track" required="true" value="#{productDetailBean.newTrack}" label="Track" /> 
        <p:commandButton value="Add" actionListener="#{productDetailBean.doAddTracks}" icon="ui-icon-check"  update="trackGrowl, :mainForm:trackPanel" oncomplete="addTrackDlg.hide()"/>
    </p:dialog> 
   </h:form> 
   
   
   <h:form>
    <p:growl id="chapterGrowl" sticky="true"  /> 
    <p:dialog id="addChapterDlg" header="Adding Chapters for the product" widgetVar="addChapterDlg" modal="true" height="100" width="450" resizable="false">  
        <h:outputLabel for="chapter" value="Chapter: " /> 
        <p:inputText id="chapter" required="true" value="#{productDetailBean.newChapter}" label="Chapter" /> 
        <p:commandButton value="Add" actionListener="#{productDetailBean.doAddChapters}" icon="ui-icon-check"  update="chapterGrowl, :mainForm:chapterPanel" oncomplete="addChapterDlg.hide()"/>
        
    </p:dialog> 
   </h:form> 
  </ui:define>
 </ui:composition>
</html>  

ProductDetailsBean.java
package com.borislam.view;

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

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.dao.DataAccessException;

import com.borislam.domain.Detail;
import com.borislam.domain.Pricing;
import com.borislam.domain.Product;
import com.borislam.service.ProductService;

@Component
@Scope("session")
public class ProductDetailBean {
 
 @Autowired
 private ProductService productService;
 private boolean newProduct;
 private Product product;
 private String newTrack;
 private String newChapter;
 
 
 
 public boolean isNewProduct() {
  return newProduct;
 }

 public void setNewProduct(boolean newProduct) {
  this.newProduct = newProduct;
 }

 public Product getProduct() {
  return product;
 }

 public void setProduct(Product product) {
  this.product = product;
 }

 
 public String getNewTrack() {
  return newTrack;
 }

 public void setNewTrack(String newTrack) {
  this.newTrack = newTrack;
 }

 public String getNewChapter() {
  return newChapter;
 }

 public void setNewChapter(String newChapter) {
  this.newChapter = newChapter;
 }

 public void initProduct(){
  Object selectedProduct = (FacesContext.getCurrentInstance().getExternalContext().getFlash()).get("selected");
  
  if (selectedProduct==null &&  !FacesContext.getCurrentInstance().isPostback()) {
   product = new Product();
   product.setDetails(new Detail());
   product.setPricing(new Pricing(0,0));
   setNewProduct(true);
  }
  if (selectedProduct!=null) {
   product = (Product)selectedProduct;
   setNewProduct(false);
  }
  
 }
 
 public void  doSave(ActionEvent event) {

  try {
   productService.saveProduct(product);
   
   FacesContext context = FacesContext.getCurrentInstance();  
         context.addMessage(null, new FacesMessage("Save Successfully!"));
  }
  catch (DataAccessException e)
  { 
   e.printStackTrace();
   
   FacesContext context = FacesContext.getCurrentInstance();  
         context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,"Error when saving product!",null));
   
  }
    
 }
 
 public void  doAddTracks(ActionEvent event) {
  List<String> tracks = product.getDetails().getTracks();
  if (CollectionUtils.isEmpty(tracks)) {
   product.getDetails().setTracks(new ArrayList<String>());
  }
  product.getDetails().getTracks().add(this.newTrack);

 }
 
 public void  doAddChapters(ActionEvent event) {
  List<String> tracks = product.getDetails().getChapters();
  if (CollectionUtils.isEmpty(tracks)) {
   product.getDetails().setChapters(new ArrayList<String>() );
  }
  product.getDetails().getChapters().add(this.newChapter);

 }
 
 public void clearDetails(ValueChangeEvent  event) {

  if ("Audio Album".equalsIgnoreCase(event.getNewValue().toString()) ) {
   product.getDetails().setChapters(null);
  }
  if ("Book".equalsIgnoreCase( event.getNewValue().toString())) {
   product.getDetails().setTracks(null);
  }
 }
}


updated ProductService.java
package com.borislam.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.borislam.domain.Product;
import com.borislam.repository.ProductRepository;
import com.borislam.view.ProductSearchCriteria;

@Service
public class ProductService {
 
 @Autowired
 private ProductRepository productRepository;
 
 public List<Product> searchByCriteria(ProductSearchCriteria criteria){
  return productRepository.searchByCriteria(criteria);
 }
 
 public Product getProduct(String sku) {
  return productRepository.findBySku(sku);
 }
  
 public void saveProduct(Product p){  
  productRepository.save(p);
 }
 
 public void deleteProduct(Product p){  
  productRepository.delete(p);
 }
 
}


Conclusion:
1. Spring Data Mongo DB provides MongoTemplate which allow you to perform MongoDB operation easily.
2. MongoDB JSON-style document could mapped to POJO easily with the help of Spring Data MongoDB
3. Repository abstraction of spring data reduces the boilerplate code write for accessing MongoDB.
4. You add custom behaviour to spring data repository.


Get the source code

5 comments:

xorty said...

Hi when I "mvn clean install" on your project I am getting "Could not find artifact org.jboss.el:com.springsource.org.jboss.el:jar:2.0.0.GA in java.net (https://maven.java.net/content/repositories/public/)"

Any ideas?

Boris Lam said...

xorty, thanks.
I have corrected the pom.xml and commited to GitHub. You could get the latest one in GitHub

xorty said...

Thanks Boris, I was able to successfully build and run the application now :)

Mario Ramos said...

Hello Boris, I was able to successfully build and run the application, but i tried to create another application using Mojarra instead of Myfaces and using spring 3.2.1 (aop, core, beans, etc..), spring -data-commons-core (1.4.0), spring-mongo (1.1.0) and mongo driver 2.10.1. The project compile sucessfully, but does not inject dependencies at run time. Any ideas?? . I was able to run app if manually create a context variable like : FacesContext fc = FacesContext.getCurrentInstance();
ServletContext sc = (ServletContext)fc.getExternalContext().getContext();
ConfigurableApplicationContext context =
(ConfigurableApplicationContext)WebApplicationContextUtils.getWebApplicationContext(sc);


Thanks....

Mario Ramos said...

Thanks... I got it!!