├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── github │ │ └── priyatam │ │ └── springrest │ │ ├── HealthCheck.java │ │ ├── controller │ │ └── QuoteController.java │ │ ├── domain │ │ ├── Address.java │ │ ├── BaseDomain.java │ │ ├── Driver.java │ │ ├── DrivingHistory.java │ │ ├── Policy.java │ │ ├── User.java │ │ └── Vehicle.java │ │ ├── helper │ │ ├── ETagHelper.java │ │ ├── MailHelper.java │ │ ├── PersistenceHelper.java │ │ ├── PolicyAsyncHelper.java │ │ ├── ResourceHelper.java │ │ ├── ResponseBuilderHelper.java │ │ ├── RuleHelper.java │ │ └── SecurityHelper.java │ │ ├── integration │ │ ├── LocalQuoteGenerator.java │ │ └── QuoteGenerator.java │ │ ├── resource │ │ ├── DriverResource.java │ │ ├── PolicyResource.java │ │ ├── UserResource.java │ │ └── VehicleResource.java │ │ ├── utils │ │ ├── AjaxUtils.java │ │ ├── CustomHibernateNamingStrategy.java │ │ ├── DefaultJacksonHttpMessageConverter.java │ │ ├── Link.java │ │ ├── LinkBuilder.java │ │ ├── aop │ │ │ └── EtagGeneratorAspect.java │ │ └── exception │ │ │ ├── InvalidTagException.java │ │ │ └── PolicyInvalidException.java │ │ └── view │ │ └── PolicyForm.java ├── resources │ ├── applicationContext-core.xml │ ├── applicationContext-persistence.xml │ ├── applicationContext-security.xml │ ├── db │ │ └── data.sql │ ├── ehcache.xml │ ├── local.properties │ ├── logback.xml │ ├── prod.properties │ └── qa.properties └── webapp │ ├── WEB-INF │ ├── views │ │ ├── about.jsp │ │ ├── common │ │ │ ├── banner.jsp │ │ │ ├── footer.jsp │ │ │ ├── menu.jsp │ │ │ ├── navbar.jsp │ │ │ └── template.jsp │ │ ├── home.jsp │ │ └── quote.jsp │ └── web.xml │ └── resources │ ├── css │ ├── bootstrap-responsive.min.css │ ├── bootstrap.min.css │ └── main.css │ ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png │ └── js │ ├── bootstrap.js │ ├── date.format.js │ ├── jquery-1.8.0-min.js │ ├── jquery.form.js │ ├── jquery.mobile-1.2.0.min.js │ ├── json2.js │ └── main.js └── test ├── java └── github │ └── priyatam │ └── springrest │ ├── ChecklistTest.java │ ├── EnvironmentModeJUnitRunner.java │ ├── MockDataHelper.java │ ├── SpringClientRestTest.java │ ├── WebContextLoader.java │ ├── domain │ └── DomainTest.java │ ├── mock │ ├── RestMocker.java │ └── RestMockerTest.java │ └── resource │ ├── DriverResourceTest.java │ ├── PolicyResourceTest.java │ └── VehicleResourceTest.java └── resources ├── accident.json ├── address.json ├── driver.json ├── drivingHistory.json ├── logback-test.xml ├── policy.json ├── restito └── policy.pol-1.json └── vehicle.json /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | .iml 5 | .idea 6 | target 7 | 8 | *.log 9 | 10 | logs/.DS_Store 11 | 12 | *.db 13 | 14 | *.iml 15 | 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SpringMVC - Best Practices 2 | =============================== 3 | 4 | A full-blown, functional, tested Spring 3.2 reference application with JPA persistence, REST Level-3 resources, asynchronous processing, jobs, security, unit, mock, integration, functional, rest client stubs, and performance tests, and many best practices I gathered over several years working in mvc / spring / grails web apps. 5 | 6 | ## How to run 7 | mvn clean package 8 | mvn jetty:run 9 | 10 | ## Best Practices 11 | 12 | ### Domain Modeling 13 | - Immutable Domain Model with Builder Pattern 14 | - Jackson JSON Annotations 15 | - JPA Annotations 16 | - Unit Tests 17 | 18 | ### REST 19 | - REST Errors and Exception Resolver 20 | - HATEOAS (REST Level 3) 21 | 22 | ### Persistence 23 | - Transaction Management & Connection Pooling 24 | - JPA / Hibernate 25 | 26 | ### AOP 27 | - Http ETag management, HTTP Caching & Resource optimistic locking 28 | 29 | ### Async 30 | - Asynchronous processing: Request-Acknowledge-Poll Pattern (Fork-Join/Future implemention on REST) 31 | - Jobs 32 | 33 | ### Caching 34 | - Simplified caching using Spring’s new @Cacheable / Eh-Cache 35 | 36 | ### Spring 37 | - Streamlined configuration for web, persistence, rest, spring, and properties 38 | 39 | ### Testing 40 | - Unit Testing (JUnit, Mockito) 41 | - Integration Testing (Spring Test, MVC Test) 42 | 43 | ## Libraries Used 44 | - Spring 3.2, JPA 2, Hibernate 4.1 45 | - JSP, JQuery, Twitter Bootstrap 2.2 46 | - H2 db (soon, MongoDb?) 47 | - JUnit, Mockito, Spring Test, Hamcrest, JsonPath, 48 | - Google Guava, Joda DateTime, Logback/Slf4j, Jackson Json 49 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/HealthCheck.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import java.text.DateFormat; 4 | import java.util.Date; 5 | import java.util.Locale; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.ResponseBody; 15 | 16 | /** 17 | * HealthCheck to verify if Resources are up 18 | */ 19 | @Controller 20 | public class HealthCheck { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(HealthCheck.class); 23 | 24 | @RequestMapping(value = { "", "/", }, method = RequestMethod.GET) 25 | @ResponseBody 26 | public ResponseEntity home(Locale locale) { 27 | logger.info("Welcome home. The client locale is " + locale.toString()); 28 | DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); 29 | String formattedDate = dateFormat.format(new Date()); 30 | return new ResponseEntity("Welcome, SpringMVC Rest Demo is running. The time on the server is: " 31 | + formattedDate, HttpStatus.OK); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/controller/QuoteController.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.controller; 2 | 3 | import github.priyatam.springrest.utils.AjaxUtils; 4 | import github.priyatam.springrest.view.PolicyForm; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.validation.BindingResult; 8 | import org.springframework.web.bind.annotation.ModelAttribute; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.SessionAttributes; 12 | import org.springframework.web.context.request.WebRequest; 13 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 14 | 15 | import javax.validation.Valid; 16 | 17 | @Controller 18 | @RequestMapping("/quote") 19 | @SessionAttributes("policyForm") 20 | public class QuoteController { 21 | 22 | // Invoked on every request 23 | @ModelAttribute 24 | public void ajaxAttribute(WebRequest request, Model model) { 25 | model.addAttribute("ajaxRequest", AjaxUtils.isAjaxRequest(request)); 26 | } 27 | 28 | // Invoked initially to create the "form" attribute 29 | // Once created the "form" attribute comes from the HTTP session (see @SessionAttributes) 30 | @ModelAttribute("policyForm") 31 | public PolicyForm getPolicyForm() { 32 | return new PolicyForm(); 33 | } 34 | 35 | @RequestMapping(method = RequestMethod.GET) 36 | public void form() { 37 | } 38 | 39 | @RequestMapping(method = RequestMethod.POST) 40 | public String processSubmit(@Valid PolicyForm policyForm, BindingResult result, 41 | @ModelAttribute("ajaxRequest") boolean ajaxRequest, Model model, RedirectAttributes redirectAttrs) { 42 | if (result.hasErrors()) { 43 | return null; 44 | } 45 | 46 | // Typically you would save to a db and clear the "form" attribute from the session 47 | // via SessionStatus.setCompleted(). For the demo we leave it in the session. 48 | String message = "Form submitted successfully. Bound " + policyForm; 49 | 50 | // Success response handling 51 | if (ajaxRequest) { 52 | // prepare model for rendering success message in this request 53 | model.addAttribute("message", message); 54 | return null; 55 | } else { 56 | // store a success message for rendering on the next request after redirect 57 | // redirect back to the form to render the success message along with newly bound values 58 | redirectAttrs.addFlashAttribute("message", message); 59 | return "redirect:/policy"; 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/Address.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.google.common.base.Objects; 6 | import com.google.common.collect.ComparisonChain; 7 | import com.google.common.collect.Ordering; 8 | 9 | import javax.persistence.*; 10 | import java.io.Serializable; 11 | 12 | @Entity 13 | @Table(name="addresses") 14 | @NamedQuery(name = "Address.findByAddrLine1CityStateZip", query = "select o from Address o where o.addrLine1 = :addrLine1" + 15 | " and o.city = :city and o.state = :state and o.zip = :zip") 16 | public class Address extends BaseDomain implements Comparable
, Serializable { 17 | 18 | private static final long serialVersionUID = 2474081809067118710L; 19 | 20 | public enum AddressType { 21 | RENTAL, HOMEOWNER, BUSINESS 22 | } 23 | 24 | private final String addrLine1; 25 | private final String addrLine2; 26 | private final String city; 27 | private final String state; 28 | private final String zip; 29 | 30 | @Enumerated(EnumType.STRING) 31 | private final AddressType type; 32 | 33 | // Default constructor used by Hibernate 34 | private Address() { 35 | this.addrLine1 = null; 36 | this.addrLine2 = null; 37 | this.city = null; 38 | this.state = null; 39 | this.zip = null; 40 | this.type = null; 41 | } 42 | 43 | @JsonCreator 44 | public Address(@JsonProperty("addrLine1") String addrLine1, @JsonProperty("addrLine2") String addrLine2, 45 | @JsonProperty("city") String city, @JsonProperty("state") String state, 46 | @JsonProperty("zip") String zip, @JsonProperty("type") AddressType type) { 47 | this.addrLine1 = addrLine1; 48 | this.addrLine2 = addrLine2; 49 | this.city = city; 50 | this.state = state; 51 | this.zip = zip; 52 | this.type = type; 53 | } 54 | 55 | public String getAddrLine1() { 56 | return addrLine1; 57 | } 58 | 59 | public String getAddrLine2() { 60 | return addrLine2; 61 | } 62 | 63 | public String getCity() { 64 | return city; 65 | } 66 | 67 | public String getState() { 68 | return state; 69 | } 70 | 71 | public String getZip() { 72 | return zip; 73 | } 74 | 75 | public AddressType getType() { 76 | return type; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return Objects.toStringHelper(this).add("addrLine1", addrLine1).add("city", city).add("state", state) 82 | .add("zip", zip).toString(); 83 | } 84 | 85 | @Override 86 | public int compareTo(Address that) { 87 | return ComparisonChain.start().compare(this.addrLine1, that.addrLine1).compare(this.city, that.city) 88 | .compare(this.city, that.city).compare(this.zip, that.zip, Ordering.natural().nullsLast()).result(); 89 | 90 | } 91 | 92 | @Override 93 | public boolean equals(Object obj) { 94 | Address that = (Address) obj; 95 | return Objects.equal(this.addrLine1, that.addrLine1) && Objects.equal(this.city, that.city) 96 | && Objects.equal(this.state, that.state) && Objects.equal(this.zip, that.zip); 97 | } 98 | 99 | @Override 100 | public int hashCode() { 101 | return Objects.hashCode(addrLine1, city, state, zip); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/BaseDomain.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import github.priyatam.springrest.utils.Link; 9 | 10 | import javax.persistence.*; 11 | 12 | /** 13 | * Adds id, version, and Heateoas Links to a domain 14 | * 15 | */ 16 | @MappedSuperclass 17 | public abstract class BaseDomain implements Serializable { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.AUTO) 21 | @Column(updatable = false, nullable = false) 22 | @JsonIgnore 23 | private Long id; 24 | 25 | @Version 26 | @JsonIgnore 27 | private Integer version; 28 | 29 | @Embedded 30 | private List links = new LinkedList(); 31 | 32 | public void addLink(Link link) { 33 | this.links.add(link); 34 | } 35 | 36 | public List getLinks() { 37 | if (links == null) { 38 | links = new LinkedList(); 39 | } 40 | return links; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/Driver.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.google.common.base.Objects; 8 | import com.google.common.collect.ComparisonChain; 9 | import com.google.common.collect.Ordering; 10 | import github.priyatam.springrest.utils.Link; 11 | import org.hibernate.annotations.Index; 12 | import org.hibernate.annotations.Type; 13 | import org.joda.time.LocalDate; 14 | 15 | import javax.persistence.*; 16 | import java.io.Serializable; 17 | import java.util.List; 18 | 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | 21 | @JsonDeserialize(builder = Driver.Builder.class) 22 | @JsonInclude(JsonInclude.Include.NON_NULL) 23 | @Entity 24 | @NamedQueries({ 25 | @NamedQuery( 26 | name = "Driver.FIND_BY_LICENSENUM", query = "select o from Driver o where o.licenseNum = :licenseNum"), 27 | @NamedQuery( 28 | name = "Driver.FIND_BY_POLICYNUM", query = "select o from Driver o where o.policy.policyNum = :policyNum") 29 | }) 30 | public final class Driver extends BaseDomain implements Comparable, Serializable { 31 | 32 | private static final long serialVersionUID = 7427627725590393187L; 33 | 34 | public enum Gender { 35 | MALE, FEMALE 36 | } 37 | 38 | // Key 39 | @Column(unique = true) 40 | @Index(name = "licenseNumIndex") 41 | private final String licenseNum; 42 | 43 | private final String licenseState; 44 | 45 | @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate") 46 | private final LocalDate licenseExpiryDate; 47 | private final String firstName; 48 | private final String lastName; 49 | 50 | @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate") 51 | private final LocalDate birthDate; 52 | 53 | @Enumerated(EnumType.STRING) 54 | private final Gender gender; 55 | private final String email; 56 | private final String phone; 57 | private final String occupation; 58 | private final Integer firstLicenseAtAge; 59 | private final Boolean isMarried; 60 | private final String priorCarrier; 61 | 62 | @OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER) 63 | @JoinColumn(name = "addressId") 64 | private final Address address; 65 | 66 | @ManyToOne(cascade = {CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY) 67 | @JoinColumn(name = "policyId") 68 | @JsonIgnore 69 | private Policy policy; 70 | 71 | @OneToMany(mappedBy = "driver", cascade = CascadeType.ALL, fetch = FetchType.EAGER) 72 | private final List drivingHistory; 73 | 74 | // Default constructor used by Hibernate 75 | private Driver() { 76 | this.licenseNum = null; 77 | this.licenseState = null; 78 | this.licenseExpiryDate = null; 79 | this.firstName = null; 80 | this.lastName = null; 81 | this.birthDate = null; 82 | this.gender = null; 83 | this.isMarried = null; 84 | this.email = null; 85 | this.phone = null; 86 | this.occupation = null; 87 | this.firstLicenseAtAge = null; 88 | this.address = null; 89 | this.drivingHistory = null; 90 | this.priorCarrier = null; 91 | } 92 | 93 | private Driver(Builder builder) { 94 | this.licenseNum = builder.licenseNum; 95 | this.licenseState = builder.licenseState; 96 | this.licenseExpiryDate = builder.licenseExpiryDate; 97 | this.firstName = builder.firstName; 98 | this.lastName = builder.lastName; 99 | this.birthDate = builder.birthDate; 100 | this.gender = builder.gender; 101 | this.isMarried = builder.isMarried; 102 | this.email = builder.email; 103 | this.phone = builder.phone; 104 | this.occupation = builder.occupation; 105 | this.firstLicenseAtAge = builder.firstLicenseAtAge; 106 | this.address = builder.address; 107 | this.drivingHistory = builder.drivingHistory; 108 | this.priorCarrier = builder.priorCarrier; 109 | } 110 | 111 | @JsonIgnoreProperties(ignoreUnknown = true) 112 | public static class Builder { 113 | 114 | private String licenseNum; 115 | private String licenseState; 116 | private LocalDate licenseExpiryDate; 117 | private String firstName; 118 | private String lastName; 119 | private LocalDate birthDate; 120 | private Gender gender; 121 | private boolean isMarried; 122 | private String email; 123 | private String phone; 124 | private String occupation; 125 | private Integer firstLicenseAtAge; 126 | private Address address; 127 | private List drivingHistory; 128 | private String priorCarrier; 129 | 130 | public Builder() { 131 | 132 | } 133 | 134 | public Builder withLicenseNum(String licenseNum) { 135 | this.licenseNum = licenseNum; 136 | return this; 137 | } 138 | 139 | public Builder withLicenseState(String licenseState) { 140 | this.licenseState = licenseState; 141 | return this; 142 | } 143 | 144 | public Builder withLicenseExpiryDate(LocalDate licenseExpiryDate) { 145 | this.licenseExpiryDate = licenseExpiryDate; 146 | return this; 147 | } 148 | 149 | public Builder withFirstName(String firstName) { 150 | this.firstName = firstName; 151 | return this; 152 | } 153 | 154 | public Builder withLastName(String lastName) { 155 | this.lastName = lastName; 156 | return this; 157 | } 158 | 159 | public Builder withBirthDate(LocalDate birthDate) { 160 | this.birthDate = birthDate; 161 | return this; 162 | } 163 | 164 | public Builder withGender(Gender gender) { 165 | this.gender = gender; 166 | return this; 167 | } 168 | 169 | public Builder withIsMarried(boolean isMarried) { 170 | this.isMarried = isMarried; 171 | return this; 172 | } 173 | 174 | public Builder withEmail(String email) { 175 | this.email = email; 176 | return this; 177 | } 178 | 179 | public Builder withPhone(String phone) { 180 | this.phone = phone; 181 | return this; 182 | } 183 | 184 | public Builder withOccupation(String occupation) { 185 | this.occupation = occupation; 186 | return this; 187 | } 188 | 189 | public Builder withAddress(Address address) { 190 | this.address = address; 191 | return this; 192 | } 193 | 194 | public Builder withFirstLicenseAtAge(Integer firstLicenseAtAge) { 195 | this.firstLicenseAtAge = firstLicenseAtAge; 196 | return this; 197 | } 198 | 199 | public Builder withDrivingHistory(List drivingHistory) { 200 | this.drivingHistory = drivingHistory; 201 | return this; 202 | } 203 | 204 | public Builder withPriorCarrier(String priorCarrier) { 205 | this.priorCarrier = priorCarrier; 206 | return this; 207 | } 208 | 209 | public Driver build() { 210 | validate(); 211 | return new Driver(this); 212 | } 213 | 214 | private void validate() { 215 | checkNotNull(licenseNum, "licenseNum may not be null"); 216 | checkNotNull(lastName, "lastName may not be blank"); 217 | checkNotNull(birthDate, "birthDate may not be null"); 218 | checkNotNull(address, "address may not be null"); 219 | } 220 | } 221 | 222 | public String getLicenseNum() { 223 | return licenseNum; 224 | } 225 | 226 | public String getLicenseState() { 227 | return licenseState; 228 | } 229 | 230 | public LocalDate getLicenseExpiryDate() { 231 | return licenseExpiryDate; 232 | } 233 | 234 | public List getDrivingHistory() { 235 | return drivingHistory; 236 | } 237 | 238 | public String getFirstName() { 239 | return firstName; 240 | } 241 | 242 | public String getLastName() { 243 | return lastName; 244 | } 245 | 246 | public LocalDate getBirthDate() { 247 | return birthDate; 248 | } 249 | 250 | public Gender getGender() { 251 | return gender; 252 | } 253 | 254 | public boolean getIsMarried() { 255 | return isMarried; 256 | } 257 | 258 | public String getEmail() { 259 | return email; 260 | } 261 | 262 | public String getPhone() { 263 | return phone; 264 | } 265 | 266 | public String getOccupation() { 267 | return occupation; 268 | } 269 | 270 | public Address getAddress() { 271 | return address; 272 | } 273 | 274 | public Integer getFirstLicenseAtAge() { 275 | return firstLicenseAtAge; 276 | } 277 | 278 | public String getPriorCarrier() { 279 | return priorCarrier; 280 | } 281 | 282 | public Policy getPolicy() { 283 | return policy; 284 | } 285 | 286 | // Deep copy 287 | public Driver deepCopyWithDrivingHistory(List _drivingHistory) { 288 | Driver d = new Driver.Builder().withLicenseNum(licenseNum).withBirthDate(birthDate).withFirstName(firstName) 289 | .withLastName(lastName).withLicenseExpiryDate(licenseExpiryDate).withGender(gender) 290 | .withEmail(email).withPhone(phone).withOccupation(occupation) 291 | .withFirstLicenseAtAge(firstLicenseAtAge).withIsMarried(isMarried).withAddress(address) 292 | .withDrivingHistory(_drivingHistory) //~ 293 | .build(); 294 | copyLinks(d); 295 | return d; 296 | } 297 | 298 | private void copyLinks(Driver d) { 299 | for (Link link : getLinks()) { 300 | d.addLink(link); 301 | } 302 | } 303 | 304 | @Override 305 | public String toString() { 306 | return Objects.toStringHelper(this).add("lastName", lastName).add("birthDate", birthDate) 307 | .add("occupation", occupation).add("licenseNum", licenseNum).toString(); 308 | } 309 | 310 | @Override 311 | public int compareTo(Driver that) { 312 | return ComparisonChain.start().compare(this.licenseNum, that.licenseNum, Ordering.natural().nullsLast()).result(); 313 | } 314 | 315 | @Override 316 | public boolean equals(Object obj) { 317 | Driver that = (Driver) obj; 318 | return Objects.equal(this.licenseNum, that.licenseNum); 319 | } 320 | 321 | @Override 322 | public int hashCode() { 323 | return Objects.hashCode(licenseNum); 324 | } 325 | 326 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/DrivingHistory.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | import com.google.common.base.Objects; 7 | import org.hibernate.annotations.Type; 8 | import org.joda.time.LocalDate; 9 | import org.joda.time.LocalDateTime; 10 | 11 | import javax.persistence.*; 12 | import java.io.Serializable; 13 | 14 | @JsonDeserialize(builder = DrivingHistory.Builder.class) 15 | @Entity 16 | @NamedQuery(name = "DrivingHistory.FIND_BY_DRIVER", query = "select o from DrivingHistory o " + 17 | "where o.driver.licenseNum = :licenseNum") 18 | public final class DrivingHistory extends BaseDomain implements Serializable { 19 | 20 | private static final long serialVersionUID = 6466627416634621081L; 21 | 22 | public enum AccidentType { 23 | MINOR, COLLISION, TOTALED 24 | } 25 | 26 | // Key 27 | @Transient 28 | private final String driverLicense; 29 | 30 | @ManyToOne(cascade = {CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY) 31 | @JoinColumn(name = "driverId") 32 | @JsonIgnore 33 | private Driver driver; 34 | 35 | private final LocalDate purchaseOrLeasedDate; 36 | private final Integer annualMileage; 37 | private final Boolean isGarageParked; 38 | private final Boolean isPrimaryOperator; 39 | private final Boolean isAccident; 40 | 41 | @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime") 42 | private final LocalDateTime accidentTime; 43 | 44 | private final Boolean isThirdPartyOffence; 45 | 46 | // Default constructor used by Hibernate 47 | private DrivingHistory() { 48 | this.driverLicense = null; 49 | this.purchaseOrLeasedDate = null; 50 | this.annualMileage = null; 51 | this.isGarageParked = null; 52 | this.isPrimaryOperator = null; 53 | this.isAccident = null; 54 | this.accidentTime = null; 55 | this.isThirdPartyOffence = null; 56 | } 57 | 58 | private DrivingHistory(Builder builder) { 59 | this.driverLicense = builder.driverLicense; 60 | this.purchaseOrLeasedDate = builder.purchaseOrLeasedDate; 61 | this.annualMileage = builder.annualMileage; 62 | this.isGarageParked = builder.isGarageParked; 63 | this.isPrimaryOperator = builder.isPrimaryOperator; 64 | this.isAccident = builder.isAccident; 65 | this.accidentTime = builder.accidentTime; 66 | this.isThirdPartyOffence = builder.isThirdPartyOffence; 67 | } 68 | 69 | @JsonIgnoreProperties(ignoreUnknown = true) 70 | public static class Builder { 71 | 72 | private String driverLicense; 73 | private LocalDate purchaseOrLeasedDate; 74 | private Integer annualMileage; 75 | private Boolean isGarageParked; 76 | private Boolean isPrimaryOperator; 77 | private Boolean isAccident; 78 | private LocalDateTime accidentTime; 79 | private Boolean isThirdPartyOffence; 80 | 81 | public Builder() { 82 | 83 | } 84 | 85 | public Builder withDriverLicense(String driverLicense) { 86 | this.driverLicense = driverLicense; 87 | return this; 88 | } 89 | 90 | public Builder withPurchaseOrLeasedDate(LocalDate purchaseOrLeasedDate) { 91 | this.purchaseOrLeasedDate = purchaseOrLeasedDate; 92 | return this; 93 | } 94 | 95 | public Builder withAnnualMileage(Integer annualMileage) { 96 | this.annualMileage = annualMileage; 97 | return this; 98 | } 99 | 100 | public Builder withIsGarageParked(boolean isGarageParked) { 101 | this.isGarageParked = isGarageParked; 102 | return this; 103 | } 104 | 105 | public Builder withIsPrimaryOperator(boolean isPrimaryOperator) { 106 | this.isPrimaryOperator = isPrimaryOperator; 107 | return this; 108 | } 109 | 110 | public Builder withIsAccident(boolean isAccident) { 111 | this.isAccident = isAccident; 112 | return this; 113 | } 114 | 115 | public Builder withAccidentTime(LocalDateTime accidentTime) { 116 | this.accidentTime = accidentTime; 117 | return this; 118 | } 119 | 120 | public Builder withIsThirdParyOffence(boolean isThirdPartyOffence) { 121 | this.isThirdPartyOffence = isThirdPartyOffence; 122 | return this; 123 | } 124 | 125 | public DrivingHistory build() { 126 | validate(); 127 | return new DrivingHistory(this); 128 | } 129 | 130 | private void validate() { 131 | 132 | } 133 | } 134 | 135 | public String getDriverLicense() { 136 | return driverLicense; 137 | } 138 | 139 | public LocalDate getPurchaseOrLeasedDate() { 140 | return purchaseOrLeasedDate; 141 | } 142 | 143 | public Integer getAnnualMileage() { 144 | return annualMileage; 145 | } 146 | 147 | public Boolean getIsGarageParked() { 148 | return isGarageParked; 149 | } 150 | 151 | public Boolean getIsPrimaryOperator() { 152 | return isPrimaryOperator; 153 | } 154 | 155 | public Driver getDriver() { 156 | return driver; 157 | } 158 | 159 | public Boolean getGarageParked() { 160 | return isGarageParked; 161 | } 162 | 163 | public Boolean getPrimaryOperator() { 164 | return isPrimaryOperator; 165 | } 166 | 167 | public Boolean getAccident() { 168 | return isAccident; 169 | } 170 | 171 | public LocalDateTime getAccidentTime() { 172 | return accidentTime; 173 | } 174 | 175 | public Boolean getThirdPartyOffence() { 176 | return isThirdPartyOffence; 177 | } 178 | 179 | 180 | // Accident Info 181 | 182 | 183 | @Override 184 | public String toString() { 185 | return Objects.toStringHelper(this).add("driverLicense", driverLicense) 186 | .add("purchaseOrLeasedDate", purchaseOrLeasedDate).add("annualMileage", annualMileage) 187 | .add("isAccident", isAccident).toString(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/Policy.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.google.common.base.Objects; 8 | import com.google.common.collect.ComparisonChain; 9 | import com.google.common.collect.Ordering; 10 | import github.priyatam.springrest.utils.Link; 11 | import org.hibernate.annotations.Index; 12 | import org.hibernate.annotations.Type; 13 | import org.joda.time.LocalDate; 14 | 15 | import javax.persistence.*; 16 | import java.io.Serializable; 17 | import java.util.List; 18 | 19 | import static com.google.common.base.Preconditions.checkArgument; 20 | import static com.google.common.base.Preconditions.checkNotNull; 21 | 22 | @JsonInclude(JsonInclude.Include.NON_NULL) 23 | @JsonDeserialize(builder = Policy.Builder.class) 24 | @Entity 25 | @NamedQuery(name = "Policy.FIND_BY_POLICYNUM", query = "select o from Policy o where o.policyNum = :policyNum") 26 | public final class Policy extends BaseDomain implements Comparable, Serializable { 27 | 28 | private static final long serialVersionUID = 2144310940122088721L; 29 | 30 | // key 31 | @Column(unique=true) 32 | @Index(name = "policyNum_ind") 33 | private final String policyNum; 34 | 35 | private final String company; 36 | 37 | @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate") 38 | private final LocalDate effectiveDate; 39 | 40 | @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate") 41 | private final LocalDate expiryDate; 42 | private final String state; 43 | private final Integer quote; 44 | private final Integer term; 45 | private final String declineReason; 46 | private final String agency; 47 | 48 | @OneToMany(mappedBy = "policy", cascade = {CascadeType.REFRESH, CascadeType.PERSIST, CascadeType.MERGE}) 49 | private final List drivers; 50 | 51 | @OneToMany(mappedBy = "policy", cascade = {CascadeType.REFRESH, CascadeType.PERSIST, CascadeType.MERGE}) 52 | private final List vehicles; 53 | 54 | // Default constructor used by Hibernate 55 | private Policy() { 56 | this.policyNum = null; 57 | this.company = null; 58 | this.effectiveDate = null; 59 | this.state = null; 60 | this.quote = null; 61 | this.expiryDate = null; 62 | this.term = null; 63 | this.declineReason = null; 64 | this.agency = null; 65 | this.drivers = null; 66 | this.vehicles = null; 67 | } 68 | 69 | private Policy(Builder builder) { 70 | this.policyNum = builder.policyNum; 71 | this.company = builder.company; 72 | this.effectiveDate = builder.effectiveDate; 73 | this.state = builder.state; 74 | this.quote = builder.quote; 75 | this.expiryDate = builder.expiryDate; 76 | this.term = builder.term; 77 | this.declineReason = builder.declineReason; 78 | this.agency = builder.agency; 79 | this.drivers = builder.drivers; 80 | this.vehicles = builder.vehicles; 81 | } 82 | 83 | @JsonIgnoreProperties(ignoreUnknown = true) 84 | public static class Builder { 85 | 86 | private String policyNum; 87 | private String company; 88 | private LocalDate effectiveDate; 89 | private String state; 90 | private Integer quote; 91 | private LocalDate expiryDate; 92 | private Integer term; 93 | private String declineReason; 94 | private String agency; 95 | private List drivers; 96 | private List vehicles; 97 | 98 | public Builder() { 99 | 100 | } 101 | 102 | public Builder withPolicyNum(String policyNum) { 103 | this.policyNum = policyNum; 104 | return this; 105 | } 106 | 107 | public Builder withCompany(String company) { 108 | this.company = company; 109 | return this; 110 | } 111 | 112 | public Builder withEffectiveDate(LocalDate effectiveDate) { 113 | this.effectiveDate = effectiveDate; 114 | return this; 115 | } 116 | 117 | public Builder withState(String state) { 118 | this.state = state; 119 | return this; 120 | } 121 | 122 | public Builder withQuote(Integer quote) { 123 | this.quote = quote; 124 | return this; 125 | } 126 | 127 | public Builder withExpiryDate(LocalDate expiryDate) { 128 | this.expiryDate = expiryDate; 129 | return this; 130 | } 131 | 132 | public Builder withTerm(Integer term) { 133 | this.term = term; 134 | return this; 135 | } 136 | 137 | public Builder withDeclineReason(String declineReason) { 138 | this.declineReason = declineReason; 139 | return this; 140 | } 141 | 142 | public Builder withAgency(String agency) { 143 | this.agency = agency; 144 | return this; 145 | } 146 | 147 | public Builder withDrivers(List drivers) { 148 | this.drivers = drivers; 149 | return this; 150 | } 151 | 152 | public Builder withVehicles(List vehicles) { 153 | this.vehicles = vehicles; 154 | return this; 155 | } 156 | 157 | public Policy build() { 158 | validate(); 159 | return new Policy(this); 160 | } 161 | 162 | private void validate() { 163 | checkNotNull(effectiveDate, "effectiveDate may not be null"); 164 | checkNotNull(expiryDate, "expiryDate may not be null"); 165 | checkNotNull(vehicles, "vehicles may not be null"); 166 | 167 | // Business Rules 168 | checkArgument(vehicles.size() > 0, "vehicles can't be empty"); 169 | checkArgument(effectiveDate.isAfter(new LocalDate()), "effectiveDate can't be in the past"); 170 | } 171 | } 172 | 173 | public String getPolicyNum() { 174 | return policyNum; 175 | } 176 | 177 | public String getCompany() { 178 | return company; 179 | } 180 | 181 | public LocalDate getEffectiveDate() { 182 | return effectiveDate; 183 | } 184 | 185 | public String getState() { 186 | return state; 187 | } 188 | 189 | public Integer getQuote() { 190 | return quote; 191 | } 192 | 193 | public LocalDate getExpiryDate() { 194 | return expiryDate; 195 | } 196 | 197 | public Integer getTerm() { 198 | return term; 199 | } 200 | 201 | public String getDeclineReason() { 202 | return declineReason; 203 | } 204 | 205 | public String getAgency() { 206 | return agency; 207 | } 208 | 209 | public List getDrivers() { 210 | return drivers; 211 | } 212 | 213 | public List getVehicles() { 214 | return vehicles; 215 | } 216 | 217 | 218 | // Deep copy 219 | private void copyLinks(Policy p) { 220 | for (Link link : getLinks()) { 221 | p.addLink(link); 222 | } 223 | } 224 | 225 | public Policy deepCopyWithDrivers(List _drivers) { 226 | Policy p = new Policy.Builder() 227 | .withPolicyNum(policyNum) 228 | .withEffectiveDate(effectiveDate) 229 | .withExpiryDate(expiryDate) 230 | .withTerm(term) 231 | .withCompany(company) 232 | .withState(state) 233 | .withQuote(quote) 234 | .withDeclineReason(declineReason) 235 | .withAgency(agency) 236 | .withDrivers(_drivers) //~ 237 | .withVehicles(vehicles) 238 | .build(); 239 | copyLinks(p); 240 | return p; 241 | } 242 | 243 | public Policy deepCopyWithVehicles(List _vehicles) { 244 | Policy p = new Policy.Builder() 245 | .withPolicyNum(policyNum) 246 | .withEffectiveDate(effectiveDate) 247 | .withExpiryDate(expiryDate) 248 | .withTerm(term) 249 | .withCompany(company) 250 | .withState(state) 251 | .withQuote(quote) 252 | .withDeclineReason(declineReason) 253 | .withAgency(agency) 254 | .withDrivers(drivers) 255 | .withVehicles(_vehicles) //~ 256 | .build(); 257 | copyLinks(p); 258 | return p; 259 | } 260 | 261 | public Policy deepCopyWithQuote(Integer _quote) { 262 | Policy p = new Policy.Builder() 263 | .withPolicyNum(policyNum) 264 | .withEffectiveDate(effectiveDate) 265 | .withExpiryDate(expiryDate) 266 | .withTerm(term) 267 | .withCompany(company) 268 | .withState(state) 269 | .withQuote(_quote) //~ 270 | .withDeclineReason(declineReason) 271 | .withAgency(agency) 272 | .withDrivers(drivers) 273 | .withVehicles(vehicles) 274 | .build(); 275 | copyLinks(p); 276 | return p; 277 | } 278 | 279 | public Policy deepCopyWithPolicyNum(String _policyNum) { 280 | Policy p = new Policy.Builder() 281 | .withPolicyNum(_policyNum) //~ 282 | .withEffectiveDate(effectiveDate) 283 | .withExpiryDate(expiryDate) 284 | .withTerm(term) 285 | .withCompany(company) 286 | .withState(state) 287 | .withQuote(quote) 288 | .withDeclineReason(declineReason) 289 | .withAgency(agency) 290 | .withDrivers(drivers) 291 | .withVehicles(vehicles) 292 | .build(); 293 | copyLinks(p); 294 | return p; 295 | } 296 | 297 | @Override 298 | public String toString() { 299 | return Objects.toStringHelper(this).add("policyNum", policyNum).add("effectiveDate", effectiveDate) 300 | .add("expiryDate", expiryDate).add("agency", agency).toString(); 301 | } 302 | 303 | @Override 304 | public int compareTo(Policy that) { 305 | return ComparisonChain.start().compare(this.policyNum, that.policyNum, Ordering.natural().nullsLast()).result(); 306 | } 307 | 308 | @Override 309 | public boolean equals(Object obj) { 310 | Policy that = (Policy) obj; 311 | return Objects.equal(this.policyNum, that.policyNum); 312 | } 313 | 314 | @Override 315 | public int hashCode() { 316 | return Objects.hashCode(policyNum); 317 | } 318 | 319 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/User.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.google.common.base.Objects; 7 | import com.google.common.collect.ComparisonChain; 8 | import com.google.common.collect.Ordering; 9 | import org.hibernate.annotations.Index; 10 | 11 | import javax.persistence.Column; 12 | import javax.persistence.Entity; 13 | import javax.persistence.NamedQuery; 14 | import java.io.Serializable; 15 | 16 | @Entity 17 | @NamedQuery(name = "User.FIND_BY_USERNAME", query = "select o from User o where o.username = :username") 18 | public final class User extends BaseDomain implements Comparable, Serializable { 19 | 20 | @Column(unique = true) 21 | @Index(name = "usernameIndex") 22 | private final String username; 23 | 24 | @JsonIgnore 25 | private final String password; 26 | 27 | private final String role; 28 | 29 | // Default constructor used by Hibernate 30 | private User() { 31 | this.username = null; 32 | this.password = null; 33 | this.role = null; 34 | } 35 | 36 | @JsonCreator 37 | public User(@JsonProperty("username") String username, @JsonProperty("password") String password, 38 | @JsonProperty("role") String role) { 39 | this.username = username; 40 | this.password = password; 41 | this.role = role; 42 | } 43 | 44 | public String getUsername() { 45 | return username; 46 | } 47 | 48 | public String getPassword() { 49 | return password; 50 | } 51 | 52 | public String getRole() { 53 | return role; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return Objects.toStringHelper(this).add("username", username).toString(); 59 | } 60 | 61 | @Override 62 | public int compareTo(User that) { 63 | return ComparisonChain.start().compare(this.username, that.username, Ordering.natural().nullsLast()).result(); 64 | } 65 | 66 | @Override 67 | public boolean equals(Object obj) { 68 | User that = (User) obj; 69 | return Objects.equal(this.username, that.username); 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return Objects.hashCode(username); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/domain/Vehicle.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.google.common.base.Objects; 6 | import com.google.common.base.Preconditions; 7 | import com.google.common.collect.ComparisonChain; 8 | import com.google.common.collect.Ordering; 9 | import org.hibernate.annotations.Index; 10 | 11 | import javax.persistence.*; 12 | import java.io.Serializable; 13 | 14 | @JsonDeserialize(builder = Vehicle.Builder.class) 15 | @JsonInclude(JsonInclude.Include.NON_NULL) 16 | @Entity 17 | @NamedQueries({ 18 | @NamedQuery( 19 | name = "Vehicle.FIND_BY_VIN", query = "select o from Vehicle o where o.vin = :vin"), 20 | @NamedQuery( 21 | name = "Vehicle.FIND_BY_POLICYNUM", query = "select o from Vehicle o where o.policy.policyNum = :policyNum") 22 | }) 23 | public final class Vehicle extends BaseDomain implements Comparable, Serializable { 24 | 25 | private static final long serialVersionUID = 2086756361985950564L; 26 | 27 | @ManyToOne(cascade = {CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY) 28 | @JoinColumn(name = "policyId") 29 | @JsonIgnore 30 | private Policy policy; 31 | 32 | public enum VehicleOwnerType { 33 | LEASED, OWNED, BUSINESS 34 | } 35 | 36 | // Key 37 | @Column(unique = true) 38 | @Index(name = "vinIndex") 39 | private final String vin; 40 | 41 | private final String make; 42 | private final String model; 43 | private final Integer year; 44 | private final String plateNum; 45 | private final Integer odomoterReading; 46 | 47 | @Enumerated(EnumType.STRING) 48 | private final VehicleOwnerType ownerType; 49 | 50 | // Default constructor used by Hibernate 51 | private Vehicle() { 52 | this.vin = null; 53 | this.plateNum = null; 54 | this.make = null; 55 | this.model = null; 56 | this.year = null; 57 | this.ownerType = null; 58 | this.odomoterReading = null; 59 | } 60 | 61 | private Vehicle(Builder builder) { 62 | this.vin = builder.vin; 63 | this.plateNum = builder.plateNum; 64 | this.make = builder.make; 65 | this.model = builder.model; 66 | this.year = builder.year; 67 | this.ownerType = builder.ownerType; 68 | this.odomoterReading = builder.odomoterReading; 69 | } 70 | 71 | @JsonIgnoreProperties(ignoreUnknown = true) 72 | public static class Builder { 73 | 74 | private String vin; 75 | private String plateNum; 76 | private String make; 77 | private String model; 78 | private Integer year; 79 | private VehicleOwnerType ownerType; 80 | private Integer odomoterReading; 81 | 82 | public Builder() { 83 | 84 | } 85 | 86 | public Builder withVin(String vin) { 87 | this.vin = vin; 88 | return this; 89 | } 90 | 91 | public Builder withPlateNum(String plateNum) { 92 | this.plateNum = plateNum; 93 | return this; 94 | } 95 | 96 | public Builder withMake(String make) { 97 | this.make = make; 98 | return this; 99 | } 100 | 101 | public Builder withModel(String model) { 102 | this.model = model; 103 | return this; 104 | } 105 | 106 | public Builder withYear(Integer year) { 107 | this.year = year; 108 | return this; 109 | } 110 | 111 | public Builder withOwnerType(VehicleOwnerType ownerType) { 112 | this.ownerType = ownerType; 113 | return this; 114 | } 115 | 116 | public Builder withOdomoterReading(Integer odomoterReading) { 117 | this.odomoterReading = odomoterReading; 118 | return this; 119 | } 120 | 121 | public Vehicle build() { 122 | validate(); 123 | return new Vehicle(this); 124 | } 125 | 126 | public Vehicle buildAsKey(String vin) { 127 | this.vin = vin; 128 | return new Vehicle(this); 129 | } 130 | 131 | private void validate() { 132 | Preconditions.checkNotNull(vin, "vin may not be null"); 133 | } 134 | } 135 | 136 | public String getVin() { 137 | return vin; 138 | } 139 | 140 | public String getPlateNum() { 141 | return plateNum; 142 | } 143 | 144 | public Integer getOdomoterReading() { 145 | return odomoterReading; 146 | } 147 | 148 | public String getMake() { 149 | return make; 150 | } 151 | 152 | public String getModel() { 153 | return model; 154 | } 155 | 156 | public Integer getYear() { 157 | return year; 158 | } 159 | 160 | public VehicleOwnerType getOwnerType() { 161 | return ownerType; 162 | } 163 | 164 | public Policy getPolicy() { 165 | return policy; 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | return Objects.toStringHelper(this).add("vin", vin).add("plateNum", plateNum).add("year", year) 171 | .add("odomoterReading", odomoterReading).toString(); 172 | } 173 | 174 | @Override 175 | public int compareTo(Vehicle that) { 176 | return ComparisonChain.start().compare(this.vin, that.vin) 177 | .compare(this.plateNum, that.plateNum, Ordering.natural().nullsLast()).result(); 178 | } 179 | 180 | @Override 181 | public boolean equals(Object obj) { 182 | Vehicle that = (Vehicle) obj; 183 | return Objects.equal(this.vin, that.vin); 184 | } 185 | 186 | @Override 187 | public int hashCode() { 188 | return Objects.hashCode(vin); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/ETagHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import java.io.IOException; 4 | import java.io.StringWriter; 5 | import java.util.Map; 6 | 7 | import org.springframework.cache.annotation.CacheEvict; 8 | import org.springframework.cache.annotation.Cacheable; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.util.DigestUtils; 11 | 12 | import com.fasterxml.jackson.core.JsonGenerationException; 13 | import com.fasterxml.jackson.databind.JsonMappingException; 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | import github.priyatam.springrest.utils.exception.InvalidTagException; 16 | import com.google.common.collect.Maps; 17 | 18 | @Service 19 | public class ETagHelper { 20 | 21 | // Local Storage 22 | private static final Map ETAGS = Maps.newConcurrentMap(); 23 | 24 | ObjectMapper mapper = new ObjectMapper(); 25 | 26 | @Cacheable(value = "etags", key = "#url") 27 | public String generate(String url, Object entity) { 28 | String etag = createETag(entity); 29 | ETAGS.put(url, etag); 30 | return etag; 31 | } 32 | 33 | @CacheEvict(value = "etags", key = "#url") 34 | public void remove(String url) { 35 | ETAGS.remove(url); 36 | } 37 | 38 | @Cacheable(value = "etags", key = "#url") 39 | public String get(String url) { 40 | if (!ETAGS.containsKey(url)) { 41 | throw new InvalidTagException(); 42 | } 43 | return ETAGS.get(url); 44 | } 45 | 46 | @CacheEvict(value = "etags", key = "#url") 47 | public String update(String url, Object entity) { 48 | return ETAGS.put(entity, url); 49 | } 50 | 51 | private String createETag(Object entity) { 52 | StringWriter writer = new StringWriter(); 53 | try { 54 | mapper.writeValue(writer, entity); 55 | } catch (JsonGenerationException e) { 56 | e.printStackTrace(); 57 | } catch (JsonMappingException e) { 58 | e.printStackTrace(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | return DigestUtils.md5DigestAsHex(writer.toString().getBytes()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/MailHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.mail.MailException; 6 | import org.springframework.mail.MailSender; 7 | import org.springframework.mail.SimpleMailMessage; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.inject.Inject; 12 | import java.util.Date; 13 | 14 | @Service 15 | public class MailHelper { 16 | 17 | final Logger logger = LoggerFactory.getLogger(MailHelper.class); 18 | 19 | @Inject 20 | private MailSender mailSender; 21 | 22 | // Flag to turn off email (useful for unit testing) 23 | private boolean isMailEnabled = true; 24 | 25 | @Async 26 | public void sendAsyncEmail(final String from, final String to, final String subject, final String body) { 27 | logger.info(Thread.currentThread().getName() + " begin asynchronous process ..."); 28 | sendMail(from, to, subject, body); 29 | } 30 | 31 | public void sendMail(final String from, final String to, final String subject, final String body) { 32 | if (isMailEnabled()) { 33 | logger.info("Sending email: " + body); 34 | SimpleMailMessage msg = new SimpleMailMessage(); 35 | msg.setFrom(from); 36 | msg.setTo(to); 37 | msg.setSubject(subject); 38 | msg.setSentDate(new Date()); 39 | msg.setText(body); 40 | try { 41 | mailSender.send(msg); 42 | } catch (final MailException ex) { 43 | logger.error("Error while sending mail: " + ex.getMessage()); 44 | } 45 | } else { 46 | logger.warn("Mail is not enabled. Check if isMailEnabled is set to false in your spring config!"); 47 | return; 48 | } 49 | } 50 | 51 | public boolean isMailEnabled() { 52 | return isMailEnabled; 53 | } 54 | 55 | public void setMailEnabled(boolean mailEnabled) { 56 | isMailEnabled = mailEnabled; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/PersistenceHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import github.priyatam.springrest.domain.*; 4 | import github.priyatam.springrest.integration.QuoteGenerator; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.cache.annotation.Cacheable; 8 | import org.springframework.stereotype.Repository; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import javax.inject.Inject; 12 | import javax.persistence.EntityManager; 13 | import javax.persistence.PersistenceContext; 14 | import javax.persistence.PersistenceException; 15 | import java.util.List; 16 | import java.util.Random; 17 | 18 | @Repository 19 | public class PersistenceHelper { 20 | 21 | final static Logger logger = LoggerFactory.getLogger(PersistenceHelper.class); 22 | 23 | @Inject 24 | ETagHelper eTagHelper; 25 | 26 | @Inject 27 | QuoteGenerator quoteGenerator; 28 | 29 | @PersistenceContext 30 | private EntityManager entityManager; 31 | 32 | @Transactional 33 | public void saveUser(User user) { 34 | entityManager.persist(user); 35 | } 36 | 37 | public User findByUsername(String username) { 38 | try { 39 | return entityManager.createNamedQuery("User.FIND_BY_USERNAME", User.class) 40 | .setParameter("username", username) 41 | .getSingleResult(); 42 | } catch (PersistenceException e) { 43 | return null; 44 | } 45 | } 46 | 47 | @Cacheable(value = "policies") 48 | public Policy loadPolicy(String policyNum) { 49 | return entityManager.createNamedQuery("Policy.FIND_BY_POLICYNUM", Policy.class) 50 | .setParameter("policyNum", policyNum) 51 | .getSingleResult(); 52 | } 53 | 54 | @Cacheable(value = "drivers") 55 | public Driver loadDriverByLicenseNum(String licenseNum) { 56 | return entityManager.createNamedQuery("Driver.FIND_BY_LICENSENUM", Driver.class) 57 | .setParameter("licenseNum", licenseNum) 58 | .getSingleResult(); 59 | } 60 | 61 | @Cacheable(value = "policy-drivers", key = "#policyNum") 62 | public List loadDriverByPolicyNum(String policyNum) { 63 | return entityManager.createNamedQuery("Driver.FIND_BY_POLICYNUM", Driver.class) 64 | .setParameter("policyNum", policyNum) 65 | .getResultList(); 66 | } 67 | 68 | @Cacheable(value = "vehicles", key = "#vin") 69 | public Vehicle loadVehicleByVin(String vin) { 70 | return entityManager.createNamedQuery("Vehicle.FIND_BY_VIN", Vehicle.class) 71 | .setParameter("vin", vin) 72 | .getSingleResult(); 73 | } 74 | 75 | @Cacheable(value = "policy-vehicles") 76 | public List loadVehicleByPolicyNum(String policyNum) { 77 | return entityManager.createNamedQuery("Vehicle.FIND_BY_POLICYNUM", Vehicle.class) 78 | .setParameter("policyNum", policyNum) 79 | .getResultList(); 80 | } 81 | 82 | @Cacheable(value = "driving-history") 83 | public List loadDrivingHistory(String vin, String licenseNum) { 84 | return entityManager.createNamedQuery("DrivingHistory.FIND_BY_DRIVER_VEHICLE", DrivingHistory.class) 85 | .setParameter("licenseNum", licenseNum) 86 | .setParameter("vin", vin) 87 | .getResultList(); 88 | } 89 | 90 | @Transactional 91 | public String create(final Policy policy) { 92 | logger.debug("Saving Policy"); 93 | 94 | // Invoke Quote Service 95 | Policy policyWithQuote = policy.deepCopyWithQuote(quoteGenerator.generateQuote(policy)); 96 | 97 | // Generate Policy Number 98 | Policy policyWithNum = policyWithQuote.deepCopyWithPolicyNum(policyWithQuote.getAgency() + "-" 99 | + new Random().nextInt(10000)); 100 | 101 | // Save policy 102 | logger.debug("Finished Saving Policy: " + policy); 103 | entityManager.persist(policyWithNum); 104 | 105 | return policyWithNum.getPolicyNum(); 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/PolicyAsyncHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Service; 11 | 12 | import github.priyatam.springrest.domain.Policy; 13 | 14 | import javax.inject.Inject; 15 | 16 | /** 17 | * Async processing using direct JDK 6 Executor interface 18 | * 19 | * @author pmudivarti 20 | * 21 | */ 22 | @Service 23 | public class PolicyAsyncHelper { 24 | 25 | final static Logger logger = LoggerFactory.getLogger(PolicyAsyncHelper.class); 26 | 27 | @Inject 28 | ResourceHelper resourceHelper; 29 | 30 | @Inject 31 | MailHelper mailer; 32 | 33 | @Inject 34 | PersistenceHelper persistence; 35 | 36 | /* 37 | * Thread pool Acts as a throttle preventing out of memory exception 38 | */ 39 | private ExecutorService executor = new ThreadPoolExecutor(10, 50, Long.MAX_VALUE, TimeUnit.SECONDS, 40 | new LinkedBlockingQueue(2000), new ThreadPoolExecutor.DiscardOldestPolicy()); 41 | 42 | public void createPolicy(final String futureKey, final Policy policy) { 43 | logger.debug("Processing Policy creation asynchornously ... "); 44 | executor.execute(new Runnable() { 45 | 46 | @Override 47 | public void run() { 48 | String threadName = Thread.currentThread().getName(); 49 | logger.debug(threadName + " beginning work on create policy ... "); 50 | logger.debug("Executing Create Policy asynchronously ..."); 51 | 52 | String policyNum = null; 53 | try { 54 | policyNum = persistence.create(policy); 55 | } catch (Exception e) { 56 | logger.error("Unable to save Policy, updating resource status ..."); 57 | resourceHelper.createError(futureKey, e); 58 | } 59 | 60 | resourceHelper.createPermLocation("/policy", futureKey, policyNum); 61 | 62 | mailer.sendMail("wara-ws-demo@plymouthrock.com", "pmudivarti@plymouthrock.com", "New Policy Created", 63 | "A new policy has been created at: " + resourceHelper.getPermLocation(futureKey)); 64 | 65 | logger.debug(threadName + " completed create policy and updated resource cache."); 66 | } 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/ResourceHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import java.util.Date; 4 | import java.util.Map; 5 | import java.util.UUID; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.cache.annotation.Cacheable; 11 | import org.springframework.stereotype.Service; 12 | 13 | import com.google.common.collect.Maps; 14 | 15 | @Service 16 | public class ResourceHelper { 17 | 18 | public static enum ProcessingStatus { 19 | PROCESSING, COMPLETED, ERROR 20 | } 21 | 22 | protected static final Map RESOURCE_STATUS = Maps.newConcurrentMap(); 23 | protected static final Map RESOURCE_STATUS_ERRORS = Maps.newConcurrentMap(); 24 | protected static final Map RESOURCE_LAST_MODIFIED = Maps.newConcurrentMap(); 25 | 26 | final static Logger logger = LoggerFactory.getLogger(ResourceHelper.class); 27 | 28 | @Value("${wara.ws.virtualhost}") 29 | private String vhost; 30 | 31 | public String generateFutureKey() { 32 | String futureKey = UUID.randomUUID().toString(); 33 | RESOURCE_STATUS.put(futureKey, ProcessingStatus.PROCESSING.toString()); 34 | logger.debug("Generated futureKey: " + futureKey); 35 | return futureKey; 36 | } 37 | 38 | public ProcessingStatus createPermLocation(String requestMapping, String futureKey, String permKey) { 39 | if (RESOURCE_STATUS.get(futureKey) == ProcessingStatus.PROCESSING.toString()) { 40 | String location = buildLocationUrl(requestMapping, permKey); 41 | logger.debug("Creating Perm location and updating resource status cache: " + location); 42 | RESOURCE_STATUS.put(futureKey, location); 43 | 44 | // Update timestamp 45 | RESOURCE_LAST_MODIFIED.put(permKey, new Date().getTime()); 46 | 47 | return ProcessingStatus.COMPLETED; 48 | } 49 | return ProcessingStatus.PROCESSING; 50 | } 51 | 52 | @Cacheable(value = "resource-status", key = "#futureKey") 53 | public String getPermLocation(String futureKey) { 54 | return RESOURCE_STATUS.get(futureKey); 55 | } 56 | 57 | public String createFutureLocation(String requestMapping, String futureKey) { 58 | return buildLocationUrl(requestMapping + "/future", futureKey); 59 | } 60 | 61 | public String parseKey(String location) { 62 | String[] parts = location.split("/"); 63 | return parts[parts.length - 1]; 64 | } 65 | 66 | @Cacheable(value = "resource-status", key = "#futureKey") 67 | private String buildLocationUrl(String requestMapping, String uri) { 68 | logger.debug(String.format("uri [%s], requestMapping [%s]", uri, requestMapping)); 69 | String location = vhost + requestMapping + "/" + uri; 70 | logger.debug("Built LocationUri: " + location); 71 | return location; 72 | } 73 | 74 | public void createError(String futureKey, Exception e) { 75 | RESOURCE_STATUS.put(futureKey, ProcessingStatus.ERROR.toString()); 76 | RESOURCE_STATUS_ERRORS.put(futureKey, e.getMessage()); 77 | } 78 | 79 | public String getError(String futureKey) { 80 | return RESOURCE_STATUS_ERRORS.get(futureKey); 81 | } 82 | 83 | public long lastModified(String permKey) { 84 | return RESOURCE_LAST_MODIFIED.get(permKey); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/ResponseBuilderHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import com.google.common.collect.Lists; 4 | import github.priyatam.springrest.domain.Driver; 5 | import github.priyatam.springrest.domain.DrivingHistory; 6 | import github.priyatam.springrest.domain.Policy; 7 | import github.priyatam.springrest.domain.Vehicle; 8 | import github.priyatam.springrest.utils.Link; 9 | import github.priyatam.springrest.utils.LinkBuilder; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Service; 14 | 15 | import javax.inject.Inject; 16 | import java.util.List; 17 | 18 | @Service 19 | public class ResponseBuilderHelper { 20 | 21 | final static Logger logger = LoggerFactory.getLogger(ResponseBuilderHelper.class); 22 | 23 | @Inject 24 | protected ETagHelper eTagHelper; 25 | 26 | @Inject 27 | private PersistenceHelper persistenceHelper; 28 | 29 | @Value("${virtualhost}") 30 | private String vhost; 31 | 32 | public enum URLS { 33 | 34 | POLICY("/policy/{policyNum}"), 35 | POLICY_VEHICLE("/policy/{policyNum}/vehicle/{vin}"), 36 | POLICY_DRIVER("/policy/{policyNum}/driver/{licenseNo}"), 37 | DRIVING_HISTORY("/policy/{policyNum}/driver/{licenseNo}/drivinghistory"), 38 | VEHICLE("/vehicle/{vin}"), 39 | DRIVER("/driver/{licenseNo}"); 40 | 41 | final String url; 42 | 43 | URLS(String url) { 44 | this.url = url; 45 | } 46 | 47 | public String expand(String policyNum, String param) { 48 | switch (this) { 49 | case POLICY_DRIVER: 50 | return url.replaceAll("\\{policyNum\\}", policyNum).replaceAll("\\{licenseNo\\}", param); 51 | case POLICY_VEHICLE: 52 | return url.replaceAll("\\{policyNum\\}", policyNum).replaceAll("\\{vin\\}", param); 53 | case DRIVING_HISTORY: 54 | return url.replaceAll("\\{policyNum\\}", policyNum).replaceAll("\\{licenseNo\\}", param); 55 | default: 56 | return ""; 57 | } 58 | } 59 | 60 | public String expand(String param) { 61 | switch (this) { 62 | case POLICY: 63 | return url.replaceAll("\\{policyNum\\}", param); 64 | case DRIVER: 65 | return url.replaceAll("\\{licenseNo\\}", param); 66 | case VEHICLE: 67 | return url.replaceAll("\\{vin\\}", param); 68 | default: 69 | return ""; 70 | } 71 | } 72 | } 73 | 74 | public void toPolicy(Policy policy) { 75 | // Build policy link 76 | if (policy.getLinks().size() != 1) { 77 | policy.addLink(LinkBuilder.build(URLS.POLICY.expand(policy.getPolicyNum()), policy.toString())); 78 | } 79 | 80 | // Build driver links 81 | List drivers = persistenceHelper.loadDriverByPolicyNum(policy.getPolicyNum()); 82 | 83 | toDriver(policy, policy.getPolicyNum(), drivers); 84 | 85 | // Build vehicle links 86 | List vehicles = persistenceHelper.loadVehicleByPolicyNum(policy.getPolicyNum()); 87 | 88 | toVehicle(policy, policy.getPolicyNum(), vehicles); 89 | 90 | // Update Etag 91 | eTagHelper.generate(vhost + URLS.POLICY.expand(policy.getPolicyNum()), policy); 92 | } 93 | 94 | public void toPolicyFull(Policy policy) { 95 | // Build policy link 96 | if (policy.getLinks().size() != 1) { 97 | policy.addLink(LinkBuilder.build(URLS.POLICY.expand(policy.getPolicyNum()), policy.toString())); 98 | } 99 | 100 | // Build driver links 101 | List drivers = persistenceHelper.loadDriverByPolicyNum(policy.getPolicyNum()); 102 | 103 | toDriver(policy, policy.getPolicyNum(), drivers); 104 | policy = policy.deepCopyWithDrivers(drivers); 105 | 106 | // Build vehicle links 107 | List vehicles = persistenceHelper.loadVehicleByPolicyNum(policy.getPolicyNum()); 108 | policy = policy.deepCopyWithVehicles(vehicles); 109 | 110 | toVehicle(policy, policy.getPolicyNum(), vehicles); 111 | 112 | // Update Etag 113 | eTagHelper.generate(vhost + URLS.POLICY.expand(policy.getPolicyNum()), policy); 114 | } 115 | 116 | public void toDriver(Policy policy, String policyNum, List drivers) { 117 | for (Driver _d : drivers) { 118 | Driver driver = new Driver.Builder().withLicenseNum(_d.getLicenseNum()).build(); 119 | driver.addLink(LinkBuilder.build(URLS.POLICY.expand(policyNum), "Policy", Link.REL_PARENT)); 120 | driver.addLink(LinkBuilder.build(URLS.POLICY_DRIVER.expand(policyNum, _d.getLicenseNum()), _d.toString())); 121 | 122 | List dh = persistenceHelper.loadDrivingHistory(policyNum, _d.getLicenseNum()); 123 | toDrivingHistory(driver, policyNum, _d.getLicenseNum(), dh); 124 | 125 | if (policy != null) { 126 | policy = policy.deepCopyWithDrivers(Lists.newArrayList(driver)); 127 | } 128 | 129 | // Update Etag 130 | eTagHelper.generate(vhost + URLS.POLICY_DRIVER.expand(policyNum, _d.getLicenseNum()), _d); 131 | } 132 | } 133 | 134 | public void toDriver(List drivers) { 135 | for (Driver driver : drivers) { 136 | if (driver.getLinks().size() != 1) { 137 | driver.addLink(LinkBuilder.build(URLS.DRIVER.expand(driver.getLicenseNum()), driver.toString())); 138 | } 139 | 140 | // Update Etag 141 | eTagHelper.generate(vhost + URLS.DRIVER.expand(driver.getLicenseNum()), driver); 142 | } 143 | } 144 | 145 | public void toDriver(String policyNum, List drivers) { 146 | for (Driver driver : drivers) { 147 | if (driver.getLinks().size() != 2) { 148 | driver.addLink(LinkBuilder.build(URLS.POLICY.expand(policyNum), "Policy", Link.REL_PARENT)); 149 | driver.addLink(LinkBuilder.build(URLS.POLICY_DRIVER.expand(policyNum, driver.getLicenseNum()), 150 | driver.toString())); 151 | } 152 | 153 | List dh = persistenceHelper.loadDrivingHistory(policyNum, driver.getLicenseNum()); 154 | toDrivingHistory(policyNum, driver.getLicenseNum(), dh); 155 | for (DrivingHistory history : dh) { 156 | driver = driver.deepCopyWithDrivingHistory(Lists.newArrayList(history)); 157 | } 158 | 159 | // Update Etag 160 | eTagHelper.generate(vhost + URLS.POLICY_DRIVER.expand(policyNum, driver.getLicenseNum()), driver); 161 | } 162 | } 163 | 164 | public void toDrivingHistory(String policyNum, String licenseNo, List historyList) { 165 | for (DrivingHistory history : historyList) { 166 | if (history.getLinks().size() != 2) { 167 | history.addLink(LinkBuilder.build(URLS.DRIVER.expand(licenseNo), "Driver", Link.REL_PARENT)); 168 | history.addLink(LinkBuilder.build(URLS.DRIVING_HISTORY.expand(policyNum, licenseNo), history.toString())); 169 | } 170 | 171 | // Update Etag 172 | eTagHelper.generate(vhost + URLS.DRIVING_HISTORY.expand(policyNum, licenseNo), history); 173 | } 174 | } 175 | 176 | public void toDrivingHistory(Driver driver, String policyNum, String licenseNo, List historyList) { 177 | for (DrivingHistory _h : historyList) { 178 | DrivingHistory history = new DrivingHistory.Builder().build(); 179 | history.addLink(LinkBuilder.build(URLS.DRIVER.expand(licenseNo), driver.toString(), Link.REL_PARENT)); 180 | history.addLink(LinkBuilder.build(URLS.DRIVING_HISTORY.expand(policyNum, licenseNo), _h.toString())); 181 | 182 | if (driver != null) { 183 | driver = driver.deepCopyWithDrivingHistory(Lists.newArrayList(history)); 184 | } 185 | 186 | // Update Etag 187 | eTagHelper.generate(vhost + URLS.DRIVING_HISTORY.expand(policyNum, licenseNo), _h); 188 | } 189 | } 190 | 191 | public void toVehicle(List vehicles) { 192 | for (Vehicle vehicle : vehicles) { 193 | if (vehicle.getLinks().size() != 1) { 194 | vehicle.addLink(LinkBuilder.build(URLS.VEHICLE.expand(vehicle.getVin()), vehicle.toString())); 195 | 196 | // Update Etag 197 | eTagHelper.generate(vhost + URLS.VEHICLE.expand(vehicle.getVin()), vehicle); 198 | } 199 | } 200 | } 201 | 202 | public void toVehicle(Policy policy, String policyNum, List vehicles) { 203 | for (Vehicle _v : vehicles) { 204 | if (_v.getLinks().size() != 2) { 205 | Vehicle vehicle = new Vehicle.Builder().withVin(_v.getVin()).build(); 206 | vehicle.addLink(LinkBuilder.build(URLS.POLICY.expand(policyNum), "Policy", Link.REL_PARENT)); 207 | vehicle.addLink(LinkBuilder.build(URLS.POLICY_VEHICLE.expand(policyNum, _v.getVin()), _v.toString())); 208 | 209 | if (policy != null) { 210 | policy = policy.deepCopyWithVehicles(Lists.newArrayList(vehicle)); 211 | } 212 | } 213 | // Update Etag 214 | eTagHelper.generate(vhost + URLS.POLICY_VEHICLE.expand(policyNum, _v.getVin()), _v); 215 | } 216 | } 217 | 218 | public void toVehicle(String policyNum, List vehicles) { 219 | for (Vehicle vehicle : vehicles) { 220 | if (vehicle.getLinks().size() != 2) { 221 | vehicle.addLink(LinkBuilder.build(URLS.POLICY.expand(policyNum), "Policy", Link.REL_PARENT)); 222 | vehicle.addLink(LinkBuilder.build(URLS.POLICY_VEHICLE.expand(policyNum, vehicle.getVin()), 223 | vehicle.toString())); 224 | } 225 | 226 | // Update Etag 227 | eTagHelper.generate(vhost + URLS.POLICY_VEHICLE.expand(policyNum, vehicle.getVin()), vehicle); 228 | } 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/RuleHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import github.priyatam.springrest.domain.Policy; 4 | import github.priyatam.springrest.utils.exception.PolicyInvalidException.ErrorCode; 5 | import com.google.common.base.Strings; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | // This can be hooked to a Drools Service 11 | @Component 12 | public class RuleHelper { 13 | 14 | final static Logger logger = LoggerFactory.getLogger(RuleHelper.class); 15 | 16 | public ErrorCode validatePolicy(Policy policy) { 17 | 18 | if (!Strings.isNullOrEmpty(policy.getPolicyNum().toString())) { 19 | logger.info("Pre-condition failed: Policy Number should be null"); 20 | return ErrorCode.NON_NULL_POLICY_NUM; 21 | } 22 | 23 | if (policy.getQuote() != null) { 24 | logger.info("Pre-condition failed: Quote should be null"); 25 | return ErrorCode.NON_NULL_POLICY_NUM; 26 | } 27 | 28 | if (policy.getVehicles() != null && policy.getVehicles().size() > 10) { 29 | logger.info("Pre-condition failed: Policy can't have more than 10 vehicles"); 30 | return ErrorCode.TOO_MANY_VEHICLES; 31 | } 32 | 33 | if (policy.getDrivers() != null && policy.getDrivers().size() > 10) { 34 | logger.info("Pre-condition failed: Policy can't have more than 10 drivers"); 35 | return ErrorCode.TOO_MANY_DRIVERS; 36 | } 37 | 38 | return null; // pass 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/helper/SecurityHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.helper; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.security.core.userdetails.UserDetailsService; 7 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.annotation.PostConstruct; 11 | import javax.inject.Inject; 12 | import java.util.Collections; 13 | 14 | public class SecurityHelper implements UserDetailsService { 15 | 16 | @Inject 17 | private PersistenceHelper persistenceHelper; 18 | 19 | @Override 20 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 21 | github.priyatam.springrest.domain.User user = persistenceHelper.findByUsername(username); 22 | if (user == null) { 23 | throw new UsernameNotFoundException("user not found"); 24 | } 25 | GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole()); 26 | return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.singleton(authority)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/integration/LocalQuoteGenerator.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.integration; 2 | 3 | import github.priyatam.springrest.domain.Policy; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.Random; 7 | 8 | @Service("quoteGenerator") 9 | public class LocalQuoteGenerator implements QuoteGenerator { 10 | 11 | public Integer generateQuote(Policy policy) { 12 | return new Random().nextInt(2000); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/integration/QuoteGenerator.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.integration; 2 | 3 | import github.priyatam.springrest.domain.Policy; 4 | 5 | /** 6 | * Third Party Integration Service 7 | */ 8 | public interface QuoteGenerator { 9 | 10 | Integer generateQuote(Policy policy); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/resource/DriverResource.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.google.common.collect.Lists; 4 | import github.priyatam.springrest.domain.Driver; 5 | import github.priyatam.springrest.helper.PersistenceHelper; 6 | import github.priyatam.springrest.helper.ResponseBuilderHelper; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | @Controller 20 | public class DriverResource { 21 | 22 | final static Logger logger = LoggerFactory.getLogger(DriverResource.class); 23 | 24 | @Autowired 25 | protected ResponseBuilderHelper responseBuilder; 26 | 27 | @Autowired 28 | protected PersistenceHelper persistenceHelper; 29 | 30 | 31 | @RequestMapping(method = RequestMethod.GET, value = "/driver/{licenseNo}") 32 | @ResponseBody 33 | public ResponseEntity getDriver(@PathVariable String licenseNo) { 34 | logger.debug(String.format("Retrieving Driver %s :", licenseNo)); 35 | 36 | Driver driver = persistenceHelper.loadDriverByLicenseNum(licenseNo); 37 | if (driver == null) { 38 | logger.warn("No Driver found"); 39 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 40 | } 41 | 42 | // Convert to Restful Resource, update ETag and HATEOAS references 43 | responseBuilder.toDriver(Lists.newArrayList(driver)); 44 | 45 | return new ResponseEntity(driver, HttpStatus.OK); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/resource/PolicyResource.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.google.common.base.Strings; 4 | import com.google.common.collect.Lists; 5 | import github.priyatam.springrest.domain.Driver; 6 | import github.priyatam.springrest.domain.DrivingHistory; 7 | import github.priyatam.springrest.domain.Policy; 8 | import github.priyatam.springrest.domain.Vehicle; 9 | import github.priyatam.springrest.utils.exception.InvalidTagException; 10 | import github.priyatam.springrest.utils.exception.PolicyInvalidException; 11 | import github.priyatam.springrest.utils.exception.PolicyInvalidException.ErrorCode; 12 | import github.priyatam.springrest.helper.*; 13 | import github.priyatam.springrest.helper.ResponseBuilderHelper.URLS; 14 | import github.priyatam.springrest.helper.PolicyAsyncHelper; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.beans.factory.annotation.Value; 18 | import org.springframework.dao.DataIntegrityViolationException; 19 | import org.springframework.http.HttpHeaders; 20 | import org.springframework.http.HttpStatus; 21 | import org.springframework.http.ResponseEntity; 22 | import org.springframework.stereotype.Controller; 23 | import org.springframework.web.bind.annotation.*; 24 | import org.springframework.web.context.request.WebRequest; 25 | 26 | import javax.inject.Inject; 27 | import javax.servlet.http.HttpServletRequest; 28 | import javax.validation.constraints.NotNull; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | @Controller 33 | public class PolicyResource { 34 | 35 | final static Logger logger = LoggerFactory.getLogger(PolicyResource.class); 36 | 37 | @Inject 38 | private PolicyAsyncHelper policyWorker; 39 | 40 | @Inject 41 | protected ResourceHelper resourceHelper; 42 | 43 | @Inject 44 | protected ETagHelper eTagHelper; 45 | 46 | @Inject 47 | protected ResponseBuilderHelper responseBuilder; 48 | 49 | @Inject 50 | MailHelper mailer; 51 | 52 | @Inject 53 | RuleHelper ruleHelper; 54 | 55 | @Inject 56 | protected PersistenceHelper persistenceHelper; 57 | 58 | @Value("${virtualhost}") 59 | private String vhost; 60 | 61 | @RequestMapping(method = RequestMethod.OPTIONS, value = "/policy") 62 | public ResponseEntity options(HttpServletRequest arg0) { 63 | // TODO Return all the Links for the resource 64 | return null; 65 | } 66 | 67 | @RequestMapping(method = RequestMethod.GET, value = "/policy") 68 | @ResponseBody 69 | public ResponseEntity> getAll() { 70 | List policies = new ArrayList(); // TODO: Implement mapper.getAll 71 | if (policies.size() == 0) { 72 | logger.debug("No Policies found"); 73 | return new ResponseEntity>(policies, HttpStatus.NOT_FOUND); 74 | } 75 | return new ResponseEntity>(policies, HttpStatus.OK); 76 | } 77 | 78 | @RequestMapping(method = RequestMethod.POST, value = "/policy") 79 | @ResponseBody 80 | public ResponseEntity save(final @RequestBody Policy policy) { 81 | 82 | // Input Sanitization / Validations 83 | ErrorCode code = ruleHelper.validatePolicy(policy); 84 | if (code != null) { 85 | throw new PolicyInvalidException(code); 86 | } 87 | 88 | logger.debug("Generating a Future Url ..."); 89 | String futureKey = resourceHelper.generateFutureKey(); 90 | String futureLocation = resourceHelper.createFutureLocation("/policy", futureKey); 91 | 92 | // build header 93 | HttpHeaders headers = new HttpHeaders(); 94 | headers.add("Location", futureLocation); 95 | 96 | // async 97 | policyWorker.createPolicy(futureKey, policy); 98 | 99 | return new ResponseEntity(null, headers, HttpStatus.ACCEPTED); 100 | } 101 | 102 | @RequestMapping(method = RequestMethod.HEAD, value = "/policy/{policyNum}") 103 | @ResponseBody 104 | public ResponseEntity head(HttpServletRequest request, @PathVariable String policyNum) { 105 | logger.debug("Requested head for: " + policyNum); 106 | // / String url = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 107 | try { 108 | String tag = eTagHelper.get(vhost + URLS.POLICY.expand(policyNum)); 109 | HttpHeaders headers = new HttpHeaders(); 110 | headers.add("Etag", tag); 111 | return new ResponseEntity(null, headers, HttpStatus.NO_CONTENT); 112 | } catch (InvalidTagException e) { 113 | return new ResponseEntity(HttpStatus.NOT_FOUND); 114 | } 115 | } 116 | 117 | @RequestMapping(method = RequestMethod.GET, value = "/policy/{policyNum}") 118 | @ResponseBody 119 | public ResponseEntity get(@PathVariable String policyNum, WebRequest request) { 120 | logger.debug("Retrieving Policy :" + policyNum); 121 | 122 | if (request.checkNotModified(resourceHelper.lastModified(policyNum))) { 123 | // shortcut exit - no further processing necessary 124 | return null; 125 | } 126 | 127 | // Load Policy 128 | Policy policy = persistenceHelper.loadPolicy(policyNum); 129 | if (policy == null) { 130 | logger.warn("No Policy found"); 131 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 132 | } 133 | 134 | // Convert to Restful Resource, update ETag and HATEOAS references 135 | responseBuilder.toPolicy(policy); 136 | 137 | return new ResponseEntity(policy, HttpStatus.OK); 138 | } 139 | 140 | @RequestMapping(method = RequestMethod.GET, value = "/policy/{policyNum}/drivers") 141 | @ResponseBody 142 | public ResponseEntity> getDrivers(@PathVariable String policyNum) { 143 | logger.debug("Retrieving Driver for Policy :" + policyNum); 144 | 145 | // Load drivers 146 | List drivers = persistenceHelper.loadDriverByPolicyNum(policyNum); 147 | if (drivers == null) { 148 | logger.warn("No Drivers found"); 149 | return new ResponseEntity>(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 150 | } 151 | 152 | // Convert to Restful Resource, update ETag and HATEOAS references 153 | responseBuilder.toDriver(policyNum, drivers); 154 | 155 | return new ResponseEntity>(drivers, HttpStatus.OK); 156 | } 157 | 158 | @RequestMapping(method = RequestMethod.GET, value = "/policy/{policyNum}/driver/{licenseNo}") 159 | @ResponseBody 160 | public ResponseEntity getDriver(@PathVariable String policyNum, @NotNull @PathVariable String licenseNo) { 161 | logger.debug(String.format("Retrieving Driver %s for Policy %s :", licenseNo, policyNum)); 162 | 163 | if (licenseNo.length() > 20) { 164 | logger.warn("licenseNo too large"); 165 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.BAD_REQUEST); 166 | } 167 | 168 | Driver driver = persistenceHelper.loadDriverByLicenseNum(licenseNo); 169 | if (driver == null) { 170 | logger.warn("No Driver found"); 171 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 172 | } 173 | 174 | // Convert to Restful Resource, update ETag and HATEOAS references 175 | responseBuilder.toDriver(policyNum, Lists.newArrayList(driver)); 176 | 177 | return new ResponseEntity(driver, HttpStatus.OK); 178 | } 179 | 180 | @RequestMapping(method = RequestMethod.GET, value = "/policy/{policyNum}/driver/{licenseNo}/drivinghistory") 181 | @ResponseBody 182 | public ResponseEntity> getDrivingHistory(@PathVariable String policyNum, 183 | @PathVariable String licenseNo) { 184 | logger.debug(String.format("Retrieving Driver %s for Policy %s :", licenseNo, policyNum)); 185 | 186 | List history = persistenceHelper.loadDrivingHistory(policyNum, licenseNo); 187 | if (history == null) { 188 | logger.warn("No Driving history found"); 189 | return new ResponseEntity>(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 190 | } 191 | 192 | // Convert to Restful Resource, update ETag and HATEOAS references 193 | responseBuilder.toDrivingHistory(policyNum, licenseNo, Lists.newArrayList(history)); 194 | 195 | return new ResponseEntity>(history, HttpStatus.OK); 196 | } 197 | 198 | @RequestMapping(method = RequestMethod.GET, value = "/policy/{policyNum}/vehicle/{vin}") 199 | @ResponseBody 200 | public ResponseEntity getVehicle(@PathVariable String policyNum, @PathVariable String vin) { 201 | logger.debug(String.format("Retrieving Vehicle %s for Policy %s :", vin, policyNum)); 202 | 203 | Vehicle vehicle = persistenceHelper.loadVehicleByVin(vin); 204 | if (vehicle == null) { 205 | logger.warn("No Vehicle found"); 206 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 207 | } 208 | 209 | // Convert to Restful Resource, update ETag and HATEOAS references 210 | responseBuilder.toVehicle(policyNum, Lists.newArrayList(vehicle)); 211 | 212 | return new ResponseEntity(vehicle, HttpStatus.OK); 213 | } 214 | 215 | @RequestMapping(method = RequestMethod.GET, value = "/policy/future/{futureKey}") 216 | @ResponseBody 217 | public ResponseEntity poll(@PathVariable String futureKey) { 218 | logger.debug("Polling policy with future:" + futureKey); 219 | 220 | String location = resourceHelper.getPermLocation(futureKey); 221 | if (Strings.isNullOrEmpty(location)) { 222 | logger.warn("No Policy found for future. Is future expired?"); 223 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 224 | } 225 | if (ResourceHelper.ProcessingStatus.PROCESSING.toString().equals(location)) { 226 | logger.debug("Policy is still processing."); 227 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.ACCEPTED); 228 | } 229 | if (ResourceHelper.ProcessingStatus.ERROR.toString().equals(location)) { 230 | logger.debug("Policy was not created but resulted in an error."); 231 | throw new DataIntegrityViolationException("Unable to save Policy", new RuntimeException( 232 | resourceHelper.getError(futureKey))); 233 | } 234 | 235 | logger.debug("Received notification that policy creation is completed"); 236 | String policyNum = resourceHelper.parseKey(resourceHelper.getPermLocation(futureKey)); 237 | 238 | // Lookup 239 | Policy policy = persistenceHelper.loadPolicy(policyNum); 240 | if (policy == null) { 241 | logger.error("Can't read policy with key: " + policyNum); 242 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); 243 | } 244 | 245 | // Convert to Restful Resource with sparse objects 246 | responseBuilder.toPolicy(policy); 247 | 248 | return new ResponseEntity(policy, HttpStatus.CREATED); 249 | } 250 | 251 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/resource/UserResource.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import github.priyatam.springrest.domain.User; 4 | import github.priyatam.springrest.helper.PersistenceHelper; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.security.access.annotation.Secured; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | @Controller 16 | @Secured("ROLE_USER") 17 | public class UserResource { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(UserResource.class); 20 | 21 | @Autowired 22 | private PersistenceHelper persistenceHelper; 23 | 24 | @RequestMapping(value = "user", method = RequestMethod.GET) 25 | public String index(UserDetails userDetails, Model model) { 26 | LOG.info(userDetails.toString()); 27 | return "user/index"; 28 | } 29 | 30 | @RequestMapping(value = "user.json", method = RequestMethod.GET) 31 | @ResponseStatus(value = HttpStatus.OK) 32 | @ResponseBody 33 | public User jsonGetUser(UserDetails userDetails) { 34 | return persistenceHelper.findByUsername(userDetails.getUsername()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/resource/VehicleResource.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.google.common.collect.Lists; 4 | import github.priyatam.springrest.domain.Vehicle; 5 | import github.priyatam.springrest.helper.PersistenceHelper; 6 | import github.priyatam.springrest.helper.ResponseBuilderHelper; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | @Controller 20 | public class VehicleResource { 21 | 22 | final static Logger logger = LoggerFactory.getLogger(VehicleResource.class); 23 | 24 | @Autowired 25 | protected PersistenceHelper persistenceHelper; 26 | 27 | @Autowired 28 | protected ResponseBuilderHelper responseBuilder; 29 | 30 | @RequestMapping(method = RequestMethod.GET, value = "/vehicle/{vin}") 31 | @ResponseBody 32 | public ResponseEntity getVehicle(@PathVariable String vin) { 33 | logger.debug(String.format("Retrieving Vehicle %s :", vin)); 34 | 35 | Vehicle vehicle = persistenceHelper.loadVehicleByVin(vin); 36 | if (vehicle == null) { 37 | logger.warn("No Vehicle found"); 38 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_FOUND); 39 | } 40 | 41 | // Convert to Restful Resource, update ETag and HATEOAS references 42 | responseBuilder.toVehicle(Lists.newArrayList(vehicle)); 43 | 44 | return new ResponseEntity(vehicle, HttpStatus.OK); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/AjaxUtils.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils; 2 | 3 | import org.springframework.web.context.request.WebRequest; 4 | 5 | /** 6 | * From: 7 | * https://github.com/SpringSource/spring-mvc-showcase 8 | */ 9 | public class AjaxUtils { 10 | 11 | public static boolean isAjaxRequest(WebRequest webRequest) { 12 | String requestedWith = webRequest.getHeader("X-Requested-With"); 13 | return requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false; 14 | } 15 | 16 | public static boolean isAjaxUploadRequest(WebRequest webRequest) { 17 | return webRequest.getParameter("ajaxUpload") != null; 18 | } 19 | 20 | private AjaxUtils() {} 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/CustomHibernateNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils; 2 | 3 | import org.hibernate.cfg.ImprovedNamingStrategy; 4 | 5 | /** 6 | * A Custom Naming Strategy with standard Database naming conventions Add this class in 7 | * persistence.xml, as hibernate.ejb.naming_strategy property 8 | * 9 | */ 10 | public class CustomHibernateNamingStrategy 11 | extends ImprovedNamingStrategy { 12 | 13 | private static final long serialVersionUID = 4674532118559124654L; 14 | 15 | @Override 16 | public String foreignKeyColumnName( 17 | String propertyName, 18 | String propertyEntityName, 19 | String propertyTableName, 20 | String referencedColumnName ) { 21 | 22 | return columnName( propertyName ) + "_id"; 23 | } 24 | 25 | @Override 26 | public String classToTableName( 27 | String className ) { 28 | 29 | return toLowerCase( pluralize( super.classToTableName( className ) ) ); 30 | } 31 | 32 | // IMPORTANT! To ensure compatibility across windows and unix, convert all table 33 | // names to lowercase 34 | private String toLowerCase( 35 | String name ) { 36 | 37 | return name.toLowerCase(); 38 | } 39 | 40 | private String pluralize( 41 | String name ) { 42 | 43 | StringBuilder p = new StringBuilder( name ); 44 | if ( name.endsWith( "y" ) ) { 45 | p.deleteCharAt( p.length() - 1 ); 46 | p.append( "ies" ); 47 | } else { 48 | p.append( 's' ); 49 | } 50 | return p.toString(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/DefaultJacksonHttpMessageConverter.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.Charset; 5 | 6 | import org.springframework.http.HttpInputMessage; 7 | import org.springframework.http.HttpOutputMessage; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.converter.AbstractHttpMessageConverter; 10 | import org.springframework.http.converter.HttpMessageNotReadableException; 11 | import org.springframework.http.converter.HttpMessageNotWritableException; 12 | import org.springframework.util.Assert; 13 | 14 | import com.fasterxml.jackson.core.JsonEncoding; 15 | import com.fasterxml.jackson.core.JsonGenerationException; 16 | import com.fasterxml.jackson.core.JsonGenerator; 17 | import com.fasterxml.jackson.core.JsonParseException; 18 | import com.fasterxml.jackson.databind.JavaType; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import com.fasterxml.jackson.databind.type.TypeFactory; 21 | import com.fasterxml.jackson.datatype.guava.GuavaModule; 22 | import com.fasterxml.jackson.datatype.joda.JodaModule; 23 | 24 | /** 25 | * Replaces Spring's {@link org.springframework.http.converter.json.MappingJacksonHttpMessageConverter}, which is 26 | * difficult to configure for pretty-printing. This implementation enables pretty-printing easily via a setter/getter. 27 | *

28 | * See When using Spring MVC for REST, how do you enable Jackson to pretty-print rendered JSON? and the latest Spring Framework incarnation supporting pretty printing (not yet released 32 | * at the time of writing). 33 | * 34 | * @author Les Hazlewood 35 | */ 36 | public class DefaultJacksonHttpMessageConverter extends AbstractHttpMessageConverter { 37 | 38 | public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 39 | 40 | private ObjectMapper objectMapper = new ObjectMapper(); 41 | private boolean prefixJson = false; 42 | private boolean prettyPrint = false; 43 | 44 | /** 45 | * Construct a new {@code DefaultJacksonHttpMessageConverter}. 46 | */ 47 | public DefaultJacksonHttpMessageConverter() { 48 | super(new MediaType("application", "json", DEFAULT_CHARSET)); 49 | 50 | // Register modules 51 | objectMapper.registerModule(new JodaModule()); 52 | objectMapper.registerModule(new GuavaModule()); 53 | 54 | // Sample Date Format 55 | // objectMapper.setDateFormat(new SimpleDateFormat("MM-dd-yyyy")); 56 | } 57 | 58 | @Override 59 | public boolean canRead(Class clazz, MediaType mediaType) { 60 | JavaType javaType = getJavaType(clazz); 61 | return objectMapper.canDeserialize(javaType) && canRead(mediaType); 62 | } 63 | 64 | @Override 65 | public boolean canWrite(Class clazz, MediaType mediaType) { 66 | return objectMapper.canSerialize(clazz) && canWrite(mediaType); 67 | } 68 | 69 | /** 70 | * Returns the Jackson {@link JavaType} for the specific class. 71 | *

72 | *

73 | * Default implementation returns {@link org.codehaus.jackson.map.type.TypeFactory#type(java.lang.reflect.Type)}, 74 | * but this can be overridden in subclasses, to allow for custom generic collection handling. For instance: 75 | * 76 | *

 77 | 	 * protected JavaType getJavaType(Class<?> clazz) {
 78 | 	 * 	if (List.class.isAssignableFrom(clazz)) {
 79 | 	 * 		return TypeFactory.collectionType(ArrayList.class, MyBean.class);
 80 | 	 * 	} else {
 81 | 	 * 		return super.getJavaType(clazz);
 82 | 	 * 	}
 83 | 	 * }
 84 | 	 * 
85 | * 86 | * @param clazz the class to return the java type for 87 | * @return the java type 88 | */ 89 | protected JavaType getJavaType(Class clazz) { 90 | return TypeFactory.defaultInstance().constructType(clazz); 91 | } 92 | 93 | @Override 94 | protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, 95 | HttpMessageNotReadableException { 96 | JavaType javaType = getJavaType(clazz); 97 | try { 98 | return objectMapper.readValue(inputMessage.getBody(), javaType); 99 | } catch (JsonParseException ex) { 100 | throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex); 101 | } 102 | } 103 | 104 | @Override 105 | protected boolean supports(Class clazz) { 106 | // should not be called, since we override canRead/Write instead 107 | throw new UnsupportedOperationException(); 108 | } 109 | 110 | @Override 111 | protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, 112 | HttpMessageNotWritableException { 113 | JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType()); 114 | JsonGenerator jsonGenerator = getObjectMapper().getJsonFactory().createJsonGenerator(outputMessage.getBody(), 115 | encoding); 116 | try { 117 | if (prefixJson) { 118 | jsonGenerator.writeRaw("{} && "); 119 | } 120 | if (isPrettyPrint()) { 121 | jsonGenerator.useDefaultPrettyPrinter(); 122 | } 123 | getObjectMapper().writeValue(jsonGenerator, o); 124 | } catch (JsonGenerationException ex) { 125 | throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 126 | } 127 | } 128 | 129 | private JsonEncoding getEncoding(MediaType contentType) { 130 | if (contentType != null && contentType.getCharSet() != null) { 131 | Charset charset = contentType.getCharSet(); 132 | for (JsonEncoding encoding : JsonEncoding.values()) { 133 | if (charset.name().equals(encoding.getJavaName())) { 134 | return encoding; 135 | } 136 | } 137 | } 138 | return JsonEncoding.UTF8; 139 | } 140 | 141 | public ObjectMapper getObjectMapper() { 142 | return objectMapper; 143 | } 144 | 145 | /** 146 | * Sets the {@code ObjectMapper} for this view. If not set, a default {@link ObjectMapper#ObjectMapper() 147 | * ObjectMapper} is used. 148 | *

149 | * Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON serialization 150 | * process. For example, an extended {@link org.codehaus.jackson.map.SerializerFactory} can be configured that 151 | * provides custom serializers for specific types. The other option for refining the serialization process is to use 152 | * Jackson's provided annotations on the types to be serialized, in which case a custom-configured ObjectMapper is 153 | * unnecessary. 154 | * 155 | * @param objectMapper - 156 | */ 157 | public void setObjectMapper(ObjectMapper objectMapper) { 158 | Assert.notNull(objectMapper, "'objectMapper' must not be null"); 159 | this.objectMapper = objectMapper; 160 | } 161 | 162 | public boolean isPrettyPrint() { 163 | return prettyPrint; 164 | } 165 | 166 | public void setPrettyPrint(boolean prettyPrint) { 167 | this.prettyPrint = prettyPrint; 168 | } 169 | 170 | /** 171 | * Indicates whether the JSON output by this view should be prefixed with "{} &&". Default is false. 172 | *

173 | * Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. The prefix renders the string 174 | * syntactically invalid as a script so that it cannot be hijacked. This prefix does not affect the evaluation of 175 | * JSON, but if JSON validation is performed on the string, the prefix would need to be ignored. 176 | * 177 | * @param prefixJson - 178 | */ 179 | public void setPrefixJson(boolean prefixJson) { 180 | this.prefixJson = prefixJson; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/Link.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils; 2 | 3 | import javax.persistence.Embeddable; 4 | import java.io.Serializable; 5 | 6 | @Embeddable 7 | public class Link implements Serializable { 8 | 9 | private static final long serialVersionUID = 2688912817762587701L; 10 | 11 | public static final String REL_SELF = "self"; 12 | public static final String REL_PARENT = "parent"; 13 | public static final String REL_FIRST = "first"; 14 | public static final String REL_PREVIOUS = "previous"; 15 | public static final String REL_NEXT = "next"; 16 | public static final String REL_LAST = "last"; 17 | 18 | private String name; 19 | private String rel; 20 | private String href; 21 | 22 | /** 23 | * Creates a new link to the given URI with the self rel. 24 | * 25 | * @see #REL_SELF 26 | * @param href must not be {@literal null} or empty. 27 | */ 28 | public Link(String href) { 29 | this("", href, REL_SELF); 30 | } 31 | 32 | /** 33 | * Creates a new {@link com.github.priyatam.springrest.utils.Link} to the given URI with the given rel. 34 | * 35 | * @param href must not be {@literal null} or empty. 36 | * @param rel must not be {@literal null} or empty. 37 | */ 38 | public Link(String name, String href, String rel) { 39 | this.name = name; 40 | this.href = href; 41 | this.rel = rel; 42 | } 43 | 44 | /** 45 | * Empty constructor required by the marshalling framework. 46 | */ 47 | protected Link() { 48 | 49 | } 50 | 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | public String getHref() { 56 | return href; 57 | } 58 | 59 | public String getRel() { 60 | return rel; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object obj) { 65 | if (this == obj) { 66 | return true; 67 | } 68 | 69 | if (!(obj instanceof Link)) { 70 | return false; 71 | } 72 | 73 | Link that = (Link) obj; 74 | 75 | return this.href.equals(that.href) && this.rel.equals(that.rel); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | int result = 17; 81 | result += 31 * href.hashCode(); 82 | result += 31 * rel.hashCode(); 83 | return result; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return String.format("{ rel : %s, href : %s }", rel, href); 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/LinkBuilder.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils; 2 | 3 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 4 | 5 | /** 6 | * LinkBuilder for constructing links in BaseDomain 7 | * 8 | */ 9 | public class LinkBuilder { 10 | 11 | public static Link build(String requestMapping, String name, String rel) { 12 | ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping(); 13 | String root = builder.build().toString(); 14 | String href = root + requestMapping; 15 | return new Link(name, href, rel); 16 | } 17 | 18 | public static Link build(String requestMapping, String name) { 19 | ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping(); 20 | String root = builder.build().toString(); 21 | String href = root + requestMapping; 22 | return new Link(name, href, Link.REL_SELF); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/aop/EtagGeneratorAspect.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils.aop; 2 | 3 | import github.priyatam.springrest.domain.BaseDomain; 4 | import github.priyatam.springrest.utils.exception.InvalidTagException; 5 | import github.priyatam.springrest.helper.ETagHelper; 6 | import org.aspectj.lang.ProceedingJoinPoint; 7 | import org.aspectj.lang.annotation.Around; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.util.ReflectionUtils; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.context.request.RequestContextHolder; 19 | import org.springframework.web.context.request.ServletRequestAttributes; 20 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 21 | 22 | import javax.servlet.http.HttpServletRequest; 23 | 24 | /** 25 | * Based on http://blog.furiousbob.com/2011/12/06/hateoas-restful-services-using-spring-3-1/ 26 | */ 27 | 28 | @Component 29 | @Aspect 30 | public class EtagGeneratorAspect { 31 | 32 | final static Logger logger = LoggerFactory.getLogger(EtagGeneratorAspect.class); 33 | 34 | @Autowired 35 | private ETagHelper eTagHelper; 36 | 37 | @SuppressWarnings({ "rawtypes", "unchecked" }) 38 | private Object handlePOST(ProceedingJoinPoint pjp) throws Throwable { 39 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) 40 | .getRequest(); 41 | 42 | Object retVal = pjp.proceed(); 43 | 44 | ResponseEntity entity = (ResponseEntity) retVal; 45 | HttpHeaders headers = new HttpHeaders(); 46 | BaseDomain resource = (BaseDomain) entity.getBody(); 47 | if (resource == null) { // async operation? 48 | return retVal; 49 | } 50 | 51 | Object key = ReflectionUtils.findMethod(resource.getClass(), "getPolicy").invoke(resource); 52 | headers.add("Location", 53 | ServletUriComponentsBuilder.fromRequest(request).path("/{policyNum}").build().expand(key.toString()) 54 | .toString()); 55 | 56 | return new ResponseEntity(null, headers, entity.getStatusCode()); 57 | } 58 | 59 | @SuppressWarnings({ "rawtypes", "unchecked" }) 60 | private Object handleGET(ProceedingJoinPoint pjp) throws Throwable { 61 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) 62 | .getRequest(); 63 | String url1 = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 64 | 65 | // Check eTags 66 | String clientEtag = request.getHeader("If-None-Match"); 67 | String latestEtag = null; 68 | try { 69 | latestEtag = eTagHelper.get(url1); 70 | } catch (InvalidTagException e) { 71 | // ignore 72 | } 73 | if (latestEtag != null && clientEtag != null && clientEtag.equals(latestEtag)) { 74 | return new ResponseEntity(null, new HttpHeaders(), HttpStatus.NOT_MODIFIED); 75 | } 76 | 77 | Object retVal = pjp.proceed(); 78 | ResponseEntity entity = (ResponseEntity) retVal; 79 | HttpHeaders headers = new HttpHeaders(); 80 | String url = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 81 | String tag = null; 82 | try { 83 | tag = eTagHelper.get(url); 84 | } catch (InvalidTagException e) { 85 | tag = eTagHelper.generate(url, entity.getBody()); 86 | } 87 | headers.add("Etag", tag); 88 | return new ResponseEntity(entity.getBody(), headers, entity.getStatusCode()); 89 | } 90 | 91 | @SuppressWarnings({ "rawtypes", "unchecked" }) 92 | private Object handlePUT(ProceedingJoinPoint pjp) throws Throwable { 93 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) 94 | .getRequest(); 95 | ResponseEntity responseEntity = (ResponseEntity) pjp.proceed(); 96 | String url = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 97 | HttpHeaders headers = new HttpHeaders(); 98 | BaseDomain domain = (BaseDomain) responseEntity.getBody(); 99 | String newTag = eTagHelper.update(url, domain); 100 | headers.add("Etag", newTag); 101 | return new ResponseEntity(responseEntity.getBody(), headers, responseEntity.getStatusCode()); 102 | } 103 | 104 | private boolean checkEtag() { 105 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) 106 | .getRequest(); 107 | String url = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 108 | String storedTag = null; 109 | try { 110 | storedTag = eTagHelper.get(url); 111 | String providedTag = request.getHeader("If-Match"); 112 | if (providedTag == null || !storedTag.equals(providedTag)) { 113 | logger.debug("Etag didn't match ... " + providedTag); 114 | return false; 115 | } else { 116 | logger.debug("Etag matched ... " + providedTag); 117 | return true; 118 | } 119 | } catch (Exception e) { 120 | return false; 121 | } 122 | } 123 | 124 | @SuppressWarnings("rawtypes") 125 | private Object handleDELETE(ProceedingJoinPoint pjp) throws Throwable { 126 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) 127 | .getRequest(); 128 | String url = ServletUriComponentsBuilder.fromRequest(request).build().toString(); 129 | pjp.proceed(); 130 | eTagHelper.remove(url); 131 | return new ResponseEntity(HttpStatus.NO_CONTENT); 132 | } 133 | 134 | @Around("execution(public org.springframework.http.ResponseEntity github.priyatam.springrest.resource.*.*(..)) && @annotation(requestMapping)") 135 | @SuppressWarnings({ "rawtypes" }) 136 | public Object handleMethod(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable { 137 | logger.info("Handling Etag ..."); 138 | 139 | Object retVal = null; 140 | switch (requestMapping.method()[0]) { 141 | case GET: 142 | retVal = handleGET(pjp); 143 | break; 144 | 145 | case POST: 146 | retVal = handlePOST(pjp); 147 | break; 148 | 149 | case PUT: 150 | if (checkEtag()) { 151 | retVal = handlePUT(pjp); 152 | } else { 153 | retVal = new ResponseEntity(HttpStatus.PRECONDITION_FAILED); 154 | } 155 | break; 156 | 157 | case DELETE: 158 | if (checkEtag()) { 159 | retVal = handleDELETE(pjp); 160 | } else { 161 | retVal = new ResponseEntity(HttpStatus.PRECONDITION_FAILED); 162 | } 163 | break; 164 | 165 | case HEAD: 166 | retVal = pjp.proceed(); 167 | 168 | case OPTIONS: 169 | retVal = pjp.proceed(); 170 | 171 | default: 172 | break; 173 | 174 | } 175 | return retVal; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/exception/InvalidTagException.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils.exception; 2 | 3 | public class InvalidTagException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -1544195970963068724L; 6 | 7 | public InvalidTagException() { 8 | super(); 9 | } 10 | 11 | public InvalidTagException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public InvalidTagException(String message) { 16 | super(message); 17 | } 18 | 19 | public InvalidTagException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/utils/exception/PolicyInvalidException.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.utils.exception; 2 | 3 | /** 4 | * Sample Business Exception for Policy Validation 5 | * 6 | */ 7 | public class PolicyInvalidException extends RuntimeException { 8 | 9 | public static enum ErrorCode { 10 | TOO_MANY_DRIVERS(41201), TOO_MANY_VEHICLES(41202), NON_NULL_POLICY_NUM(41203), NON_NULL_QUOTE(41204); 11 | 12 | final int code; 13 | 14 | ErrorCode(int code) { 15 | this.code = code; 16 | } 17 | 18 | public int value() { 19 | return this.code; 20 | } 21 | } 22 | 23 | private static final long serialVersionUID = -5424196757379520963L; 24 | 25 | ErrorCode errorCode; 26 | 27 | public PolicyInvalidException(ErrorCode errorCode) { 28 | super(errorCode.toString() + ", code: " + errorCode.value()); 29 | this.errorCode = errorCode; 30 | } 31 | 32 | public ErrorCode getErrorCode() { 33 | return errorCode; 34 | } 35 | 36 | public void setErrorCode(ErrorCode errorCode) { 37 | this.errorCode = errorCode; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/github/priyatam/springrest/view/PolicyForm.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.view; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import javax.validation.constraints.Past; 7 | 8 | import org.hibernate.validator.constraints.NotEmpty; 9 | import org.springframework.format.annotation.DateTimeFormat; 10 | import org.springframework.format.annotation.DateTimeFormat.ISO; 11 | 12 | public class PolicyForm { 13 | 14 | private Date effectiveDate; 15 | 16 | @NotEmpty 17 | private String state; 18 | 19 | private List drivers; 20 | private List vehicles; 21 | 22 | public static class Driver { 23 | private String firstName; 24 | 25 | @NotEmpty 26 | private String lastName; 27 | 28 | @DateTimeFormat(iso = ISO.DATE) 29 | @Past 30 | private Date birthDate; 31 | 32 | private String gender; 33 | 34 | @NotEmpty 35 | private String email; 36 | private String phone; 37 | private String occupation; 38 | 39 | @NotEmpty 40 | private String firstLicenseAtAge; 41 | 42 | @NotEmpty 43 | private String priorCarrier; 44 | 45 | private Boolean married; 46 | 47 | private String addrLine1; 48 | 49 | private String addrLine2; 50 | private String city; 51 | private String state; 52 | private String zip; 53 | private String type; 54 | 55 | public String getFirstName() { 56 | return firstName; 57 | } 58 | 59 | public void setFirstName(String firstName) { 60 | this.firstName = firstName; 61 | } 62 | 63 | public String getLastName() { 64 | return lastName; 65 | } 66 | 67 | public void setLastName(String lastName) { 68 | this.lastName = lastName; 69 | } 70 | 71 | public Date getBirthDate() { 72 | return birthDate; 73 | } 74 | 75 | public void setBirthDate(Date birthDate) { 76 | this.birthDate = birthDate; 77 | } 78 | 79 | public String getGender() { 80 | return gender; 81 | } 82 | 83 | public void setGender(String gender) { 84 | this.gender = gender; 85 | } 86 | 87 | public String getEmail() { 88 | return email; 89 | } 90 | 91 | public void setEmail(String email) { 92 | this.email = email; 93 | } 94 | 95 | public String getPhone() { 96 | return phone; 97 | } 98 | 99 | public void setPhone(String phone) { 100 | this.phone = phone; 101 | } 102 | 103 | public String getOccupation() { 104 | return occupation; 105 | } 106 | 107 | public void setOccupation(String occupation) { 108 | this.occupation = occupation; 109 | } 110 | 111 | public String getFirstLicenseAtAge() { 112 | return firstLicenseAtAge; 113 | } 114 | 115 | public void setFirstLicenseAtAge(String firstLicenseAtAge) { 116 | this.firstLicenseAtAge = firstLicenseAtAge; 117 | } 118 | 119 | public String getPriorCarrier() { 120 | return priorCarrier; 121 | } 122 | 123 | public void setPriorCarrier(String priorCarrier) { 124 | this.priorCarrier = priorCarrier; 125 | } 126 | 127 | public Boolean getMarried() { 128 | return married; 129 | } 130 | 131 | public void setMarried(Boolean married) { 132 | this.married = married; 133 | } 134 | 135 | public String getAddrLine1() { 136 | return addrLine1; 137 | } 138 | 139 | public void setAddrLine1(String addrLine1) { 140 | this.addrLine1 = addrLine1; 141 | } 142 | 143 | public String getAddrLine2() { 144 | return addrLine2; 145 | } 146 | 147 | public void setAddrLine2(String addrLine2) { 148 | this.addrLine2 = addrLine2; 149 | } 150 | 151 | public String getCity() { 152 | return city; 153 | } 154 | 155 | public void setCity(String city) { 156 | this.city = city; 157 | } 158 | 159 | public String getState() { 160 | return state; 161 | } 162 | 163 | public void setState(String state) { 164 | this.state = state; 165 | } 166 | 167 | public String getZip() { 168 | return zip; 169 | } 170 | 171 | public void setZip(String zip) { 172 | this.zip = zip; 173 | } 174 | 175 | public String getType() { 176 | return type; 177 | } 178 | 179 | public void setType(String type) { 180 | this.type = type; 181 | } 182 | 183 | } 184 | 185 | public static class Vehicle { 186 | private String make; 187 | private String model; 188 | private Integer year; 189 | private String plateNum; 190 | private Integer odomoterReading; 191 | 192 | private List drivingHistory; 193 | 194 | public String getMake() { 195 | return make; 196 | } 197 | 198 | public void setMake(String make) { 199 | this.make = make; 200 | } 201 | 202 | public String getModel() { 203 | return model; 204 | } 205 | 206 | public void setModel(String model) { 207 | this.model = model; 208 | } 209 | 210 | public Integer getYear() { 211 | return year; 212 | } 213 | 214 | public void setYear(Integer year) { 215 | this.year = year; 216 | } 217 | 218 | public String getPlateNum() { 219 | return plateNum; 220 | } 221 | 222 | public void setPlateNum(String plateNum) { 223 | this.plateNum = plateNum; 224 | } 225 | 226 | public Integer getOdomoterReading() { 227 | return odomoterReading; 228 | } 229 | 230 | public void setOdomoterReading(Integer odomoterReading) { 231 | this.odomoterReading = odomoterReading; 232 | } 233 | 234 | public List getDrivingHistory() { 235 | return drivingHistory; 236 | } 237 | 238 | public void setDrivingHistory(List drivingHistory) { 239 | this.drivingHistory = drivingHistory; 240 | } 241 | 242 | } 243 | 244 | public static class DrivingHistory { 245 | private Integer annualMileage; 246 | private Date accidentTime; 247 | private String accidentType; 248 | private Boolean thirdPartyOffence; 249 | 250 | public Integer getAnnualMileage() { 251 | return annualMileage; 252 | } 253 | 254 | public void setAnnualMileage(Integer annualMileage) { 255 | this.annualMileage = annualMileage; 256 | } 257 | 258 | public Date getAccidentTime() { 259 | return accidentTime; 260 | } 261 | 262 | public void setAccidentTime(Date accidentTime) { 263 | this.accidentTime = accidentTime; 264 | } 265 | 266 | public String getAccidentType() { 267 | return accidentType; 268 | } 269 | 270 | public void setAccidentType(String accidentType) { 271 | this.accidentType = accidentType; 272 | } 273 | 274 | public Boolean getThirdPartyOffence() { 275 | return thirdPartyOffence; 276 | } 277 | 278 | public void setThirdPartyOffence(Boolean thirdPartyOffence) { 279 | this.thirdPartyOffence = thirdPartyOffence; 280 | } 281 | 282 | } 283 | 284 | public Date getEffectiveDate() { 285 | return effectiveDate; 286 | } 287 | 288 | public void setEffectiveDate(Date effectiveDate) { 289 | this.effectiveDate = effectiveDate; 290 | } 291 | 292 | public String getState() { 293 | return state; 294 | } 295 | 296 | public void setState(String state) { 297 | this.state = state; 298 | } 299 | 300 | public List getDrivers() { 301 | return drivers; 302 | } 303 | 304 | public void setDrivers(List drivers) { 305 | this.drivers = drivers; 306 | } 307 | 308 | public List getVehicles() { 309 | return vehicles; 310 | } 311 | 312 | public void setVehicles(List vehicles) { 313 | this.vehicles = vehicles; 314 | } 315 | 316 | } 317 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext-core.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | html=text/html 47 | json=application/json 48 | *=*/* 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 130 | 132 | 133 | 134 | 135 | 136 | 137 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | smtp 151 | 152 | 153 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext-persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | create 30 | github.priyatam.springrest.utils.CustomHibernateNamingStrategy 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext-security.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/resources/db/data.sql: -------------------------------------------------------------------------------- 1 | INSERT into addresses(addr_line1,addr_line2,city,state,zip,type) VALUES('100 memorial dr','','cambridge','MA','02139','HOMEOWNER'); 2 | INSERT into addresses(addr_line1,addr_line2,city,state,zip,type) VALUES('100 cambridge st','Apt 1','cambridge','MA','02141','HOMEOWNER'); 3 | INSERT into addresses(addr_line1,addr_line2,city,state,zip,type) VALUES('100 Mass Ave','Ste 1','Boston','MA','02116','RENTAL'); 4 | 5 | INSERT into vehicles(vin,make,model,year,plate_num,owner_type) VALUES('vin-1','bmw','335xi',2005,'AMX123','OWNED'); 6 | INSERT into vehicles(vin,make,model,year,plate_num,owner_type) VALUES('vin-2','honda','civic',2010,'768DGZ','LEASED'); 7 | INSERT into vehicles(vin,make,model,year,plate_num,owner_type) VALUES('vin-3','toyota','camry',2004,'998XUY','LEASED'); 8 | 9 | INSERT into drivers(address_id,license_num,license_expiry_date,first_name,last_name,birth_date,gender,email,phone,occupation,first_license_at_age,is_married) 10 | VALUES(1,'lic-1','2014-10-10','Ernest','Hemmingway','1899-11-30','MALE','ehemmingway@writers.com','6177189876','writer',22,FALSE); 11 | INSERT into drivers(address_id,license_num,license_expiry_date,first_name,last_name,birth_date,gender,email,phone,occupation,first_license_at_age,is_married) 12 | VALUES(2,'lic-2','2016-10-10','Franz','Kafka','1849-11-30','MALE','fkafka@writers.com','9187189876','writer',12,FALSE); 13 | 14 | INSERT INTO driving_histories(driver_id, annual_mileage,is_primary_operator,is_accident) VALUES(1,15000,TRUE,FALSE); 15 | INSERT INTO driving_histories(driver_id, annual_mileage,is_primary_operator,is_accident,is_third_party_offence) VALUES(1,25000,FALSE,TRUE,TRUE); 16 | 17 | INSERT INTO policies(policy_num,effective_date,expiry_date,term,company,state,quote,agency) VALUES('pol-1','2014-01-01','2015-01-01',3,'commerce','MA','1033','commerce one'); 18 | INSERT INTO policies(policy_num,effective_date,expiry_date,term,company,state,quote,agency) VALUES('pol-2','2013-02-01','2014-01-01',2,'geico','MA','1633','agency one'); 19 | 20 | INSERT INTO users(username, password, role) VALUES('user', 'user', 'ROLE_USER'); 21 | INSERT INTO users(username, password, role) VALUES('admin', 'admin', 'ROLE_ADMIN'); -------------------------------------------------------------------------------- /src/main/resources/ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 21 | 22 | 30 | 31 | 39 | 40 | 48 | 49 | 57 | 58 | 66 | 67 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/resources/local.properties: -------------------------------------------------------------------------------- 1 | virtualhost=http://localhost:8080 2 | jdbc.driverClass=org.h2.Driver 3 | jdbc.url=jdbc:h2:~/springmvc-rest;DB_CLOSE_ON_EXIT=FALSE 4 | #jdbc.url=jdbc:h2:tcp://localhost/~springmvc-rest;DB_CLOSE_ON_EXIT=FALSE 5 | jdbc.user=sa 6 | jdbc.password= 7 | json.prettyprint=true -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | logs/application.log 11 | 12 | 13 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/prod.properties: -------------------------------------------------------------------------------- 1 | virtualhost=http://localhost:8080 2 | jdbc.driverClass=org.h2.Driver 3 | jdbc.url=jdbc:h2:~/springmvc-rest 4 | jdbc.user=sa 5 | jdbc.password= 6 | json.prettyprint=false -------------------------------------------------------------------------------- /src/main/resources/qa.properties: -------------------------------------------------------------------------------- 1 | virtualhost=http://localhost:8080 2 | jdbc.driverClass=org.h2.Driver 3 | jdbc.url=jdbc:h2:~/springmvc-rest 4 | jdbc.user=sa 5 | jdbc.password= 6 | json.prettyprint=false -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/about.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

6 |
7 |

Who We Are

8 | 9 |

Just a friendly bunch of guys trying to give you the best auto quote

10 | 11 | 12 |

13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/common/banner.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 2 | 3 |

Insurance Quote 2.0

4 |

Your social Insurance company

5 |

6 | " class="btn btn-primary btn-large">Start Your 7 | Quote » 8 |

9 | 10 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/common/footer.jsp: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Back to top

4 |

© Your Friendly Insurance, 2013

5 |
6 |
7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/common/menu.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 2 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/common/navbar.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 2 | 5 | 6 | Your Friendly Insurance 7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/common/template.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 | 7 | 8 | 9 | WARA UI Demo 10 | 11 | 12 | 13 | " rel="stylesheet" type="text/css" /> 14 | " rel="stylesheet" type="text/css" /> 15 | " rel="stylesheet" type="text/css" /> 16 | 17 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 |
44 |
45 |
46 | 49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 | 59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/home.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 2 | 3 |
4 |
5 |

Best Coverage

6 |

Get a quote online, with service from local independent agents.

7 | 8 | 26 | 28 |
29 | 30 |
31 |

Exceptional service

32 |

33 | From the moment you file a claim until your car is repaired, we're 34 | with you every step of the way » 35 |

36 |
37 | 38 |
39 | 40 | 41 |
42 |
43 | 44 | Test 45 |
46 | 47 | 48 |
49 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/quote.jsp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/priyatam/spring-best-practices/0f21ae59622225005ba2f223892531c58179e2ac/src/main/webapp/WEB-INF/views/quote.jsp -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | contextConfigLocation 10 | classpath:applicationContext-security.xml, 11 | 12 | 13 | 14 | 15 | org.springframework.web.context.ContextLoaderListener 16 | 17 | 18 | 19 | application 20 | org.springframework.web.servlet.DispatcherServlet 21 | 22 | contextConfigLocation 23 | 24 | classpath:applicationContext-core.xml, 25 | classpath:applicationContext-persistence.xml 26 | 27 | 28 | 1 29 | 30 | 31 | 32 | application 33 | / 34 | 35 | 36 | 37 | springSecurityFilterChain 38 | org.springframework.web.filter.DelegatingFilterProxy 39 | 40 | 41 | springSecurityFilterChain 42 | /* 43 | 44 | 45 | 60 | 61 | 62 | 120 63 | 64 | 65 | 66 | 67 | defaultHtmlEscape 68 | true 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/webapp/resources/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /src/main/webapp/resources/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | .summary { 3 | width: 100%; 4 | border: 1px solid #414f23; 5 | border-collapse: collapse; 6 | } 7 | 8 | .summary thead th { 9 | border-left: 1px solid #414f23; 10 | background: #fff url(../images/th.bg.gif) 0 100% repeat-x; 11 | border-bottom: 1px solid #414f23; 12 | padding: 6px; 13 | text-align: left; 14 | font-size: small; 15 | } 16 | 17 | .summary tbody td { 18 | border-left: 1px solid #9cac7c; 19 | padding: 4px; 20 | border-bottom: 1px solid #9cac7c; 21 | font-size: 8pt; 22 | } -------------------------------------------------------------------------------- /src/main/webapp/resources/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/priyatam/spring-best-practices/0f21ae59622225005ba2f223892531c58179e2ac/src/main/webapp/resources/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/main/webapp/resources/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/priyatam/spring-best-practices/0f21ae59622225005ba2f223892531c58179e2ac/src/main/webapp/resources/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/main/webapp/resources/js/date.format.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | var dateFormat = function () { 16 | var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 17 | timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, 18 | timezoneClip = /[^-+\dA-Z]/g, 19 | pad = function (val, len) { 20 | val = String(val); 21 | len = len || 2; 22 | while (val.length < len) val = "0" + val; 23 | return val; 24 | }; 25 | 26 | // Regexes and supporting functions are cached through closure 27 | return function (date, mask, utc) { 28 | var dF = dateFormat; 29 | 30 | // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 31 | if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { 32 | mask = date; 33 | date = undefined; 34 | } 35 | 36 | // Passing date through Date applies Date.parse, if necessary 37 | date = date ? new Date(date) : new Date; 38 | if (isNaN(date)) throw SyntaxError("invalid date"); 39 | 40 | mask = String(dF.masks[mask] || mask || dF.masks["default"]); 41 | 42 | // Allow setting the utc argument via the mask 43 | if (mask.slice(0, 4) == "UTC:") { 44 | mask = mask.slice(4); 45 | utc = true; 46 | } 47 | 48 | var _ = utc ? "getUTC" : "get", 49 | d = date[_ + "Date"](), 50 | D = date[_ + "Day"](), 51 | m = date[_ + "Month"](), 52 | y = date[_ + "FullYear"](), 53 | H = date[_ + "Hours"](), 54 | M = date[_ + "Minutes"](), 55 | s = date[_ + "Seconds"](), 56 | L = date[_ + "Milliseconds"](), 57 | o = utc ? 0 : date.getTimezoneOffset(), 58 | flags = { 59 | d: d, 60 | dd: pad(d), 61 | ddd: dF.i18n.dayNames[D], 62 | dddd: dF.i18n.dayNames[D + 7], 63 | m: m + 1, 64 | mm: pad(m + 1), 65 | mmm: dF.i18n.monthNames[m], 66 | mmmm: dF.i18n.monthNames[m + 12], 67 | yy: String(y).slice(2), 68 | yyyy: y, 69 | h: H % 12 || 12, 70 | hh: pad(H % 12 || 12), 71 | H: H, 72 | HH: pad(H), 73 | M: M, 74 | MM: pad(M), 75 | s: s, 76 | ss: pad(s), 77 | l: pad(L, 3), 78 | L: pad(L > 99 ? Math.round(L / 10) : L), 79 | t: H < 12 ? "a" : "p", 80 | tt: H < 12 ? "am" : "pm", 81 | T: H < 12 ? "A" : "P", 82 | TT: H < 12 ? "AM" : "PM", 83 | Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), 84 | o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 85 | S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 86 | }; 87 | 88 | return mask.replace(token, function ($0) { 89 | return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 90 | }); 91 | }; 92 | }(); 93 | 94 | // Some common format strings 95 | dateFormat.masks = { 96 | "default": "ddd mmm dd yyyy HH:MM:ss", 97 | shortDate: "m/d/yy", 98 | mediumDate: "mmm d, yyyy", 99 | longDate: "mmmm d, yyyy", 100 | fullDate: "dddd, mmmm d, yyyy", 101 | shortTime: "h:MM TT", 102 | mediumTime: "h:MM:ss TT", 103 | longTime: "h:MM:ss TT Z", 104 | isoDate: "yyyy-mm-dd", 105 | isoTime: "HH:MM:ss", 106 | isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 107 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 108 | }; 109 | 110 | // Internationalization strings 111 | dateFormat.i18n = { 112 | dayNames: [ 113 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 114 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 115 | ], 116 | monthNames: [ 117 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 118 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 119 | ] 120 | }; 121 | 122 | // For convenience... 123 | Date.prototype.format = function (mask, utc) { 124 | return dateFormat(this, mask, utc); 125 | }; 126 | 127 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-03-20 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | text = String(text); 437 | cx.lastIndex = 0; 438 | if (cx.test(text)) { 439 | text = text.replace(cx, function (a) { 440 | return '\\u' + 441 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 442 | }); 443 | } 444 | 445 | // In the second stage, we run the text against regular expressions that look 446 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 447 | // because they can cause invocation, and '=' because it can cause mutation. 448 | // But just to be safe, we want to reject all unexpected forms. 449 | 450 | // We split the second stage into 4 regexp operations in order to work around 451 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 452 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 453 | // replace all simple value tokens with ']' characters. Third, we delete all 454 | // open brackets that follow a colon or comma or that begin the text. Finally, 455 | // we look to see that the remaining characters are only whitespace or ']' or 456 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 457 | 458 | if (/^[\],:{}\s]*$/. 459 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 460 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 461 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 462 | 463 | // In the third stage we use the eval function to compile the text into a 464 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 465 | // in JavaScript: it can begin a block or an object literal. We wrap the text 466 | // in parens to eliminate the ambiguity. 467 | 468 | j = eval('(' + text + ')'); 469 | 470 | // In the optional fourth stage, we recursively walk the new structure, passing 471 | // each name/value pair to a reviver function for possible transformation. 472 | 473 | return typeof reviver === 'function' ? 474 | walk({'': j}, '') : j; 475 | } 476 | 477 | // If the text is not JSON parseable, then a SyntaxError is thrown. 478 | 479 | throw new SyntaxError('JSON.parse'); 480 | }; 481 | } 482 | }()); 483 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/priyatam/spring-best-practices/0f21ae59622225005ba2f223892531c58179e2ac/src/main/webapp/resources/js/main.js -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/ChecklistTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import github.priyatam.springrest.resource.PolicyResource; 4 | import junit.framework.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.transaction.TransactionConfiguration; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import static org.junit.Assert.assertNotNull; 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 18 | 19 | /** 20 | * Checklist of Tests to ensure the sanity of a Spring ws web application. 21 | * 22 | * @author pmudivarti 23 | */ 24 | @RunWith(EnvironmentModeJUnitRunner.class) 25 | @ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:applicationContext-core.xml", "classpath*:applicationContext-persistence.xml"}) 26 | @TransactionConfiguration 27 | public class ChecklistTest { 28 | 29 | @Autowired 30 | PolicyResource resource; 31 | 32 | @Autowired 33 | WebApplicationContext wac; 34 | 35 | MockMvc mockMvc; 36 | 37 | @Before 38 | public void setup() { 39 | this.mockMvc = webAppContextSetup(this.wac).build(); 40 | } 41 | 42 | // Check if Spring appServlet context is configured properly 43 | @Test 44 | public void testServletContext() throws Exception { 45 | assertNotNull(mockMvc); 46 | } 47 | 48 | // Check if Transactions are used with cglib proxy 49 | @Test 50 | public void transactionCGLIB() { 51 | String resourceClassName = resource.getClass().getName(); 52 | Assert.assertTrue(resourceClassName.contains("EnhancerByCGLIB")); 53 | } 54 | 55 | @Test 56 | public void testHealthCheck() throws Exception { 57 | this.mockMvc.perform(get("/")) 58 | .andExpect(status().isOk()); 59 | // .andExpect(content().string(contains("Welcome, SpringMVC Rest Demo is running"))); 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/EnvironmentModeJUnitRunner.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import org.junit.runners.model.InitializationError; 4 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 5 | 6 | /** 7 | * Base JUnit Runner that adds env mode to all unit tests 8 | */ 9 | public class EnvironmentModeJUnitRunner extends SpringJUnit4ClassRunner { 10 | 11 | public EnvironmentModeJUnitRunner(Class clazz) throws InitializationError { 12 | super(clazz); 13 | initJvmParams(); 14 | } 15 | 16 | public static void initJvmParams() { 17 | System.getProperties().put("properties.root", "classpath:"); 18 | System.getProperties().put("env.mode", "local"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/MockDataHelper.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import com.google.common.collect.Lists; 4 | import github.priyatam.springrest.domain.*; 5 | import org.joda.time.LocalDate; 6 | import org.joda.time.LocalDateTime; 7 | 8 | import java.io.File; 9 | 10 | public class MockDataHelper { 11 | 12 | static String jsonRoot = System.getProperty("user.dir") + "/src/test/resources/"; 13 | 14 | public static Address createAddress() { 15 | return new Address("100 cambridge st", "apt 1", "cambridge", "MA", "02139", Address.AddressType.HOMEOWNER); 16 | } 17 | 18 | public static Driver createDriver1() { 19 | Address a = createAddress(); 20 | DrivingHistory dh = MockDataHelper.createDrivingHistory(); 21 | 22 | return new Driver.Builder().withLicenseNum("Lic-123").withBirthDate(new LocalDate()).withFirstName("Sarah") 23 | .withLastName("Conor").withLicenseExpiryDate(new LocalDate()).withGender(Driver.Gender.FEMALE) 24 | .withEmail("cooldriver@junkmail123.com").withPhone("6178769876").withOccupation("hacker") 25 | .withFirstLicenseAtAge(23).withIsMarried(false).withAddress(a) 26 | .withDrivingHistory(Lists.newArrayList(dh)).build(); 27 | } 28 | 29 | public static Driver createDriverFull(String licenseNo, String accident) { 30 | return new Driver.Builder().withLicenseNum(licenseNo).withBirthDate(new LocalDate()).withFirstName("Stephen") 31 | .withLastName("King").withLicenseExpiryDate(new LocalDate()).withGender(Driver.Gender.MALE) 32 | .withEmail("baddriver@junkmail123.com").withPhone("6178769876").withOccupation("writer") 33 | .withFirstLicenseAtAge(17).withIsMarried(false).withAddress(createAddress()) 34 | .withDrivingHistory(Lists.newArrayList(createDrivingHistoryFull(accident))).build(); 35 | } 36 | 37 | public static Vehicle createVehicle(String vin) { 38 | return new Vehicle.Builder().withVin(vin).withMake("bmw").withModel("335xi").withYear(2012) 39 | .withPlateNum("1234567890").withOdomoterReading(24000).withOwnerType(Vehicle.VehicleOwnerType.LEASED).build(); 40 | } 41 | 42 | public static DrivingHistory createDrivingHistory() { 43 | return new DrivingHistory.Builder().withAnnualMileage(2000).withIsGarageParked(true).withIsPrimaryOperator(true) 44 | .withIsAccident(false).build(); 45 | } 46 | 47 | public static DrivingHistory createDrivingHistoryFull(String accident) { 48 | return new DrivingHistory.Builder().withAnnualMileage(2000).withIsGarageParked(true).withIsPrimaryOperator(true) 49 | .withIsAccident(true).withAccidentTime(new LocalDateTime()).withIsThirdParyOffence(true).build(); 50 | } 51 | 52 | 53 | public static Policy createPolicyFull(String licenseNo, String vin, String accident) { 54 | return new Policy.Builder().withEffectiveDate(new LocalDate("2014-01-01")).withExpiryDate(new LocalDate()).withTerm(3) 55 | .withCompany("PlymouthRock").withState("MA").withDeclineReason(null) 56 | .withAgency("Commerce").withDrivers(Lists.newArrayList(createDriverFull(licenseNo, accident))) 57 | .withVehicles(Lists.newArrayList(createVehicle(vin))).build(); 58 | } 59 | 60 | public static Policy createPolicyFull(String policyNum) { 61 | return new Policy.Builder().withPolicyNum(policyNum).withEffectiveDate(new LocalDate("2015-1-1")).withExpiryDate(new LocalDate()) 62 | .withTerm(3).withCompany("PlymouthRock").withState("MA").withQuote(1250).withDeclineReason(null) 63 | .withAgency("Commerce").withDrivers(Lists.newArrayList(createDriver1())) 64 | .withVehicles(Lists.newArrayList(createVehicle("123456789"))).build(); 65 | } 66 | 67 | public static File file(String name) { 68 | return new File(jsonRoot + name); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/SpringClientRestTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.datatype.guava.GuavaModule; 5 | import com.fasterxml.jackson.datatype.joda.JodaModule; 6 | import github.priyatam.springrest.domain.Policy; 7 | import junit.framework.Assert; 8 | import org.junit.BeforeClass; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.web.client.MockRestServiceServer; 13 | import org.springframework.web.client.RestTemplate; 14 | 15 | import java.io.IOException; 16 | 17 | import static github.priyatam.springrest.MockDataHelper.createPolicyFull; 18 | import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; 19 | import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; 20 | 21 | public class SpringClientRestTest { 22 | 23 | static RestTemplate restTemplate = new RestTemplate(); 24 | static MockRestServiceServer mockServer; 25 | static ObjectMapper mapper = new ObjectMapper(); 26 | 27 | @BeforeClass 28 | public static void setup() { 29 | mockServer = MockRestServiceServer.createServer(restTemplate); 30 | mapper.registerModule(new JodaModule()); 31 | mapper.registerModule(new GuavaModule()); 32 | } 33 | 34 | @Test 35 | @Ignore // FIXME: Jackson/Spring Bug: doesn't deserialize joda time when called through spring 36 | public void stubRestCalls() throws IOException { 37 | mockServer.expect(requestTo("/policy/pol-1")) 38 | .andRespond(withSuccess(mockPolicy(), MediaType.APPLICATION_JSON)); 39 | 40 | // Use RestTemplate ... 41 | Policy policy = restTemplate.getForObject("/policy/{policyNum}", Policy.class, "pol-1"); 42 | Assert.assertNotNull(policy); 43 | 44 | mockServer.verify(); 45 | } 46 | 47 | String mockPolicy() throws IOException { 48 | Policy p = createPolicyFull("pol-1000", "vin-1000", "lic-1000"); 49 | String str = mapper.writeValueAsString(p); 50 | return str; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/WebContextLoader.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest; 2 | 3 | import javax.servlet.RequestDispatcher; 4 | 5 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; 8 | import org.springframework.context.annotation.AnnotationConfigUtils; 9 | import org.springframework.core.io.DefaultResourceLoader; 10 | import org.springframework.core.io.FileSystemResourceLoader; 11 | import org.springframework.core.io.ResourceLoader; 12 | import org.springframework.mock.web.MockRequestDispatcher; 13 | import org.springframework.mock.web.MockServletContext; 14 | import org.springframework.test.context.MergedContextConfiguration; 15 | import org.springframework.test.context.support.AbstractContextLoader; 16 | import org.springframework.web.context.WebApplicationContext; 17 | import org.springframework.web.context.support.GenericWebApplicationContext; 18 | 19 | /** 20 | * This class is here temporarily until the TestContext framework provides support for WebApplicationContext yet: 21 | * 22 | * https://jira.springsource.org/browse/SPR-5243 23 | * 24 | *

25 | * After that this class will no longer be needed. It's provided here as an example and to serve as a temporary 26 | * solution. 27 | * 28 | */ 29 | public class WebContextLoader extends AbstractContextLoader { 30 | protected final MockServletContext servletContext; 31 | 32 | public WebContextLoader() { 33 | ResourceLoader resourceLoader = new FileSystemResourceLoader(); 34 | this.servletContext = initServletContext("src/main/webapp", resourceLoader); 35 | } 36 | 37 | public WebContextLoader(String warRootDir, boolean isClasspathRelative) { 38 | ResourceLoader resourceLoader = isClasspathRelative ? new DefaultResourceLoader() 39 | : new FileSystemResourceLoader(); 40 | this.servletContext = initServletContext(warRootDir, resourceLoader); 41 | } 42 | 43 | private MockServletContext initServletContext(String warRootDir, ResourceLoader resourceLoader) { 44 | return new MockServletContext(warRootDir, resourceLoader) { 45 | // Required for DefaultServletHttpRequestHandler... 46 | public RequestDispatcher getNamedDispatcher(String path) { 47 | return (path.equals("default")) ? new MockRequestDispatcher(path) : super.getNamedDispatcher(path); 48 | } 49 | }; 50 | } 51 | 52 | public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { 53 | GenericWebApplicationContext context = new GenericWebApplicationContext(); 54 | context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); 55 | prepareContext(context); 56 | loadBeanDefinitions(context, mergedConfig); 57 | return context; 58 | } 59 | 60 | public ApplicationContext loadContext(String... locations) throws Exception { 61 | // should never be called 62 | throw new UnsupportedOperationException(); 63 | } 64 | 65 | protected void prepareContext(GenericWebApplicationContext context) { 66 | this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); 67 | context.setServletContext(this.servletContext); 68 | } 69 | 70 | protected void loadBeanDefinitions(GenericWebApplicationContext context, String[] locations) { 71 | new XmlBeanDefinitionReader(context).loadBeanDefinitions(locations); 72 | AnnotationConfigUtils.registerAnnotationConfigProcessors(context); 73 | context.refresh(); 74 | context.registerShutdownHook(); 75 | } 76 | 77 | protected void loadBeanDefinitions(GenericWebApplicationContext context, MergedContextConfiguration mergedConfig) { 78 | new AnnotatedBeanDefinitionReader(context).register(mergedConfig.getClasses()); 79 | loadBeanDefinitions(context, mergedConfig.getLocations()); 80 | } 81 | 82 | @Override 83 | protected String getResourceSuffix() { 84 | return "-context.xml"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/domain/DomainTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.domain; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.datatype.guava.GuavaModule; 5 | import com.fasterxml.jackson.datatype.joda.JodaModule; 6 | import github.priyatam.springrest.MockDataHelper; 7 | import org.joda.time.LocalDate; 8 | import org.junit.BeforeClass; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertNotNull; 17 | 18 | public class DomainTest { 19 | 20 | static String jsonRoot = System.getProperty("user.dir") + "/src/test/resources/"; 21 | 22 | static ObjectMapper mapper = new ObjectMapper(); 23 | 24 | @BeforeClass 25 | public static void initModules() { 26 | mapper.registerModule(new JodaModule()); 27 | mapper.registerModule(new GuavaModule()); 28 | } 29 | 30 | @Test 31 | public void testJodaDate() throws IOException { 32 | LocalDate date = new LocalDate(2001, 5, 25); 33 | assertEquals("[2001,5,25]", mapper.writeValueAsString(date)); 34 | } 35 | 36 | @Test 37 | public void testAddress() throws IOException { 38 | mapper.writeValue(file("address.json"), MockDataHelper.createAddress()); 39 | Address result = mapper.readValue(file("address.json"), Address.class); 40 | assertNotNull(result); 41 | } 42 | 43 | @Test 44 | public void testVehicle() throws IOException { 45 | mapper.writeValue(file("vehicle.json"), MockDataHelper.createVehicle("vin-1")); 46 | Vehicle result = mapper.readValue(file("vehicle.json"), Vehicle.class); 47 | assertNotNull(result); 48 | } 49 | 50 | @Test 51 | public void testDrivingHistory() throws IOException { 52 | DrivingHistory h = MockDataHelper.createDrivingHistory(); 53 | mapper.writeValue(file("drivingHistory.json"), h); 54 | DrivingHistory result = mapper.readValue(file("drivingHistory.json"), DrivingHistory.class); 55 | assertNotNull(result); 56 | } 57 | 58 | @Test 59 | public void testDriver() throws IOException { 60 | Driver d = MockDataHelper.createDriver1(); 61 | mapper.writeValue(file("driver.json"), d); 62 | 63 | Driver result = mapper.readValue(file("driver.json"), Driver.class); 64 | assertNotNull(result); 65 | } 66 | 67 | @Test 68 | public void testPolicy() throws IOException { 69 | Policy p = MockDataHelper.createPolicyFull("S123"); 70 | mapper.writeValue(file("policy.json"), p); 71 | Policy result = mapper.readValue(file("policy.json"), Policy.class); 72 | assertNotNull(result); 73 | } 74 | 75 | public static File file(String name) { 76 | return new File(jsonRoot + name); 77 | } 78 | 79 | public static void assertPolicy(Policy p) { 80 | assertNotNull(p); 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/mock/RestMocker.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.mock; 2 | 3 | import com.jayway.restassured.RestAssured; 4 | import com.xebialabs.restito.server.StubServer; 5 | 6 | public class RestMocker { 7 | private static StubServer server; 8 | 9 | public RestMocker() { 10 | // default port = 6666 11 | server = new StubServer().run(); 12 | RestAssured.port = server.getPort(); 13 | } 14 | 15 | public static void main(String args[]) { 16 | new RestMocker(); 17 | } 18 | 19 | public void stop() { 20 | server.stop(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/mock/RestMockerTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.mock; 2 | 3 | 4 | import com.jayway.restassured.RestAssured; 5 | import com.jayway.restassured.http.ContentType; 6 | import com.xebialabs.restito.server.StubServer; 7 | import org.glassfish.grizzly.http.Method; 8 | import org.glassfish.grizzly.http.util.HttpStatus; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | import static com.jayway.restassured.RestAssured.expect; 14 | import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp; 15 | import static com.xebialabs.restito.builder.verify.VerifyHttp.verifyHttp; 16 | import static com.xebialabs.restito.semantics.Action.resourceContent; 17 | import static com.xebialabs.restito.semantics.Action.status; 18 | import static com.xebialabs.restito.semantics.Action.success; 19 | import static com.xebialabs.restito.semantics.Condition.*; 20 | import static org.hamcrest.CoreMatchers.containsString; 21 | 22 | public class RestMockerTest { 23 | 24 | private static StubServer server; 25 | 26 | @Before 27 | public void setup() { 28 | server = new StubServer().run(); 29 | RestAssured.port = server.getPort(); 30 | } 31 | 32 | @Test 33 | public void shouldTestStatus() { 34 | // Restito 35 | whenHttp(server). 36 | match(get("/policy/pol-1")). 37 | then(status(HttpStatus.OK_200)); 38 | 39 | // Rest-assured 40 | expect().statusCode(200).when().get("/policy/pol-1"); 41 | 42 | // Restito 43 | verifyHttp(server).once( 44 | method(Method.GET), 45 | uri("/policy/pol-1") 46 | ); 47 | } 48 | 49 | @Test 50 | public void shouldDoContentNegotiation() { 51 | whenHttp(server). 52 | match(get("/policy/pol-1")). 53 | then(resourceContent("policy.json")); 54 | 55 | expect().contentType(ContentType.JSON).when().get("/policy/pol-1"); 56 | } 57 | 58 | @Test 59 | public void shouldFindJsonFileByUrl() { 60 | whenHttp(server). 61 | match(get("/policy/pol-1")). 62 | then(success()); 63 | 64 | expect().content(containsString("policyNum\":\"S123\"")).when().get("/policy/pol-1"); 65 | } 66 | 67 | @After 68 | public void stop() { 69 | server.stop(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/resource/DriverResourceTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import github.priyatam.springrest.EnvironmentModeJUnitRunner; 5 | import github.priyatam.springrest.WebContextLoader; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 18 | 19 | @RunWith(EnvironmentModeJUnitRunner.class) 20 | @ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:applicationContext-core.xml", "classpath*:applicationContext-persistence.xml"}) 21 | public class DriverResourceTest { 22 | 23 | @Autowired 24 | WebApplicationContext wac; 25 | 26 | MockMvc mockMvc; 27 | 28 | ObjectMapper mapper = new ObjectMapper(); 29 | 30 | @Before 31 | public void setup() { 32 | this.mockMvc = webAppContextSetup(this.wac).build(); 33 | } 34 | 35 | @Test 36 | public void getDriver() throws Exception { 37 | mockMvc.perform( 38 | get("/driver/lic-1")) 39 | .andExpect(status().isOk()) 40 | .andExpect(jsonPath("$.licenseNum").value("lic-1")); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/resource/PolicyResourceTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import github.priyatam.springrest.EnvironmentModeJUnitRunner; 5 | import github.priyatam.springrest.MockDataHelper; 6 | import github.priyatam.springrest.WebContextLoader; 7 | import github.priyatam.springrest.domain.Policy; 8 | import org.junit.Before; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.web.servlet.MockMvc; 15 | import org.springframework.web.context.WebApplicationContext; 16 | 17 | import static org.mockito.Matchers.contains; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 20 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 21 | 22 | @RunWith(EnvironmentModeJUnitRunner.class) 23 | @ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:applicationContext-core.xml", "classpath*:applicationContext-persistence.xml"}) 24 | public class PolicyResourceTest { 25 | 26 | @Autowired 27 | WebApplicationContext wac; 28 | 29 | MockMvc mockMvc; 30 | 31 | ObjectMapper mapper = new ObjectMapper(); 32 | 33 | @Before 34 | public void setup() { 35 | this.mockMvc = webAppContextSetup(this.wac) 36 | .alwaysExpect(content().contentType("application/json;charset=UTF-8")) 37 | .build(); 38 | } 39 | 40 | @Test 41 | @Ignore 42 | public void getPolicy() throws Exception { 43 | mockMvc.perform( 44 | get("/policy/pol-1")) 45 | .andExpect(status().isOk()) 46 | .andExpect(jsonPath("$.company").value("PlymouthRock")) 47 | .andExpect(jsonPath("$.state").value("MA")) 48 | .andExpect(jsonPath("$.quote").value(1250)); 49 | } 50 | 51 | @Test 52 | @Ignore //FIXME 53 | public void savePolicy() throws Exception { 54 | Policy policy = MockDataHelper.createPolicyFull("lic-test1", "vin-test1", "acc1"); 55 | mockMvc.perform( 56 | post("/policy") 57 | .header("Content-Type", "application/json") 58 | .content(mapper.writeValueAsBytes(policy))) 59 | .andExpect(status().isAccepted()) 60 | .andExpect(header().string("Location", contains("/future"))); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/github/priyatam/springrest/resource/VehicleResourceTest.java: -------------------------------------------------------------------------------- 1 | package github.priyatam.springrest.resource; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import github.priyatam.springrest.EnvironmentModeJUnitRunner; 5 | import github.priyatam.springrest.WebContextLoader; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; 18 | 19 | @RunWith(EnvironmentModeJUnitRunner.class) 20 | @ContextConfiguration(loader = WebContextLoader.class, locations = {"classpath:applicationContext-core.xml", "classpath*:applicationContext-persistence.xml"}) 21 | public class VehicleResourceTest { 22 | 23 | @Autowired 24 | WebApplicationContext wac; 25 | 26 | MockMvc mockMvc; 27 | 28 | ObjectMapper mapper = new ObjectMapper(); 29 | 30 | @Before 31 | public void setup() { 32 | this.mockMvc = webAppContextSetup(this.wac).build(); 33 | } 34 | 35 | @Test 36 | public void getVehicle() throws Exception { 37 | mockMvc.perform( 38 | get("/vehicle/vin-1")) 39 | .andExpect(status().isOk()) 40 | .andExpect(jsonPath("$.vin").value("vin-1")); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/resources/accident.json: -------------------------------------------------------------------------------- 1 | {"name":"john-first accident","incidentTime":null,"thirdParyOffence":true,"type":"MINOR","links":[]} -------------------------------------------------------------------------------- /src/test/resources/address.json: -------------------------------------------------------------------------------- 1 | {"addrLine1":"100 cambridge st","addrLine2":"apt 1","city":"cambridge","state":"MA","zip":"02139","type":"HOMEOWNER","links":[]} -------------------------------------------------------------------------------- /src/test/resources/driver.json: -------------------------------------------------------------------------------- 1 | {"links":[],"licenseNum":"Lic-123","licenseExpiryDate":[2013,10,13],"firstName":"Sarah","lastName":"Conor","birthDate":[2013,10,13],"gender":"FEMALE","email":"cooldriver@junkmail123.com","phone":"6178769876","occupation":"hacker","firstLicenseAtAge":23,"isMarried":false,"address":{"addrLine1":"100 cambridge st","addrLine2":"apt 1","city":"cambridge","state":"MA","zip":"02139","type":"HOMEOWNER","links":[]},"drivingHistory":[{"links":[],"driverLicense":null,"purchaseOrLeasedDate":null,"annualMileage":2000,"isGarageParked":true,"isPrimaryOperator":true,"accidentTime":null,"garageParked":true,"primaryOperator":true,"accident":false,"thirdPartyOffence":null}]} -------------------------------------------------------------------------------- /src/test/resources/drivingHistory.json: -------------------------------------------------------------------------------- 1 | {"links":[],"driverLicense":null,"purchaseOrLeasedDate":null,"annualMileage":2000,"isGarageParked":true,"isPrimaryOperator":true,"accidentTime":null,"garageParked":true,"primaryOperator":true,"accident":false,"thirdPartyOffence":null} -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/policy.json: -------------------------------------------------------------------------------- 1 | {"links":[],"policyNum":"S123","company":"PlymouthRock","effectiveDate":[2015,1,1],"expiryDate":[2013,10,13],"state":"MA","quote":1250,"term":3,"agency":"Commerce","drivers":[{"links":[],"licenseNum":"Lic-123","licenseExpiryDate":[2013,10,13],"firstName":"Sarah","lastName":"Conor","birthDate":[2013,10,13],"gender":"FEMALE","email":"cooldriver@junkmail123.com","phone":"6178769876","occupation":"hacker","firstLicenseAtAge":23,"isMarried":false,"address":{"addrLine1":"100 cambridge st","addrLine2":"apt 1","city":"cambridge","state":"MA","zip":"02139","type":"HOMEOWNER","links":[]},"drivingHistory":[{"links":[],"driverLicense":null,"purchaseOrLeasedDate":null,"annualMileage":2000,"isGarageParked":true,"isPrimaryOperator":true,"accidentTime":null,"garageParked":true,"primaryOperator":true,"accident":false,"thirdPartyOffence":null}]}],"vehicles":[{"links":[],"vin":"123456789","make":"bmw","model":"335xi","year":2012,"plateNum":"1234567890","odomoterReading":24000,"ownerType":"LEASED"}]} -------------------------------------------------------------------------------- /src/test/resources/restito/policy.pol-1.json: -------------------------------------------------------------------------------- 1 | {"links":[],"policyNum":"S123","company":"PlymouthRock","effectiveDate":[2015,1,1],"expiryDate":[2013,2,12],"state":"MA","quote":1250,"term":3,"agency":"Commerce","drivers":[{"links":[],"licenseNum":"Lic-123","licenseExpiryDate":[2013,2,12],"firstName":"Sarah","lastName":"Conor","birthDate":[2013,2,12],"gender":"FEMALE","email":"cooldriver@junkmail123.com","phone":"6178769876","occupation":"hacker","firstLicenseAtAge":23,"isMarried":false,"address":{"addrLine1":"100 cambridge st","addrLine2":"apt 1","city":"cambridge","state":"MA","zip":"02139","type":"HOMEOWNER","links":[]},"drivingHistory":[{"links":[],"driverLicense":null,"purchaseOrLeasedDate":null,"annualMileage":2000,"isGarageParked":true,"isPrimaryOperator":true,"accidentTime":null,"garageParked":true,"primaryOperator":true,"accident":false,"thirdPartyOffence":null}]}],"vehicles":[{"links":[],"vin":"123456789","make":"bmw","model":"335xi","year":2012,"plateNum":"1234567890","odomoterReading":24000,"ownerType":"LEASED"}]} -------------------------------------------------------------------------------- /src/test/resources/vehicle.json: -------------------------------------------------------------------------------- 1 | {"links":[],"vin":"vin-1","make":"bmw","model":"335xi","year":2012,"plateNum":"1234567890","odomoterReading":24000,"ownerType":"LEASED"} --------------------------------------------------------------------------------