├── .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 |
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 |
16 |
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 |
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 |
21 |
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 |
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 |
51 |
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 |
--------------------------------------------------------------------------------