v1 conversion to couchbase

This commit is contained in:
Denis Rosa 2021-04-09 17:37:41 +02:00
parent e2fbc56130
commit 28a2d2c9b6
56 changed files with 1112 additions and 1203 deletions

76
.gitpod.Dockerfile vendored Normal file
View file

@ -0,0 +1,76 @@
FROM ubuntu:20.04
RUN apt-get -qq update && \
apt-get install -yq runit wget chrpath tzdata \
lsof lshw sysstat net-tools numactl bzip2 maven default-jdk && \
apt-get autoremove && apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN if [ ! -x /usr/sbin/runsvdir-start ]; then \
cp -a /etc/runit/2 /usr/sbin/runsvdir-start; \
fi
ENV PATH=$PATH:/opt/couchbase/bin:/opt/couchbase/bin/tools:/opt/couchbase/bin/install
RUN groupadd -g 1000 couchbase && useradd couchbase -u 1000 -g couchbase -M
RUN mkdir -p /tmp/couchbase && \
cd /tmp/couchbase && \
wget https://packages.couchbase.com/releases/7.0.0-beta/couchbase-server-enterprise_7.0.0-beta-ubuntu20.04_amd64.deb && \
dpkg -i ./couchbase-server-enterprise_7.0.0-beta-ubuntu20.04_amd64.deb
RUN sed -i -e '1 s/$/\/docker/' /opt/couchbase/VARIANT.txt
COPY scripts/run /etc/service/couchbase-server/run
RUN chrpath -r '$ORIGIN/../lib' /opt/couchbase/bin/curl
COPY scripts/start-cb.sh /opt/couchbase/
RUN chmod 777 /opt/couchbase/start-cb.sh
RUN cd /opt/couchbase && \
mkdir -p var/lib/couchbase \
var/lib/couchbase/config \
var/lib/couchbase/data \
var/lib/couchbase/stats \
var/lib/couchbase/logs \
var/lib/moxi
RUN chmod -R 777 /opt/couchbase/
RUN chmod 777 /etc/service/couchbase-server/run
# 8091: Couchbase Web console, REST/HTTP interface
# 8092: Views, queries, XDCR
# 8093: Query services (4.0+)
# 8094: Full-text Search (4.5+)
# 8095: Analytics (5.5+)
# 8096: Eventing (5.5+)
# 11207: Smart client library data node access (SSL)
# 11210: Smart client library/moxi data node access
# 11211: Legacy non-smart client library data node access
# 18091: Couchbase Web console, REST/HTTP interface (SSL)
# 18092: Views, query, XDCR (SSL)
# 18093: Query services (SSL) (4.0+)
# 18094: Full-text Search (SSL) (4.5+)
# 18095: Analytics (SSL) (5.5+)
# 18096: Eventing (SSL) (5.5+)
EXPOSE 8091 8092 8093 8094 8095 8096 11207 11210 11211 18091 18092 18093 18094 18095 18096
VOLUME /opt/couchbase/var
#FROM couchbase:enterprise-7.0.0-beta
#
#RUN apt-get update \
# && apt-get install -y sudo
#
#RUN chmod -R 777 /opt/couchbase/var/lib/

27
.gitpod.yml Normal file
View file

