├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── marakana │ └── contacts │ ├── controllers │ ├── CompanyController.java │ ├── ContactController.java │ ├── OfficeController.java │ └── PersonController.java │ ├── entities │ ├── Address.java │ ├── BaseEntity.java │ ├── Company.java │ ├── Contact.java │ ├── Office.java │ ├── Person.java │ └── UrlEntity.java │ ├── repositories │ ├── CompanyRepository.java │ ├── ContactRepository.java │ ├── OfficeRepository.java │ └── PersonRepository.java │ ├── representations │ ├── AddressRepresentation.java │ └── PersonRepresentation.java │ └── validators │ ├── Employee.java │ ├── EmployeeValidator.java │ ├── ZipCode.java │ └── ZipCodeValidator.java ├── resources └── META-INF │ └── persistence.xml └── webapp ├── WEB-INF ├── applicationContext.xml ├── spring-servlet.xml └── web.xml └── view ├── company ├── add.jsp ├── edit.jsp └── view.jsp ├── contact └── list.jsp ├── office ├── add.jsp ├── edit.jsp └── view.jsp └── person ├── add.jsp ├── edit.jsp └── view.jsp /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .classpath 3 | .project 4 | .settings -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | spring-hibernate-20120924 2 | ========================= 3 | 4 | Course Outline 5 | -------------- 6 | 7 | Day 1 8 | 9 | - class design: 10 | Contact, Address 11 | ContactRepository (init, findAll, find, create, update, delete), AddressRepository (...) 12 | Setup (servlet context listener) 13 | 14 | - servlets / jsps: 15 | view all contact names, add a contact 16 | contact: edit name, address, delete contact 17 | 18 | - JDBC / JNDI / DataSource 19 | initialize tables in repositories, use raw sql 20 | 21 | Day 2 22 | 23 | - finish servlets 24 | 25 | - intro hibernate/jpa 26 | META-INF/persistence.xml, persistence-unit-ref in web.xml 27 | javax.persistence.{ Persistence, EntityManager } 28 | @Entity, @Column, @OneToOne 29 | Rewrite ContactRepository and AddressRepository (findAll, find, create, update, delete) and refactor 30 | 31 | Day 3 32 | 33 | - intro spring 34 | container-managed objects, dependency injection 35 | Spring MVC: controllers, @RequestMapping 36 | Spring JPA: JpaRepository, bytecode generation 37 | 38 | - refactor controller methods 39 | model, view, injecting request parameters, redirects 40 | 41 | - new class design: 42 | Contact, Person, Company, Office, Address 43 | how to represent polymorphism in jpa? 44 | @OneToMany, @ManyToOne, @ManyToMany 45 | 46 | - controllers: 47 | person: edit name, address, delete person 48 | company: edit name, view all offices, add an office, delete company 49 | 50 | Day 4 51 | 52 | - office front-end 53 | introduce UrlEntity helper 54 | 55 | - horrible bugs! 56 | why doesn't delete work? ... orphan removal 57 | lazy loaded associations, open session in view filter 58 | 59 | - discussion: Spring high level 60 | - discussion: Transactions and AOP 61 | - discussion: Entity versioning, optimistic locking 62 | 63 | - data constraints 64 | nullable, length 65 | Validation (ex: @NotBlank, all employees of a manager must work for the same company) 66 | 67 | Day 5 68 | 69 | - REST services 70 | more Spring MVC annotations 71 | Jackson 72 | 73 | 74 | JDBC Pain Points / ORM Requirements 75 | ----------------------------------- 76 | 77 | Code maintenance/evolution issues: 78 | - entity and sql can get out of sync as code evolves 79 | => objects must determine DB schema! 80 | - one must know sql 81 | => should be able to just get objects from somewhere / persist objects 82 | - no explicit relationship between entities (one-to-one???) 83 | => system must know relationships and constraints 84 | 85 | Flexibility/reuse issues: 86 | - code is tied to a specific SQL implementation (dialect) 87 | 88 | General ugliness/boilerplate: 89 | - very repetitive code, lots of try-finally 90 | => forget JDBC 91 | - checked SQLExceptions everywhere 92 | => forget JDBC 93 | - marshalling and unmarshalling is very clumsy 94 | 95 | Performance/correctness issues: 96 | - no joins 97 | - no transactions 98 | - no good way to "create table if not exists" 99 | 100 | Reference Material 101 | ------------------ 102 | 103 | * http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html 104 | * http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/ 105 | 106 | Videos 107 | ------ 108 | 109 | * Day 1 110 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/1.1.mov 111 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/1.2.mov 112 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/1.3.mov 113 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/1.4.mov 114 | * Day 2 115 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/2.1.mov 116 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/2.2.mov 117 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/2.3.mov 118 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/2.4.mov 119 | * Day 3 120 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/3.1.mov 121 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/3.2.mov 122 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/3.3.mov 123 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/3.4.mov 124 | * Day 4 125 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/4.1.mov 126 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/4.2.mov 127 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/4.3.mov 128 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/4.4.mov 129 | * Day 5 130 | * https://mrkn.s3.amazonaws.com/recordings/spring-hibernate-20120924/5.1.mov 131 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.marakana 5 | training 6 | war 7 | 0.1-SNAPSHOT 8 | 9 | 10 | 11 | 12 | javax 13 | javaee-api 14 | 6.0 15 | provided 16 | 17 | 18 | 19 | 20 | org.hsqldb 21 | hsqldb 22 | 2.2.8 23 | runtime 24 | 25 | 26 | com.jolbox 27 | bonecp 28 | 0.7.1.RELEASE 29 | runtime 30 | 31 | 32 | 33 | 34 | org.hibernate 35 | hibernate-entitymanager 36 | 4.1.7.Final 37 | runtime 38 | 39 | 40 | org.hibernate 41 | hibernate-validator 42 | 4.3.0.Final 43 | compile 44 | 45 | 46 | 47 | 48 | org.codehaus.jackson 49 | jackson-mapper-asl 50 | 1.9.10 51 | compile 52 | 53 | 54 | 55 | 56 | org.springframework 57 | spring-webmvc 58 | 3.1.1.RELEASE 59 | compile 60 | 61 | 62 | org.springframework.data 63 | spring-data-jpa 64 | 1.0.3.RELEASE 65 | compile 66 | 67 | 68 | 69 | 70 | org.slf4j 71 | slf4j-simple 72 | 1.5.10 73 | runtime 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-compiler-plugin 82 | 2.5.1 83 | 84 | 1.6 85 | 1.6 86 | 87 | 88 | 89 | org.mortbay.jetty 90 | jetty-maven-plugin 91 | 8.1.7.v20120910 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/controllers/CompanyController.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | import com.marakana.contacts.entities.Company; 11 | import com.marakana.contacts.repositories.CompanyRepository; 12 | 13 | @Controller 14 | public class CompanyController { 15 | 16 | @Autowired 17 | private CompanyRepository companyRepository; 18 | 19 | @RequestMapping(value = "/company", params = "add", method = RequestMethod.GET) 20 | public String getAddCompany() { 21 | return "company/add"; 22 | } 23 | 24 | @RequestMapping(value = "/company", params = "edit", method = RequestMethod.GET) 25 | public String getEditCompany(@RequestParam long id, Model model) { 26 | model.addAttribute("company", companyRepository.findOne(id)); 27 | return "company/edit"; 28 | } 29 | 30 | @RequestMapping(value = "/company", method = RequestMethod.GET) 31 | public String getViewCompany(@RequestParam long id, Model model) { 32 | model.addAttribute("company", companyRepository.findOne(id)); 33 | return "company/view"; 34 | } 35 | 36 | @RequestMapping(value = "/company", params = "add", method = RequestMethod.POST) 37 | public String postAddCompany(@RequestParam String name) { 38 | Company company = new Company(name, null); 39 | company = companyRepository.save(company); 40 | 41 | return "redirect:company?id=" + company.getId(); 42 | } 43 | 44 | @RequestMapping(value = "/company", params = "edit", method = RequestMethod.POST) 45 | public String postEditCompany(@RequestParam long id, 46 | @RequestParam String name) { 47 | Company company = companyRepository.findOne(id); 48 | company.setName(name); 49 | companyRepository.save(company); 50 | 51 | return "redirect:company?id=" + company.getId(); 52 | } 53 | 54 | @RequestMapping(value = "/company", params = "delete", method = RequestMethod.POST) 55 | public String postDeleteCompany(@RequestParam long id) { 56 | companyRepository.delete(id); 57 | return "redirect:contacts"; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/controllers/ContactController.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.controllers; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | import com.marakana.contacts.entities.Contact; 13 | import com.marakana.contacts.repositories.ContactRepository; 14 | 15 | @Controller 16 | public class ContactController { 17 | 18 | @Autowired 19 | private ContactRepository contactRepository; 20 | 21 | @RequestMapping(value = "/contacts", method = RequestMethod.GET) 22 | public String getContactList(Model model) { 23 | model.addAttribute("contacts", contactRepository.findAll()); 24 | return "contact/list"; 25 | } 26 | 27 | @RequestMapping(value = "/contacts", method = RequestMethod.GET, produces = "application/json") 28 | public @ResponseBody List getContactList() { 29 | return contactRepository.findAll(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/controllers/OfficeController.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | import com.marakana.contacts.entities.Address; 11 | import com.marakana.contacts.entities.Company; 12 | import com.marakana.contacts.entities.Office; 13 | import com.marakana.contacts.repositories.CompanyRepository; 14 | import com.marakana.contacts.repositories.OfficeRepository; 15 | 16 | @Controller 17 | public class OfficeController { 18 | 19 | @Autowired 20 | private OfficeRepository officeRepository; 21 | 22 | @Autowired 23 | private CompanyRepository companyRepository; 24 | 25 | @RequestMapping(value = "/office", params = "add", method = RequestMethod.GET) 26 | public String getAddOffice(@RequestParam("company_id") long companyId, 27 | Model model) { 28 | model.addAttribute("company", companyRepository.findOne(companyId)); 29 | return "office/add"; 30 | } 31 | 32 | @RequestMapping(value = "/office", params = "edit", method = RequestMethod.GET) 33 | public String getEditOffice(@RequestParam long id, Model model) { 34 | model.addAttribute("office", officeRepository.findOne(id)); 35 | return "office/edit"; 36 | } 37 | 38 | @RequestMapping(value = "/office", method = RequestMethod.GET) 39 | public String getViewOffice(@RequestParam long id, Model model) { 40 | model.addAttribute("office", officeRepository.findOne(id)); 41 | return "office/view"; 42 | } 43 | 44 | @RequestMapping(value = "/office", params = "add", method = RequestMethod.POST) 45 | public String postAddOffice(@RequestParam("company_id") long companyId, 46 | @RequestParam String name, @RequestParam String street, 47 | @RequestParam String city, @RequestParam String state, 48 | @RequestParam String zip) { 49 | Address address = new Address(street, city, state, zip); 50 | Company company = companyRepository.findOne(companyId); 51 | Office office = new Office(name, address, company); 52 | office = officeRepository.save(office); 53 | 54 | return "redirect:office?id=" + office.getId(); 55 | } 56 | 57 | @RequestMapping(value = "/office", params = "edit", method = RequestMethod.POST) 58 | public String postEditOffice(@RequestParam long id, 59 | @RequestParam String name, @RequestParam String street, 60 | @RequestParam String city, @RequestParam String state, 61 | @RequestParam String zip) { 62 | Office office = officeRepository.findOne(id); 63 | Address address = office.getAddress(); 64 | office.setName(name); 65 | address.setStreet(street); 66 | address.setCity(city); 67 | address.setState(state); 68 | address.setZip(zip); 69 | officeRepository.save(office); 70 | 71 | return "redirect:office?id=" + office.getId(); 72 | } 73 | 74 | @RequestMapping(value = "/office", params = "delete", method = RequestMethod.POST) 75 | public String postDeleteOffice(@RequestParam long id) { 76 | Office office = officeRepository.findOne(id); 77 | officeRepository.delete(office); 78 | return "redirect:" + office.getCompany().getUrl(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/controllers/PersonController.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | import com.marakana.contacts.entities.Address; 14 | import com.marakana.contacts.entities.Person; 15 | import com.marakana.contacts.repositories.CompanyRepository; 16 | import com.marakana.contacts.repositories.PersonRepository; 17 | import com.marakana.contacts.representations.PersonRepresentation; 18 | 19 | @Controller 20 | public class PersonController { 21 | 22 | @Autowired 23 | private PersonRepository personRepository; 24 | 25 | @Autowired 26 | private CompanyRepository companyRepository; 27 | 28 | @RequestMapping(value = "/person", params = "add", method = RequestMethod.GET) 29 | public String getAddPerson() { 30 | return "person/add"; 31 | } 32 | 33 | @RequestMapping(value = "/person", params = "edit", method = RequestMethod.GET) 34 | public String getEditPerson(@RequestParam long id, Model model) { 35 | model.addAttribute("person", personRepository.findOne(id)); 36 | model.addAttribute("managers", personRepository.findAll()); 37 | model.addAttribute("employers", companyRepository.findAll()); 38 | return "person/edit"; 39 | } 40 | 41 | @RequestMapping(value = "/person", method = RequestMethod.GET) 42 | public String getViewPerson(@RequestParam long id, Model model) { 43 | model.addAttribute("person", personRepository.findOne(id)); 44 | return "person/view"; 45 | } 46 | 47 | @RequestMapping(value = "/person/{id}", method = RequestMethod.GET, produces = "application/json") 48 | public @ResponseBody PersonRepresentation getPerson(@PathVariable long id) { 49 | return new PersonRepresentation(personRepository.findOne(id)); 50 | } 51 | 52 | @RequestMapping(value = "/person", params = "add", method = RequestMethod.POST) 53 | public String postAddPerson(@RequestParam String name, 54 | @RequestParam String street, @RequestParam String city, 55 | @RequestParam String state, @RequestParam String zip) { 56 | Address address = new Address(street, city, state, zip); 57 | Person person = new Person(name, address); 58 | person = personRepository.save(person); 59 | 60 | return "redirect:person?id=" + person.getId(); 61 | } 62 | 63 | @RequestMapping(value = "/person", params = "edit", method = RequestMethod.POST) 64 | @Transactional 65 | public String postEditPerson(@RequestParam long id, 66 | @RequestParam String name, @RequestParam String street, 67 | @RequestParam String city, @RequestParam String state, 68 | @RequestParam String zip, 69 | @RequestParam("manager_id") long managerId, 70 | @RequestParam("employer_id") long employerId) { 71 | Person person = personRepository.findOne(id); 72 | Address address = person.getAddress(); 73 | person.setName(name); 74 | person.setManager(personRepository.findOne(managerId)); 75 | person.setEmployer(companyRepository.findOne(employerId)); 76 | address.setStreet(street); 77 | address.setCity(city); 78 | address.setState(state); 79 | address.setZip(zip); 80 | personRepository.save(person); 81 | 82 | return "redirect:person?id=" + person.getId(); 83 | } 84 | 85 | @RequestMapping(value = "/person", params = "delete", method = RequestMethod.POST) 86 | public String postDeletePerson(@RequestParam long id) { 87 | personRepository.delete(id); 88 | return "redirect:contacts"; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/Address.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | 6 | import org.hibernate.validator.constraints.NotBlank; 7 | 8 | import com.marakana.contacts.validators.ZipCode; 9 | 10 | @Entity 11 | public class Address extends BaseEntity { 12 | 13 | @Column(nullable = false) 14 | @NotBlank 15 | private String street; 16 | 17 | @Column(nullable = false) 18 | @NotBlank 19 | private String city; 20 | 21 | @Column(nullable = false, length = 64) 22 | @NotBlank 23 | private String state; 24 | 25 | @Column(nullable = false, length = 64) 26 | @ZipCode 27 | private String zip; 28 | 29 | public Address() { 30 | } 31 | 32 | public Address(String street, String city, String state, String zip) { 33 | this.street = street; 34 | this.city = city; 35 | this.state = state; 36 | this.zip = zip; 37 | } 38 | 39 | public String getStreet() { 40 | return street; 41 | } 42 | 43 | public void setStreet(String street) { 44 | this.street = street; 45 | } 46 | 47 | public String getCity() { 48 | return city; 49 | } 50 | 51 | public void setCity(String city) { 52 | this.city = city; 53 | } 54 | 55 | public String getState() { 56 | return state; 57 | } 58 | 59 | public void setState(String state) { 60 | this.state = state; 61 | } 62 | 63 | public String getZip() { 64 | return zip; 65 | } 66 | 67 | public void setZip(String zip) { 68 | this.zip = zip; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.GeneratedValue; 4 | import javax.persistence.Id; 5 | import javax.persistence.MappedSuperclass; 6 | import javax.persistence.Version; 7 | 8 | @MappedSuperclass 9 | public abstract class BaseEntity { 10 | 11 | @Id 12 | @GeneratedValue 13 | private Long id; 14 | 15 | @Version 16 | private Long version; 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public Long getVersion() { 27 | return version; 28 | } 29 | 30 | public void setVersion(Long version) { 31 | this.version = version; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/Company.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import java.util.Set; 4 | 5 | import javax.persistence.CascadeType; 6 | import javax.persistence.Entity; 7 | import javax.persistence.OneToMany; 8 | 9 | @Entity 10 | public class Company extends Contact { 11 | 12 | @OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true) 13 | private Set offices; 14 | 15 | public Company() { 16 | } 17 | 18 | public Company(String name, Set offices) { 19 | super(name); 20 | this.offices = offices; 21 | } 22 | 23 | public Set getOffices() { 24 | return offices; 25 | } 26 | 27 | public void setOffices(Set offices) { 28 | this.offices = offices; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/Contact.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Inheritance; 6 | import javax.persistence.InheritanceType; 7 | 8 | import org.hibernate.validator.constraints.NotBlank; 9 | 10 | @Entity 11 | @Inheritance(strategy=InheritanceType.JOINED) 12 | public abstract class Contact extends UrlEntity { 13 | 14 | @Column(nullable = false) 15 | @NotBlank 16 | private String name; 17 | 18 | public Contact() { 19 | } 20 | 21 | public Contact(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/Office.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.CascadeType; 4 | import javax.persistence.Column; 5 | import javax.persistence.Entity; 6 | import javax.persistence.ManyToOne; 7 | import javax.persistence.OneToOne; 8 | 9 | import org.hibernate.validator.constraints.NotBlank; 10 | 11 | @Entity 12 | public class Office extends UrlEntity { 13 | 14 | @Column(nullable = false, length = 64) 15 | @NotBlank 16 | private String name; 17 | 18 | @OneToOne(optional = false, cascade = CascadeType.ALL) 19 | private Address address; 20 | 21 | @ManyToOne(optional = false) 22 | private Company company; 23 | 24 | public Office() { 25 | } 26 | 27 | public Office(String name, Address address, Company company) { 28 | this.name = name; 29 | this.address = address; 30 | this.company = company; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public Address getAddress() { 42 | return address; 43 | } 44 | 45 | public void setAddress(Address address) { 46 | this.address = address; 47 | } 48 | 49 | public Company getCompany() { 50 | return company; 51 | } 52 | 53 | public void setCompany(Company company) { 54 | this.company = company; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/Person.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.CascadeType; 4 | import javax.persistence.Entity; 5 | import javax.persistence.ManyToOne; 6 | import javax.persistence.OneToOne; 7 | 8 | import com.marakana.contacts.validators.Employee; 9 | 10 | @Entity 11 | @Employee 12 | public class Person extends Contact { 13 | 14 | @OneToOne(optional = false, cascade = CascadeType.ALL) 15 | private Address address; 16 | 17 | @ManyToOne 18 | private Person manager; 19 | 20 | @ManyToOne 21 | private Company employer; 22 | 23 | public Person() { 24 | } 25 | 26 | public Person(String name, Address address) { 27 | super(name); 28 | this.address = address; 29 | } 30 | 31 | public Address getAddress() { 32 | return address; 33 | } 34 | 35 | public void setAddress(Address address) { 36 | this.address = address; 37 | } 38 | 39 | public Person getManager() { 40 | return manager; 41 | } 42 | 43 | public void setManager(Person manager) { 44 | this.manager = manager; 45 | } 46 | 47 | public Company getEmployer() { 48 | return employer; 49 | } 50 | 51 | public void setEmployer(Company employer) { 52 | this.employer = employer; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/entities/UrlEntity.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.entities; 2 | 3 | import javax.persistence.MappedSuperclass; 4 | 5 | @MappedSuperclass 6 | public class UrlEntity extends BaseEntity { 7 | 8 | public String getUrl() { 9 | return getClass().getSimpleName().toLowerCase() + "?id=" + getId(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/repositories/CompanyRepository.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.marakana.contacts.entities.Company; 6 | 7 | public interface CompanyRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/repositories/ContactRepository.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.marakana.contacts.entities.Contact; 6 | 7 | public interface ContactRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/repositories/OfficeRepository.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.marakana.contacts.entities.Office; 6 | 7 | public interface OfficeRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/repositories/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.marakana.contacts.entities.Person; 6 | 7 | public interface PersonRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/representations/AddressRepresentation.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.representations; 2 | 3 | import com.marakana.contacts.entities.Address; 4 | 5 | public class AddressRepresentation { 6 | 7 | public final String street; 8 | public final String city; 9 | public final String state; 10 | public final String zip; 11 | 12 | public AddressRepresentation(Address address) { 13 | this.street = address.getStreet(); 14 | this.city = address.getCity(); 15 | this.state = address.getState(); 16 | this.zip = address.getZip(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/representations/PersonRepresentation.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.representations; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import com.marakana.contacts.entities.Person; 9 | 10 | public class PersonRepresentation { 11 | 12 | public final String name; 13 | public final AddressRepresentation address; 14 | public final Map links = new HashMap(); 15 | 16 | public PersonRepresentation(Person person) { 17 | this.name = person.getName(); 18 | this.address = new AddressRepresentation(person.getAddress()); 19 | try { 20 | links.put("self", new URI("/person/" + person.getId())); 21 | links.put("collection", new URI("/contacts")); 22 | } catch (URISyntaxException e) { 23 | e.printStackTrace(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/validators/Employee.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.validators; 2 | 3 | import javax.validation.Constraint; 4 | import java.lang.annotation.*; 5 | 6 | @Documented 7 | @Constraint(validatedBy = EmployeeValidator.class) 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Employee { 11 | String message() default "manager and employee must work for the same company, management chain must be acyclic"; 12 | Class[] groups() default {}; 13 | Class[] payload() default {}; 14 | } -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/validators/EmployeeValidator.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.validators; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | 6 | import com.marakana.contacts.entities.Company; 7 | import com.marakana.contacts.entities.Person; 8 | 9 | public class EmployeeValidator implements ConstraintValidator { 10 | 11 | @Override 12 | public void initialize(Employee employee) { 13 | } 14 | 15 | @Override 16 | public boolean isValid(Person person, ConstraintValidatorContext context) { 17 | Company employer = person.getEmployer(); 18 | Person manager = person.getManager(); 19 | if (employer == null) { 20 | return manager == null; 21 | } 22 | 23 | while (manager != null) { 24 | if (person.equals(manager) || !employer.equals(manager.getEmployer())) 25 | return false; 26 | manager = manager.getManager(); 27 | } 28 | return true; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/validators/ZipCode.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.validators; 2 | 3 | import javax.validation.Constraint; 4 | import java.lang.annotation.*; 5 | 6 | @Documented 7 | @Constraint(validatedBy = ZipCodeValidator.class) 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ZipCode { 11 | String message() default "zip code must be five numeric characters"; 12 | 13 | Class[] groups() default {}; 14 | 15 | Class[] payload() default {}; 16 | } -------------------------------------------------------------------------------- /src/main/java/com/marakana/contacts/validators/ZipCodeValidator.java: -------------------------------------------------------------------------------- 1 | package com.marakana.contacts.validators; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | 6 | public class ZipCodeValidator implements ConstraintValidator { 7 | 8 | @Override 9 | public void initialize(ZipCode zipCode) { 10 | } 11 | 12 | @Override 13 | public boolean isValid(String string, ConstraintValidatorContext context) { 14 | if (string.length() != 5) 15 | return false; 16 | for (char c : string.toCharArray()) { 17 | if (!Character.isDigit(c)) 18 | return false; 19 | } 20 | return true; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/spring-servlet.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | spring 8 | org.springframework.web.servlet.DispatcherServlet 9 | 1 10 | 11 | 12 | spring 13 | /* 14 | 15 | 16 | 17 | jsp 18 | /view/* 19 | 20 | 21 | 22 | openEntityManagerInViewFilter 23 | org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter 24 | 25 | entityManagerFactoryBeanName 26 | entityManagerFactory 27 | 28 | 29 | 30 | 31 | openEntityManagerInViewFilter 32 | /* 33 | 34 | 35 | 36 | org.springframework.web.context.ContextLoaderListener 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/webapp/view/company/add.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | add company 5 | 6 | 7 |

