diff --git a/src/main/java/org/springframework/samples/petclinic/model/Pet.java b/src/main/java/org/springframework/samples/petclinic/model/Pet.java index 593798033..d618f08bb 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Pet.java @@ -34,7 +34,6 @@ import org.hibernate.annotations.Type; import org.joda.time.DateTime; import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.PropertyComparator; -import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -51,7 +50,6 @@ public class Pet extends NamedEntity { @Column(name = "birth_date") @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime") - @DateTimeFormat(pattern = "yyyy/MM/dd") private DateTime birthDate; @ManyToOne diff --git a/src/main/java/org/springframework/samples/petclinic/web/OwnerResource.java b/src/main/java/org/springframework/samples/petclinic/web/OwnerResource.java index 315cb7011..851a3ff75 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/OwnerResource.java +++ b/src/main/java/org/springframework/samples/petclinic/web/OwnerResource.java @@ -18,7 +18,7 @@ package org.springframework.samples.petclinic.web; import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; import org.springframework.samples.petclinic.model.Owner; import org.springframework.samples.petclinic.service.ClinicService; import org.springframework.web.bind.WebDataBinder; @@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; /** @@ -56,26 +57,27 @@ public class OwnerResource { return this.clinicService.findOwnerById(ownerId); } - @RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.PUT) - public Owner updateOwner(@PathVariable("ownerId") int ownerId, @RequestBody Owner ownerRequest) { - Owner ownerModel = retrieveOwner(ownerId); - // This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct. - ownerModel.setFirstName(ownerRequest.getFirstName()); - ownerModel.setLastName(ownerRequest.getLastName()); - ownerModel.setCity(ownerRequest.getCity()); - ownerModel.setAddress(ownerRequest.getAddress()); - ownerModel.setTelephone(ownerRequest.getTelephone()); - this.clinicService.saveOwner(ownerModel); - return ownerModel; - // TODO: need to handle failure + /** + * Create Owner + */ + @RequestMapping(value = "/owner", method = RequestMethod.POST) + @ResponseStatus(HttpStatus.CREATED) + public void createOwner(@RequestBody Owner owner) { + this.clinicService.saveOwner(owner); + // TODO: need to handle failure } - - + + /** + * Read single Owner + */ @RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.GET) public Owner findOwner(@PathVariable("ownerId") int ownerId) { return retrieveOwner(ownerId); } + /** + * Read List of Owners + */ @RequestMapping(value = "/owner/list", method = RequestMethod.GET) public Collection findOwnerCollection(@RequestParam("lastName") String ownerLastName) { @@ -91,5 +93,24 @@ public class OwnerResource { return results; } } + + /** + * Update Owner + */ + @RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.PUT) + public Owner updateOwner(@PathVariable("ownerId") int ownerId, @RequestBody Owner ownerRequest) { + Owner ownerModel = retrieveOwner(ownerId); + // This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct. + ownerModel.setFirstName(ownerRequest.getFirstName()); + ownerModel.setLastName(ownerRequest.getLastName()); + ownerModel.setCity(ownerRequest.getCity()); + ownerModel.setAddress(ownerRequest.getAddress()); + ownerModel.setTelephone(ownerRequest.getTelephone()); + this.clinicService.saveOwner(ownerModel); + return ownerModel; + // TODO: need to handle failure + } + + } diff --git a/src/main/java/org/springframework/samples/petclinic/web/PetController.java b/src/main/java/org/springframework/samples/petclinic/web/PetResource.java similarity index 86% rename from src/main/java/org/springframework/samples/petclinic/web/PetController.java rename to src/main/java/org/springframework/samples/petclinic/web/PetResource.java index ea8aeaaa8..3651968bb 100644 --- a/src/main/java/org/springframework/samples/petclinic/web/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/web/PetResource.java @@ -23,7 +23,6 @@ import org.springframework.samples.petclinic.model.Owner; import org.springframework.samples.petclinic.model.Pet; import org.springframework.samples.petclinic.model.PetType; import org.springframework.samples.petclinic.service.ClinicService; -import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; @@ -31,7 +30,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.support.SessionStatus; /** @@ -39,15 +38,14 @@ import org.springframework.web.bind.support.SessionStatus; * @author Ken Krebs * @author Arjen Poutsma */ -@Controller -@SessionAttributes("pet") -public class PetController { +@RestController +public class PetResource { private final ClinicService clinicService; @Autowired - public PetController(ClinicService clinicService) { + public PetResource(ClinicService clinicService) { this.clinicService = clinicService; } @@ -78,15 +76,14 @@ public class PetController { } else { this.clinicService.savePet(pet); status.setComplete(); - return "redirect:/owners/{ownerId}"; + return "redirect:/owner/{ownerId}"; } } - @RequestMapping(value = "/owners/*/pets/{petId}/edit", method = RequestMethod.GET) - public String initUpdateForm(@PathVariable("petId") int petId, Map model) { + @RequestMapping(value = "/owner/*/pet/{petId}", method = RequestMethod.GET) + public Pet findPet(@PathVariable("petId") int petId) { Pet pet = this.clinicService.findPetById(petId); - model.put("pet", pet); - return "pets/createOrUpdatePetForm"; + return pet; } @RequestMapping(value = "/owners/{ownerId}/pets/{petId}/edit", method = {RequestMethod.PUT, RequestMethod.POST}) diff --git a/src/main/resources/spring/mvc-core-config.xml b/src/main/resources/spring/mvc-core-config.xml index ceb7d99ca..51d2ce828 100644 --- a/src/main/resources/spring/mvc-core-config.xml +++ b/src/main/resources/spring/mvc-core-config.xml @@ -21,7 +21,7 @@ --> - + @@ -31,7 +31,7 @@ - + diff --git a/src/main/webapp/WEB-INF/jsp/pets/createOrUpdatePetForm.jsp b/src/main/webapp/WEB-INF/jsp/pets/createOrUpdatePetForm.jsp deleted file mode 100644 index c41f02ddf..000000000 --- a/src/main/webapp/WEB-INF/jsp/pets/createOrUpdatePetForm.jsp +++ /dev/null @@ -1,64 +0,0 @@ - - -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> -<%@ taglib prefix="petclinic" tagdir="/WEB-INF/tags" %> - - - - - - - - -
- - - - - - - - - - -

- New - Pet -

- - -
- - - -
- - -
- -
-
- - - - - - - - -
-
- - - -
- - - diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 7a0d87412..44123baf8 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -15,6 +15,7 @@ + diff --git a/src/main/webapp/scripts/app/app.js b/src/main/webapp/scripts/app/app.js index 5510889e3..671bb041c 100644 --- a/src/main/webapp/scripts/app/app.js +++ b/src/main/webapp/scripts/app/app.js @@ -29,8 +29,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv url: 'owner/search', views: { 'content@': { - templateUrl: 'scripts/app/owner/ownerSearchForm.html', - controller: 'ownerSearchController' + controller: 'ownerSearchController', + templateUrl: 'scripts/app/owner/ownerSearchForm.html' } } @@ -39,8 +39,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv url: 'owner/list?lastName', views: { 'content@': { - templateUrl: 'scripts/app/owner/ownerList.html', - controller: 'ownerListController' + controller: 'ownerListController', + templateUrl: 'scripts/app/owner/ownerList.html' } } @@ -49,8 +49,18 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv url: 'owner/:id', views: { 'content@': { - templateUrl: 'scripts/app/owner/ownerDetail.html', - controller: 'ownerDetailController' + controller: 'ownerDetailController', + templateUrl: 'scripts/app/owner/ownerDetail.html' + } + } + + }). + state('app.ownercreate', { + url: 'owner', + views: { + 'content@': { + controller: 'ownerFormController', + templateUrl: 'scripts/app/owner/ownerForm.html' } } @@ -59,8 +69,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv url: 'owner/:id/edit', views: { 'content@': { - templateUrl: 'scripts/app/owner/ownerForm.html', - controller: 'ownerFormController' + controller: 'ownerFormController', + templateUrl: 'scripts/app/owner/ownerForm.html' } } @@ -69,12 +79,32 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv url: 'vets', views: { 'content@': { - templateUrl: 'scripts/app/vet/vetList.html', - controller: 'vetController' + controller: 'vetController', + templateUrl: 'scripts/app/vet/vetList.html' } } - }); + }). + state('app.petedit', { + url: 'owner/:ownerid/pet/:petid', + views: { + 'content@': { + controller: 'petFormController', + templateUrl: 'scripts/app/pet/petForm.html' + } + } + + }). + state('app.petcreate', { + url: 'owner/:ownerid/pet', + views: { + 'content@': { + controller: 'petFormController', + templateUrl: 'scripts/app/pet/petForm.html' + } + } + + }); }]); diff --git a/src/main/webapp/scripts/app/owner/OwnerController.js b/src/main/webapp/scripts/app/owner/OwnerController.js index 07acfc0e8..b03f7a2d1 100644 --- a/src/main/webapp/scripts/app/owner/OwnerController.js +++ b/src/main/webapp/scripts/app/owner/OwnerController.js @@ -1,5 +1,12 @@ 'use strict'; +function loadOwner($scope, $resource, $stateParams) { + var ownerResource = $resource('/petclinic/owner/' + $stateParams.id); + $scope.owner = ownerResource.get(); +} + + + /* * Owner Search */ @@ -36,16 +43,13 @@ angular.module('controllers').controller('ownerDetailController', ['$scope', '$r loadOwner ]); -function loadOwner($scope, $resource, $stateParams) { - var ownerResource = $resource('/petclinic/owner/' + $stateParams.id); - $scope.owner = ownerResource.get(); -} + /* - * Owner Edit Form + * Form used to create and edit owners */ -angular.module('controllers').controller('ownerFormController', ['$scope', '$resource', '$http', '$stateParams', '$state', -function($scope, $resource, $http, $stateParams, $state) { +angular.module('controllers').controller('ownerFormController', ['$scope', '$http', '$resource', '$stateParams', '$state', +function($scope, $http, $resource, $stateParams, $state) { $scope.submitOwnerForm = {}; @@ -60,12 +64,22 @@ function($scope, $resource, $http, $stateParams, $state) { city: form.city, telephone: form.telephone }; - var restUrl = "/petclinic/owner/" + $stateParams.id; - $http.put(restUrl, data); - $state.go('app.ownerlist'); + + if ($state.current.name == 'app.owneredit') { + var restUrl = "/petclinic/owner/" + $stateParams.id; + $http.put(restUrl, data); + $state.go('app.ownerlist'); + } + else { // in case of owner creation + var restUrl = "/petclinic/owner"; + $http.post(restUrl, data); + $state.go('app.ownerlist'); + } } - loadOwner($scope, $resource, $stateParams); + if ($state.current.name == 'app.owneredit') { + loadOwner($scope, $resource, $stateParams); + } }]); diff --git a/src/main/webapp/scripts/app/owner/ownerDetail.html b/src/main/webapp/scripts/app/owner/ownerDetail.html index c3ba84aef..45a7b9fdd 100644 --- a/src/main/webapp/scripts/app/owner/ownerDetail.html +++ b/src/main/webapp/scripts/app/owner/ownerDetail.html @@ -23,10 +23,8 @@ Edit Owner - - - - Add New Pet + Add New Pet + @@ -58,11 +56,7 @@ - - - - - Edit Pet + Edit Pet diff --git a/src/main/webapp/scripts/app/owner/ownerSearchForm.html b/src/main/webapp/scripts/app/owner/ownerSearchForm.html index 7b9fe5c48..548c452a8 100644 --- a/src/main/webapp/scripts/app/owner/ownerSearchForm.html +++ b/src/main/webapp/scripts/app/owner/ownerSearchForm.html @@ -12,4 +12,4 @@
- Add Owners + Add Owners diff --git a/src/main/webapp/scripts/app/pet/PetController.js b/src/main/webapp/scripts/app/pet/PetController.js new file mode 100644 index 000000000..a766a281c --- /dev/null +++ b/src/main/webapp/scripts/app/pet/PetController.js @@ -0,0 +1,64 @@ +'use strict'; + +function loadPet($scope, $resource, $stateParams) { + var petResource = $resource('/petclinic/owner/' + $stateParams.ownerid +"/pet/" +$stateParams.petid); + $scope.pet = petResource.get(); +} + + + + +/* + * Form used to create and edit pets + */ +angular.module('controllers').controller('petFormController', ['$scope', '$http', '$resource', '$stateParams', '$state', +function($scope, $http, $resource, $stateParams, $state) { + + $scope.submitPetForm = {}; + + $scope.submitPetForm = function() { + var form = $scope.pet; + + // Creating a Javascript object + var data = { + name: form.name, + birthDate: form.birthDate, + type: form.type + }; + + if ($state.current.name == 'app.petedit') { + var restUrl = "/petclinic/owner/" + $stateParams.ownerid +"/pet/" +$stateParams.petid; + $http.put(restUrl, data); + $state.go('app.ownerdetail'); + } + else { // in case of pet creation + var restUrl = "/petclinic/owner/" + $stateParams.ownerid +"/pet"; + $http.post(restUrl, data); + $state.go('app.ownerdetail'); + } + } + + if ($state.current.name == 'app.petedit') { + loadPet($scope, $resource, $stateParams); + } + +}]); + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/scripts/app/pet/petForm.html b/src/main/webapp/scripts/app/pet/petForm.html new file mode 100644 index 000000000..d8aa79bdc --- /dev/null +++ b/src/main/webapp/scripts/app/pet/petForm.html @@ -0,0 +1,44 @@ + + + +
+ +

Pet (work in progress)

+
+
+
+ + {{pet.owner.firstName}} {{pet.owner.lastName}} +
+
+ + + Name is required. +
+
+ + + birth date is required. +
+ + +
+ +
+
+ + + + + + + + +
+
+
+
diff --git a/src/test/java/org/springframework/samples/petclinic/web/AbstractWebResourceTests.java b/src/test/java/org/springframework/samples/petclinic/web/AbstractWebResourceTests.java new file mode 100644 index 000000000..9c28da2ba --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/web/AbstractWebResourceTests.java @@ -0,0 +1,28 @@ +package org.springframework.samples.petclinic.web; + +import org.junit.runner.RunWith; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"classpath:spring/business-config.xml", "classpath:spring/tools-config.xml", "classpath:spring/mvc-core-config.xml"}) +@WebAppConfiguration +@ActiveProfiles("spring-data-jpa") + +public abstract class AbstractWebResourceTests { + + protected MockMvc mockMvc; + + public void runMockSpringMVC(Object resource) { + this.mockMvc = MockMvcBuilders.standaloneSetup(resource).build(); + } + + public AbstractWebResourceTests() { + super(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/samples/petclinic/web/PetResourceTests.java b/src/test/java/org/springframework/samples/petclinic/web/PetResourceTests.java new file mode 100644 index 000000000..0bd74cb39 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/web/PetResourceTests.java @@ -0,0 +1,53 @@ +package org.springframework.samples.petclinic.web; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; + +/** + * Test class for the UserResource REST controller. + * + * @see UserResource + */ +public class PetResourceTests extends AbstractWebResourceTests { + + @Autowired + private PetResource petResource; + + @Before + public void setup() { + runMockSpringMVC(petResource); + } + + /** + * Expected JSon result: + * { + "id":2, + "name":"Basil", + "birthDate":1344211200000, + "type":{ + "id":6, + "name":"hamster", + "new":false + }, + "visits":[], + "new":false + } + */ + @Test + public void shouldGetAPetInJSonFormat() throws Exception { + ResultActions actions = mockMvc.perform(get("/owner/2/pet/2.json").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + actions.andExpect(content().contentType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.id").value(2)) + .andExpect(jsonPath("$.name").value("Basil")) + .andExpect(jsonPath("$.type.id").value(6)); + } +} diff --git a/src/test/java/org/springframework/samples/petclinic/web/VetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/web/VetResourceTests.java similarity index 55% rename from src/test/java/org/springframework/samples/petclinic/web/VetControllerTests.java rename to src/test/java/org/springframework/samples/petclinic/web/VetResourceTests.java index c8b2d08bd..4e6283e63 100644 --- a/src/test/java/org/springframework/samples/petclinic/web/VetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/web/VetResourceTests.java @@ -7,44 +7,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; /** * Test class for the UserResource REST controller. * * @see UserResource */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration({"classpath:spring/business-config.xml", "classpath:spring/tools-config.xml", "classpath:spring/mvc-core-config.xml"}) -@WebAppConfiguration -@ActiveProfiles("spring-data-jpa") -public class VetControllerTests { +public class VetResourceTests extends AbstractWebResourceTests { @Autowired private VetResource vetResource; - @Autowired - private WebApplicationContext ctx; - - private MockMvc mockMvc; - @Before public void setup() { - this.mockMvc = MockMvcBuilders.standaloneSetup(vetResource).build(); + runMockSpringMVC(vetResource); } @Test - public void testGetExistingUser() throws Exception { + public void shouldGetAListOfVetsInJSonFormat() throws Exception { ResultActions actions = mockMvc.perform(get("/vets.json").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); actions.andExpect(content().contentType("application/json"))