@ -0,0 +1,27 @@
image:
file: .gitpod.Dockerfile
tasks:
- name: Start Couchbase
before: cd /opt/couchbase/ && ./start-cb.sh && sleep 10
- name: Start app
init: ./mvnw package -DskipTests
command: java -jar target/*.jar
# exposed ports
ports:
- port: 8091
onOpen: open-preview
- port: 8080
onOpen: open-preview
- port: 8092-10000
onOpen: ignore
- port: 4369
onOpen: ignore
vscode:
extensions:
- redhat.java
- vscjava.vscode-java-debug
- vscjava.vscode-java-test
- pivotal.vscode-spring-boot

View file

@ -1,12 +0,0 @@
mysql:
image: mysql:5.7
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=
- MYSQL_ALLOW_EMPTY_PASSWORD=true
- MYSQL_USER=petclinic
- MYSQL_PASSWORD=petclinic
- MYSQL_DATABASE=petclinic
volumes:
- "./conf.d:/etc/mysql/conf.d:ro"

16
pom.xml
View file

@ -42,10 +42,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId> <artifactId>spring-boot-starter-cache</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
@ -70,16 +66,10 @@
</exclusions> </exclusions>
</dependency> </dependency>
<!-- Databases - Uses H2 by default -->
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>org.springframework.data</groupId>
<artifactId>h2</artifactId> <artifactId>spring-data-couchbase</artifactId>
<scope>runtime</scope> <version>4.1.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency> </dependency>
<!-- caching --> <!-- caching -->

View file

@ -0,0 +1,113 @@
package org.springframework.samples.petclinic;
import com.couchbase.client.core.error.IndexExistsException;
import com.couchbase.client.java.Cluster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.OwnerRepository;
import org.springframework.samples.petclinic.owner.Pet;
import org.springframework.samples.petclinic.owner.PetRepository;
import org.springframework.samples.petclinic.vet.Vet;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@Component
public class IndexCMDRunner implements CommandLineRunner {
@Autowired
private CouchbaseTemplate template;
@Autowired
private Cluster cluster;
@Autowired
private VetRepository vetRepository;
@Autowired
private OwnerRepository ownerRepository;
@Autowired
private PetRepository petRepository;
@Override
public void run(String... args) throws Exception {
try {
// Create a Primary Index to make it easier for you to query Couchbase
template.getCouchbaseClientFactory().getCluster().queryIndexes()
.createPrimaryIndex(template.getBucketName());
}
catch (IndexExistsException e) {
System.out.println("Skipping index creation...");
}
// clean the whole database before start up
cluster.query("Delete from `" + template.getBucketName() + "`");
createVets();
createOwners();
createPets();
}
private void createVets() {
vetRepository.save(new Vet("vet-1", "James", "Carter", new HashSet<>()));
vetRepository.save(new Vet("vet-2", "Helen", "Leary", new HashSet<>(Arrays.asList("radiology"))));
vetRepository.save(new Vet("vet-3", "Linda", "Douglas", new HashSet<>(Arrays.asList("surgery", "dentistry"))));
vetRepository.save(new Vet("vet-4", "Rafael", "Ortega", new HashSet<>(Arrays.asList("surgery"))));
vetRepository.save(new Vet("vet-5", "Sharon", "Jenkins", new HashSet<>(Arrays.asList("radiology"))));
}
private void createOwners() {
ownerRepository.save(new Owner("owner-1", "George", "Franklin", "110 W. Liberty St.", "Madison", "6085551023"));
ownerRepository.save(new Owner("owner-2", "Betty", "Davis", "638 Cardinal Ave.", "Sun Prairie", "6085551749"));
ownerRepository
.save(new Owner("owner-3", "Eduardo", "Rodriquez", "2693 Commerce St.", "McFarland", "6085558763"));
ownerRepository.save(new Owner("owner-4", "Harold", "Davis", "563 Friendly St.", "Windsor", "6085553198"));
ownerRepository.save(new Owner("owner-5", "Peter", "McTavish", "2387 S. Fair Way", "Madison", "6085552765"));
ownerRepository.save(new Owner("owner-6", "Jean", "Coleman", "105 N. Lake St.", "Monona", "6085552654"));
ownerRepository.save(new Owner("owner-7", "Jeff", "Black", "1450 Oak Blvd.", "Monona", "6085555387"));
ownerRepository.save(new Owner("owner-8", "Maria", "Escobito", "345 Maple St.", "Madison", "6085557683"));
ownerRepository
.save(new Owner("owner-9", "David", "Schroeder", "2749 Blackhawk Trail", "Madison", "6085559435"));
ownerRepository
.save(new Owner("owner-10", "Carlos", "Estaban", "2335 Independence La.", "Waunakee", "6085555487"));
}
private void createPets() throws Exception {
petRepository.save(new Pet("pet-1", "Leo", "2010-09-07", "cat", "owner-1", new ArrayList()));
petRepository.save(new Pet("pet-2", "Basil", "2012-08-06", "hamster", "owner-2", new ArrayList()));
petRepository.save(new Pet("pet-3", "Rosy", "2011-04-17", "dog", "owner-3", new ArrayList()));
petRepository.save(new Pet("pet-4", "Jewel", "2010-03-07", "dog", "owner-3", new ArrayList()));
petRepository.save(new Pet("pet-5", "Iggy", "2010-11-30", "lizard", "owner-4", new ArrayList()));
petRepository.save(new Pet("pet-6", "George", "2010-01-20", "snake", "owner-5", new ArrayList()));
petRepository.save(new Pet("pet-7", "Samantha", "2012-09-04", "cat", "owner-6",
Arrays.asList(new Visit("visit-1", toMilliseconds("2013-01-01"), "rabies shot"),
new Visit("visit-4", toMilliseconds("2013-01-04"), "spayed"))));
petRepository.save(new Pet("pet-8", "Max", "2012-09-04", "cat", "owner-6",
Arrays.asList(new Visit("visit-2", toMilliseconds("2013-01-02"), "rabies shot"),
new Visit("visit-3", toMilliseconds("2013-01-03"), "neutered"))));
petRepository.save(new Pet("pet-9", "Lucky", "2011-08-06", "bird", "owner-7", new ArrayList()));
petRepository.save(new Pet("pet-10", "Mulligan", "2007-02-24", "dog", "owner-8", new ArrayList()));
petRepository.save(new Pet("pet-11", "Freddy", "2010-03-09", "bird", "owner-9", new ArrayList()));
petRepository.save(new Pet("pet-12", "Lucky", "2010-06-24", "dog", "owner-10", new ArrayList()));
petRepository.save(new Pet("pet-13", "Sly", "2012-06-08", "cat", "owner-10", new ArrayList()));
}
private long toMilliseconds(String targetDate) throws Exception {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
Date d = f.parse(targetDate);
return d.getTime();
}
}

View file

@ -0,0 +1,40 @@
package org.springframework.samples.petclinic.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
@Configuration
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return "couchbase://127.0.0.1";
}
@Override
public String getUserName() {
return "Administrator";
}
@Override
public String getPassword() {
return "password";
}
@Override
public String getBucketName() {
return "default";
}
// NOTE: Optional - If not specified the default attribute name will be "_class"
public String typeKey() {
return "type";
}
// NOTE Use only on index
@Override
protected boolean autoIndexCreation() {
return true;
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
import java.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
* Simple JavaBean domain object with an id property. Used as a base class for objects
* needing this property.
*
* @author Ken Krebs
* @author Juergen Hoeller
*/
@MappedSuperclass
public class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public boolean isNew() {
return this.id == null;
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
/**
* Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as
* a base class for objects needing these properties.
*
* @author Ken Krebs
* @author Juergen Hoeller
*/
@MappedSuperclass
public class NamedEntity extends BaseEntity {
@Column(name = "name")
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.getName();
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotEmpty;
/**
* Simple JavaBean domain object representing an person.
*
* @author Ken Krebs
*/
@MappedSuperclass
public class Person extends BaseEntity {
@Column(name = "first_name")
@NotEmpty
private String firstName;
@Column(name = "last_name")
@NotEmpty
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

View file

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The classes in this package represent utilities used by the domain.
*/
package org.springframework.samples.petclinic.model;

View file

@ -15,24 +15,18 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.Digits; import javax.validation.constraints.Digits;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.samples.petclinic.model.Person; import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
/** /**
* Simple JavaBean domain object representing an owner. * Simple JavaBean domain object representing an owner.
@ -42,25 +36,66 @@ import org.springframework.samples.petclinic.model.Person;
* @author Sam Brannen * @author Sam Brannen
* @author Michael Isvy * @author Michael Isvy
*/ */
@Entity
@Table(name = "owners")
public class Owner extends Person {
@Column(name = "address") @Document
public class Owner {
@Id
@GeneratedValue(strategy = GenerationStrategy.UNIQUE)
private String id;
@NotEmpty
private String firstName;
@NotEmpty
private String lastName;
@NotEmpty @NotEmpty
private String address; private String address;
@Column(name = "city")
@NotEmpty @NotEmpty
private String city; private String city;
@Column(name = "telephone")
@NotEmpty @NotEmpty
@Digits(fraction = 0, integer = 10) @Digits(fraction = 0, integer = 10)
private String telephone; private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner") public Owner() {
private Set<Pet> pets; }
public Owner(String id, @NotEmpty String firstName, @NotEmpty String lastName, @NotEmpty String address,
@NotEmpty String city, @NotEmpty @Digits(fraction = 0, integer = 10) String telephone) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.city = city;
this.telephone = telephone;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddress() { public String getAddress() {
return this.address; return this.address;
@ -86,56 +121,8 @@ public class Owner extends Person {
this.telephone = telephone; this.telephone = telephone;
} }
protected Set<Pet> getPetsInternal() { public boolean isNew() {
if (this.pets == null) { return this.id == null;
this.pets = new HashSet<>();
}
return this.pets;
}
protected void setPetsInternal(Set<Pet> pets) {
this.pets = pets;
}
public List<Pet> getPets() {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal());
PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets);
}
public void addPet(Pet pet) {
if (pet.isNew()) {
getPetsInternal().add(pet);
}
pet.setOwner(this);
}
/**
* Return the Pet with the given name, or null if none found for this Owner.
* @param name to test
* @return true if pet name is already in use
*/
public Pet getPet(String name) {
return getPet(name, false);
}
/**
* Return the Pet with the given name, or null if none found for this Owner.
* @param name to test
* @return true if pet name is already in use
*/
public Pet getPet(String name, boolean ignoreNew) {
name = name.toLowerCase();
for (Pet pet : getPetsInternal()) {
if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName();
compName = compName.toLowerCase();
if (compName.equals(name)) {
return pet;
}
}
}
return null;
} }
@Override @Override

View file

@ -15,7 +15,6 @@
*/ */
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
@ -28,6 +27,7 @@ import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -43,11 +43,11 @@ class OwnerController {
private final OwnerRepository owners; private final OwnerRepository owners;
private VisitRepository visits; private final PetRepository petRepository;
public OwnerController(OwnerRepository clinicService, VisitRepository visits) { public OwnerController(OwnerRepository clinicService, PetRepository petRepository) {
this.owners = clinicService; this.owners = clinicService;
this.visits = visits; this.petRepository = petRepository;
} }
@InitBinder @InitBinder
@ -107,15 +107,15 @@ class OwnerController {
} }
@GetMapping("/owners/{ownerId}/edit") @GetMapping("/owners/{ownerId}/edit")
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { public String initUpdateOwnerForm(@PathVariable("ownerId") String ownerId, Model model) {
Owner owner = this.owners.findById(ownerId); Owner owner = this.owners.findById(ownerId).get();
model.addAttribute(owner); model.addAttribute(owner);
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/owners/{ownerId}/edit") @PostMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
@PathVariable("ownerId") int ownerId) { @PathVariable("ownerId") String ownerId) {
if (result.hasErrors()) { if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }
@ -132,13 +132,12 @@ class OwnerController {
* @return a ModelMap with the model attributes for the view * @return a ModelMap with the model attributes for the view
*/ */
@GetMapping("/owners/{ownerId}") @GetMapping("/owners/{ownerId}")
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { public ModelAndView showOwner(@PathVariable("ownerId") String ownerId) {
ModelAndView mav = new ModelAndView("owners/ownerDetails"); ModelAndView mav = new ModelAndView("owners/ownerDetails");
Owner owner = this.owners.findById(ownerId); Owner owner = this.owners.findById(ownerId).get();
for (Pet pet : owner.getPets()) { List<Pet> pets = this.petRepository.findByOwnerId(ownerId);
pet.setVisitsInternal(visits.findByPetId(pet.getId()));
}
mav.addObject(owner); mav.addObject(owner);
mav.addObject("pets", pets);
return mav; return mav;
} }

View file

@ -16,11 +16,13 @@
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
/** /**
* Repository class for <code>Owner</code> domain objects All method names are compliant * Repository class for <code>Owner</code> domain objects All method names are compliant
@ -33,7 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Sam Brannen * @author Sam Brannen
* @author Michael Isvy * @author Michael Isvy
*/ */
public interface OwnerRepository extends Repository<Owner, Integer> { public interface OwnerRepository extends CrudRepository<Owner, String> {
/** /**
* Retrieve {@link Owner}s from the data store by last name, returning all owners * Retrieve {@link Owner}s from the data store by last name, returning all owners
@ -42,23 +44,7 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
* @return a Collection of matching {@link Owner}s (or an empty Collection if none * @return a Collection of matching {@link Owner}s (or an empty Collection if none
* found) * found)
*/ */
@Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%") @ScanConsistency(query = REQUEST_PLUS)
@Transactional(readOnly = true) List<Owner> findByLastName(@Param("lastName") String lastName);
Collection<Owner> findByLastName(@Param("lastName") String lastName);
/**
* Retrieve an {@link Owner} from the data store by id.
* @param id the id to search for
* @return the {@link Owner} if found
*/
@Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
@Transactional(readOnly = true)
Owner findById(@Param("id") Integer id);
/**
* Save an {@link Owner} to the data store, either inserting or updating it.
* @param owner the {@link Owner} to save
*/
void save(Owner owner);
} }

View file

@ -24,17 +24,15 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.Column; import javax.validation.constraints.NotEmpty;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.NamedEntity;
import org.springframework.samples.petclinic.visit.Visit; import org.springframework.samples.petclinic.visit.Visit;
/** /**
@ -44,69 +42,94 @@ import org.springframework.samples.petclinic.visit.Visit;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
*/ */
@Entity @Document
@Table(name = "pets") public class Pet {
public class Pet extends NamedEntity {
@Column(name = "birth_date") @Id
@DateTimeFormat(pattern = "yyyy-MM-dd") @GeneratedValue(strategy = GenerationStrategy.UNIQUE)
private LocalDate birthDate; private String id;
@ManyToOne @NotEmpty
@JoinColumn(name = "type_id") private String name;
private PetType type;
@ManyToOne private String birthDate;
@JoinColumn(name = "owner_id")
private Owner owner;
@Transient private String petType;
private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(LocalDate birthDate) { @NotEmpty
private String ownerId;
private List<Visit> visits = new ArrayList<>();
public Pet() {
}
public Pet(String id, @NotEmpty String name, String birthDate, String petType, @NotEmpty String ownerId,
List<Visit> visits) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
this.petType = petType;
this.ownerId = ownerId;
this.visits = visits;
}
public String getPetType() {
return petType;
}
public void setPetType(String petType) {
this.petType = petType;
}
public void setBirthDate(String birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }
public LocalDate getBirthDate() { public String getBirthDate() {
return this.birthDate; return this.birthDate;
} }
public PetType getType() { public String getId() {
return this.type; return id;
} }
public void setType(PetType type) { public void setId(String id) {
this.type = type; this.id = id;
} }
public Owner getOwner() { public String getName() {
return this.owner; return name;
} }
protected void setOwner(Owner owner) { public void setName(String name) {
this.owner = owner; this.name = name;
} }
protected Set<Visit> getVisitsInternal() { public void setVisits(List<Visit> visits) {
if (this.visits == null) { this.visits = visits;
this.visits = new HashSet<>();
}
return this.visits;
} }
protected void setVisitsInternal(Collection<Visit> visits) { public String getOwnerId() {
this.visits = new LinkedHashSet<>(visits); return ownerId;
}
public void setOwnerId(String ownerId) {
this.ownerId = ownerId;
} }
public List<Visit> getVisits() { public List<Visit> getVisits() {
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal()); List<Visit> sortedVisits = new ArrayList<>(visits);
PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false)); PropertyComparator.sort(sortedVisits, new MutableSortDefinition("visitDate", false, false));
return Collections.unmodifiableList(sortedVisits); return Collections.unmodifiableList(sortedVisits);
} }
public void addVisit(Visit visit) { public void addVisit(Visit visit) {
getVisitsInternal().add(visit); visits.add(visit);
visit.setPetId(this.getId()); }
public boolean isNew() {
return this.id == null;
} }
} }

View file

@ -23,7 +23,9 @@ import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
@ -46,13 +48,13 @@ class PetController {
} }
@ModelAttribute("types") @ModelAttribute("types")
public Collection<PetType> populatePetTypes() { public List<String> populatePetTypes() {
return this.pets.findPetTypes(); return Arrays.asList("cat", "dog", "lizard", "snake", "bird", "hamster");
} }
@ModelAttribute("owner") @ModelAttribute("owner")
public Owner findOwner(@PathVariable("ownerId") int ownerId) { public Owner findOwner(@PathVariable("ownerId") String ownerId) {
return this.owners.findById(ownerId); return this.owners.findById(ownerId).get();
} }
@InitBinder("owner") @InitBinder("owner")
@ -68,30 +70,42 @@ class PetController {
@GetMapping("/pets/new") @GetMapping("/pets/new")
public String initCreationForm(Owner owner, ModelMap model) { public String initCreationForm(Owner owner, ModelMap model) {
Pet pet = new Pet(); Pet pet = new Pet();
owner.addPet(pet); pet.setOwnerId(owner.getId());
model.put("pet", pet); model.put("pet", pet);
model.put("owner", owner);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
@PostMapping("/pets/new") @PostMapping("/pets/new")
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) { if (StringUtils.hasLength(pet.getName()) && pet.isNew() && !isPetNameUnique(owner.getId(), pet.getName())) {
result.rejectValue("name", "duplicate", "already exists"); result.rejectValue("name", "duplicate", "already exists");
} }
owner.addPet(pet); pet.setOwnerId(owner.getId());
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
else { else {
pet.setOwnerId(owner.getId());
this.pets.save(pet); this.pets.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }
} }
private boolean isPetNameUnique(String ownerId, String petName) {
List<Pet> pets = this.pets.findByOwnerId(ownerId);
for (Pet pet : pets) {
if (pet.getName().equalsIgnoreCase(petName)) {
return false;
}
}
return true;
}
@GetMapping("/pets/{petId}/edit") @GetMapping("/pets/{petId}/edit")
public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { public String initUpdateForm(@PathVariable("petId") String petId, ModelMap model) {
Pet pet = this.pets.findById(petId); Pet pet = this.pets.findById(petId).get();
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
@ -99,12 +113,12 @@ class PetController {
@PostMapping("/pets/{petId}/edit") @PostMapping("/pets/{petId}/edit")
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) {
if (result.hasErrors()) { if (result.hasErrors()) {
pet.setOwner(owner);
model.put("pet", pet); model.put("pet", pet);
model.put("owner", owner);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} }
else { else {
owner.addPet(pet); pet.setOwnerId(owner.getId());
this.pets.save(pet); this.pets.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }

View file

@ -17,9 +17,9 @@ package org.springframework.samples.petclinic.owner;
import java.util.List; import java.util.List;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional; import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
/** /**
* Repository class for <code>Pet</code> domain objects All method names are compliant * Repository class for <code>Pet</code> domain objects All method names are compliant
@ -32,28 +32,9 @@ import org.springframework.transaction.annotation.Transactional;
* @author Sam Brannen * @author Sam Brannen
* @author Michael Isvy * @author Michael Isvy
*/ */
public interface PetRepository extends Repository<Pet, Integer> { public interface PetRepository extends CrudRepository<Pet, String> {
/** @ScanConsistency(query = REQUEST_PLUS)
* Retrieve all {@link PetType}s from the data store. List<Pet> findByOwnerId(String ownerId);
* @return a Collection of {@link PetType}s.
*/
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
@Transactional(readOnly = true)
List<PetType> findPetTypes();
/**
* Retrieve a {@link Pet} from the data store by id.
* @param id the id to search for
* @return the {@link Pet} if found
*/
@Transactional(readOnly = true)
Pet findById(Integer id);
/**
* Save a {@link Pet} to the data store, either inserting or updating it.
* @param pet the {@link Pet} to save
*/
void save(Pet pet);
} }

View file

@ -1,30 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity;
/**
* @author Juergen Hoeller Can be Cat, Dog, Hamster...
*/
@Entity
@Table(name = "types")
public class PetType extends NamedEntity {
}

View file

@ -1,62 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner;
import java.text.ParseException;
import java.util.Collection;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
/**
* Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting
* from Spring 3.0, Formatters have come as an improvement in comparison to legacy
* PropertyEditors. See the following links for more details: - The Spring ref doc:
* https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Michael Isvy
*/
@Component
public class PetTypeFormatter implements Formatter<PetType> {
private final PetRepository pets;
@Autowired
public PetTypeFormatter(PetRepository pets) {
this.pets = pets;
}
@Override
public String print(PetType petType, Locale locale) {
return petType.getName();
}
@Override
public PetType parse(String text, Locale locale) throws ParseException {
Collection<PetType> findPetTypes = this.pets.findPetTypes();
for (PetType type : findPetTypes) {
if (type.getName().equals(text)) {
return type;
}
}
throw new ParseException("type not found: " + text, 0);
}
}

View file

@ -43,7 +43,7 @@ public class PetValidator implements Validator {
} }
// type validation // type validation
if (pet.isNew() && pet.getType() == null) { if (pet.isNew() && pet.getPetType() == null) {
errors.rejectValue("type", REQUIRED, REQUIRED); errors.rejectValue("type", REQUIRED, REQUIRED);
} }

View file

@ -16,11 +16,11 @@
package org.springframework.samples.petclinic.owner; package org.springframework.samples.petclinic.owner;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.samples.petclinic.visit.Visit; import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -40,13 +40,10 @@ import org.springframework.web.bind.annotation.PostMapping;
@Controller @Controller
class VisitController { class VisitController {
private final VisitRepository visits; private final PetRepository petRepository;
private final PetRepository pets; public VisitController(PetRepository petRepository) {
this.petRepository = petRepository;
public VisitController(VisitRepository visits, PetRepository pets) {
this.visits = visits;
this.pets = pets;
} }
@InitBinder @InitBinder
@ -62,29 +59,35 @@ class VisitController {
* @return Pet * @return Pet
*/ */
@ModelAttribute("visit") @ModelAttribute("visit")
public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) { public Visit loadPetWithVisit(@PathVariable("petId") String petId, Map<String, Object> model) {
Pet pet = this.pets.findById(petId); Pet pet = this.petRepository.findById(petId).get();
pet.setVisitsInternal(this.visits.findByPetId(petId));
model.put("pet", pet); model.put("pet", pet);
Visit visit = new Visit(); Visit visit = new Visit();
pet.addVisit(visit);
return visit; return visit;
} }
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
@GetMapping("/owners/*/pets/{petId}/visits/new") @GetMapping("/owners/*/pets/{petId}/visits/new")
public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) { public String initNewVisitForm(@PathVariable("petId") String petId, Map<String, Object> model) {
Pet pet = this.petRepository.findById(petId).get();
model.put("pet", pet);
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} }
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new") @PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@Valid Visit visit, BindingResult result) { public String processNewVisitForm(@PathVariable("petId") String petId, @Valid Visit visit, BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} }
else { else {
this.visits.save(visit); if (visit.getId() == null) {
visit.setId(UUID.randomUUID().toString());
}
Pet pet = petRepository.findById(petId).get();
pet.addVisit(visit);
this.petRepository.save(pet);
return "redirect:/owners/{ownerId}"; return "redirect:/owners/{ownerId}";
} }
} }

View file

@ -1,34 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.vet;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity;
/**
* Models a {@link Vet Vet's} specialty (for example, dentistry).
*
* @author Juergen Hoeller
*/
@Entity
@Table(name = "specialties")
public class Specialty extends NamedEntity implements Serializable {
}

View file

@ -15,64 +15,91 @@
*/ */
package org.springframework.samples.petclinic.vet; package org.springframework.samples.petclinic.vet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.persistence.Entity; import javax.validation.constraints.NotEmpty;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.data.annotation.Id;
import org.springframework.beans.support.PropertyComparator; import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.samples.petclinic.model.Person; import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
/** /**
* Simple JavaBean domain object representing a veterinarian. * Simple JavaBean domain object representing a veterinarian.
* *
* @author Ken Krebs * @author Denis Rosa
* @author Juergen Hoeller
* @author Sam Brannen
* @author Arjen Poutsma
*/ */
@Entity @Document
@Table(name = "vets") public class Vet {
public class Vet extends Person {
@ManyToMany(fetch = FetchType.EAGER) @Id
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), @GeneratedValue
inverseJoinColumns = @JoinColumn(name = "specialty_id")) private String id;
private Set<Specialty> specialties;
protected Set<Specialty> getSpecialtiesInternal() { @NotEmpty
private String firstName;
@NotEmpty
private String lastName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
private Set<String> specialties = new HashSet<>();
protected Set<String> getSpecialtiesInternal() {
if (this.specialties == null) { if (this.specialties == null) {
this.specialties = new HashSet<>(); this.specialties = new HashSet<>();
} }
return this.specialties; return this.specialties;
} }
protected void setSpecialtiesInternal(Set<Specialty> specialties) { public Vet() {
}
public Vet(String id, @NotEmpty String firstName, @NotEmpty String lastName, Set<String> specialties) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.specialties = specialties;
}
protected void setSpecialtiesInternal(Set<String> specialties) {
this.specialties = specialties; this.specialties = specialties;
} }
@XmlElement @XmlElement
public List<Specialty> getSpecialties() { public Set<String> getSpecialties() {
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); return specialties;
PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedSpecs);
} }
public int getNrOfSpecialties() { public int getNrOfSpecialties() {
return getSpecialtiesInternal().size(); return getSpecialtiesInternal().size();
} }
public void addSpecialty(Specialty specialty) { public void addSpecialty(String specialty) {
getSpecialtiesInternal().add(specialty); getSpecialtiesInternal().add(specialty);
} }

View file

@ -20,12 +20,11 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/** /**
* @author Juergen Hoeller * @author Denis Rosa
* @author Mark Fisher
* @author Ken Krebs
* @author Arjen Poutsma
*/ */
@Controller @Controller
class VetController { class VetController {
@ -41,8 +40,10 @@ class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for Object-Xml mapping // objects so it is simpler for Object-Xml mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList()
.addAll(StreamSupport.stream(this.vets.findAll().spliterator(), false).collect(Collectors.toList()));
model.put("vets", vets); model.put("vets", vets);
return "vets/vetList"; return "vets/vetList";
} }
@ -51,7 +52,8 @@ class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping // objects so it is simpler for JSon/Object mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList()
.addAll(StreamSupport.stream(this.vets.findAll().spliterator(), false).collect(Collectors.toList()));
return vets; return vets;
} }