add company

8 |
9 | 10 |
    11 |
  • name:
  • 12 |
13 | 14 |
15 | back to contact list 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/view/company/edit.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | edit company 5 | 6 | 7 |

edit company

8 |
9 | 10 | 11 |
    12 |
  • name:
  • 13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 |
21 | back to contact list 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/view/company/view.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | ${company.name} 5 | 6 | 7 |

${company.name}

8 | 13 | edit company | 14 | add office | 15 | back to contact list 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/view/contact/list.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | contacts 5 | 6 | 7 |

contacts

8 | 13 | add a new person | add a new company 14 | 15 | -------------------------------------------------------------------------------- /src/main/webapp/view/office/add.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | add office 5 | 6 | 7 |

add office

8 |
9 | 10 | 11 |
    12 |
  • name:
  • 13 |
  • street:
  • 14 |
  • city:
  • 15 |
  • state:
  • 16 |
  • zip:
  • 17 |
18 | 19 |
20 | back to ${company.name} 21 | 22 | -------------------------------------------------------------------------------- /src/main/webapp/view/office/edit.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | edit office 5 | 6 | 7 |

edit office

8 |
9 | 10 | 11 |
    12 | 13 |
  • name:
  • 14 |
  • street:
  • 15 |
  • city:
  • 16 |
  • state:
  • 17 |
  • zip:
  • 18 |
