Wednesday, January 9, 2013

Spring MVC3 Hibernate CRUD Sample Application

To learn any web framework starting with a HelloWorld application is a good idea. Once we get familiarity with the framework configuration it would be better to do a CRUD (Create,Read,Update,Delete) application which covers various aspects of a web framework like Validations, Request URL Mappings, Request Parameter Binding, Pre-populating forms etc.
Now I am going to explain how to write a Simple CRUD application using Spring MVC3, Hibernate and MySQL. Our Application is ContactsManagements where you can view or search contacts, create new contacts, edit or delete existing contacts.
Step#1: Create the CONTACTS Table
01CREATE TABLE  CONTACTS
02(
03  id int(10) unsigned NOT NULL AUTO_INCREMENT,
04  name varchar(45) NOT NULL,
05  address varchar(45) DEFAULT NULL,
06  gender char(1) DEFAULT 'M',
07  dob datetime DEFAULT NULL,
08  email varchar(45) DEFAULT NULL,
09  mobile varchar(15) DEFAULT NULL,
10  phone varchar(15) DEFAULT NULL,
11  PRIMARY KEY (id)
12);
Step#2: Copy the SpringMVC, Hibernate and their dependent jars into WEB-INF/lib folder. If you are using Maven you can mention the following dependencies.
001<dependencies>
002  <dependency>
003    <groupid>junit</groupid>
004    <artifactid>junit</artifactid>
005    <version>4.8.1</version>
006    <type>jar</type>
007    <scope>compile</scope>
008   </dependency>
009   <dependency>
010     <groupid>org.springframework</groupid>
011     <artifactid>spring-web</artifactid>
012     <version>3.0.5.RELEASE</version>
013     <type>jar</type>
014     <scope>compile</scope>
015    </dependency>
016    <dependency>
017     <groupid>org.springframework</groupid>
018     <artifactid>spring-core</artifactid>
019     <version>3.0.5.RELEASE</version>
020     <type>jar</type>
021     <scope>compile</scope>
022     <exclusions>
023      <exclusion>
024       <artifactid>commons-logging</artifactid>
025       <groupid>commons-logging</groupid>
026      </exclusion>
027     </exclusions>
028    </dependency>
029    <dependency>
030     <groupid>log4j</groupid>
031     <artifactid>log4j</artifactid>
032     <version>1.2.14</version>
033     <type>jar</type>
034     <scope>compile</scope>
035    </dependency>
036    <dependency>
037     <groupid>org.springframework</groupid>
038     <artifactid>spring-tx</artifactid>
039     <version>3.0.5.RELEASE</version>
040     <type>jar</type>
041     <scope>compile</scope>
042    </dependency>
043    <dependency>
044     <groupid>jstl</groupid>
045     <artifactid>jstl</artifactid>
046     <version>1.1.2</version>
047     <type>jar</type>
048     <scope>compile</scope>
049    </dependency>
050    <dependency>
051     <groupid>taglibs</groupid>
052     <artifactid>standard</artifactid>
053     <version>1.1.2</version>
054     <type>jar</type>
055     <scope>compile</scope>
056    </dependency>
057    <dependency>
058     <groupid>org.springframework</groupid>
059     <artifactid>spring-webmvc</artifactid>
060     <version>3.0.5.RELEASE</version>
061     <type>jar</type>
062     <scope>compile</scope>
063    </dependency>
064    <dependency>
065     <groupid>org.springframework</groupid>
066     <artifactid>spring-aop</artifactid>
067     <version>3.0.5.RELEASE</version>
068     <type>jar</type>
069     <scope>compile</scope>
070    </dependency>
071    <dependency>
072     <groupid>commons-digester</groupid>
073     <artifactid>commons-digester</artifactid>
074     <version>2.1</version>
075     <type>jar</type>
076     <scope>compile</scope>
077    </dependency>
078    <dependency>
079     <groupid>commons-collections</groupid>
080     <artifactid>commons-collections</artifactid>
081     <version>3.2.1</version>
082     <type>jar</type>
083     <scope>compile</scope>
084    </dependency>
085    <dependency>
086     <groupid>org.hibernate</groupid>
087     <artifactid>hibernate-core</artifactid>
088     <version>3.3.2.GA</version>
089     <type>jar</type>
090     <scope>compile</scope>
091    </dependency>
092    <dependency>
093     <groupid>javax.persistence</groupid>
094     <artifactid>persistence-api</artifactid>
095     <version>1.0</version>
096     <type>jar</type>
097     <scope>compile</scope>
098    </dependency>
099    <dependency>
100     <groupid>c3p0</groupid>
101     <artifactid>c3p0</artifactid>
102     <version>0.9.1.2</version>
103     <type>jar</type>
104     <scope>compile</scope>
105    </dependency>
106    <dependency>
107     <groupid>org.springframework</groupid>
108     <artifactid>spring-orm</artifactid>
109     <version>3.0.5.RELEASE</version>
110     <type>jar</type>
111     <scope>compile</scope>
112    </dependency>
113    <dependency>
114     <groupid>org.slf4j</groupid>
115     <artifactid>slf4j-api</artifactid>
116     <version>1.6.1</version>
117     <type>jar</type>
118     <scope>compile</scope>
119    </dependency>
120    <dependency>
121     <groupid>org.slf4j</groupid>
122     <artifactid>slf4j-log4j12</artifactid>
123     <version>1.6.1</version>
124     <type>jar</type>
125     <scope>compile</scope>
126    </dependency>
127    <dependency>
128     <groupid>cglib</groupid>
129     <artifactid>cglib-nodep</artifactid>
130     <version>2.2</version>
131     <type>jar</type>
132     <scope>compile</scope>
133    </dependency>
134    <dependency>
135     <groupid>org.hibernate</groupid>
136     <artifactid>hibernate-annotations</artifactid>
137     <version>3.4.0.GA</version>
138     <type>jar</type>
139     <scope>compile</scope>
140    </dependency>
141    <dependency>
142     <groupid>jboss</groupid>
143     <artifactid>javassist</artifactid>
144     <version>3.7.ga</version>
145     <type>jar</type>
146     <scope>compile</scope>
147    </dependency>
148    <dependency>
149     <groupid>mysql</groupid>
150     <artifactid>mysql-connector-java</artifactid>
151     <version>5.1.14</version>
152     <type>jar</type>
153     <scope>compile</scope>
154    </dependency>
155  </dependencies>
Step#3: Configure SpringMVC
a) Configure DispatcherServlet in web.xml
01<servlet>
02  <servlet-name>dispatcher</servlet-name>
03  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
04  <load-on-startup>1</load-on-startup>
05 </servlet>
06   
07 <servlet-mapping>
08  <servlet-name>dispatcher</servlet-name>
09  <url-pattern>*.do</url-pattern>
10 </servlet-mapping>
11  
12 <listener>
13  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
14 </listener>
15 <context-param>
16     <param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value>
17  </context-param>
b) Configure View Resolver in WEB-INF/dispatcher-servlet.xml
1<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
2 p:prefix="/jsp/" p:suffix=".jsp">
3</bean>
c) Configure Annotation support, PropertyPlaceHolderConfigurer, ResourceBundleMessageSource in WEB-INF/classes/applicationContext.xml
01<context:annotation-config></context:annotation-config>
02 
03<context:component-scan base-package="com.sivalabs"></context:component-scan>
04 
05<mvc:annotation-driven> </mvc:annotation-driven>
06 
07<context:property-placeholder location="classpath:config.properties"></context:property-placeholder>
08 
09<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
10 p:basename="Messages"></bean>
Step#4: Configure JDBC connection parameters and Hibernate properties in config.properties
01################### JDBC Configuration ##########################
02jdbc.driverClassName=com.mysql.jdbc.Driver
04jdbc.username=root
05jdbc.password=admin
06  
07################### Hibernate Configuration ##########################
08hibernate.dialect=org.hibernate.dialect.MySQLDialect
09hibernate.show_sql=true
10#hibernate.hbm2ddl.auto=update
11hibernate.generate_statistics=true
Step#5: Configure DataSource, SessionFactory, TransactionManagement support in WEB-INF/classes/applicationContext.xml
01<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
02 p:driverclassname="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}">
03</bean>
04 
05<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
06 <property name="dataSource" ref="dataSource"></property>
07 <property name="hibernateProperties">
08  <props>      
09  <prop key="hibernate.dialect">${hibernate.dialect}</prop>        
10  <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
11  </props>
12 </property>
13 <property name="packagesToScan" value="com.sivalabs"></property>
14</bean>
15 
16 
17<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
18  p:sessionfactory-ref="sessionFactory">
19</bean>
20 
21<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
Step#6: Configure the Labels, error messages in WEB-INF/classes/Messages.properties
1App.Title=SivaLabs
2typeMismatch.java.util.Date={0} is Invalid Date.
3dob=DOB
Step#7: Create the Entity class Contact.java
01package com.sivalabs.contacts;
02  
03import java.util.Date;
04  
05import javax.persistence.Column;
06import javax.persistence.Entity;
07import javax.persistence.GeneratedValue;
08import javax.persistence.GenerationType;
09import javax.persistence.Id;
10import javax.persistence.Table;
11  
12import org.apache.commons.lang.builder.ToStringBuilder;
13  
14@Entity
15@Table(name="CONTACTS")
16public class Contact
17{
18 @Id
19 @GeneratedValue(strategy = GenerationType.AUTO)
20 private int id;
21 @Column private String name;
22 @Column private String address;
23 @Column private String gender;
24 @Column private Date dob;
25 @Column private String email;
26 @Column private String mobile;
27 @Column private String phone;
28   
29 @Override
30 public String toString()
31 {
32  return ToStringBuilder.reflectionToString(this);
33 }
34 //setters & getters
35}
Step#8: Create the ContactsDAO.java which performs CRUD operations on CONTACTS table.
01package com.sivalabs.contacts;
02  
03import java.util.List;
04  
05import org.hibernate.Criteria;
06import org.hibernate.SessionFactory;
07import org.hibernate.criterion.Restrictions;
08import org.springframework.beans.factory.annotation.Autowired;
09import org.springframework.stereotype.Repository;
10import org.springframework.transaction.annotation.Transactional;
11  
12@Repository
13@Transactional
14public class ContactsDAO
15{
16 @Autowired
17 private SessionFactory sessionFactory;
18   
19 public Contact getById(int id)
20 {
21  return (Contact) sessionFactory.getCurrentSession().get(Contact.class, id);
22 }
23   
24 @SuppressWarnings("unchecked")
25 public List searchContacts(String name)
26 {
27  Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Contact.class);
28  criteria.add(Restrictions.ilike("name", name+"%"));
29  return criteria.list();
30 }
31   
32 @SuppressWarnings("unchecked")
33 public List getAllContacts()
34 {
35  Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Contact.class);
36  return criteria.list();
37 }
38   
39 public int save(Contact contact)
40 {
41  return (Integer) sessionFactory.getCurrentSession().save(contact);
42 }
43   
44 public void update(Contact contact)
45 {
46  sessionFactory.getCurrentSession().merge(contact);
47 }
48   
49 public void delete(int id)
50 {
51  Contact c = getById(id);
52  sessionFactory.getCurrentSession().delete(c);
53 }
54}
Step#9: Create ContactFormValidator.java which performs the validations on saving/updating a contact.
01package com.sivalabs.contacts;
02  
03import org.springframework.stereotype.Component;
04import org.springframework.validation.Errors;
05import org.springframework.validation.ValidationUtils;
06import org.springframework.validation.Validator;
07  
08@Component("contactFormValidator")
09public class ContactFormValidator implements Validator
10{
11 @SuppressWarnings("unchecked")
12 @Override
13 public boolean supports(Class clazz)
14 {
15  return Contact.class.isAssignableFrom(clazz);
16 }
17  
18 @Override
19 public void validate(Object model, Errors errors)
20 {
21  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name","required.name", "Name is required.");
22 }
23}
Step#10: Create ContactsControllers.java which processes all the CRUD requests.
001package com.sivalabs.contacts;
002  
003import java.text.SimpleDateFormat;
004import java.util.Date;
005import java.util.List;
006  
007import org.springframework.beans.factory.annotation.Autowired;
008import org.springframework.beans.propertyeditors.CustomDateEditor;
009import org.springframework.stereotype.Controller;
010import org.springframework.validation.BindingResult;
011import org.springframework.web.bind.WebDataBinder;
012import org.springframework.web.bind.annotation.InitBinder;
013import org.springframework.web.bind.annotation.ModelAttribute;
014import org.springframework.web.bind.annotation.RequestMapping;
015import org.springframework.web.bind.annotation.RequestMethod;
016import org.springframework.web.bind.annotation.RequestParam;
017import org.springframework.web.bind.support.SessionStatus;
018import org.springframework.web.servlet.ModelAndView;
019  
020@Controller
021public class ContactsControllers
022{
023 @Autowired
024 private ContactsDAO contactsDAO;
025   
026 @Autowired
027 private ContactFormValidator validator;
028    
029 @InitBinder
030 public void initBinder(WebDataBinder binder)
031 {
032  SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
033  dateFormat.setLenient(false);
034  binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
035 }
036    
037 @RequestMapping("/searchContacts")
038 public ModelAndView searchContacts(@RequestParam(required= false, defaultValue="") String name)
039 {
040  ModelAndView mav = new ModelAndView("showContacts");
041  List contacts = contactsDAO.searchContacts(name.trim());
042  mav.addObject("SEARCH_CONTACTS_RESULTS_KEY", contacts);
043  return mav;
044 }
045   
046 @RequestMapping("/viewAllContacts")
047 public ModelAndView getAllContacts()
048 {
049  ModelAndView mav = new ModelAndView("showContacts");
050  List contacts = contactsDAO.getAllContacts();
051  mav.addObject("SEARCH_CONTACTS_RESULTS_KEY", contacts);
052  return mav;
053 }
054   
055 @RequestMapping(value="/saveContact", method=RequestMethod.GET)
056 public ModelAndView newuserForm()
057 {
058  ModelAndView mav = new ModelAndView("newContact");
059  Contact contact = new Contact();
060  mav.getModelMap().put("newContact", contact);
061  return mav;
062 }
063   
064 @RequestMapping(value="/saveContact", method=RequestMethod.POST)
065 public String create(@ModelAttribute("newContact")Contact contact, BindingResult result, SessionStatus status)
066 {
067  validator.validate(contact, result);
068  if (result.hasErrors())
069  {  
070   return "newContact";
071  }
072  contactsDAO.save(contact);
073  status.setComplete();
074  return "redirect:viewAllContacts.do";
075 }
076   
077 @RequestMapping(value="/updateContact", method=RequestMethod.GET)
078 public ModelAndView edit(@RequestParam("id")Integer id)
079 {
080  ModelAndView mav = new ModelAndView("editContact");
081  Contact contact = contactsDAO.getById(id);
082  mav.addObject("editContact", contact);
083  return mav;
084 }
085   
086 @RequestMapping(value="/updateContact", method=RequestMethod.POST)
087 public String update(@ModelAttribute("editContact") Contact contact, BindingResult result, SessionStatus status)
088 {
089  validator.validate(contact, result);
090  if (result.hasErrors()) {
091   return "editContact";
092  }
093  contactsDAO.update(contact);
094  status.setComplete();
095  return "redirect:viewAllContacts.do";
096 }
097    
098 @RequestMapping("deleteContact")
099 public ModelAndView delete(@RequestParam("id")Integer id)
100 {
101  ModelAndView mav = new ModelAndView("redirect:viewAllContacts.do");
102  contactsDAO.delete(id);
103  return mav;
104 }
105}
Step#11: Instead of writing the JSTL tag library declerations in all the JSPs, declare them in one JSP and include that JSP in other JSPs.
taglib_includes.jsp
1<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
2<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
3  
4<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
5<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
Step#12: Create the JSPs.
a)showContacts.jsp
01<%@include file="taglib_includes.jsp" %>
02  
03<html>
04<head>
05  
06<title><spring:message code="App.Title"></spring:message> </title>
07<script type="text/javascript" src="js/contacts.js"></script>
08</head>
09<body style="font-family: Arial; font-size:smaller;">
10 <center>
11 <form action="searchContacts.do" method="post"><table style="border-collapse: collapse;" width="500" border="0" bordercolor="#006699"><tbody><tr>     <td>Enter Contact Name</td>      <td><input name="name" type="text">
12  
13  <input value="Search" type="submit">
14  
15  <input value="New Contact" onclick="javascript:go('saveContact.do');" type="button">
16  
17</td></tr>
18</tbody></table></form><c:if test="${empty SEARCH_CONTACTS_RESULTS_KEY}">
19</c:if><c:if test="${! empty SEARCH_CONTACTS_RESULTS_KEY}">    <c:foreach var="contact" items="${SEARCH_CONTACTS_RESULTS_KEY}">
20</c:foreach></c:if><table style="border-collapse: collapse;" width="500" border="1" bordercolor="#006699"><tbody><tr bgcolor="lightblue">    <th>Id</th>    <th>Name</th>       <th>Address</th>     <th>Mobile</th>    <th></th>   </tr>
21<tr>    <td colspan="4">No Results found</td>   </tr>
22   <tr>     <td><c:out value="${contact.id}"></c:out></td>     <td><c:out value="${contact.name}"></c:out></td>     <td><c:out value="${contact.address}"></c:out> </td>     <td><c:out value="${contact.mobile}"></c:out></td>     <td>
23  
24 <a href="updateContact.do?id=$%7Bcontact.id%7D">Edit</a>
25  
26  <a href="javascript:deleteContact('deleteContact.do?id=${contact.id}');">Delete</a>
27  
28</td>    </tr>
29         </tbody></table></center>
30    
31</body>
32</html>
b)newContact.jsp
01<%@include file="taglib_includes.jsp" %>
02  
03<html>
04<head>
05 <script type="text/javascript" src="js/contacts.js"></script>
06 <title><spring:message code="App.Title"></spring:message> </title>
07</head>
08<body style="font-family: Arial; font-size:smaller;">
09  
10<table style="border-collapse: collapse;" width="750" align="center" bgcolor="lightblue" border="1" bordercolor="#006699" height="500"><tbody><tr>   <td align="center"><h3>Edit Contact Form</h3></td>  </tr>
11<tr valign="top" align="center">     <td align="center">
12  
13<form:form action="saveContact.do" method="post" commandname="newContact"><table style="border-collapse: collapse;" width="500" border="0" bordercolor="#006699" cellpadding="2" cellspacing="2"><tbody><tr>       <td width="100" align="right">Name</td>       <td width="150">
14  
15<form:input path="name"></form:input></td>       <td align="left">
16  
17<form:errors path="name" cssstyle="color:red"></form:errors></td>      </tr>
18<tr>       <td width="100" align="right">DOB</td>       <td><form:input path="dob"></form:input></td>       <td align="left"><form:errors path="dob" cssstyle="color:red"></form:errors></td>      </tr>
19<tr>       <td width="100" align="right">Gender</td>       <td>    
20  
21<form:select path="gender"><form:option value="M" label="Male"><form:option value="F" label="Female"></form:option></form:option></form:select></td>       <td>
22  
23</td>            </tr>
24<tr>       <td width="100" align="right">Address</td>       <td><form:input path="address"></form:input></td>       <td align="left">
25  
26<form:errors path="address" cssstyle="color:red"></form:errors></td>      </tr>
27<tr>       <td width="100" align="right">Email</td>       <td><form:input path="email"></form:input></td>       <td align="left"><form:errors path="email" cssstyle="color:red"></form:errors></td>      </tr>
28<tr>       <td width="100" align="right">Mobile</td>       <td><form:input path="mobile"></form:input></td>       <td align="left">
29  
30<form:errors path="mobile" cssstyle="color:red"></form:errors></td>      </tr>
31<tr>       <td colspan="3" align="center">
32  
33<input name="" value="Save" type="submit">
34  
35    
36  
37<input name="" value="Reset" type="reset">
38  
39    
40  
41<input value="Back" onclick="javascript:go('viewAllContacts.do');" type="button">
42  
43</td>      </tr>
44</tbody></table></form:form></td>       </tr>
45</tbody></table></body>
46</html>
a)editContact.jsp
01<%@include file="taglib_includes.jsp" %>
02  
03<html>
04<head>
05 <script type="text/javascript" src="js/contacts.js"></script>
06 <title><spring:message code="App.Title"></spring:message> </title>
07</head>
08<body style="font-family: Arial; font-size:smaller;">
09  
10<table style="border-collapse: collapse;" width="750" align="center" bgcolor="lightblue" border="1" bordercolor="#006699" height="500"><tbody><tr>   <td align="center"><h3>Edit Contact Form</h3></td>  </tr>
11<tr valign="top" align="center">     <td align="center">
12  
13<form:form action="updateContact.do" method="post" commandname="editContact"><table style="border-collapse: collapse;" width="500" border="0" bordercolor="#006699" cellpadding="2" cellspacing="2"><tbody><tr>       <td width="100" align="right">Id</td>       <td width="150">
14  
15<form:input path="id" readonly="true"></form:input></td>       <td align="left">
16  
17<form:errors path="id" cssstyle="color:red"></form:errors></td>      </tr>
18<tr>       <td width="100" align="right">Name</td>       <td>
19  
20<form:input path="name"></form:input></td>       <td align="left">
21  
22<form:errors path="name" cssstyle="color:red"></form:errors></td>      </tr>
23<tr>       <td width="100" align="right">DOB</td>       <td><form:input path="dob"></form:input></td>       <td align="left"><form:errors path="dob" cssstyle="color:red"></form:errors></td>      </tr>
24<tr>       <td width="100" align="right">Gender</td>       <td>    
25  
26<form:select path="gender"><form:option value="M" label="Male"><form:option value="F" label="Female"></form:option></form:option></form:select></td>       <td>
27  
28</td>            </tr>
29<tr>       <td width="100" align="right">Address</td>       <td><form:input path="address"></form:input></td>       <td align="left">
30  
31<form:errors path="address" cssstyle="color:red"></form:errors></td>      </tr>
32<tr>       <td width="100" align="right">Email</td>       <td><form:input path="email"></form:input></td>       <td align="left"><form:errors path="email" cssstyle="color:red"></form:errors></td>      </tr>
33<tr>       <td width="100" align="right">Mobile</td>       <td><form:input path="mobile"></form:input></td>       <td align="left">
34  
35<form:errors path="mobile" cssstyle="color:red"></form:errors></td>      </tr>
36<tr valign="bottom">       <td colspan="3" align="center">
37  
38<input value="Delete" onclick="javascript:deleteContact('deleteContact.do?id=${editContact.id}');" type="button">
39  
40    
41  
42<input name="" value="Save" type="submit">    
43  
44    
45  
46<input value="Back" onclick="javascript:go('viewAllContacts.do');" type="button">
47  
48</td>      </tr>
49</tbody></table></form:form></td>       </tr>
50</tbody></table></body>
51</html>
Step#13: Write the javascript file js/contacts.js containing the utility methods
01function go(url)
02{
03 window.location = url;
04}
05  
06function deleteContact(url)
07{
08 var isOK = confirm("Are you sure to delete?");
09 if(isOK)
10 {
11  go(url);
12 }
13}
Step#14: The welcome file index.jsp
1<%
2response.sendRedirect("viewAllContacts.do");
3%>
Step#15: Start the server and point your browser URL to http://localhost:8080/SpringMVCHibernate

No comments:

Post a Comment