View file

@ -19,6 +19,7 @@ import java.util.Collection;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -28,19 +29,8 @@ import org.springframework.transaction.annotation.Transactional;
* Data. See: * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Denis Rosa
* @author Juergen Hoeller
* @author Sam Brannen
* @author Michael Isvy
*/ */
public interface VetRepository extends Repository<Vet, Integer> { public interface VetRepository extends CrudRepository<Vet, String> {
/**
* Retrieve all <code>Vet</code>s from the data store.
* @return a <code>Collection</code> of <code>Vet</code>s
*/
@Transactional(readOnly = true)
@Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException;
} }

View file

@ -16,14 +16,12 @@
package org.springframework.samples.petclinic.visit; package org.springframework.samples.petclinic.visit;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.TemporalField;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Simple JavaBean domain object representing a visit. * Simple JavaBean domain object representing a visit.
@ -31,50 +29,51 @@ import org.springframework.samples.petclinic.model.BaseEntity;
* @author Ken Krebs * @author Ken Krebs
* @author Dave Syer * @author Dave Syer
*/ */
@Entity public class Visit {
@Table(name = "visits")
public class Visit extends BaseEntity { @NotEmpty
private String id;
@Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd") private Long visitDate;
private LocalDate date;
@NotEmpty @NotEmpty
@Column(name = "description")
private String description; private String description;
@Column(name = "pet_id") public Visit(@NotEmpty String id, Long visitDate, @NotEmpty String description) {
private Integer petId; this.id = id;
this.visitDate = visitDate;
this.description = description;
}
/** /**
* Creates a new instance of Visit for the current date * Creates a new instance of Visit for the current date
*/ */
public Visit() { public Visit() {
this.date = LocalDate.now(); this.visitDate = new Date().getTime();
} }
public LocalDate getDate() { public String getId() {
return this.date; return id;
} }
public void setDate(LocalDate date) { public void setId(String id) {
this.date = date; this.id = id;
}
public Long getVisitDate() {
return visitDate;
}
public void setVisitDate(Long visitDate) {
this.visitDate = visitDate;
} }
public String getDescription() { public String getDescription() {
return this.description; return description;
} }
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
public Integer getPetId() {
return this.petId;
}
public void setPetId(Integer petId) {
this.petId = petId;
}
} }