19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 | back to ${office.company.name} 27 | 28 | -------------------------------------------------------------------------------- /src/main/webapp/view/office/view.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | ${office.name} 5 | 6 | 7 |

${office.name}

8 |
    9 | 10 |
  • ${address.street}
  • 11 |
  • ${address.city}, ${address.state} ${address.zip}
  • 12 |
13 | edit office | 14 | back to ${office.company.name} 15 | 16 | -------------------------------------------------------------------------------- /src/main/webapp/view/person/add.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | add person 5 | 6 | 7 |

add person

8 |
9 | 10 |
    11 |
  • name:
  • 12 |
  • street:
  • 13 |
  • city:
  • 14 |
  • state:
  • 15 |
  • zip:
  • 16 |
17 | 18 |
19 | back to contact list 20 | 21 | -------------------------------------------------------------------------------- /src/main/webapp/view/person/edit.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | edit person 5 | 6 | 7 |

edit person

8 |
9 | 10 | 11 |
    12 | 13 |
  • name:
  • 14 |
  • street:
  • 15 |
  • city:
  • 16 |
  • state:
  • 17 |
  • zip:
  • 18 |
  • manager: 19 | 32 |
  • 33 |
  • employer: 34 | 47 |
  • 48 |
49 | 50 |
51 |
52 | 53 | 54 | 55 |
56 | back to contact list 57 | 58 | -------------------------------------------------------------------------------- /src/main/webapp/view/person/view.jsp: -------------------------------------------------------------------------------- 1 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 2 | 3 | 4 | ${person.name} 5 | 6 | 7 |

${person.name}

8 |
    9 | 10 |
  • ${address.street}
  • 11 |
  • ${address.city}, ${address.state} ${address.zip}
  • 12 |
  • manager: ${person.manager.name}
  • 13 |
  • employer: ${person.employer.name}
  • 14 |
15 | edit person | 16 | back to contact list 17 | 18 | --------------------------------------------------------------------------------