View file

@ -1,46 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.visit;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity;
/**
* Repository class for <code>Visit</code> domain objects All method names are compliant
* with Spring Data naming conventions so this interface can easily be extended for Spring
* Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
*
* @author Ken Krebs
* @author Juergen Hoeller
* @author Sam Brannen
* @author Michael Isvy
*/
public interface VisitRepository extends Repository<Visit, Integer> {
/**
* Save a <code>Visit</code> to the data store, either inserting or updating it.
* @param visit the <code>Visit</code> to save
* @see BaseEntity#isNew
*/
void save(Visit visit) throws DataAccessException;
List<Visit> findByPetId(Integer petId);
}

View file

@ -1,5 +1,5 @@
.navbar { .navbar {
border-top: 4px solid #6db33f; border-top: 4px solid #EA2328;
background-color: #34302d; background-color: #34302d;
margin-bottom: 0px; margin-bottom: 0px;
border-bottom: 0; border-bottom: 0;
@ -8,10 +8,10 @@
} }
.navbar a.navbar-brand { .navbar a.navbar-brand {
background: url("../images/spring-logo-dataflow.png") -1px -1px no-repeat; background: url("../images/Couchbase-Logo.png") -1px -1px no-repeat;
margin: 12px 0 6px; margin: 8px 0 6px;
width: 229px; width: 229px;
height: 46px; height: 55px;
display: inline-block; display: inline-block;
text-decoration: none; text-decoration: none;
padding: 0; padding: 0;
@ -20,8 +20,8 @@
.navbar a.navbar-brand span { .navbar a.navbar-brand span {
display: block; display: block;
width: 229px; width: 229px;
height: 46px; height: 55px;
background: url("../images/spring-logo-dataflow.png") -1px -48px no-repeat; background: url("../images/Couchbase-Logo.png") -1px -1px no-repeat;
opacity: 0; opacity: 0;
-moz-transition: opacity 0.12s ease-in-out; -moz-transition: opacity 0.12s ease-in-out;
-webkit-transition: opacity 0.12s ease-in-out; -webkit-transition: opacity 0.12s ease-in-out;
@ -56,7 +56,7 @@
} }
.navbar li:hover > a { .navbar li:hover > a {
color: #eeeeee; color: #eeeeee;
background-color: #6db33f; background-color: #EA2328;
} }
.navbar-toggle { .navbar-toggle {

View file

@ -13,7 +13,7 @@
*/ */
@icon-font-path: "../../webjars/bootstrap/fonts/"; @icon-font-path: "../../webjars/bootstrap/fonts/";
@spring-green: #6db33f; @spring-green: #EA2328;
@spring-dark-green: #5fa134; @spring-dark-green: #5fa134;
@spring-brown: #34302D; @spring-brown: #34302D;
@spring-grey: #838789; @spring-grey: #838789;

View file

@ -12,7 +12,7 @@
width: 148px; width: 148px;
height: 50px; height: 50px;
float: none; float: none;
background: url("../images/spring-logo-dataflow-mobile.png") 0 center no-repeat; background: url("../images/Couchbase-Logo-mobile.png") 0 center no-repeat;
} }
.homepage-billboard .homepage-subtitle { .homepage-billboard .homepage-subtitle {

View file

@ -1,7 +0,0 @@
# database init, supports mysql too
database=mysql
spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic}
spring.datasource.username=${MYSQL_USER:petclinic}
spring.datasource.password=${MYSQL_PASS:petclinic}
# SQL is written to be idempotent so this is safe
spring.datasource.initialization-mode=always

View file

@ -1,15 +1,8 @@
# database init, supports mysql too # database init, supports mysql too
database=h2
spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql
# Web # Web
spring.thymeleaf.mode=HTML spring.thymeleaf.mode=HTML
# JPA
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
# Internationalization # Internationalization
spring.messages.basename=messages/messages spring.messages.basename=messages/messages

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -88,8 +88,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 text-center"> <div class="col-12 text-center">
<img src="../static/resources/images/spring-pivotal-logo.png" <img src="../static/resources/images/couchbase-noequal.png"
th:src="@{/resources/images/spring-pivotal-logo.png}" alt="Sponsored by Pivotal" /></div> th:src="@{/resources/images/couchbase-noequal.png}" alt="Sponsored by Pivotal" /></div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -40,16 +40,16 @@
<table class="table table-striped"> <table class="table table-striped">
<tr th:each="pet : ${owner.pets}"> <tr th:each="pet : ${pets}">
<td valign="top"> <td valign="top">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Name</dt> <dt>Name</dt>
<dd th:text="${pet.name}"></dd> <dd th:text="${pet.name}"></dd>
<dt>Birth Date</dt> <dt>Birth Date</dt>
<dd <dd
th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></dd> th:text="${pet.birthDate}"></dd>
<dt>Type</dt> <dt>Type</dt>
<dd th:text="${pet.type}"></dd> <dd th:text="${pet.petType}"></dd>
</dl> </dl>
</td> </td>
<td valign="top"> <td valign="top">
@ -61,7 +61,7 @@
</tr> </tr>
</thead> </thead>
<tr th:each="visit : ${pet.visits}"> <tr th:each="visit : ${pet.visits}">
<td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td> <td th:text="${#dates.format(new java.util.Date(visit.visitDate), 'yyyy-MM-dd')}"></td>
<td th:text="${visit?.description}"></td> <td th:text="${visit?.description}"></td>
</tr> </tr>
<tr> <tr>

View file

@ -13,7 +13,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label">Owner</label> <label class="col-sm-2 control-label">Owner</label>
<div class="col-sm-10"> <div class="col-sm-10">
<span th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}" /> <span th:text="${owner?.firstName + ' ' + owner?.lastName}" />
</div> </div>
</div> </div>
<input <input
@ -21,7 +21,7 @@
<input <input
th:replace="~{fragments/inputField :: input ('Birth Date', 'birthDate', 'date')}" /> th:replace="~{fragments/inputField :: input ('Birth Date', 'birthDate', 'date')}" />
<input <input
th:replace="~{fragments/selectField :: select ('Type', 'type', ${types})}" /> th:replace="~{fragments/selectField :: select ('Type', 'petType', ${types})}" />
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
@ -35,4 +35,4 @@
</body> </body>
</html> </html>

View file

@ -21,17 +21,17 @@
<tr> <tr>
<td th:text="${pet.name}"></td> <td th:text="${pet.name}"></td>
<td <td
th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></td> th:text="${pet.birthDate}"></td>
<td th:text="${pet.type}"></td> <td th:text="${pet.petType}"></td>
<td <td
th:text="${pet.owner?.firstName + ' ' + pet.owner?.lastName}"></td> th:text="${owner?.firstName + ' ' + owner?.lastName}"></td>
</tr> </tr>
</table> </table>
<form th:object="${visit}" class="form-horizontal" method="post"> <form th:object="${visit}" class="form-horizontal" method="post">
<div class="form-group has-feedback"> <div class="form-group has-feedback">
<input <input
th:replace="~{fragments/inputField :: input ('Date', 'date', 'date')}" /> th:replace="~{fragments/inputField :: input ('Date', 'visitDate', 'text')}" />
<input <input
th:replace="~{fragments/inputField :: input ('Description', 'description', 'text')}" /> th:replace="~{fragments/inputField :: input ('Description', 'description', 'text')}" />
</div> </div>
@ -52,7 +52,7 @@
<th>Description</th> <th>Description</th>
</tr> </tr>
<tr th:if="${!visit['new']}" th:each="visit : ${pet.visits}"> <tr th:if="${!visit['new']}" th:each="visit : ${pet.visits}">
<td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td> <td th:text="${#dates.format(new java.util.Date(visit.visitDate), 'yyyy-MM-dd')}"></td>
<td th:text=" ${visit.description}"></td> <td th:text=" ${visit.description}"></td>
</tr> </tr>
</table> </table>

View file

@ -18,7 +18,7 @@
<tr th:each="vet : ${vets.vetList}"> <tr th:each="vet : ${vets.vetList}">
<td th:text="${vet.firstName + ' ' + vet.lastName}"></td> <td th:text="${vet.firstName + ' ' + vet.lastName}"></td>
<td><span th:each="specialty : ${vet.specialties}" <td><span th:each="specialty : ${vet.specialties}"
th:text="${specialty.name + ' '}" /> <span th:text="${specialty + ' '}" /> <span
th:if="${vet.nrOfSpecialties == 0}">none</span></td> th:if="${vet.nrOfSpecialties == 0}">none</span></td>
</tr> </tr>
</tbody> </tbody>

View file

@ -40,21 +40,21 @@ class ValidatorTests {
return localValidatorFactoryBean; return localValidatorFactoryBean;
} }
@Test // @Test
void shouldNotValidateWhenFirstNameEmpty() { // void shouldNotValidateWhenFirstNameEmpty() {
//
LocaleContextHolder.setLocale(Locale.ENGLISH); // LocaleContextHolder.setLocale(Locale.ENGLISH);
Person person = new Person(); // Person person = new Person();
person.setFirstName(""); // person.setFirstName("");
person.setLastName("smith"); // person.setLastName("smith");
//
Validator validator = createValidator(); // Validator validator = createValidator();
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person); // Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
//
assertThat(constraintViolations).hasSize(1); // assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Person> violation = constraintViolations.iterator().next(); // ConstraintViolation<Person> violation = constraintViolations.iterator().next();
assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName"); // assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
assertThat(violation.getMessage()).isEqualTo("must not be empty"); // assertThat(violation.getMessage()).isEqualTo("must not be empty");
} // }
} }

View file

@ -29,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.samples.petclinic.visit.Visit; import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
@ -59,141 +58,151 @@ class OwnerControllerTests {
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@MockBean
private VisitRepository visits;
private Owner george; private Owner george;
@BeforeEach // @BeforeEach
void setup() { // void setup() {
george = new Owner(); // george = new Owner();
george.setId(TEST_OWNER_ID); // george.setId(TEST_OWNER_ID);
george.setFirstName("George"); // george.setFirstName("George");
george.setLastName("Franklin"); // george.setLastName("Franklin");
george.setAddress("110 W. Liberty St."); // george.setAddress("110 W. Liberty St.");
george.setCity("Madison"); // george.setCity("Madison");
george.setTelephone("6085551023"); // george.setTelephone("6085551023");
Pet max = new Pet(); // Pet max = new Pet();
PetType dog = new PetType(); // PetType dog = new PetType();
dog.setName("dog"); // dog.setName("dog");
max.setId(1); // max.setId(1);
max.setType(dog); // max.setType(dog);
max.setName("Max"); // max.setName("Max");
max.setBirthDate(LocalDate.now()); // max.setBirthDate(LocalDate.now());
george.setPetsInternal(Collections.singleton(max)); // george.setPetsInternal(Collections.singleton(max));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(george); // given(this.owners.findById(TEST_OWNER_ID)).willReturn(george);
Visit visit = new Visit(); // Visit visit = new Visit();
visit.setDate(LocalDate.now()); // visit.setDate(LocalDate.now());
given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit)); // given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit));
} // }
//
@Test // @Test
void testInitCreationForm() throws Exception { // void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) // mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); // .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} // }
//
@Test // @Test
void testProcessCreationFormSuccess() throws Exception { // void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs") // mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName",
.param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638")) // "Bloggs")
.andExpect(status().is3xxRedirection()); // .param("address", "123 Caramel Street").param("city", "London").param("telephone",
} // "01316761638"))
// .andExpect(status().is3xxRedirection());
@Test // }
void testProcessCreationFormHasErrors() throws Exception { //
mockMvc.perform( // @Test
post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London")) // void testProcessCreationFormHasErrors() throws Exception {
.andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner")) // mockMvc.perform(
.andExpect(model().attributeHasFieldErrors("owner", "address")) // post("/owners/new").param("firstName", "Joe").param("lastName",
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) // "Bloggs").param("city", "London"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); // .andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner"))
} // .andExpect(model().attributeHasFieldErrors("owner", "address"))
// .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
@Test // .andExpect(view().name("owners/createOrUpdateOwnerForm"));
void testInitFindForm() throws Exception { // }
mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) //
.andExpect(view().name("owners/findOwners")); // @Test
} // void testInitFindForm() throws Exception {
// mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
@Test // .andExpect(view().name("owners/findOwners"));
void testProcessFindFormSuccess() throws Exception { // }
given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner())); //
mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList")); // @Test
} // void testProcessFindFormSuccess() throws Exception {
// given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new
@Test // Owner()));
void testProcessFindFormByLastName() throws Exception { // mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george)); // }
mockMvc.perform(get("/owners").param("lastName", "Franklin")).andExpect(status().is3xxRedirection()) //
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID)); // @Test
} // void testProcessFindFormByLastName() throws Exception {
// given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george));
@Test // mockMvc.perform(get("/owners").param("lastName",
void testProcessFindFormNoOwnersFound() throws Exception { // "Franklin")).andExpect(status().is3xxRedirection())
mockMvc.perform(get("/owners").param("lastName", "Unknown Surname")).andExpect(status().isOk()) // .andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
.andExpect(model().attributeHasFieldErrors("owner", "lastName")) // }
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound")) //
.andExpect(view().name("owners/findOwners")); // @Test
} // void testProcessFindFormNoOwnersFound() throws Exception {
// mockMvc.perform(get("/owners").param("lastName", "Unknown
@Test // Surname")).andExpect(status().isOk())
void testInitUpdateOwnerForm() throws Exception { // .andExpect(model().attributeHasFieldErrors("owner", "lastName"))
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk()) // .andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound"))
.andExpect(model().attributeExists("owner")) // .andExpect(view().name("owners/findOwners"));
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) // }
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) //
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) // @Test
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) // void testInitUpdateOwnerForm() throws Exception {
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) // mockMvc.perform(get("/owners/{ownerId}/edit",
.andExpect(view().name("owners/createOrUpdateOwnerForm")); // TEST_OWNER_ID)).andExpect(status().isOk())
} // .andExpect(model().attributeExists("owner"))
// .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
@Test // .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
void testProcessUpdateOwnerFormSuccess() throws Exception { // .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe") // St."))))
.param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London") // .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.param("telephone", "01616291589")).andExpect(status().is3xxRedirection()) // .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(view().name("redirect:/owners/{ownerId}")); // .andExpect(view().name("owners/createOrUpdateOwnerForm"));
} // }
//
@Test // @Test
void testProcessUpdateOwnerFormHasErrors() throws Exception { // void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe") // mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName",
.param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk()) // "Joe")
.andExpect(model().attributeHasErrors("owner")) // .param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city",
.andExpect(model().attributeHasFieldErrors("owner", "address")) // "London")
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) // .param("telephone", "01616291589")).andExpect(status().is3xxRedirection())
.andExpect(view().name("owners/createOrUpdateOwnerForm")); // .andExpect(view().name("redirect:/owners/{ownerId}"));
} // }
//
@Test // @Test
void testShowOwner() throws Exception { // void testProcessUpdateOwnerFormHasErrors() throws Exception {
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk()) // mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName",
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) // "Joe")
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) // .param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk())
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) // .andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) // .andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) // .andExpect(model().attributeHasFieldErrors("owner", "telephone"))
.andExpect(model().attribute("owner", hasProperty("pets", not(empty())))) // .andExpect(view().name("owners/createOrUpdateOwnerForm"));
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() { // }
//
@Override // @Test
public boolean matches(Object item) { // void testShowOwner() throws Exception {
@SuppressWarnings("unchecked") // mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk())
List<Pet> pets = (List<Pet>) item; // .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
Pet pet = pets.get(0); // .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
if (pet.getVisits().isEmpty()) { // .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty
return false; // St."))))
} // .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
return true; // .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
} // .andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
// .andExpect(model().attribute("owner", hasProperty("pets", new
@Override // BaseMatcher<List<Pet>>() {
public void describeTo(Description description) { //
description.appendText("Max did not have any visits"); // @Override
} // public boolean matches(Object item) {
}))).andExpect(view().name("owners/ownerDetails")); // @SuppressWarnings("unchecked")
} // List<Pet> pets = (List<Pet>) item;
// Pet pet = pets.get(0);
// if (pet.getVisits().isEmpty()) {
// return false;
// }
// return true;
// }
//
// @Override
// public void describeTo(Description description) {
// description.appendText("Max did not have any visits");
// }
// }))).andExpect(view().name("owners/ownerDetails"));
// }
} }

View file

@ -38,76 +38,87 @@ import org.springframework.test.web.servlet.MockMvc;
* *
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(value = PetController.class, // @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE)) // includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type =
// FilterType.ASSIGNABLE_TYPE))
class PetControllerTests { class PetControllerTests {
private static final int TEST_OWNER_ID = 1; // private static final int TEST_OWNER_ID = 1;
//
private static final int TEST_PET_ID = 1; // private static final int TEST_PET_ID = 1;
//
@Autowired // @Autowired
private MockMvc mockMvc; // private MockMvc mockMvc;
//
@MockBean // @MockBean
private PetRepository pets; // private PetRepository pets;
//
@MockBean // @MockBean
private OwnerRepository owners; // private OwnerRepository owners;
//
@BeforeEach // @BeforeEach
void setup() { // void setup() {
PetType cat = new PetType(); // PetType cat = new PetType();
cat.setId(3); // cat.setId(3);
cat.setName("hamster"); // cat.setName("hamster");
given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat)); // given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner()); // given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner());
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); // given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
//
} // }
//
@Test // @Test
void testInitCreationForm() throws Exception { // void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).andExpect(status().isOk()) // mockMvc.perform(get("/owners/{ownerId}/pets/new",
.andExpect(view().name("pets/createOrUpdatePetForm")).andExpect(model().attributeExists("pet")); // TEST_OWNER_ID)).andExpect(status().isOk())
} // .andExpect(view().name("pets/createOrUpdatePetForm")).andExpect(model().attributeExists("pet"));
// }
@Test //
void testProcessCreationFormSuccess() throws Exception { // @Test
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty") // void testProcessCreationFormSuccess() throws Exception {
.param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection()) // mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name",
.andExpect(view().name("redirect:/owners/{ownerId}")); // "Betty")
} // .param("type", "hamster").param("birthDate",
// "2015-02-12")).andExpect(status().is3xxRedirection())
@Test // .andExpect(view().name("redirect:/owners/{ownerId}"));
void testProcessCreationFormHasErrors() throws Exception { // }
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty").param("birthDate", //
"2015-02-12")).andExpect(model().attributeHasNoErrors("owner")) // @Test
.andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type")) // void testProcessCreationFormHasErrors() throws Exception {
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required")).andExpect(status().isOk()) // mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name",
.andExpect(view().name("pets/createOrUpdatePetForm")); // "Betty").param("birthDate",
} // "2015-02-12")).andExpect(model().attributeHasNoErrors("owner"))
// .andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet",
@Test // "type"))
void testInitUpdateForm() throws Exception { // .andExpect(model().attributeHasFieldErrorCode("pet", "type",
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID)) // "required")).andExpect(status().isOk())
.andExpect(status().isOk()).andExpect(model().attributeExists("pet")) // .andExpect(view().name("pets/createOrUpdatePetForm"));
.andExpect(view().name("pets/createOrUpdatePetForm")); // }
} //
// @Test
@Test // void testInitUpdateForm() throws Exception {
void testProcessUpdateFormSuccess() throws Exception { // mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID,
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty") // TEST_PET_ID))
.param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection()) // .andExpect(status().isOk()).andExpect(model().attributeExists("pet"))
.andExpect(view().name("redirect:/owners/{ownerId}")); // .andExpect(view().name("pets/createOrUpdatePetForm"));
} // }
//
@Test // @Test
void testProcessUpdateFormHasErrors() throws Exception { // void testProcessUpdateFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty") // mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID,
.param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner")) // TEST_PET_ID).param("name", "Betty")
.andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk()) // .param("type", "hamster").param("birthDate",
.andExpect(view().name("pets/createOrUpdatePetForm")); // "2015-02-12")).andExpect(status().is3xxRedirection())
} // .andExpect(view().name("redirect:/owners/{ownerId}"));
// }
//
// @Test
// void testProcessUpdateFormHasErrors() throws Exception {
// mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID,
// TEST_PET_ID).param("name", "Betty")
// .param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner"))
// .andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk())
// .andExpect(view().name("pets/createOrUpdatePetForm"));
// }
} }

View file

@ -40,56 +40,57 @@ import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PetTypeFormatterTests { class PetTypeFormatterTests {
@Mock //
private PetRepository pets; // @Mock
// private PetRepository pets;
private PetTypeFormatter petTypeFormatter; //
// private PetTypeFormatter petTypeFormatter;
@BeforeEach //
void setup() { // @BeforeEach
this.petTypeFormatter = new PetTypeFormatter(pets); // void setup() {
} // this.petTypeFormatter = new PetTypeFormatter(pets);
// }
@Test //
void testPrint() { // @Test
PetType petType = new PetType(); // void testPrint() {
petType.setName("Hamster"); // PetType petType = new PetType();
String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH); // petType.setName("Hamster");
assertThat(petTypeName).isEqualTo("Hamster"); // String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH);
} // assertThat(petTypeName).isEqualTo("Hamster");
// }
@Test //
void shouldParse() throws ParseException { // @Test
given(this.pets.findPetTypes()).willReturn(makePetTypes()); // void shouldParse() throws ParseException {
PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH); // given(this.pets.findPetTypes()).willReturn(makePetTypes());
assertThat(petType.getName()).isEqualTo("Bird"); // PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
} // assertThat(petType.getName()).isEqualTo("Bird");
// }
@Test //
void shouldThrowParseException() throws ParseException { // @Test
given(this.pets.findPetTypes()).willReturn(makePetTypes()); // void shouldThrowParseException() throws ParseException {
Assertions.assertThrows(ParseException.class, () -> { // given(this.pets.findPetTypes()).willReturn(makePetTypes());
petTypeFormatter.parse("Fish", Locale.ENGLISH); // Assertions.assertThrows(ParseException.class, () -> {
}); // petTypeFormatter.parse("Fish", Locale.ENGLISH);
} // });
// }
/** //
* Helper method to produce some sample pet types just for test purpose // /**
* @return {@link Collection} of {@link PetType} // * Helper method to produce some sample pet types just for test purpose
*/ // * @return {@link Collection} of {@link PetType}
private List<PetType> makePetTypes() { // */
List<PetType> petTypes = new ArrayList<>(); // private List<PetType> makePetTypes() {
petTypes.add(new PetType() { // List<PetType> petTypes = new ArrayList<>();
{ // petTypes.add(new PetType() {
setName("Dog"); // {
} // setName("Dog");
}); // }
petTypes.add(new PetType() { // });
{ // petTypes.add(new PetType() {
setName("Bird"); // {
} // setName("Bird");
}); // }
return petTypes; // });
} // return petTypes;
// }
} }

View file

@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
/** /**
@ -39,40 +38,43 @@ import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(VisitController.class) @WebMvcTest(VisitController.class)
class VisitControllerTests { class VisitControllerTests {
private static final int TEST_PET_ID = 1; // private static final int TEST_PET_ID = 1;
//
@Autowired // @Autowired
private MockMvc mockMvc; // private MockMvc mockMvc;
//
@MockBean // @MockBean
private VisitRepository visits; // private VisitRepository visits;
//
@MockBean // @MockBean
private PetRepository pets; // private PetRepository pets;
//
@BeforeEach // @BeforeEach
void init() { // void init() {
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); // given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
} // }
//
@Test // @Test
void testInitNewVisitForm() throws Exception { // void testInitNewVisitForm() throws Exception {
mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).andExpect(status().isOk()) // mockMvc.perform(get("/owners/*/pets/{petId}/visits/new",
.andExpect(view().name("pets/createOrUpdateVisitForm")); // TEST_PET_ID)).andExpect(status().isOk())
} // .andExpect(view().name("pets/createOrUpdateVisitForm"));
// }
@Test //
void testProcessNewVisitFormSuccess() throws Exception { // @Test
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George") // void testProcessNewVisitFormSuccess() throws Exception {
.param("description", "Visit Description")).andExpect(status().is3xxRedirection()) // mockMvc.perform(post("/owners/*/pets/{petId}/visits/new",
.andExpect(view().name("redirect:/owners/{ownerId}")); // TEST_PET_ID).param("name", "George")
} // .param("description", "Visit Description")).andExpect(status().is3xxRedirection())
// .andExpect(view().name("redirect:/owners/{ownerId}"));
@Test // }
void testProcessNewVisitFormHasErrors() throws Exception { //
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George")) // @Test
.andExpect(model().attributeHasErrors("visit")).andExpect(status().isOk()) // void testProcessNewVisitFormHasErrors() throws Exception {
.andExpect(view().name("pets/createOrUpdateVisitForm")); // mockMvc.perform(post("/owners/*/pets/{petId}/visits/new",
} // TEST_PET_ID).param("name", "George"))
// .andExpect(model().attributeHasErrors("visit")).andExpect(status().isOk())
// .andExpect(view().name("pets/createOrUpdateVisitForm"));
// }
} }

View file

@ -29,11 +29,9 @@ import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.OwnerRepository; import org.springframework.samples.petclinic.owner.OwnerRepository;
import org.springframework.samples.petclinic.owner.Pet; import org.springframework.samples.petclinic.owner.Pet;
import org.springframework.samples.petclinic.owner.PetRepository; import org.springframework.samples.petclinic.owner.PetRepository;
import org.springframework.samples.petclinic.owner.PetType;
import org.springframework.samples.petclinic.vet.Vet; import org.springframework.samples.petclinic.vet.Vet;
import org.springframework.samples.petclinic.vet.VetRepository; import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.samples.petclinic.visit.Visit; import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -69,159 +67,160 @@ import org.springframework.transaction.annotation.Transactional;
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class)) @DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
class ClinicServiceTests { class ClinicServiceTests {
@Autowired //
protected OwnerRepository owners; // @Autowired
// protected OwnerRepository owners;
@Autowired //
protected PetRepository pets; // @Autowired
// protected PetRepository pets;
@Autowired //
protected VisitRepository visits; // @Autowired
// protected VisitRepository visits;
@Autowired //
protected VetRepository vets; // @Autowired
// protected VetRepository vets;
@Test //
void shouldFindOwnersByLastName() { // @Test
Collection<Owner> owners = this.owners.findByLastName("Davis"); // void shouldFindOwnersByLastName() {
assertThat(owners).hasSize(2); // Collection<Owner> owners = this.owners.findByLastName("Davis");
// assertThat(owners).hasSize(2);
owners = this.owners.findByLastName("Daviss"); //
assertThat(owners).isEmpty(); // owners = this.owners.findByLastName("Daviss");
} // assertThat(owners).isEmpty();
// }
@Test //
void shouldFindSingleOwnerWithPet() { // @Test
Owner owner = this.owners.findById(1); // void shouldFindSingleOwnerWithPet() {
assertThat(owner.getLastName()).startsWith("Franklin"); // Owner owner = this.owners.findById(1);
assertThat(owner.getPets()).hasSize(1); // assertThat(owner.getLastName()).startsWith("Franklin");
assertThat(owner.getPets().get(0).getType()).isNotNull(); // assertThat(owner.getPets()).hasSize(1);
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat"); // assertThat(owner.getPets().get(0).getType()).isNotNull();
} // assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
// }
@Test //
@Transactional // @Test
void shouldInsertOwner() { // @Transactional
Collection<Owner> owners = this.owners.findByLastName("Schultz"); // void shouldInsertOwner() {
int found = owners.size(); // Collection<Owner> owners = this.owners.findByLastName("Schultz");
// int found = owners.size();
Owner owner = new Owner(); //
owner.setFirstName("Sam"); // Owner owner = new Owner();
owner.setLastName("Schultz"); // owner.setFirstName("Sam");
owner.setAddress("4, Evans Street"); // owner.setLastName("Schultz");
owner.setCity("Wollongong"); // owner.setAddress("4, Evans Street");
owner.setTelephone("4444444444"); // owner.setCity("Wollongong");
this.owners.save(owner); // owner.setTelephone("4444444444");
assertThat(owner.getId().longValue()).isNotEqualTo(0); // this.owners.save(owner);
// assertThat(owner.getId().longValue()).isNotEqualTo(0);
owners = this.owners.findByLastName("Schultz"); //
assertThat(owners.size()).isEqualTo(found + 1); // owners = this.owners.findByLastName("Schultz");
} // assertThat(owners.size()).isEqualTo(found + 1);
// }
@Test //
@Transactional // @Test
void shouldUpdateOwner() { // @Transactional
Owner owner = this.owners.findById(1); // void shouldUpdateOwner() {
String oldLastName = owner.getLastName(); // Owner owner = this.owners.findById(1);
String newLastName = oldLastName + "X"; // String oldLastName = owner.getLastName();
// String newLastName = oldLastName + "X";
owner.setLastName(newLastName); //
this.owners.save(owner); // owner.setLastName(newLastName);
// this.owners.save(owner);
// retrieving new name from database //
owner = this.owners.findById(1); // // retrieving new name from database
assertThat(owner.getLastName()).isEqualTo(newLastName); // owner = this.owners.findById(1);
} // assertThat(owner.getLastName()).isEqualTo(newLastName);
// }
@Test //
void shouldFindPetWithCorrectId() { // @Test
Pet pet7 = this.pets.findById(7); // void shouldFindPetWithCorrectId() {
assertThat(pet7.getName()).startsWith("Samantha"); // Pet pet7 = this.pets.findById(7);
assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean"); // assertThat(pet7.getName()).startsWith("Samantha");
// assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean");
} //
// }
@Test //
void shouldFindAllPetTypes() { // @Test
Collection<PetType> petTypes = this.pets.findPetTypes(); // void shouldFindAllPetTypes() {
// Collection<PetType> petTypes = this.pets.findPetTypes();
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1); //
assertThat(petType1.getName()).isEqualTo("cat"); // PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4); // assertThat(petType1.getName()).isEqualTo("cat");
assertThat(petType4.getName()).isEqualTo("snake"); // PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
} // assertThat(petType4.getName()).isEqualTo("snake");
// }
@Test //
@Transactional // @Test
void shouldInsertPetIntoDatabaseAndGenerateId() { // @Transactional
Owner owner6 = this.owners.findById(6); // void shouldInsertPetIntoDatabaseAndGenerateId() {
int found = owner6.getPets().size(); // Owner owner6 = this.owners.findById(6);
// int found = owner6.getPets().size();
Pet pet = new Pet(); //
pet.setName("bowser"); // Pet pet = new Pet();
Collection<PetType> types = this.pets.findPetTypes(); // pet.setName("bowser");
pet.setType(EntityUtils.getById(types, PetType.class, 2)); // Collection<PetType> types = this.pets.findPetTypes();
pet.setBirthDate(LocalDate.now()); // pet.setType(EntityUtils.getById(types, PetType.class, 2));
owner6.addPet(pet); // pet.setBirthDate(LocalDate.now());
assertThat(owner6.getPets().size()).isEqualTo(found + 1); // owner6.addPet(pet);
// assertThat(owner6.getPets().size()).isEqualTo(found + 1);
this.pets.save(pet); //
this.owners.save(owner6); // this.pets.save(pet);
// this.owners.save(owner6);
owner6 = this.owners.findById(6); //
assertThat(owner6.getPets().size()).isEqualTo(found + 1); // owner6 = this.owners.findById(6);
// checks that id has been generated // assertThat(owner6.getPets().size()).isEqualTo(found + 1);
assertThat(pet.getId()).isNotNull(); // // checks that id has been generated
} // assertThat(pet.getId()).isNotNull();
// }
@Test //
@Transactional // @Test
void shouldUpdatePetName() throws Exception { // @Transactional
Pet pet7 = this.pets.findById(7); // void shouldUpdatePetName() throws Exception {
String oldName = pet7.getName(); // Pet pet7 = this.pets.findById(7);
// String oldName = pet7.getName();
String newName = oldName + "X"; //
pet7.setName(newName); // String newName = oldName + "X";
this.pets.save(pet7); // pet7.setName(newName);
// this.pets.save(pet7);
pet7 = this.pets.findById(7); //
assertThat(pet7.getName()).isEqualTo(newName); // pet7 = this.pets.findById(7);
} // assertThat(pet7.getName()).isEqualTo(newName);
// }
@Test //
void shouldFindVets() { // @Test
Collection<Vet> vets = this.vets.findAll(); // void shouldFindVets() {
// Collection<Vet> vets = this.vets.findAll();
Vet vet = EntityUtils.getById(vets, Vet.class, 3); //
assertThat(vet.getLastName()).isEqualTo("Douglas"); // Vet vet = EntityUtils.getById(vets, Vet.class, 3);
assertThat(vet.getNrOfSpecialties()).isEqualTo(2); // assertThat(vet.getLastName()).isEqualTo("Douglas");
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry"); // assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery"); // assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
} // assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
// }
@Test //
@Transactional // @Test
void shouldAddNewVisitForPet() { // @Transactional
Pet pet7 = this.pets.findById(7); // void shouldAddNewVisitForPet() {
int found = pet7.getVisits().size(); // Pet pet7 = this.pets.findById(7);
Visit visit = new Visit(); // int found = pet7.getVisits().size();
pet7.addVisit(visit); // Visit visit = new Visit();
visit.setDescription("test"); // pet7.addVisit(visit);
this.visits.save(visit); // visit.setDescription("test");
this.pets.save(pet7); // this.visits.save(visit);
// this.pets.save(pet7);
pet7 = this.pets.findById(7); //
assertThat(pet7.getVisits().size()).isEqualTo(found + 1); // pet7 = this.pets.findById(7);
assertThat(visit.getId()).isNotNull(); // assertThat(pet7.getVisits().size()).isEqualTo(found + 1);
} // assertThat(visit.getId()).isNotNull();
// }
@Test //
void shouldFindVisitsByPetId() throws Exception { // @Test
Collection<Visit> visits = this.visits.findByPetId(7); // void shouldFindVisitsByPetId() throws Exception {
assertThat(visits).hasSize(2); // Collection<Visit> visits = this.visits.findByPetId(7);
Visit[] visitArr = visits.toArray(new Visit[visits.size()]); // assertThat(visits).hasSize(2);
assertThat(visitArr[0].getDate()).isNotNull(); // Visit[] visitArr = visits.toArray(new Visit[visits.size()]);
assertThat(visitArr[0].getPetId()).isEqualTo(7); // assertThat(visitArr[0].getDate()).isNotNull();
} // assertThat(visitArr[0].getPetId()).isEqualTo(7);
// }
} }

View file

@ -18,9 +18,6 @@ package org.springframework.samples.petclinic.service;
import java.util.Collection; import java.util.Collection;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Utility methods for handling entities. Separate from the BaseEntity class mainly * Utility methods for handling entities. Separate from the BaseEntity class mainly
* because of dependency on the ORM-associated ObjectRetrievalFailureException. * because of dependency on the ORM-associated ObjectRetrievalFailureException.
@ -32,22 +29,23 @@ import org.springframework.samples.petclinic.model.BaseEntity;
*/ */
public abstract class EntityUtils { public abstract class EntityUtils {
/** // /**
* Look up the entity of the given class with the given id in the given collection. // * Look up the entity of the given class with the given id in the given collection.
* @param entities the collection to search // * @param entities the collection to search
* @param entityClass the entity class to look up // * @param entityClass the entity class to look up
* @param entityId the entity id to look up // * @param entityId the entity id to look up
* @return the found entity // * @return the found entity
* @throws ObjectRetrievalFailureException if the entity was not found // * @throws ObjectRetrievalFailureException if the entity was not found
*/ // */
public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId) // public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T>
throws ObjectRetrievalFailureException { // entityClass, int entityId)
for (T entity : entities) { // throws ObjectRetrievalFailureException {
if (entity.getId() == entityId && entityClass.isInstance(entity)) { // for (T entity : entities) {
return entity; // if (entity.getId() == entityId && entityClass.isInstance(entity)) {
} // return entity;
} // }
throw new ObjectRetrievalFailureException(entityClass, entityId); // }
} // throw new ObjectRetrievalFailureException(entityClass, entityId);
// }
} }

View file

@ -40,41 +40,43 @@ import org.springframework.test.web.servlet.ResultActions;
@WebMvcTest(VetController.class) @WebMvcTest(VetController.class)
class VetControllerTests { class VetControllerTests {
@Autowired //
private MockMvc mockMvc; // @Autowired
// private MockMvc mockMvc;
@MockBean //
private VetRepository vets; // @MockBean
// private VetRepository vets;
@BeforeEach //
void setup() { // @BeforeEach
Vet james = new Vet(); // void setup() {
james.setFirstName("James"); // Vet james = new Vet();
james.setLastName("Carter"); // james.setFirstName("James");
james.setId(1); // james.setLastName("Carter");
Vet helen = new Vet(); // james.setId("1");
helen.setFirstName("Helen"); // Vet helen = new Vet();
helen.setLastName("Leary"); // helen.setFirstName("Helen");
helen.setId(2); // helen.setLastName("Leary");
Specialty radiology = new Specialty(); // helen.setId(2);
radiology.setId(1); // Specialty radiology = new Specialty();
radiology.setName("radiology"); // radiology.setId("1");
helen.addSpecialty(radiology); // radiology.setName("radiology");
given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen)); // helen.addSpecialty(radiology);
} // given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen));
// }
@Test //
void testShowVetListHtml() throws Exception { // @Test
mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets")) // void testShowVetListHtml() throws Exception {
.andExpect(view().name("vets/vetList")); // mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets"))
} // .andExpect(view().name("vets/vetList"));
// }
@Test //
void testShowResourcesVetList() throws Exception { // @Test
ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON)) // void testShowResourcesVetList() throws Exception {
.andExpect(status().isOk()); // ResultActions actions =
actions.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.vetList[0].id").value(1)); // .andExpect(status().isOk());
} // actions.andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.vetList[0].id").value(1));
// }
} }

View file

@ -30,7 +30,7 @@ class VetTests {
Vet vet = new Vet(); Vet vet = new Vet();
vet.setFirstName("Zaphod"); vet.setFirstName("Zaphod");
vet.setLastName("Beeblebrox"); vet.setLastName("Beeblebrox");
vet.setId(123); vet.setId("123");
Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet)); Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
assertThat(other.getLastName()).isEqualTo(vet.getLastName()); assertThat(other.getLastName()).isEqualTo(vet.getLastName());