first commit
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||
<suppressions>
|
||||
<suppress files="node_modules/.*" checks=".*"/>
|
||||
<suppress files="node/.*" checks=".*"/>
|
||||
<suppress files="build/.*" checks=".*"/>
|
||||
<suppress files="[\\/]build.log" checks="NoHttp"/>
|
||||
<suppress files=".+\.(jar|git|ico|p12|gif|jks|jpg|svg)" checks="NoHttp"/>
|
||||
</suppressions>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_2.dtd">
|
||||
<module name="com.puppycrawl.tools.checkstyle.Checker">
|
||||
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/>
|
||||
</module>
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
|
||||
/**
|
||||
* PetClinic Spring Boot Application.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ImportRuntimeHints(PetClinicRuntimeHints.class)
|
||||
public class PetClinicApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PetClinicApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.samples.petclinic.model.BaseEntity;
|
||||
import org.springframework.samples.petclinic.model.Person;
|
||||
import org.springframework.samples.petclinic.vet.Vet;
|
||||
|
||||
public class PetClinicRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerPattern("db/*"); // https://github.com/spring-projects/spring-boot/issues/32654
|
||||
hints.resources().registerPattern("messages/*");
|
||||
hints.resources().registerPattern("META-INF/resources/webjars/*");
|
||||
hints.resources().registerPattern("mysql-default-conf");
|
||||
hints.serialization().registerType(BaseEntity.class);
|
||||
hints.serialization().registerType(Person.class);
|
||||
hints.serialization().registerType(Vet.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 jakarta.persistence.Column;
|
||||
import jakarta.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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Simple JavaBean domain object representing an person.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public class Person extends BaseEntity {
|
||||
|
||||
@Column(name = "first_name")
|
||||
@NotBlank
|
||||
private String firstName;
|
||||
|
||||
@Column(name = "last_name")
|
||||
@NotBlank
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.samples.petclinic.model.Person;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Digits;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Simple JavaBean domain object representing an owner.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Michael Isvy
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "owners")
|
||||
public class Owner extends Person {
|
||||
|
||||
@Column(name = "address")
|
||||
@NotBlank
|
||||
private String address;
|
||||
|
||||
@Column(name = "city")
|
||||
@NotBlank
|
||||
private String city;
|
||||
|
||||
@Column(name = "telephone")
|
||||
@NotBlank
|
||||
@Digits(fraction = 0, integer = 10)
|
||||
private String telephone;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "owner_id")
|
||||
@OrderBy("name")
|
||||
private List<Pet> pets = new ArrayList<>();
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return this.city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getTelephone() {
|
||||
return this.telephone;
|
||||
}
|
||||
|
||||
public void setTelephone(String telephone) {
|
||||
this.telephone = telephone;
|
||||
}
|
||||
|
||||
public List<Pet> getPets() {
|
||||
return this.pets;
|
||||
}
|
||||
|
||||
public void addPet(Pet pet) {
|
||||
if (pet.isNew()) {
|
||||
getPets().add(pet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Pet with the given name, or null if none found for this Owner.
|
||||
* @param name to test
|
||||
* @return a pet if pet name is already in use
|
||||
*/
|
||||
public Pet getPet(String name) {
|
||||
return getPet(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Pet with the given id, or null if none found for this Owner.
|
||||
* @param id to test
|
||||
* @return a pet if pet id is already in use
|
||||
*/
|
||||
public Pet getPet(Integer id) {
|
||||
for (Pet pet : getPets()) {
|
||||
if (!pet.isNew()) {
|
||||
Integer compId = pet.getId();
|
||||
if (compId.equals(id)) {
|
||||
return pet;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Pet with the given name, or null if none found for this Owner.
|
||||
* @param name to test
|
||||
* @return a pet if pet name is already in use
|
||||
*/
|
||||
public Pet getPet(String name, boolean ignoreNew) {
|
||||
name = name.toLowerCase();
|
||||
for (Pet pet : getPets()) {
|
||||
String compName = pet.getName();
|
||||
if (compName != null && compName.equalsIgnoreCase(name)) {
|
||||
if (!ignoreNew || !pet.isNew()) {
|
||||
return pet;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("id", this.getId())
|
||||
.append("new", this.isNew())
|
||||
.append("lastName", this.getLastName())
|
||||
.append("firstName", this.getFirstName())
|
||||
.append("address", this.address)
|
||||
.append("city", this.city)
|
||||
.append("telephone", this.telephone)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Visit} to the {@link Pet} with the given identifier.
|
||||
* @param petId the identifier of the {@link Pet}, must not be {@literal null}.
|
||||
* @param visit the visit to add, must not be {@literal null}.
|
||||
*/
|
||||
public void addVisit(Integer petId, Visit visit) {
|
||||
|
||||
Assert.notNull(petId, "Pet identifier must not be null!");
|
||||
Assert.notNull(visit, "Visit must not be null!");
|
||||
|
||||
Pet pet = getPet(petId);
|
||||
|
||||
Assert.notNull(pet, "Invalid Pet identifier!");
|
||||
|
||||
pet.addVisit(visit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
* @author Arjen Poutsma
|
||||
* @author Michael Isvy
|
||||
*/
|
||||
@Controller
|
||||
class OwnerController {
|
||||
|
||||
private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
|
||||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
public OwnerController(OwnerRepository clinicService) {
|
||||
this.owners = clinicService;
|
||||
}
|
||||
|
||||
@InitBinder
|
||||
public void setAllowedFields(WebDataBinder dataBinder) {
|
||||
dataBinder.setDisallowedFields("id");
|
||||
}
|
||||
|
||||
@ModelAttribute("owner")
|
||||
public Owner findOwner(@PathVariable(name = "ownerId", required = false) Integer ownerId) {
|
||||
return ownerId == null ? new Owner() : this.owners.findById(ownerId);
|
||||
}
|
||||
|
||||
@GetMapping("/owners/new")
|
||||
public String initCreationForm(Map<String, Object> model) {
|
||||
Owner owner = new Owner();
|
||||
model.put("owner", owner);
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
@PostMapping("/owners/new")
|
||||
public String processCreationForm(@Valid Owner owner, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
this.owners.save(owner);
|
||||
return "redirect:/owners/" + owner.getId();
|
||||
}
|
||||
|
||||
@GetMapping("/owners/find")
|
||||
public String initFindForm() {
|
||||
return "owners/findOwners";
|
||||
}
|
||||
|
||||
@GetMapping("/owners")
|
||||
public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
|
||||
Model model) {
|
||||
// allow parameterless GET request for /owners to return all records
|
||||
if (owner.getLastName() == null) {
|
||||
owner.setLastName(""); // empty string signifies broadest possible search
|
||||
}
|
||||
|
||||
// find owners by last name
|
||||
Page<Owner> ownersResults = findPaginatedForOwnersLastName(page, owner.getLastName());
|
||||
if (ownersResults.isEmpty()) {
|
||||
// no owners found
|
||||
result.rejectValue("lastName", "notFound", "not found");
|
||||
return "owners/findOwners";
|
||||
}
|
||||
|
||||
if (ownersResults.getTotalElements() == 1) {
|
||||
// 1 owner found
|
||||
owner = ownersResults.iterator().next();
|
||||
return "redirect:/owners/" + owner.getId();
|
||||
}
|
||||
|
||||
// multiple owners found
|
||||
return addPaginationModel(page, model, ownersResults);
|
||||
}
|
||||
|
||||
private String addPaginationModel(int page, Model model, Page<Owner> paginated) {
|
||||
List<Owner> listOwners = paginated.getContent();
|
||||
model.addAttribute("currentPage", page);
|
||||
model.addAttribute("totalPages", paginated.getTotalPages());
|
||||
model.addAttribute("totalItems", paginated.getTotalElements());
|
||||
model.addAttribute("listOwners", listOwners);
|
||||
return "owners/ownersList";
|
||||
}
|
||||
|
||||
private Page<Owner> findPaginatedForOwnersLastName(int page, String lastname) {
|
||||
int pageSize = 5;
|
||||
Pageable pageable = PageRequest.of(page - 1, pageSize);
|
||||
return owners.findByLastName(lastname, pageable);
|
||||
}
|
||||
|
||||
@GetMapping("/owners/{ownerId}/edit")
|
||||
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
model.addAttribute(owner);
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
@PostMapping("/owners/{ownerId}/edit")
|
||||
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
|
||||
@PathVariable("ownerId") int ownerId) {
|
||||
if (result.hasErrors()) {
|
||||
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
owner.setId(ownerId);
|
||||
this.owners.save(owner);
|
||||
return "redirect:/owners/{ownerId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom handler for displaying an owner.
|
||||
* @param ownerId the ID of the owner to display
|
||||
* @return a ModelMap with the model attributes for the view
|
||||
*/
|
||||
@GetMapping("/owners/{ownerId}")
|
||||
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
|
||||
ModelAndView mav = new ModelAndView("owners/ownerDetails");
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
mav.addObject(owner);
|
||||
return mav;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Repository class for <code>Owner</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 OwnerRepository extends Repository<Owner, Integer> {
|
||||
|
||||
/**
|
||||
* Retrieve all {@link PetType}s from the data store.
|
||||
* @return a Collection of {@link PetType}s.
|
||||
*/
|
||||
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
|
||||
@Transactional(readOnly = true)
|
||||
List<PetType> findPetTypes();
|
||||
|
||||
/**
|
||||
* Retrieve {@link Owner}s from the data store by last name, returning all owners
|
||||
* whose last name <i>starts</i> with the given name.
|
||||
* @param lastName Value to search for
|
||||
* @return a Collection of matching {@link Owner}s (or an empty Collection if none
|
||||
* found)
|
||||
*/
|
||||
|
||||
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName% ")
|
||||
@Transactional(readOnly = true)
|
||||
Page<Owner> findByLastName(@Param("lastName") String lastName, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Returns all the owners from data store
|
||||
**/
|
||||
@Query("SELECT owner FROM Owner owner")
|
||||
@Transactional(readOnly = true)
|
||||
Page<Owner> findAll(Pageable pageable);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.samples.petclinic.model.NamedEntity;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* Simple business object representing a pet.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "pets")
|
||||
public class Pet extends NamedEntity {
|
||||
|
||||
@Column(name = "birth_date")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "type_id")
|
||||
private PetType type;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "pet_id")
|
||||
@OrderBy("visit_date ASC")
|
||||
private Set<Visit> visits = new LinkedHashSet<>();
|
||||
|
||||
public void setBirthDate(LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
public LocalDate getBirthDate() {
|
||||
return this.birthDate;
|
||||
}
|
||||
|
||||
public PetType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(PetType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Collection<Visit> getVisits() {
|
||||
return this.visits;
|
||||
}
|
||||
|
||||
public void addVisit(Visit visit) {
|
||||
getVisits().add(visit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/owners/{ownerId}")
|
||||
class PetController {
|
||||
|
||||
private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
|
||||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
public PetController(OwnerRepository owners) {
|
||||
this.owners = owners;
|
||||
}
|
||||
|
||||
@ModelAttribute("types")
|
||||
public Collection<PetType> populatePetTypes() {
|
||||
return this.owners.findPetTypes();
|
||||
}
|
||||
|
||||
@ModelAttribute("owner")
|
||||
public Owner findOwner(@PathVariable("ownerId") int ownerId) {
|
||||
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
if (owner == null) {
|
||||
throw new IllegalArgumentException("Owner ID not found: " + ownerId);
|
||||
}
|
||||
return owner;
|
||||
}
|
||||
|
||||
@ModelAttribute("pet")
|
||||
public Pet findPet(@PathVariable("ownerId") int ownerId,
|
||||
@PathVariable(name = "petId", required = false) Integer petId) {
|
||||
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
if (owner == null) {
|
||||
throw new IllegalArgumentException("Owner ID not found: " + ownerId);
|
||||
}
|
||||
return petId == null ? new Pet() : owner.getPet(petId);
|
||||
}
|
||||
|
||||
@InitBinder("owner")
|
||||
public void initOwnerBinder(WebDataBinder dataBinder) {
|
||||
dataBinder.setDisallowedFields("id");
|
||||
}
|
||||
|
||||
@InitBinder("pet")
|
||||
public void initPetBinder(WebDataBinder dataBinder) {
|
||||
dataBinder.setValidator(new PetValidator());
|
||||
}
|
||||
|
||||
@GetMapping("/pets/new")
|
||||
public String initCreationForm(Owner owner, ModelMap model) {
|
||||
Pet pet = new Pet();
|
||||
owner.addPet(pet);
|
||||
model.put("pet", pet);
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
@PostMapping("/pets/new")
|
||||
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
|
||||
if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
|
||||
result.rejectValue("name", "duplicate", "already exists");
|
||||
}
|
||||
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
|
||||
result.rejectValue("birthDate", "typeMismatch.birthDate");
|
||||
}
|
||||
|
||||
owner.addPet(pet);
|
||||
if (result.hasErrors()) {
|
||||
model.put("pet", pet);
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
this.owners.save(owner);
|
||||
return "redirect:/owners/{ownerId}";
|
||||
}
|
||||
|
||||
@GetMapping("/pets/{petId}/edit")
|
||||
public String initUpdateForm(Owner owner, @PathVariable("petId") int petId, ModelMap model) {
|
||||
Pet pet = owner.getPet(petId);
|
||||
model.put("pet", pet);
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
@PostMapping("/pets/{petId}/edit")
|
||||
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) {
|
||||
|
||||
String petName = pet.getName();
|
||||
|
||||
// checking if the pet name already exist for the owner
|
||||
if (StringUtils.hasText(petName)) {
|
||||
Pet existingPet = owner.getPet(petName.toLowerCase(), false);
|
||||
if (existingPet != null && existingPet.getId() != pet.getId()) {
|
||||
result.rejectValue("name", "duplicate", "already exists");
|
||||
}
|
||||
}
|
||||
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
|
||||
result.rejectValue("birthDate", "typeMismatch.birthDate");
|
||||
}
|
||||
|
||||
if (result.hasErrors()) {
|
||||
model.put("pet", pet);
|
||||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
owner.addPet(pet);
|
||||
this.owners.save(owner);
|
||||
return "redirect:/owners/{ownerId}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 org.springframework.samples.petclinic.model.NamedEntity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller Can be Cat, Dog, Hamster...
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "types")
|
||||
public class PetType extends NamedEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 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 OwnerRepository owners;
|
||||
|
||||
@Autowired
|
||||
public PetTypeFormatter(OwnerRepository owners) {
|
||||
this.owners = owners;
|
||||
}
|
||||
|
||||
@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.owners.findPetTypes();
|
||||
for (PetType type : findPetTypes) {
|
||||
if (type.getName().equals(text)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new ParseException("type not found: " + text, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* <code>Validator</code> for <code>Pet</code> forms.
|
||||
* <p>
|
||||
* We're not using Bean Validation annotations here because it is easier to define such
|
||||
* validation rule in Java.
|
||||
* </p>
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class PetValidator implements Validator {
|
||||
|
||||
private static final String REQUIRED = "required";
|
||||
|
||||
@Override
|
||||
public void validate(Object obj, Errors errors) {
|
||||
Pet pet = (Pet) obj;
|
||||
String name = pet.getName();
|
||||
// name validation
|
||||
if (!StringUtils.hasText(name)) {
|
||||
errors.rejectValue("name", REQUIRED, REQUIRED);
|
||||
}
|
||||
|
||||
// type validation
|
||||
if (pet.isNew() && pet.getType() == null) {
|
||||
errors.rejectValue("type", REQUIRED, REQUIRED);
|
||||
}
|
||||
|
||||
// birth date validation
|
||||
if (pet.getBirthDate() == null) {
|
||||
errors.rejectValue("birthDate", REQUIRED, REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This Validator validates *just* Pet instances
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return Pet.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.time.LocalDate;
|
||||
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.samples.petclinic.model.BaseEntity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Simple JavaBean domain object representing a visit.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "visits")
|
||||
public class Visit extends BaseEntity {
|
||||
|
||||
@Column(name = "visit_date")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate date;
|
||||
|
||||
@NotBlank
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Visit for the current date
|
||||
*/
|
||||
public Visit() {
|
||||
this.date = LocalDate.now();
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Ken Krebs
|
||||
* @author Arjen Poutsma
|
||||
* @author Michael Isvy
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Controller
|
||||
class VisitController {
|
||||
|
||||
private final OwnerRepository owners;
|
||||
|
||||
public VisitController(OwnerRepository owners) {
|
||||
this.owners = owners;
|
||||
}
|
||||
|
||||
@InitBinder
|
||||
public void setAllowedFields(WebDataBinder dataBinder) {
|
||||
dataBinder.setDisallowedFields("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before each and every @RequestMapping annotated method. 2 goals: - Make sure
|
||||
* we always have fresh data - Since we do not use the session scope, make sure that
|
||||
* Pet object always has an id (Even though id is not part of the form fields)
|
||||
* @param petId
|
||||
* @return Pet
|
||||
*/
|
||||
@ModelAttribute("visit")
|
||||
public Visit loadPetWithVisit(@PathVariable("ownerId") int ownerId, @PathVariable("petId") int petId,
|
||||
Map<String, Object> model) {
|
||||
Owner owner = this.owners.findById(ownerId);
|
||||
|
||||
Pet pet = owner.getPet(petId);
|
||||
model.put("pet", pet);
|
||||
model.put("owner", owner);
|
||||
|
||||
Visit visit = new Visit();
|
||||
pet.addVisit(visit);
|
||||
return visit;
|
||||
}
|
||||
|
||||
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is
|
||||
// called
|
||||
@GetMapping("/owners/{ownerId}/pets/{petId}/visits/new")
|
||||
public String initNewVisitForm() {
|
||||
return "pets/createOrUpdateVisitForm";
|
||||
}
|
||||
|
||||
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is
|
||||
// called
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
|
||||
public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable int petId, @Valid Visit visit,
|
||||
BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
return "pets/createOrUpdateVisitForm";
|
||||
}
|
||||
|
||||
owner.addVisit(petId, visit);
|
||||
this.owners.save(owner);
|
||||
return "redirect:/owners/{ownerId}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.system;
|
||||
|
||||
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.cache.configuration.MutableConfiguration;
|
||||
|
||||
/**
|
||||
* Cache configuration intended for caches providing the JCache API. This configuration
|
||||
* creates the used cache for the application and enables statistics that become
|
||||
* accessible via JMX.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableCaching
|
||||
class CacheConfiguration {
|
||||
|
||||
@Bean
|
||||
public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
|
||||
return cm -> cm.createCache("vets", cacheConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple configuration that enable statistics via the JCache programmatic
|
||||
* configuration API.
|
||||
* <p>
|
||||
* Within the configuration object that is provided by the JCache API standard, there
|
||||
* is only a very limited set of configuration options. The really relevant
|
||||
* configuration options (like the size limit) must be set via a configuration
|
||||
* mechanism that is provided by the selected JCache implementation.
|
||||
*/
|
||||
private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() {
|
||||
return new MutableConfiguration<>().setStatisticsEnabled(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.system;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* Controller used to showcase what happens when an exception is thrown
|
||||
*
|
||||
* @author Michael Isvy
|
||||
* <p/>
|
||||
* Also see how a view that resolves to "error" has been added ("error.html").
|
||||
*/
|
||||
@Controller
|
||||
class CrashController {
|
||||
|
||||
@GetMapping("/oups")
|
||||
public String triggerException() {
|
||||
throw new RuntimeException(
|
||||
"Expected: controller used to showcase what " + "happens when an exception is thrown");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.system;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
class WelcomeController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String welcome() {
|
||||
return "welcome";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 org.springframework.samples.petclinic.model.NamedEntity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* Models a {@link Vet Vet's} specialty (for example, dentistry).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "specialties")
|
||||
public class Specialty extends NamedEntity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.support.MutableSortDefinition;
|
||||
import org.springframework.beans.support.PropertyComparator;
|
||||
import org.springframework.samples.petclinic.model.Person;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
|
||||
/**
|
||||
* Simple JavaBean domain object representing a veterinarian.
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "vets")
|
||||
public class Vet extends Person {
|
||||
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
|
||||
private Set<Specialty> specialties;
|
||||
|
||||
protected Set<Specialty> getSpecialtiesInternal() {
|
||||
if (this.specialties == null) {
|
||||
this.specialties = new HashSet<>();
|
||||
}
|
||||
return this.specialties;
|
||||
}
|
||||
|
||||
protected void setSpecialtiesInternal(Set<Specialty> specialties) {
|
||||
this.specialties = specialties;
|
||||
}
|
||||
|
||||
@XmlElement
|
||||
public List<Specialty> getSpecialties() {
|
||||
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
|
||||
PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
|
||||
return Collections.unmodifiableList(sortedSpecs);
|
||||
}
|
||||
|
||||
public int getNrOfSpecialties() {
|
||||
return getSpecialtiesInternal().size();
|
||||
}
|
||||
|
||||
public void addSpecialty(Specialty specialty) {
|
||||
getSpecialtiesInternal().add(specialty);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
* @author Ken Krebs
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@Controller
|
||||
class VetController {
|
||||
|
||||
private final VetRepository vetRepository;
|
||||
|
||||
public VetController(VetRepository clinicService) {
|
||||
this.vetRepository = clinicService;
|
||||
}
|
||||
|
||||
@GetMapping("/vets.html")
|
||||
public String showVetList(@RequestParam(defaultValue = "1") int page, Model model) {
|
||||
// Here we are returning an object of type 'Vets' rather than a collection of Vet
|
||||
// objects so it is simpler for Object-Xml mapping
|
||||
Vets vets = new Vets();
|
||||
Page<Vet> paginated = findPaginated(page);
|
||||
vets.getVetList().addAll(paginated.toList());
|
||||
return addPaginationModel(page, paginated, model);
|
||||
}
|
||||
|
||||
private String addPaginationModel(int page, Page<Vet> paginated, Model model) {
|
||||
List<Vet> listVets = paginated.getContent();
|
||||
model.addAttribute("currentPage", page);
|
||||
model.addAttribute("totalPages", paginated.getTotalPages());
|
||||
model.addAttribute("totalItems", paginated.getTotalElements());
|
||||
model.addAttribute("listVets", listVets);
|
||||
return "vets/vetList";
|
||||
}
|
||||
|
||||
private Page<Vet> findPaginated(int page) {
|
||||
int pageSize = 5;
|
||||
Pageable pageable = PageRequest.of(page - 1, pageSize);
|
||||
return vetRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@GetMapping({ "/vets" })
|
||||
public @ResponseBody Vets showResourcesVetList() {
|
||||
// Here we are returning an object of type 'Vets' rather than a collection of Vet
|
||||
// objects so it is simpler for JSon/Object mapping
|
||||
Vets vets = new Vets();
|
||||
vets.getVetList().addAll(this.vetRepository.findAll());
|
||||
return vets;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Repository class for <code>Vet</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 VetRepository extends Repository<Vet, Integer> {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Retrieve all <code>Vet</code>s from data store in Pages
|
||||
* @param pageable
|
||||
* @return
|
||||
* @throws DataAccessException
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
@Cacheable("vets")
|
||||
Page<Vet> findAll(Pageable pageable) throws DataAccessException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Simple domain object representing a list of veterinarians. Mostly here to be used for
|
||||
* the 'vets' {@link org.springframework.web.servlet.view.xml.MarshallingView}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlRootElement
|
||||
public class Vets {
|
||||
|
||||
private List<Vet> vets;
|
||||
|
||||
@XmlElement
|
||||
public List<Vet> getVetList() {
|
||||
if (vets == null) {
|
||||
vets = new ArrayList<>();
|
||||
}
|
||||
return vets;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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.sql.init.mode=always
|
||||
@@ -0,0 +1,6 @@
|
||||
database=postgres
|
||||
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost/petclinic}
|
||||
spring.datasource.username=${POSTGRES_USER:petclinic}
|
||||
spring.datasource.password=${POSTGRES_PASS:petclinic}
|
||||
# SQL is written to be idempotent so this is safe
|
||||
spring.sql.init.mode=always
|
||||
@@ -0,0 +1,25 @@
|
||||
# database init, supports mysql too
|
||||
database=h2
|
||||
spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql
|
||||
spring.sql.init.data-locations=classpath*:db/${database}/data.sql
|
||||
|
||||
# Web
|
||||
spring.thymeleaf.mode=HTML
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.jpa.open-in-view=true
|
||||
|
||||
# Internationalization
|
||||
spring.messages.basename=messages/messages
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=*
|
||||
|
||||
# Logging
|
||||
logging.level.org.springframework=INFO
|
||||
# logging.level.org.springframework.web=DEBUG
|
||||
# logging.level.org.springframework.context.annotation=TRACE
|
||||
|
||||
# Maximum time static resources should be cached
|
||||
spring.web.resources.cache.cachecontrol.max-age=12h
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
|\ _,,,--,,_
|
||||
/,`.-'`' ._ \-;;,_
|
||||
_______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______
|
||||
| | '---''(_/._)-'(_\_) | | | | | | | | |
|
||||
| _ | ___|_ _| | | | | |_| | | | __ _ _
|
||||
| |_| | |___ | | | | | | | | | | \ \ \ \
|
||||
| ___| ___| | | | _| |___| | _ | | _| \ \ \ \
|
||||
| | | |___ | | | |_| | | | | | | |_ ) ) ) )
|
||||
|___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / /
|
||||
==================================================================/_/_/_/
|
||||
|
||||
:: Built with Spring Boot :: ${spring-boot.version}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
INSERT INTO vets VALUES (default, 'James', 'Carter');
|
||||
INSERT INTO vets VALUES (default, 'Helen', 'Leary');
|
||||
INSERT INTO vets VALUES (default, 'Linda', 'Douglas');
|
||||
INSERT INTO vets VALUES (default, 'Rafael', 'Ortega');
|
||||
INSERT INTO vets VALUES (default, 'Henry', 'Stevens');
|
||||
INSERT INTO vets VALUES (default, 'Sharon', 'Jenkins');
|
||||
|
||||
INSERT INTO specialties VALUES (default, 'radiology');
|
||||
INSERT INTO specialties VALUES (default, 'surgery');
|
||||
INSERT INTO specialties VALUES (default, 'dentistry');
|
||||
|
||||
INSERT INTO vet_specialties VALUES (2, 1);
|
||||
INSERT INTO vet_specialties VALUES (3, 2);
|
||||
INSERT INTO vet_specialties VALUES (3, 3);
|
||||
INSERT INTO vet_specialties VALUES (4, 2);
|
||||
INSERT INTO vet_specialties VALUES (5, 1);
|
||||
|
||||
INSERT INTO types VALUES (default, 'cat');
|
||||
INSERT INTO types VALUES (default, 'dog');
|
||||
INSERT INTO types VALUES (default, 'lizard');
|
||||
INSERT INTO types VALUES (default, 'snake');
|
||||
INSERT INTO types VALUES (default, 'bird');
|
||||
INSERT INTO types VALUES (default, 'hamster');
|
||||
|
||||
INSERT INTO owners VALUES (default, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
|
||||
INSERT INTO owners VALUES (default, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
|
||||
INSERT INTO owners VALUES (default, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
|
||||
INSERT INTO owners VALUES (default, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
|
||||
INSERT INTO owners VALUES (default, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
|
||||
INSERT INTO owners VALUES (default, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
|
||||
INSERT INTO owners VALUES (default, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
|
||||
INSERT INTO owners VALUES (default, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
|
||||
INSERT INTO owners VALUES (default, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
|
||||
INSERT INTO owners VALUES (default, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||
|
||||
INSERT INTO pets VALUES (default, 'Leo', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (default, 'Basil', '2012-08-06', 6, 2);
|
||||
INSERT INTO pets VALUES (default, 'Rosy', '2011-04-17', 2, 3);
|
||||
INSERT INTO pets VALUES (default, 'Jewel', '2010-03-07', 2, 3);
|
||||
INSERT INTO pets VALUES (default, 'Iggy', '2010-11-30', 3, 4);
|
||||
INSERT INTO pets VALUES (default, 'George', '2010-01-20', 4, 5);
|
||||
INSERT INTO pets VALUES (default, 'Samantha', '2012-09-04', 1, 6);
|
||||
INSERT INTO pets VALUES (default, 'Max', '2012-09-04', 1, 6);
|
||||
INSERT INTO pets VALUES (default, 'Lucky', '2011-08-06', 5, 7);
|
||||
INSERT INTO pets VALUES (default, 'Mulligan', '2007-02-24', 2, 8);
|
||||
INSERT INTO pets VALUES (default, 'Freddy', '2010-03-09', 5, 9);
|
||||
INSERT INTO pets VALUES (default, 'Lucky', '2010-06-24', 2, 10);
|
||||
INSERT INTO pets VALUES (default, 'Sly', '2012-06-08', 1, 10);
|
||||
|
||||
INSERT INTO visits VALUES (default, 7, '2013-01-01', 'rabies shot');
|
||||
INSERT INTO visits VALUES (default, 8, '2013-01-02', 'rabies shot');
|
||||
INSERT INTO visits VALUES (default, 8, '2013-01-03', 'neutered');
|
||||
INSERT INTO visits VALUES (default, 7, '2013-01-04', 'spayed');
|
||||
@@ -0,0 +1,64 @@
|
||||
DROP TABLE vet_specialties IF EXISTS;
|
||||
DROP TABLE vets IF EXISTS;
|
||||
DROP TABLE specialties IF EXISTS;
|
||||
DROP TABLE visits IF EXISTS;
|
||||
DROP TABLE pets IF EXISTS;
|
||||
DROP TABLE types IF EXISTS;
|
||||
DROP TABLE owners IF EXISTS;
|
||||
|
||||
|
||||
CREATE TABLE vets (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR(30)
|
||||
);
|
||||
CREATE INDEX vets_last_name ON vets (last_name);
|
||||
|
||||
CREATE TABLE specialties (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(80)
|
||||
);
|
||||
CREATE INDEX specialties_name ON specialties (name);
|
||||
|
||||
CREATE TABLE vet_specialties (
|
||||
vet_id INTEGER NOT NULL,
|
||||
specialty_id INTEGER NOT NULL
|
||||
);
|
||||
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id);
|
||||
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id);
|
||||
|
||||
CREATE TABLE types (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(80)
|
||||
);
|
||||
CREATE INDEX types_name ON types (name);
|
||||
|
||||
CREATE TABLE owners (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR_IGNORECASE(30),
|
||||
address VARCHAR(255),
|
||||
city VARCHAR(80),
|
||||
telephone VARCHAR(20)
|
||||
);
|
||||
CREATE INDEX owners_last_name ON owners (last_name);
|
||||
|
||||
CREATE TABLE pets (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(30),
|
||||
birth_date DATE,
|
||||
type_id INTEGER NOT NULL,
|
||||
owner_id INTEGER
|
||||
);
|
||||
ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id);
|
||||
ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id);
|
||||
CREATE INDEX pets_name ON pets (name);
|
||||
|
||||
CREATE TABLE visits (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
pet_id INTEGER,
|
||||
visit_date DATE,
|
||||
description VARCHAR(255)
|
||||
);
|
||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||
@@ -0,0 +1,53 @@
|
||||
INSERT INTO vets VALUES (1, 'James', 'Carter');
|
||||
INSERT INTO vets VALUES (2, 'Helen', 'Leary');
|
||||
INSERT INTO vets VALUES (3, 'Linda', 'Douglas');
|
||||
INSERT INTO vets VALUES (4, 'Rafael', 'Ortega');
|
||||
INSERT INTO vets VALUES (5, 'Henry', 'Stevens');
|
||||
INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins');
|
||||
|
||||
INSERT INTO specialties VALUES (1, 'radiology');
|
||||
INSERT INTO specialties VALUES (2, 'surgery');
|
||||
INSERT INTO specialties VALUES (3, 'dentistry');
|
||||
|
||||
INSERT INTO vet_specialties VALUES (2, 1);
|
||||
INSERT INTO vet_specialties VALUES (3, 2);
|
||||
INSERT INTO vet_specialties VALUES (3, 3);
|
||||
INSERT INTO vet_specialties VALUES (4, 2);
|
||||
INSERT INTO vet_specialties VALUES (5, 1);
|
||||
|
||||
INSERT INTO types VALUES (1, 'cat');
|
||||
INSERT INTO types VALUES (2, 'dog');
|
||||
INSERT INTO types VALUES (3, 'lizard');
|
||||
INSERT INTO types VALUES (4, 'snake');
|
||||
INSERT INTO types VALUES (5, 'bird');
|
||||
INSERT INTO types VALUES (6, 'hamster');
|
||||
|
||||
INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
|
||||
INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
|
||||
INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
|
||||
INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
|
||||
INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
|
||||
INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
|
||||
INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
|
||||
INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
|
||||
INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
|
||||
INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||
|
||||
INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1);
|
||||
INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2);
|
||||
INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3);
|
||||
INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3);
|
||||
INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4);
|
||||
INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5);
|
||||
INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6);
|
||||
INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6);
|
||||
INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7);
|
||||
INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8);
|
||||
INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9);
|
||||
INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10);
|
||||
INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10);
|
||||
|
||||
INSERT INTO visits VALUES (1, 7, '2013-01-01', 'rabies shot');
|
||||
INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot');
|
||||
INSERT INTO visits VALUES (3, 8, '2013-01-03', 'neutered');
|
||||
INSERT INTO visits VALUES (4, 7, '2013-01-04', 'spayed');
|
||||
@@ -0,0 +1,64 @@
|
||||
DROP TABLE vet_specialties IF EXISTS;
|
||||
DROP TABLE vets IF EXISTS;
|
||||
DROP TABLE specialties IF EXISTS;
|
||||
DROP TABLE visits IF EXISTS;
|
||||
DROP TABLE pets IF EXISTS;
|
||||
DROP TABLE types IF EXISTS;
|
||||
DROP TABLE owners IF EXISTS;
|
||||
|
||||
|
||||
CREATE TABLE vets (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR(30)
|
||||
);
|
||||
CREATE INDEX vets_last_name ON vets (last_name);
|
||||
|
||||
CREATE TABLE specialties (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(80)
|
||||
);
|
||||
CREATE INDEX specialties_name ON specialties (name);
|
||||
|
||||
CREATE TABLE vet_specialties (
|
||||
vet_id INTEGER NOT NULL,
|
||||
specialty_id INTEGER NOT NULL
|
||||
);
|
||||
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id);
|
||||
ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id);
|
||||
|
||||
CREATE TABLE types (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(80)
|
||||
);
|
||||
CREATE INDEX types_name ON types (name);
|
||||
|
||||
CREATE TABLE owners (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR_IGNORECASE(30),
|
||||
address VARCHAR(255),
|
||||
city VARCHAR(80),
|
||||
telephone VARCHAR(20)
|
||||
);
|
||||
CREATE INDEX owners_last_name ON owners (last_name);
|
||||
|
||||
CREATE TABLE pets (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(30),
|
||||
birth_date DATE,
|
||||
type_id INTEGER NOT NULL,
|
||||
owner_id INTEGER
|
||||
);
|
||||
ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id);
|
||||
ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id);
|
||||
CREATE INDEX pets_name ON pets (name);
|
||||
|
||||
CREATE TABLE visits (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
pet_id INTEGER,
|
||||
visit_date DATE,
|
||||
description VARCHAR(255)
|
||||
);
|
||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||
@@ -0,0 +1,53 @@
|
||||
INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter');
|
||||
INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary');
|
||||
INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas');
|
||||
INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega');
|
||||
INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens');
|
||||
INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins');
|
||||
|
||||
INSERT IGNORE INTO specialties VALUES (1, 'radiology');
|
||||
INSERT IGNORE INTO specialties VALUES (2, 'surgery');
|
||||
INSERT IGNORE INTO specialties VALUES (3, 'dentistry');
|
||||
|
||||
INSERT IGNORE INTO vet_specialties VALUES (2, 1);
|
||||
INSERT IGNORE INTO vet_specialties VALUES (3, 2);
|
||||
INSERT IGNORE INTO vet_specialties VALUES (3, 3);
|
||||
INSERT IGNORE INTO vet_specialties VALUES (4, 2);
|
||||
INSERT IGNORE INTO vet_specialties VALUES (5, 1);
|
||||
|
||||
INSERT IGNORE INTO types VALUES (1, 'cat');
|
||||
INSERT IGNORE INTO types VALUES (2, 'dog');
|
||||
INSERT IGNORE INTO types VALUES (3, 'lizard');
|
||||
INSERT IGNORE INTO types VALUES (4, 'snake');
|
||||
INSERT IGNORE INTO types VALUES (5, 'bird');
|
||||
INSERT IGNORE INTO types VALUES (6, 'hamster');
|
||||
|
||||
INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023');
|
||||
INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749');
|
||||
INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763');
|
||||
INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198');
|
||||
INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765');
|
||||
INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654');
|
||||
INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387');
|
||||
INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683');
|
||||
INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435');
|
||||
INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487');
|
||||
|
||||
INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1);
|
||||
INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2);
|
||||
INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3);
|
||||
INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3);
|
||||
INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4);
|
||||
INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5);
|
||||
INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6);
|
||||
INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6);
|
||||
INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7);
|
||||
INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8);
|
||||
INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9);
|
||||
INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10);
|
||||
INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10);
|
||||
|
||||
INSERT IGNORE INTO visits VALUES (1, 7, '2010-03-04', 'rabies shot');
|
||||
INSERT IGNORE INTO visits VALUES (2, 8, '2011-03-04', 'rabies shot');
|
||||
INSERT IGNORE INTO visits VALUES (3, 8, '2009-06-04', 'neutered');
|
||||
INSERT IGNORE INTO visits VALUES (4, 7, '2008-09-04', 'spayed');
|
||||
@@ -0,0 +1,36 @@
|
||||
================================================================================
|
||||
=== Spring PetClinic sample application - MySQL Configuration ===
|
||||
================================================================================
|
||||
|
||||
@author Sam Brannen
|
||||
@author Costin Leau
|
||||
@author Dave Syer
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
1) Download and install the MySQL database (e.g., MySQL Community Server 5.1.x),
|
||||
which can be found here: https://dev.mysql.com/downloads/. Or run the
|
||||
"docker-compose.yml" from the root of the project (if you have docker installed
|
||||
locally):
|
||||
|
||||
$ docker-compose up
|
||||
...
|
||||
mysql_1_eedb4818d817 | MySQL init process done. Ready for start up.
|
||||
...
|
||||
|
||||
2) (Once only) create the PetClinic database and user by executing the "db/mysql/user.sql"
|
||||
scripts. You can connect to the database running in the docker container using
|
||||
`mysql -u root -h localhost --protocol tcp`, but you don't need to run the script there
|
||||
because the petclinic user is already set up if you use the provided `docker-compose.yml`.
|
||||
|
||||
3) Run the app with `spring.profiles.active=mysql` (e.g. as a System property via the command
|
||||
line, but any way that sets that property in a Spring Boot app should work). For example use
|
||||
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=mysql
|
||||
|
||||
To activate the profile on the command line.
|
||||
|
||||
N.B. the "petclinic" database has to exist for the app to work with the JDBC URL value
|
||||
as it is configured by default. This condition is taken care of automatically by the
|
||||
docker-compose configuration provided, or by the `user.sql` script if you run that as
|
||||
root.
|
||||
@@ -0,0 +1,55 @@
|
||||
CREATE TABLE IF NOT EXISTS vets (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR(30),
|
||||
INDEX(last_name)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS specialties (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80),
|
||||
INDEX(name)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vet_specialties (
|
||||
vet_id INT(4) UNSIGNED NOT NULL,
|
||||
specialty_id INT(4) UNSIGNED NOT NULL,
|
||||
FOREIGN KEY (vet_id) REFERENCES vets(id),
|
||||
FOREIGN KEY (specialty_id) REFERENCES specialties(id),
|
||||
UNIQUE (vet_id,specialty_id)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS types (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80),
|
||||
INDEX(name)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS owners (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
first_name VARCHAR(30),
|
||||
last_name VARCHAR(30),
|
||||
address VARCHAR(255),
|
||||
city VARCHAR(80),
|
||||
telephone VARCHAR(20),
|
||||
INDEX(last_name)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pets (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(30),
|
||||
birth_date DATE,
|
||||
type_id INT(4) UNSIGNED NOT NULL,
|
||||
owner_id INT(4) UNSIGNED,
|
||||
INDEX(name),
|
||||
FOREIGN KEY (owner_id) REFERENCES owners(id),
|
||||
FOREIGN KEY (type_id) REFERENCES types(id)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS visits (
|
||||
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
pet_id INT(4) UNSIGNED,
|
||||
visit_date DATE,
|
||||
description VARCHAR(255),
|
||||
FOREIGN KEY (pet_id) REFERENCES pets(id)
|
||||
) engine=InnoDB;
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE DATABASE IF NOT EXISTS petclinic;
|
||||
|
||||
ALTER DATABASE petclinic
|
||||
DEFAULT CHARACTER SET utf8
|
||||
DEFAULT COLLATE utf8_general_ci;
|
||||
|
||||
GRANT ALL PRIVILEGES ON petclinic.* TO 'petclinic'@'%' IDENTIFIED BY 'petclinic';
|
||||
@@ -0,0 +1,53 @@
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'James', 'Carter' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=1);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Helen', 'Leary' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=2);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Linda', 'Douglas' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=3);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Rafael', 'Ortega' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=4);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Henry', 'Stevens' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=5);
|
||||
INSERT INTO vets (first_name, last_name) SELECT 'Sharon', 'Jenkins' WHERE NOT EXISTS (SELECT * FROM vets WHERE id=6);
|
||||
|
||||
INSERT INTO specialties (name) SELECT 'radiology' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='radiology');
|
||||
INSERT INTO specialties (name) SELECT 'surgery' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='surgery');
|
||||
INSERT INTO specialties (name) SELECT 'dentistry' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dentistry');
|
||||
|
||||
INSERT INTO vet_specialties VALUES (2, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (3, 3) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (4, 2) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
INSERT INTO vet_specialties VALUES (5, 1) ON CONFLICT (vet_id, specialty_id) DO NOTHING;
|
||||
|
||||
INSERT INTO types (name) SELECT 'cat' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
INSERT INTO types (name) SELECT 'dog' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='dog');
|
||||
INSERT INTO types (name) SELECT 'lizard' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='lizard');
|
||||
INSERT INTO types (name) SELECT 'snake' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='snake');
|
||||
INSERT INTO types (name) SELECT 'bird' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='bird');
|
||||
INSERT INTO types (name) SELECT 'hamster' WHERE NOT EXISTS (SELECT * FROM specialties WHERE name='cat');
|
||||
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=1);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=2);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=3);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=4);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=5);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=6);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=7);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=8);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=9);
|
||||
INSERT INTO owners (first_name, last_name, address, city, telephone) SELECT 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487' WHERE NOT EXISTS (SELECT * FROM owners WHERE id=10);
|
||||
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Leo', '2000-09-07', 1, 1 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=1);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Basil', '2002-08-06', 6, 2 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=2);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Rosy', '2001-04-17', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=3);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Jewel', '2000-03-07', 2, 3 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=4);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Iggy', '2000-11-30', 3, 4 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=5);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'George', '2000-01-20', 4, 5 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=6);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Samantha', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=7);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Max', '1995-09-04', 1, 6 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=8);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '1999-08-06', 5, 7 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=9);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Mulligan', '1997-02-24', 2, 8 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=10);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Freddy', '2000-03-09', 5, 9 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=11);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Lucky', '2000-06-24', 2, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=12);
|
||||
INSERT INTO pets (name, birth_date, type_id, owner_id) SELECT 'Sly', '2002-06-08', 1, 10 WHERE NOT EXISTS (SELECT * FROM pets WHERE id=13);
|
||||
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2010-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=1);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2011-03-04', 'rabies shot' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=2);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 8, '2009-06-04', 'neutered' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=3);
|
||||
INSERT INTO visits (pet_id, visit_date, description) SELECT 7, '2008-09-04', 'spayed' WHERE NOT EXISTS (SELECT * FROM visits WHERE id=4);
|
||||
@@ -0,0 +1,19 @@
|
||||
===============================================================================
|
||||
=== Spring PetClinic sample application - PostgreSQL Configuration ===
|
||||
===============================================================================
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
1) Run the "docker-compose.yml" from the root of the project:
|
||||
|
||||
$ docker-compose up
|
||||
...
|
||||
spring-petclinic-postgres-1 | The files belonging to this database system will be owned by user "postgres".
|
||||
...
|
||||
|
||||
2) Run the app with `spring.profiles.active=postgres` (e.g. as a System property via the command
|
||||
line, but any way that sets that property in a Spring Boot app should work). For example use
|
||||
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=postgres
|
||||
|
||||
To activate the profile on the command line.
|
||||
@@ -0,0 +1,52 @@
|
||||
CREATE TABLE IF NOT EXISTS vets (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
last_name TEXT
|
||||
);
|
||||
CREATE INDEX ON vets (last_name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS specialties (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name TEXT
|
||||
);
|
||||
CREATE INDEX ON specialties (name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vet_specialties (
|
||||
vet_id INT NOT NULL REFERENCES vets (id),
|
||||
specialty_id INT NOT NULL REFERENCES specialties (id),
|
||||
UNIQUE (vet_id, specialty_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS types (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name TEXT
|
||||
);
|
||||
CREATE INDEX ON types (name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS owners (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
address TEXT,
|
||||
city TEXT,
|
||||
telephone TEXT
|
||||
);
|
||||
CREATE INDEX ON owners (last_name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pets (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name TEXT,
|
||||
birth_date DATE,
|
||||
type_id INT NOT NULL REFERENCES types (id),
|
||||
owner_id INT REFERENCES owners (id)
|
||||
);
|
||||
CREATE INDEX ON pets (name);
|
||||
CREATE INDEX ON pets (owner_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS visits (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
pet_id INT REFERENCES pets (id),
|
||||
visit_date DATE,
|
||||
description TEXT
|
||||
);
|
||||
CREATE INDEX ON visits (pet_id);
|
||||
@@ -0,0 +1,8 @@
|
||||
welcome=Welcome
|
||||
required=is required
|
||||
notFound=has not been found
|
||||
duplicate=is already in use
|
||||
nonNumeric=must be all numeric
|
||||
duplicateFormSubmission=Duplicate form submission is not allowed
|
||||
typeMismatch.date=invalid date
|
||||
typeMismatch.birthDate=invalid date
|
||||
@@ -0,0 +1,9 @@
|
||||
welcome=Willkommen
|
||||
required=muss angegeben werden
|
||||
notFound=wurde nicht gefunden
|
||||
duplicate=ist bereits vergeben
|
||||
nonNumeric=darf nur numerisch sein
|
||||
duplicateFormSubmission=Wiederholtes Absenden des Formulars ist nicht erlaubt
|
||||
typeMismatch.date=ung�ltiges Datum
|
||||
typeMismatch.birthDate=ung�ltiges Datum
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# This file is intentionally empty. Message look-ups will fall back to the default "messages.properties" file.
|
||||
@@ -0,0 +1,9 @@
|
||||
welcome=Bienvenido
|
||||
required=Es requerido
|
||||
notFound=No ha sido encontrado
|
||||
duplicate=Ya se encuentra en uso
|
||||
nonNumeric=Sólo debe contener numeros
|
||||
duplicateFormSubmission=No se permite el envío de formularios duplicados
|
||||
typeMismatch.date=Fecha invalida
|
||||
typeMismatch.birthDate=Fecha invalida
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
welcome=환영합니다
|
||||
required=입력이 필요합니다
|
||||
notFound=찾을 수 없습니다
|
||||
duplicate=이미 존재합니다
|
||||
nonNumeric=모두 숫자로 입력해야 합니다
|
||||
duplicateFormSubmission=중복 제출은 허용되지 않습니다
|
||||
typeMismatch.date=잘못된 날짜입니다
|
||||
typeMismatch.birthDate=잘못된 날짜입니다
|
||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 528 B |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,66 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 2674.9 417">
|
||||
<style>
|
||||
.st0, .st1 {
|
||||
fill: #6db33f;
|
||||
}
|
||||
.st3 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0"
|
||||
d="M366.9,29c-5.8,14.1-13.3,26.6-21.6,37.8c-36.6-37.3-87.8-61-144.3-61C90,5.8-0.7,96-0.7,207.4 c0,58.2,24.9,110.6,64.4,147.6l7.5,6.7c34.9,29.5,80.3,47.4,129.7,47.4c106,0,193.3-82.7,200.8-187.1 C407.7,171.3,392.3,106.4,366.9,29z M92.9,356.7c-5.8,7.5-16.6,8.3-24.1,2.5s-8.3-16.6-2.5-24.1s16.6-8.3,24.1-2.5 C97.5,338.4,98.7,349.2,92.9,356.7z M365.7,296.4c-49.5,66.1-155.9,43.7-223.7,47c0,0-12.1,0.8-24.1,2.5c0,0,4.6-2.1,10.4-4.2 c47.8-16.6,70.3-20,99.4-34.9c54.5-27.9,108.9-89,119.8-152.2c-20.8,60.7-84,113.1-141.4,134.3c-39.5,14.6-110.6,28.7-110.6,28.7 l-2.9-1.7c-48.2-23.7-49.9-128.5,38.3-162.2c38.7-15,75.3-6.7,117.3-16.6c44.5-10.4,96.1-43.7,116.8-87.3 C388.1,120.1,416.4,229,365.7,296.4z"></path>
|
||||
<g>
|
||||
<path class="st1"
|
||||
d="M516.2,286.4c-5-2.5-8.3-8.3-8.3-15.4c0-10,7.9-18.3,18.3-18.3c3.7,0,7.1,1.2,9.6,2.5 c18.7,12.5,38.7,18.7,56.1,18.7c19.1,0,30.4-8.3,30.4-21.2v-0.8c0-15.4-20.8-20.4-43.7-27.4c-28.7-8.3-61.1-20-61.1-57.4v-0.8 c0-37,30.8-59.5,69.4-59.5c20.8,0,42.4,5.8,61.5,15.8c6.2,3.3,10.8,9.1,10.8,17c0,10.4-8.3,18.3-18.7,18.3c-3.7,0-5.8-0.8-8.7-2.1 c-15.8-8.3-32-13.3-45.7-13.3c-17.5,0-27.4,8.3-27.4,19.1v0.8c0,14.6,21.2,20.4,44.1,27.9c28.7,8.7,60.7,22,60.7,57v0.8 c0,41.2-32,61.5-72.3,61.5C565.7,309.7,538.6,301.8,516.2,286.4z"></path>
|
||||
<path class="st1"
|
||||
d="M680,129.7c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5V143c14.6-20.4,34.9-36.6,66.5-36.6 c45.7,0,90.6,36.2,90.6,101.5v0.8c0,64.9-44.5,101.5-90.6,101.5c-32.4,0-52.8-16.2-66.5-34.5v69c0,12.5-10,22.5-22.5,22.5 c-12.1,0-22-9.6-22-22.5V129.7z M836.8,208.7v-0.8c0-37.8-25.4-62.4-55.7-62.4c-30.4,0-57,25.4-57,62.4v0.8 c0,37.4,26.6,62.4,57,62.4C811.4,271.5,836.8,247.3,836.8,208.7z"></path>
|
||||
<path class="st1"
|
||||
d="M899.1,129.7c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v10.8c2.1-16.6,29.5-33.3,49.1-33.3 c14.1,0,22,9.1,22,22c0,11.6-7.9,19.5-17.9,21.6c-32,5.4-53.6,33.3-53.6,71.9v64.4c0,12.1-10,22-22.5,22c-12.1,0-22-9.6-22-22 V129.7H899.1z"></path>
|
||||
<path class="st1"
|
||||
d="M1032.6,130.1c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v157.6c0,12.5-10,22-22.5,22c-12.1,0-22-9.6-22-22 V130.1z"></path>
|
||||
<path class="st1"
|
||||
d="M1100,130.1c0-12.5,9.6-22.5,22-22.5s22.5,10,22.5,22.5v9.1c12.5-18.3,30.8-32,61.1-32 c44.1,0,69.4,29.5,69.4,74.8v105.2c0,12.5-9.6,22-22,22s-22.5-9.6-22.5-22v-91.5c0-30.4-15-47.8-42-47.8 c-25.8,0-44.1,18.3-44.1,48.6v91.1c0,12.5-10,22-22.5,22c-12.1,0-22-9.6-22-22L1100,130.1L1100,130.1z"></path>
|
||||
<path class="st1"
|
||||
d="M1472.1,106.8c-12.5,0-22.5,10-22.5,22.5v13.3c-14.6-20.4-34.9-36.6-66.5-36.6c-45.7,0-90.6,36.2-90.6,101.5 v0.8c0,64.9,44.5,101.5,90.6,101.5c32.4,0,52.8-16.2,66.5-34.1c-2.1,35.3-23.7,53.6-61.5,53.6c-22.5,0-42-5.4-59.9-15.4 c-2.1-1.2-5-1.7-7.9-1.7c-10.4,0-19.1,8.3-19.1,18.3c0,8.7,5,15,12.5,17.9c23.7,11.6,48.2,17.5,75.7,17.5 c35.3,0,62.8-8.3,80.3-26.2c16.2-16.2,24.9-40.7,24.9-73.6V129.7C1494.6,116.8,1484.6,106.8,1472.1,106.8z M1393.5,271 c-30.8,0-55.7-24.1-55.7-62.8v-0.8c0-37.8,25.4-62.4,55.7-62.4s57,25.4,57,62.4v0.8C1450.9,245.7,1424.3,271,1393.5,271z"></path>
|
||||
<path class="st1"
|
||||
d="M1077.5,53.6c0,12.5-10,22.5-22.5,22.5s-22.5-10-22.5-22.5s10-22.5,22.5-22.5 C1067.1,30.7,1077.5,40.7,1077.5,53.6z"></path>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1"
|
||||
d="M1545.7,153.8c-12.5,0-22.9-10.4-22.9-22.9c0-12.9,10.4-22.9,22.9-22.9c12.9,0,22.9,10.4,22.9,22.9 S1558.6,153.8,1545.7,153.8z M1545.7,111.4c-10.8,0-19.5,8.7-19.5,19.5s8.7,19.5,19.5,19.5s19.5-8.7,19.5-19.5 C1565.2,119.7,1556.5,111.4,1545.7,111.4z M1551.9,143.8l-6.7-10.4h-4.6v10.4h-3.7v-26.2h10.8c4.6,0,8.7,3.3,8.7,7.9 c0,5.8-5.4,7.9-6.7,7.9l7.1,10.4H1551.9L1551.9,143.8z M1547.4,120.9h-6.7v9.1h7.1c2.1,0,4.6-1.7,4.6-4.6 C1552.4,122.6,1549.9,120.9,1547.4,120.9z"></path>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1"
|
||||
d="M1652.3,260.1c0,20-12.8,34.1-30,34.1c-10.4,0-18.8-5.2-23.7-13.7v12.2h-9.4v-88h9.4v35.1 c4.9-8.6,13.2-13.7,23.7-13.7C1639.4,226.1,1652.3,240.2,1652.3,260.1z M1642.3,260.1c0-15-9.1-25.6-21.8-25.6 c-12.8,0-21.9,10.6-21.9,25.6s9.1,25.7,21.9,25.7C1633.3,285.8,1642.3,275.1,1642.3,260.1z"></path>
|
||||
<path class="st1"
|
||||
d="M1668,313.3l2.3-8.1c2.3,1.1,4.9,1.6,7.7,1.6c4.2,0,6.9-1.4,9.4-5.8l3.8-8.1l-28.3-65.3h10.4l22.8,54.1 l21.5-54.1h10.2l-29.7,72.4c-4.5,11.1-10.8,15.2-19.8,15.4C1674.6,315.4,1671,314.6,1668,313.3z"></path>
|
||||
<path class="st1"
|
||||
d="M1861.3,206.2l-38.1,86.5h-10.7l-38.1-86.5h10.7l32.9,74.4l32.7-74.4L1861.3,206.2L1861.3,206.2z"></path>
|
||||
<path class="st1"
|
||||
d="M1877.1,206.2h9.8l32.1,60.3l32.1-60.3h9.8v86.5h-9.8v-65.3l-32.1,60.3l-32.1-60.3v65.3h-9.8V206.2z"></path>
|
||||
<path class="st1"
|
||||
d="M1977.2,227.6h10.1l16.2,52.5l17.8-52.5h8.2l17.8,52.5l16.2-52.5h10.1l-21.9,65.1h-8.9l-17.2-52l-17.5,52 h-8.9L1977.2,227.6z"></path>
|
||||
<path class="st1"
|
||||
d="M2140.1,253.1v39.7h-9.4v-10.2c-4.7,7.7-14.4,11.7-22.9,11.7c-13.7,0-23.8-7.9-23.8-20.8 c0-13,11.6-21.7,25.6-21.7c7,0,14.5,1.5,21.2,3.7v-2.4c0-8.8-3.5-18.9-17.6-18.9c-6.5,0-13.1,2.9-19,5.9l-3.8-7.7 c9.3-4.7,17.1-6.3,23.4-6.3C2131.4,226.1,2140.1,237.3,2140.1,253.1z M2130.7,272.3v-9.9c-5.9-1.6-12.6-2.6-19.6-2.6 c-9.7,0-17.9,5.5-17.9,13.3c0,8.1,7,12.8,16.2,12.8C2118,285.9,2128.2,281.5,2130.7,272.3z"></path>
|
||||
<path class="st1"
|
||||
d="M2194.9,226.1v8.4c-14.7,0-25.2,9.1-25.2,21.8v36.4h-9.4v-65.1h9.4v12.7 C2173.6,231.7,2182.9,226.1,2194.9,226.1z"></path>
|
||||
<path class="st1"
|
||||
d="M2257.8,278l5.8,6.2c-5.7,6.2-17.1,10.1-26.2,10.1c-17.4,0-33-14.2-33-34.2c0-19.4,14.6-33.9,32.1-33.9 c19.6,0,30.8,14.9,30.8,37.6H2214c1.4,12.7,10.3,22,23.2,22C2245,285.8,2253.8,282.4,2257.8,278z M2214.3,255.3h43.8 c-1.3-11.7-8.2-20.8-21.2-20.8C2225.4,234.6,2216.1,242.7,2214.3,255.3z"></path>
|
||||
<path class="st1"
|
||||
d="M2344.6,215.6h-29.1v-9.3h68v9.3h-29.1v77.2h-9.8L2344.6,215.6L2344.6,215.6z"></path>
|
||||
<path class="st1"
|
||||
d="M2451.6,253.1v39.7h-9.4v-10.2c-4.7,7.7-14.4,11.7-22.9,11.7c-13.7,0-23.8-7.9-23.8-20.8 c0-13,11.6-21.7,25.6-21.7c7,0,14.5,1.5,21.2,3.7v-2.4c0-8.8-3.5-18.9-17.6-18.9c-6.5,0-13.1,2.9-19,5.9l-3.8-7.7 c9.3-4.7,17.1-6.3,23.4-6.3C2442.9,226.1,2451.6,237.3,2451.6,253.1z M2442.2,272.3v-9.9c-5.9-1.6-12.6-2.6-19.6-2.6 c-9.7,0-17.9,5.5-17.9,13.3c0,8.1,7,12.8,16.2,12.8C2429.4,285.9,2439.6,281.5,2442.2,272.3z"></path>
|
||||
<path class="st1"
|
||||
d="M2526.3,251.3v41.4h-9.4v-40.2c0-10.6-6.7-18-16.2-18c-11,0-20.1,7.7-20.1,16.7v41.4h-9.4v-65.1h9.4v10.4 c3.8-6.9,12.2-12,21.4-12C2516.2,226.1,2526.3,236.6,2526.3,251.3z"></path>
|
||||
<path class="st1"
|
||||
d="M2542.6,285.3l38.4-48.7h-37.6v-8.9h50.4v7.4l-38.5,48.7h38.8v8.9h-51.4L2542.6,285.3L2542.6,285.3z"></path>
|
||||
<path class="st1"
|
||||
d="M2665.4,227.6v65.1h-9.4v-10.4c-3.8,6.9-12.2,12-21.4,12c-14.4,0-24.4-10.4-24.4-25.2v-41.4h9.4v40.2 c0,10.6,6.7,18,16.2,18c11,0,20.1-7.7,20.1-16.7v-41.4h9.5V227.6z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st3"
|
||||
d="M92.9,356.7c-5.8,7.5-16.6,8.3-24.1,2.5s-8.3-16.6-2.5-24.1s16.6-8.3,24.1-2.5 C97.5,338.4,98.7,349.2,92.9,356.7z"></path>
|
||||
<path class="st3"
|
||||
d="M365.7,296.4c-49.5,66.1-155.9,43.7-223.7,47c0,0-12.1,0.8-24.1,2.5c0,0,4.6-2.1,10.4-4.2 c47.8-16.6,70.3-20,99.4-34.9c54.5-27.9,108.9-89,119.8-152.2c-20.8,60.7-84,113.1-141.4,134.3c-39.5,14.6-110.6,28.7-110.6,28.7 l-2.9-1.7c-48.2-23.7-49.9-128.5,38.3-162.2c38.7-15,75.3-6.7,117.3-16.6c44.5-10.4,96.1-43.7,116.8-87.3 C388.1,120.1,416.4,229,365.7,296.4z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'error')}">
|
||||
|
||||
<body>
|
||||
<img src="../static/resources/images/pets.png" th:src="@{/resources/images/pets.png}"/>
|
||||
<h2>Something happened...</h2>
|
||||
<p th:text="${message}">Exception message</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<th:block th:fragment="input (label, name, type)">
|
||||
<div th:with="valid=${!#fields.hasErrors(name)}"
|
||||
th:class="${'form-group' + (valid ? '' : ' has-error')}"
|
||||
class="form-group">
|
||||
<label class="col-sm-2 control-label" th:text="${label}">Label</label>
|
||||
<div class="col-sm-10">
|
||||
<div th:switch="${type}">
|
||||
<input th:case="'text'" class="form-control" type="text" th:field="*{__${name}__}" />
|
||||
<input th:case="'date'" class="form-control" type="date" th:field="*{__${name}__}"/>
|
||||
</div>
|
||||
<span th:if="${valid}"
|
||||
class="fa fa-ok form-control-feedback"
|
||||
aria-hidden="true"></span>
|
||||
<th:block th:if="${!valid}">
|
||||
<span
|
||||
class="fa fa-remove form-control-feedback"
|
||||
aria-hidden="true"></span>
|
||||
<span class="help-inline" th:errors="*{__${name}__}">Error</span>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!doctype html>
|
||||
<html th:fragment="layout (template, menu)">
|
||||
|
||||
<head>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" th:href="@{/resources/images/favicon.png}">
|
||||
|
||||
<title>PetClinic :: a Spring Framework demonstration</title>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<link th:href="@{/webjars/font-awesome/4.7.0/css/font-awesome.min.css}" rel="stylesheet">
|
||||
<link rel="stylesheet" th:href="@{/resources/css/petclinic.css}" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" th:href="@{/}"><span></span></a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="main-navbar" style>
|
||||
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" th:remove="all">
|
||||
|
||||
<li th:fragment="menuItem (link,active,title,glyph,text)" th:class="nav-item">
|
||||
<a th:class="${active==menu ? 'nav-link active' : 'nav-link'}" th:href="@{__${link}__}" th:title="${title}">
|
||||
<span th:class="'fa fa-'+${glyph}" class="fa fa-home"></span>
|
||||
<span th:text="${text}">Template</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav me-auto">
|
||||
|
||||
<li th:replace="~{::menuItem ('/','home','home page','home','Home')}">
|
||||
<span class="fa fa-home" aria-hidden="true"></span>
|
||||
<span>Home</span>
|
||||
</li>
|
||||
|
||||
<li th:replace="~{::menuItem ('/owners/find','owners','find owners','search','Find owners')}">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
<span>Find owners</span>
|
||||
</li>
|
||||
|
||||
<li th:replace="~{::menuItem ('/vets.html','vets','veterinarians','th-list','Veterinarians')}">
|
||||
<span class="fa fa-th-list" aria-hidden="true"></span>
|
||||
<span>Veterinarians</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
th:replace="~{::menuItem ('/oups','error','trigger a RuntimeException to see how it is handled','exclamation-triangle','Error')}">
|
||||
<span class="fa exclamation-triangle" aria-hidden="true"></span>
|
||||
<span>Error</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="container xd-container">
|
||||
|
||||
<th:block th:insert="${template}" />
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<img src="../static/images/spring-logo.svg" th:src="@{/resources/images/spring-logo.svg}" alt="VMware Tanzu Logo" class="logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:src="@{/webjars/bootstrap/5.2.3/dist/js/bootstrap.bundle.min.js}"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<body>
|
||||
<form>
|
||||
<th:block th:fragment="select (label, name, items)">
|
||||
<div th:with="valid=${!#fields.hasErrors(name)}"
|
||||
th:class="${'form-group' + (valid ? '' : ' has-error')}"
|
||||
class="form-group">
|
||||
<label class="col-sm-2 control-label" th:text="${label}">Label</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<select th:field="*{__${name}__}">
|
||||
<option th:each="item : ${items}" th:value="${item}"
|
||||
th:text="${item}">dog</option>
|
||||
</select>
|
||||
<span th:if="${valid}"
|
||||
class="fa fa-ok form-control-feedback"
|
||||
aria-hidden="true"></span>
|
||||
<th:block th:if="${!valid}">
|
||||
<span
|
||||
class="fa fa-remove form-control-feedback"
|
||||
aria-hidden="true"></span>
|
||||
<span class="help-inline" th:errors="*{__${name}__}">Error</span>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>Owner</h2>
|
||||
<form th:object="${owner}" class="form-horizontal" id="add-owner-form" method="post">
|
||||
<div class="form-group has-feedback">
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('First Name', 'firstName', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Last Name', 'lastName', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Address', 'address', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('City', 'city', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Telephone', 'telephone', 'text')}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button
|
||||
th:with="text=${owner['new']} ? 'Add Owner' : 'Update Owner'"
|
||||
class="btn btn-primary" type="submit" th:text="${text}">Add
|
||||
Owner</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,34 @@
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>Find Owners</h2>
|
||||
|
||||
<form th:object="${owner}" th:action="@{/owners}" method="get"
|
||||
class="form-horizontal" id="search-owner-form">
|
||||
<div class="form-group">
|
||||
<div class="control-group" id="lastNameGroup">
|
||||
<label class="col-sm-2 control-label">Last name </label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" th:field="*{lastName}" size="30"
|
||||
maxlength="80" /> <span class="help-inline"><div
|
||||
th:if="${#fields.hasAnyErrors()}">
|
||||
<p th:each="err : ${#fields.allErrors()}" th:text="${err}">Error</p>
|
||||
</div></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Find
|
||||
Owner</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-primary" th:href="@{/owners/new}">Add Owner</a>
|
||||
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<h2>Owner Information</h2>
|
||||
|
||||
|
||||
<table class="table table-striped" th:object="${owner}">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td><b th:text="*{firstName + ' ' + lastName}"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address</th>
|
||||
<td th:text="*{address}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<td th:text="*{city}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Telephone</th>
|
||||
<td th:text="*{telephone}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary">Edit
|
||||
Owner</a>
|
||||
<a th:href="@{__${owner.id}__/pets/new}" class="btn btn-primary">Add
|
||||
New Pet</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<h2>Pets and Visits</h2>
|
||||
|
||||
<table class="table table-striped">
|
||||
|
||||
<tr th:each="pet : ${owner.pets}">
|
||||
<td valign="top">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Name</dt>
|
||||
<dd th:text="${pet.name}"></dd>
|
||||
<dt>Birth Date</dt>
|
||||
<dd
|
||||
th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></dd>
|
||||
<dt>Type</dt>
|
||||
<dd th:text="${pet.type}"></dd>
|
||||
</dl>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<table class="table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Visit Date</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr th:each="visit : ${pet.visits}">
|
||||
<td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td>
|
||||
<td th:text="${visit?.description}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a th:href="@{__${owner.id}__/pets/__${pet.id}__/edit}">Edit Pet</a></td>
|
||||
<td><a th:href="@{__${owner.id}__/pets/__${pet.id}__/visits/new}">Add Visit</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>Owners</h2>
|
||||
|
||||
<table id="owners" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 150px;">Name</th>
|
||||
<th style="width: 200px;">Address</th>
|
||||
<th>City</th>
|
||||
<th style="width: 120px">Telephone</th>
|
||||
<th>Pets</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="owner : ${listOwners}">
|
||||
<td>
|
||||
<a th:href="@{/owners/__${owner.id}__}" th:text="${owner.firstName + ' ' + owner.lastName}"/></a>
|
||||
</td>
|
||||
<td th:text="${owner.address}"/>
|
||||
<td th:text="${owner.city}"/>
|
||||
<td th:text="${owner.telephone}"/>
|
||||
<td><span th:text="${#strings.listJoin(owner.pets, ', ')}"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div th:if="${totalPages > 1}">
|
||||
<span>Pages:</span>
|
||||
<span>[</span>
|
||||
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
|
||||
<a th:if="${currentPage != i}" th:href="@{'/owners?page=' + ${i}}">[[${i}]]</a>
|
||||
<span th:unless="${currentPage != i}">[[${i}]]</span>
|
||||
</span>
|
||||
<span>] </span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/owners?page=1'}" title="First"
|
||||
class="fa fa-fast-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="First" class="fa fa-fast-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/owners?page=__${currentPage - 1}__'}" title="Previous"
|
||||
class="fa fa-step-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="Previous" class="fa fa-step-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/owners?page=__${currentPage + 1}__'}" title="Next"
|
||||
class="fa fa-step-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Next" class="fa fa-step-forward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/owners?page=__${totalPages}__'}" title="Last"
|
||||
class="fa fa-fast-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Last" class="fa fa-step-forward"></span>
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>
|
||||
<th:block th:if="${pet['new']}">New </th:block>
|
||||
Pet
|
||||
</h2>
|
||||
<form th:object="${pet}" class="form-horizontal" method="post">
|
||||
<input type="hidden" name="id" th:value="*{id}" />
|
||||
<div class="form-group has-feedback">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Owner</label>
|
||||
<div class="col-sm-10">
|
||||
<span th:text="${owner?.firstName + ' ' + owner?.lastName}" />
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Name', 'name', 'text')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Birth Date', 'birthDate', 'date')}" />
|
||||
<input
|
||||
th:replace="~{fragments/selectField :: select ('Type', 'type', ${types})}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button
|
||||
th:with="text=${pet['new']} ? 'Add Pet' : 'Update Pet'"
|
||||
class="btn btn-primary" type="submit" th:text="${text}">Add
|
||||
Pet</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'owners')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>
|
||||
<th:block th:if="${visit['new']}">New </th:block>
|
||||
Visit
|
||||
</h2>
|
||||
|
||||
<b>Pet</b>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Birth Date</th>
|
||||
<th>Type</th>
|
||||
<th>Owner</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td th:text="${pet.name}"></td>
|
||||
<td
|
||||
th:text="${#temporals.format(pet.birthDate, 'yyyy-MM-dd')}"></td>
|
||||
<td th:text="${pet.type}"></td>
|
||||
<td
|
||||
th:text="${owner?.firstName + ' ' + owner?.lastName}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<form th:object="${visit}" class="form-horizontal" method="post">
|
||||
<div class="form-group has-feedback">
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Date', 'date', 'date')}" />
|
||||
<input
|
||||
th:replace="~{fragments/inputField :: input ('Description', 'description', 'text')}" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<input type="hidden" name="petId" th:value="${pet.id}" />
|
||||
<button class="btn btn-primary" type="submit">Add Visit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<br />
|
||||
<b>Previous Visits</b>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr th:if="${!visit['new']}" th:each="visit : ${pet.visits}">
|
||||
<td th:text="${#temporals.format(visit.date, 'yyyy-MM-dd')}"></td>
|
||||
<td th:text=" ${visit.description}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{fragments/layout :: layout (~{::body},'vets')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2>Veterinarians</h2>
|
||||
|
||||
<table id="vets" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Specialties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="vet : ${listVets}">
|
||||
<td th:text="${vet.firstName + ' ' + vet.lastName}"></td>
|
||||
<td><span th:each="specialty : ${vet.specialties}"
|
||||
th:text="${specialty.name + ' '}"/> <span
|
||||
th:if="${vet.nrOfSpecialties == 0}">none</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div th:if="${totalPages > 1}">
|
||||
<span>Pages:</span>
|
||||
<span>[</span>
|
||||
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
|
||||
<a th:if="${currentPage != i}" th:href="@{'/vets.html?page=__${i}__'}">[[${i}]]</a>
|
||||
<span th:unless="${currentPage != i}">[[${i}]]</span>
|
||||
</span>
|
||||
<span>] </span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/vets.html?page=1'}" title="First"
|
||||
class="fa fa-fast-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="First" class="fa fa-fast-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage > 1}" th:href="@{'/vets.html?page=__${currentPage - 1}__'}" title="Previous"
|
||||
class="fa fa-step-backward"></a>
|
||||
<span th:unless="${currentPage > 1}" title="Previous" class="fa fa-step-backward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/vets.html?page=__${currentPage + 1}__'}" title="Next"
|
||||
class="fa fa-step-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Next" class="fa fa-step-forward"></span>
|
||||
</span>
|
||||
<span>
|
||||
<a th:if="${currentPage < totalPages}" th:href="@{'/vets.html?page=__${totalPages}__'}" title="Last"
|
||||
class="fa fa-fast-forward"></a>
|
||||
<span th:unless="${currentPage < totalPages}" title="Last" class="fa fa-fast-forward"></span>
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'home')}">
|
||||
|
||||
<body>
|
||||
|
||||
<h2 th:text="#{welcome}">Welcome</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<img class="img-responsive" src="../static/resources/images/pets.png" th:src="@{/resources/images/pets.png}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,73 @@
|
||||
.navbar {
|
||||
border-top: 4px solid #6db33f;
|
||||
background-color: #34302d;
|
||||
margin-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.navbar a.navbar-brand {
|
||||
background: url("../images/spring-logo-dataflow.png") -1px -1px no-repeat;
|
||||
margin: 12px 0 6px;
|
||||
width: 229px;
|
||||
height: 46px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar a.navbar-brand span {
|
||||
display: block;
|
||||
width: 229px;
|
||||
height: 46px;
|
||||
background: url("../images/spring-logo-dataflow.png") -1px -48px no-repeat;
|
||||
opacity: 0;
|
||||
-moz-transition: opacity 0.12s ease-in-out;
|
||||
-webkit-transition: opacity 0.12s ease-in-out;
|
||||
-o-transition: opacity 0.12s ease-in-out;
|
||||
}
|
||||
|
||||
.navbar a:hover.navbar-brand span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.navbar li > a, .navbar-text {
|
||||
font-family: "montserratregular", sans-serif;
|
||||
text-shadow: none;
|
||||
font-size: 14px;
|
||||
|
||||
/* line-height: 14px; */
|
||||
padding: 28px 20px;
|
||||
transition: all 0.15s;
|
||||
-webkit-transition: all 0.15s;
|
||||
-moz-transition: all 0.15s;
|
||||
-o-transition: all 0.15s;
|
||||
-ms-transition: all 0.15s;
|
||||
}
|
||||
|
||||
.navbar li > a {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navbar .navbar-text {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.navbar li:hover > a {
|
||||
color: #eeeeee;
|
||||
background-color: #6db33f;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
border-width: 0;
|
||||
|
||||
.icon-bar + .icon-bar {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.icon-bar {
|
||||
width: 19px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@import "bootstrap";
|
||||
|
||||
$icon-font-path: "../../webjars/bootstrap/fonts/";
|
||||
|
||||
$spring-green: #6db33f;
|
||||
$spring-dark-green: #5fa134;
|
||||
$spring-brown: #34302D;
|
||||
$spring-grey: #838789;
|
||||
$spring-light-grey: #f1f1f1;
|
||||
|
||||
$body-bg: $spring-light-grey;
|
||||
$text-color: $spring-brown;
|
||||
$link-color: $spring-dark-green;
|
||||
$link-hover-color: $spring-dark-green;
|
||||
|
||||
$navbar-default-link-color: $spring-light-grey;
|
||||
$navbar-default-link-active-color: $spring-light-grey;
|
||||
$navbar-default-link-hover-color: $spring-light-grey;
|
||||
$navbar-default-link-hover-bg: $spring-green;
|
||||
$navbar-default-toggle-icon-bar-bg: $spring-light-grey;
|
||||
$navbar-default-toggle-hover-bg: transparent;
|
||||
$navbar-default-link-active-bg: $spring-green;
|
||||
|
||||
$border-radius-base: 0;
|
||||
$border-radius-large: 0;
|
||||
$border-radius-small: 0;
|
||||
|
||||
$nav-tabs-active-link-hover-color: $spring-light-grey;
|
||||
$nav-tabs-active-link-hover-bg: $spring-brown;
|
||||
$nav-tabs-active-link-hover-border-color: $spring-brown;
|
||||
$nav-tabs-border-color: $spring-brown;
|
||||
|
||||
$pagination-active-bg: $spring-brown;
|
||||
$pagination-active-border: $spring-green;
|
||||
$table-border-color: $spring-brown;
|
||||
|
||||
.table > thead > tr > th {
|
||||
background-color: lighten($spring-brown, 3%);
|
||||
color: $spring-light-grey;
|
||||
}
|
||||
|
||||
.table-filter {
|
||||
background-color: $spring-brown;
|
||||
padding: 9px 12px;
|
||||
}
|
||||
|
||||
.nav > li > a {
|
||||
color: $spring-grey;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin-top: 12px;
|
||||
border-width: 2px;
|
||||
transition: border 0.15s;
|
||||
color: $spring-light-grey;
|
||||
background: $spring-brown;
|
||||
border-color: $spring-green;
|
||||
-webkit-transition: border 0.15s;
|
||||
-moz-transition: border 0.15s;
|
||||
-o-transition: border 0.15s;
|
||||
-ms-transition: border 0.15s;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.active,
|
||||
.open .dropdown-toggle {
|
||||
background-color: $spring-brown;
|
||||
border-color: $spring-brown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.container .text-muted {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.xd-container {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 100px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 15px
|
||||
}
|
||||
|
||||
.index-page--subtitle {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin: 0 0 30px;
|
||||
}
|
||||
|
||||
.form-horizontal button.btn-inverse {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
#job-params-modal .modal-dialog {
|
||||
width: 90%;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
}
|
||||
|
||||
[ng-cloak].splash {
|
||||
display: block !important;
|
||||
}
|
||||
[ng-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.splash {
|
||||
background: $spring-green;
|
||||
color: $spring-brown;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-page {
|
||||
margin-top: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-page .error-title {
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
margin: 30px 0 0;
|
||||
}
|
||||
|
||||
table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table td .progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table td.action-column {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.help-block {
|
||||
color: lighten($text-color, 50%); // lighten the text some for contrast
|
||||
}
|
||||
|
||||
.xd-containers {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.cluster-view > table td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.cluster-view .label, .cluster-view .column-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cluster-view .input-group-addon {
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.cluster-view {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container-details-table th {
|
||||
background-color: lighten($spring-brown, 3%);
|
||||
color: $spring-light-grey;
|
||||
}
|
||||
|
||||
.status-help-content-table td {
|
||||
color: $spring-brown;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.myspinner {
|
||||
animation-name: spinner;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
|
||||
-webkit-transform-origin: 49% 50%;
|
||||
-webkit-animation-name: spinner;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-timing-function: linear;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px dotted $spring-brown;
|
||||
}
|
||||
|
||||
@import "typography.scss";
|
||||
@import "header.scss";
|
||||
@import "responsive.scss";
|
||||
@@ -0,0 +1,41 @@
|
||||
@media (max-width: 768px) {
|
||||
.navbar-toggle {
|
||||
position:absolute;
|
||||
z-index: 9999;
|
||||
left:0px;
|
||||
top:0px;
|
||||
}
|
||||
|
||||
.navbar a.navbar-brand {
|
||||
display: block;
|
||||
margin: 0 auto 0 auto;
|
||||
width: 148px;
|
||||
height: 50px;
|
||||
float: none;
|
||||
background: url("../images/spring-logo-dataflow-mobile.png") 0 center no-repeat;
|
||||
}
|
||||
|
||||
.homepage-billboard .homepage-subtitle {
|
||||
font-size: 21px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.navbar a.navbar-brand span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.xd-container {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.index-page--subtitle {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@font-face {
|
||||
font-family: 'varela_roundregular';
|
||||
|
||||
src: url('../fonts/varela_round-webfont.eot');
|
||||
src: url('../fonts/varela_round-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/varela_round-webfont.woff') format('woff'),
|
||||
url('../fonts/varela_round-webfont.ttf') format('truetype'),
|
||||
url('../fonts/varela_round-webfont.svg#varela_roundregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'montserratregular';
|
||||
src: url('../fonts/montserrat-webfont.eot');
|
||||
src: url('../fonts/montserrat-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('../fonts/montserrat-webfont.woff') format('woff'),
|
||||
url('../fonts/montserrat-webfont.ttf') format('truetype'),
|
||||
url('../fonts/montserrat-webfont.svg#montserratregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, p, input {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
font-family: "varela_roundregular", sans-serif;
|
||||
color: #34302d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
font-family: "montserratregular", sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin-bottom: 10px;
|
||||
font-family: "montserratregular", sans-serif;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
//font-size: 15px;
|
||||
//line-height: 24px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
font-family: "montserratregular", sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.testcontainers.containers.MySQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("mysql")
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@DisabledInNativeImage
|
||||
class MySqlIntegrationTests {
|
||||
|
||||
@ServiceConnection
|
||||
@Container
|
||||
static MySQLContainer<?> container = new MySQLContainer<>("mysql:5.7");
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
private VetRepository vets;
|
||||
|
||||
@Autowired
|
||||
private RestTemplateBuilder builder;
|
||||
|
||||
@Test
|
||||
void testFindAll() throws Exception {
|
||||
vets.findAll();
|
||||
vets.findAll(); // served from cache
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOwnerDetails() {
|
||||
RestTemplate template = builder.rootUri("http://localhost:" + port).build();
|
||||
ResponseEntity<String> result = template.exchange(RequestEntity.get("/owners/1").build(), String.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.testcontainers.containers.MySQLContainer;
|
||||
|
||||
/**
|
||||
* PetClinic Spring Boot Application.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class MysqlTestApplication {
|
||||
|
||||
@ServiceConnection
|
||||
@Profile("mysql")
|
||||
@Bean
|
||||
static MySQLContainer<?> container() {
|
||||
return new MySQLContainer<>("mysql:5.7");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PetClinicApplication.class, "--spring.profiles.active=mysql");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class PetClinicIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
private VetRepository vets;
|
||||
|
||||
@Autowired
|
||||
private RestTemplateBuilder builder;
|
||||
|
||||
@Test
|
||||
void testFindAll() throws Exception {
|
||||
vets.findAll();
|
||||
vets.findAll(); // served from cache
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOwnerDetails() {
|
||||
RestTemplate template = builder.rootUri("http://localhost:" + port).build();
|
||||
ResponseEntity<String> result = template.exchange(RequestEntity.get("/owners/1").build(), String.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PetClinicApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.testcontainers.DockerClientFactory;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "spring.docker.compose.skip.in-tests=false", //
|
||||
"spring.docker.compose.profiles.active=postgres" })
|
||||
@ActiveProfiles("postgres")
|
||||
@DisabledInNativeImage
|
||||
public class PostgresIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
private VetRepository vets;
|
||||
|
||||
@Autowired
|
||||
private RestTemplateBuilder builder;
|
||||
|
||||
@BeforeAll
|
||||
static void available() {
|
||||
assumeTrue(DockerClientFactory.instance().isDockerAvailable(), "Docker not available");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(PetClinicApplication.class) //
|
||||
.profiles("postgres") //
|
||||
.properties( //
|
||||
"spring.docker.compose.profiles.active=postgres" //
|
||||
) //
|
||||
.listeners(new PropertiesLogger()) //
|
||||
.run(args);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindAll() throws Exception {
|
||||
vets.findAll();
|
||||
vets.findAll(); // served from cache
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOwnerDetails() {
|
||||
RestTemplate template = builder.rootUri("http://localhost:" + port).build();
|
||||
ResponseEntity<String> result = template.exchange(RequestEntity.get("/owners/1").build(), String.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
static class PropertiesLogger implements ApplicationListener<ApplicationPreparedEvent> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(PropertiesLogger.class);
|
||||
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
private boolean isFirstRun = true;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationPreparedEvent event) {
|
||||
if (isFirstRun) {
|
||||
environment = event.getApplicationContext().getEnvironment();
|
||||
printProperties();
|
||||
}
|
||||
isFirstRun = false;
|
||||
}
|
||||
|
||||
public void printProperties() {
|
||||
for (EnumerablePropertySource<?> source : findPropertiesPropertySources()) {
|
||||
log.info("PropertySource: " + source.getName());
|
||||
String[] names = source.getPropertyNames();
|
||||
Arrays.sort(names);
|
||||
for (String name : names) {
|
||||
String resolved = environment.getProperty(name);
|
||||
String value = source.getProperty(name).toString();
|
||||
if (resolved.equals(value)) {
|
||||
log.info(name + "=" + resolved);
|
||||
}
|
||||
else {
|
||||
log.info(name + "=" + value + " OVERRIDDEN to " + resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<EnumerablePropertySource<?>> findPropertiesPropertySources() {
|
||||
List<EnumerablePropertySource<?>> sources = new LinkedList<>();
|
||||
for (PropertySource<?> source : environment.getPropertySources()) {
|
||||
if (source instanceof EnumerablePropertySource enumerable) {
|
||||
sources.add(enumerable);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Michael Isvy Simple test to make sure that Bean Validation is working (useful
|
||||
* when upgrading to a new version of Hibernate Validator/ Bean Validation)
|
||||
*/
|
||||
class ValidatorTests {
|
||||
|
||||
private Validator createValidator() {
|
||||
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
|
||||
localValidatorFactoryBean.afterPropertiesSet();
|
||||
return localValidatorFactoryBean;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotValidateWhenFirstNameEmpty() {
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.ENGLISH);
|
||||
Person person = new Person();
|
||||
person.setFirstName("");
|
||||
person.setLastName("smith");
|
||||
|
||||
Validator validator = createValidator();
|
||||
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
|
||||
|
||||
assertThat(constraintViolations).hasSize(1);
|
||||
ConstraintViolation<Person> violation = constraintViolations.iterator().next();
|
||||
assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
|
||||
assertThat(violation.getMessage()).isEqualTo("must not be blank");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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 static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasProperty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* Test class for {@link OwnerController}
|
||||
*
|
||||
* @author Colin But
|
||||
*/
|
||||
@WebMvcTest(OwnerController.class)
|
||||
@DisabledInNativeImage
|
||||
class OwnerControllerTests {
|
||||
|
||||
private static final int TEST_OWNER_ID = 1;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private OwnerRepository owners;
|
||||
|
||||
private Owner george() {
|
||||
Owner george = new Owner();
|
||||
george.setId(TEST_OWNER_ID);
|
||||
george.setFirstName("George");
|
||||
george.setLastName("Franklin");
|
||||
george.setAddress("110 W. Liberty St.");
|
||||
george.setCity("Madison");
|
||||
george.setTelephone("6085551023");
|
||||
Pet max = new Pet();
|
||||
PetType dog = new PetType();
|
||||
dog.setName("dog");
|
||||
max.setType(dog);
|
||||
max.setName("Max");
|
||||
max.setBirthDate(LocalDate.now());
|
||||
george.addPet(max);
|
||||
max.setId(1);
|
||||
return george;
|
||||
};
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
|
||||
Owner george = george();
|
||||
given(this.owners.findByLastName(eq("Franklin"), any(Pageable.class)))
|
||||
.willReturn(new PageImpl<Owner>(Lists.newArrayList(george)));
|
||||
|
||||
given(this.owners.findAll(any(Pageable.class))).willReturn(new PageImpl<Owner>(Lists.newArrayList(george)));
|
||||
|
||||
given(this.owners.findById(TEST_OWNER_ID)).willReturn(george);
|
||||
Visit visit = new Visit();
|
||||
visit.setDate(LocalDate.now());
|
||||
george.getPet("Max").getVisits().add(visit);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitCreationForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/new"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("owner"))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessCreationFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/new").param("firstName", "Joe")
|
||||
.param("lastName", "Bloggs")
|
||||
.param("address", "123 Caramel Street")
|
||||
.param("city", "London")
|
||||
.param("telephone", "01316761638"))
|
||||
.andExpect(status().is3xxRedirection());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessCreationFormHasErrors() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeHasErrors("owner"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "address"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitFindForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/find"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("owner"))
|
||||
.andExpect(view().name("owners/findOwners"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessFindFormSuccess() throws Exception {
|
||||
Page<Owner> tasks = new PageImpl<Owner>(Lists.newArrayList(george(), new Owner()));
|
||||
Mockito.when(this.owners.findByLastName(anyString(), any(Pageable.class))).thenReturn(tasks);
|
||||
mockMvc.perform(get("/owners?page=1")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessFindFormByLastName() throws Exception {
|
||||
Page<Owner> tasks = new PageImpl<Owner>(Lists.newArrayList(george()));
|
||||
Mockito.when(this.owners.findByLastName(eq("Franklin"), any(Pageable.class))).thenReturn(tasks);
|
||||
mockMvc.perform(get("/owners?page=1").param("lastName", "Franklin"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessFindFormNoOwnersFound() throws Exception {
|
||||
Page<Owner> tasks = new PageImpl<Owner>(Lists.newArrayList());
|
||||
Mockito.when(this.owners.findByLastName(eq("Unknown Surname"), any(Pageable.class))).thenReturn(tasks);
|
||||
mockMvc.perform(get("/owners?page=1").param("lastName", "Unknown Surname"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "lastName"))
|
||||
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound"))
|
||||
.andExpect(view().name("owners/findOwners"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitUpdateOwnerForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("owner"))
|
||||
.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."))))
|
||||
.andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateOwnerFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
|
||||
.param("lastName", "Bloggs")
|
||||
.param("address", "123 Caramel Street")
|
||||
.param("city", "London")
|
||||
.param("telephone", "01616291589"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateOwnerFormUnchangedSuccess() throws Exception {
|
||||
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateOwnerFormHasErrors() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
|
||||
.param("lastName", "Bloggs")
|
||||
.param("address", "")
|
||||
.param("telephone", ""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeHasErrors("owner"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "address"))
|
||||
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
|
||||
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShowOwner() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID))
|
||||
.andExpect(status().isOk())
|
||||
.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."))))
|
||||
.andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
|
||||
.andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
|
||||
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
@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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 org.assertj.core.util.Lists;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
|
||||
|
||||
/**
|
||||
* Test class for the {@link PetController}
|
||||
*
|
||||
* @author Colin But
|
||||
*/
|
||||
@WebMvcTest(value = PetController.class,
|
||||
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
|
||||
@DisabledInNativeImage
|
||||
class PetControllerTests {
|
||||
|
||||
private static final int TEST_OWNER_ID = 1;
|
||||
|
||||
private static final int TEST_PET_ID = 1;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private OwnerRepository owners;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
PetType cat = new PetType();
|
||||
cat.setId(3);
|
||||
cat.setName("hamster");
|
||||
given(this.owners.findPetTypes()).willReturn(Lists.newArrayList(cat));
|
||||
Owner owner = new Owner();
|
||||
Pet pet = new Pet();
|
||||
owner.addPet(pet);
|
||||
pet.setId(TEST_PET_ID);
|
||||
given(this.owners.findById(TEST_OWNER_ID)).willReturn(owner);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitCreationForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"))
|
||||
.andExpect(model().attributeExists("pet"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessCreationFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
|
||||
.param("type", "hamster")
|
||||
.param("birthDate", "2015-02-12"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(view().name("redirect:/owners/{ownerId}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"))
|
||||
.andExpect(model().attributeHasErrors("pet"))
|
||||
.andExpect(model().attributeHasFieldErrors("pet", "type"))
|
||||
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitUpdateForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("pet"))
|
||||
.andExpect(view().name("pets/createOrUpdatePetForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessUpdateFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
|
||||
.param("type", "hamster")
|
||||
.param("birthDate", "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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Test class for {@link PetTypeFormatter}
|
||||
*
|
||||
* @author Colin But
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisabledInNativeImage
|
||||
class PetTypeFormatterTests {
|
||||
|
||||
@Mock
|
||||
private OwnerRepository pets;
|
||||
|
||||
private PetTypeFormatter petTypeFormatter;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.petTypeFormatter = new PetTypeFormatter(pets);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPrint() {
|
||||
PetType petType = new PetType();
|
||||
petType.setName("Hamster");
|
||||
String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH);
|
||||
assertThat(petTypeName).isEqualTo("Hamster");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParse() throws ParseException {
|
||||
given(this.pets.findPetTypes()).willReturn(makePetTypes());
|
||||
PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
|
||||
assertThat(petType.getName()).isEqualTo("Bird");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowParseException() throws ParseException {
|
||||
given(this.pets.findPetTypes()).willReturn(makePetTypes());
|
||||
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}
|
||||
*/
|
||||
private List<PetType> makePetTypes() {
|
||||
List<PetType> petTypes = new ArrayList<>();
|
||||
petTypes.add(new PetType() {
|
||||
{
|
||||
setName("Dog");
|
||||
}
|
||||
});
|
||||
petTypes.add(new PetType() {
|
||||
{
|
||||
setName("Bird");
|
||||
}
|
||||
});
|
||||
return petTypes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
/**
|
||||
* Test class for {@link VisitController}
|
||||
*
|
||||
* @author Colin But
|
||||
*/
|
||||
@WebMvcTest(VisitController.class)
|
||||
@DisabledInNativeImage
|
||||
class VisitControllerTests {
|
||||
|
||||
private static final int TEST_OWNER_ID = 1;
|
||||
|
||||
private static final int TEST_PET_ID = 1;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private OwnerRepository owners;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
Owner owner = new Owner();
|
||||
Pet pet = new Pet();
|
||||
owner.addPet(pet);
|
||||
pet.setId(TEST_PET_ID);
|
||||
given(this.owners.findById(TEST_OWNER_ID)).willReturn(owner);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitNewVisitForm() throws Exception {
|
||||
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdateVisitForm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessNewVisitFormSuccess() throws Exception {
|
||||
mockMvc
|
||||
.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, 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/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID).param("name",
|
||||
"George"))
|
||||
.andExpect(model().attributeHasErrors("visit"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(view().name("pets/createOrUpdateVisitForm"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
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.PetType;
|
||||
import org.springframework.samples.petclinic.owner.Visit;
|
||||
import org.springframework.samples.petclinic.vet.Vet;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration test of the Service and the Repository layer.
|
||||
* <p>
|
||||
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
|
||||
* by the Spring TestContext Framework:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
|
||||
* time between test execution.</li>
|
||||
* <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
|
||||
* don't need to perform application context lookups. See the use of
|
||||
* {@link Autowired @Autowired} on the <code> </code> instance variable, which uses
|
||||
* autowiring <em>by type</em>.
|
||||
* <li><strong>Transaction management</strong>, meaning each test method is executed in
|
||||
* its own transaction, which is automatically rolled back by default. Thus, even if tests
|
||||
* insert or otherwise change database state, there is no need for a teardown or cleanup
|
||||
* script.
|
||||
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
|
||||
* also inherited and can be used for explicit bean lookup if necessary.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Ken Krebs
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Michael Isvy
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
|
||||
// Ensure that if the mysql profile is active we connect to the real database:
|
||||
@AutoConfigureTestDatabase(replace = Replace.NONE)
|
||||
// @TestPropertySource("/application-postgres.properties")
|
||||
class ClinicServiceTests {
|
||||
|
||||
@Autowired
|
||||
protected OwnerRepository owners;
|
||||
|
||||
@Autowired
|
||||
protected VetRepository vets;
|
||||
|
||||
Pageable pageable;
|
||||
|
||||
@Test
|
||||
void shouldFindOwnersByLastName() {
|
||||
Page<Owner> owners = this.owners.findByLastName("Davis", pageable);
|
||||
assertThat(owners).hasSize(2);
|
||||
|
||||
owners = this.owners.findByLastName("Daviss", pageable);
|
||||
assertThat(owners).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindSingleOwnerWithPet() {
|
||||
Owner owner = this.owners.findById(1);
|
||||
assertThat(owner.getLastName()).startsWith("Franklin");
|
||||
assertThat(owner.getPets()).hasSize(1);
|
||||
assertThat(owner.getPets().get(0).getType()).isNotNull();
|
||||
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldInsertOwner() {
|
||||
Page<Owner> owners = this.owners.findByLastName("Schultz", pageable);
|
||||
int found = (int) owners.getTotalElements();
|
||||
|
||||
Owner owner = new Owner();
|
||||
owner.setFirstName("Sam");
|
||||
owner.setLastName("Schultz");
|
||||
owner.setAddress("4, Evans Street");
|
||||
owner.setCity("Wollongong");
|
||||
owner.setTelephone("4444444444");
|
||||
this.owners.save(owner);
|
||||
assertThat(owner.getId().longValue()).isNotEqualTo(0);
|
||||
|
||||
owners = this.owners.findByLastName("Schultz", pageable);
|
||||
assertThat(owners.getTotalElements()).isEqualTo(found + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldUpdateOwner() {
|
||||
Owner owner = this.owners.findById(1);
|
||||
String oldLastName = owner.getLastName();
|
||||
String newLastName = oldLastName + "X";
|
||||
|
||||
owner.setLastName(newLastName);
|
||||
this.owners.save(owner);
|
||||
|
||||
// retrieving new name from database
|
||||
owner = this.owners.findById(1);
|
||||
assertThat(owner.getLastName()).isEqualTo(newLastName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindAllPetTypes() {
|
||||
Collection<PetType> petTypes = this.owners.findPetTypes();
|
||||
|
||||
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
|
||||
assertThat(petType1.getName()).isEqualTo("cat");
|
||||
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
|
||||
assertThat(petType4.getName()).isEqualTo("snake");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldInsertPetIntoDatabaseAndGenerateId() {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
int found = owner6.getPets().size();
|
||||
|
||||
Pet pet = new Pet();
|
||||
pet.setName("bowser");
|
||||
Collection<PetType> types = this.owners.findPetTypes();
|
||||
pet.setType(EntityUtils.getById(types, PetType.class, 2));
|
||||
pet.setBirthDate(LocalDate.now());
|
||||
owner6.addPet(pet);
|
||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
||||
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
|
||||
// checks that id has been generated
|
||||
pet = owner6.getPet("bowser");
|
||||
assertThat(pet.getId()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldUpdatePetName() throws Exception {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
String oldName = pet7.getName();
|
||||
|
||||
String newName = oldName + "X";
|
||||
pet7.setName(newName);
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
pet7 = owner6.getPet(7);
|
||||
assertThat(pet7.getName()).isEqualTo(newName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindVets() {
|
||||
Collection<Vet> vets = this.vets.findAll();
|
||||
|
||||
Vet vet = EntityUtils.getById(vets, Vet.class, 3);
|
||||
assertThat(vet.getLastName()).isEqualTo("Douglas");
|
||||
assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
|
||||
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
|
||||
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
void shouldAddNewVisitForPet() {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
int found = pet7.getVisits().size();
|
||||
Visit visit = new Visit();
|
||||
visit.setDescription("test");
|
||||
|
||||
owner6.addVisit(pet7.getId(), visit);
|
||||
this.owners.save(owner6);
|
||||
|
||||
owner6 = this.owners.findById(6);
|
||||
|
||||
assertThat(pet7.getVisits()) //
|
||||
.hasSize(found + 1) //
|
||||
.allMatch(value -> value.getId() != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindVisitsByPetId() throws Exception {
|
||||
Owner owner6 = this.owners.findById(6);
|
||||
Pet pet7 = owner6.getPet(7);
|
||||
Collection<Visit> visits = pet7.getVisits();
|
||||
|
||||
assertThat(visits) //
|
||||
.hasSize(2) //
|
||||
.element(0)
|
||||
.extracting(Visit::getDate)
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import org.springframework.orm.ObjectRetrievalFailureException;
|
||||
import org.springframework.samples.petclinic.model.BaseEntity;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Utility methods for handling entities. Separate from the BaseEntity class mainly
|
||||
* because of dependency on the ORM-associated ObjectRetrievalFailureException.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @see org.springframework.samples.petclinic.model.BaseEntity
|
||||
* @since 29.10.2003
|
||||
*/
|
||||
public abstract class EntityUtils {
|
||||
|
||||
/**
|
||||
* Look up the entity of the given class with the given id in the given collection.
|
||||
* @param entities the collection to search
|
||||
* @param entityClass the entity class to look up
|
||||
* @param entityId the entity id to look up
|
||||
* @return the found entity
|
||||
* @throws ObjectRetrievalFailureException if the entity was not found
|
||||
*/
|
||||
public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId)
|
||||
throws ObjectRetrievalFailureException {
|
||||
for (T entity : entities) {
|
||||
if (entity.getId() == entityId && entityClass.isInstance(entity)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
throw new ObjectRetrievalFailureException(entityClass, entityId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.system;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* Integration Test for {@link CrashController}.
|
||||
*
|
||||
* @author Alex Lutz
|
||||
*/
|
||||
// NOT Waiting https://github.com/spring-projects/spring-boot/issues/5574
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT,
|
||||
properties = { "server.error.include-message=ALWAYS", "management.endpoints.enabled-by-default=false" })
|
||||
class CrashControllerIntegrationTests {
|
||||
|
||||
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
|
||||
DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
|
||||
static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Value(value = "${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate rest;
|
||||
|
||||
@Test
|
||||
void testTriggerExceptionJson() {
|
||||
ResponseEntity<Map<String, Object>> resp = rest.exchange(
|
||||
RequestEntity.get("http://localhost:" + port + "/oups").build(),
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
});
|
||||
assertThat(resp).isNotNull();
|
||||
assertThat(resp.getStatusCode().is5xxServerError());
|
||||
assertThat(resp.getBody().containsKey("timestamp"));
|
||||
assertThat(resp.getBody().containsKey("status"));
|
||||
assertThat(resp.getBody().containsKey("error"));
|
||||
assertThat(resp.getBody()).containsEntry("message",
|
||||
"Expected: controller used to showcase what happens when an exception is thrown");
|
||||
assertThat(resp.getBody()).containsEntry("path", "/oups");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTriggerExceptionHtml() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(List.of(MediaType.TEXT_HTML));
|
||||
ResponseEntity<String> resp = rest.exchange("http://localhost:" + port + "/oups", HttpMethod.GET,
|
||||
new HttpEntity<>(headers), String.class);
|
||||
assertThat(resp).isNotNull();
|
||||
assertThat(resp.getStatusCode().is5xxServerError());
|
||||
assertThat(resp.getBody()).isNotNull();
|
||||
// html:
|
||||
assertThat(resp.getBody()).containsSubsequence("<body>", "<h2>", "Something happened...", "</h2>", "<p>",
|
||||
"Expected:", "controller", "used", "to", "showcase", "what", "happens", "when", "an", "exception", "is",
|
||||
"thrown", "</p>", "</body>");
|
||||
// Not the whitelabel error page:
|
||||
assertThat(resp.getBody()).doesNotContain("Whitelabel Error Page",
|
||||
"This application has no explicit mapping for");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.system;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@link CrashController}
|
||||
*
|
||||
* @author Colin But
|
||||
* @author Alex Lutz
|
||||
*/
|
||||
// Waiting https://github.com/spring-projects/spring-boot/issues/5574 ..good
|
||||
// luck ((plain(st) UNIT test)! :)
|
||||
class CrashControllerTests {
|
||||
|
||||
CrashController testee = new CrashController();
|
||||
|
||||
@Test
|
||||
void testTriggerException() throws Exception {
|
||||
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
|
||||
testee.triggerException();
|
||||
});
|
||||
|
||||
assertEquals("Expected: controller used to showcase what happens when an exception is thrown",
|
||||
thrown.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 org.assertj.core.util.Lists;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* Test class for the {@link VetController}
|
||||
*/
|
||||
|
||||
@WebMvcTest(VetController.class)
|
||||
@DisabledInNativeImage
|
||||
class VetControllerTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private VetRepository vets;
|
||||
|
||||
private Vet james() {
|
||||
Vet james = new Vet();
|
||||
james.setFirstName("James");
|
||||
james.setLastName("Carter");
|
||||
james.setId(1);
|
||||
return james;
|
||||
}
|
||||
|
||||
private Vet helen() {
|
||||
Vet helen = new Vet();
|
||||
helen.setFirstName("Helen");
|
||||
helen.setLastName("Leary");
|
||||
helen.setId(2);
|
||||
Specialty radiology = new Specialty();
|
||||
radiology.setId(1);
|
||||
radiology.setName("radiology");
|
||||
helen.addSpecialty(radiology);
|
||||
return helen;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
given(this.vets.findAll()).willReturn(Lists.newArrayList(james(), helen()));
|
||||
given(this.vets.findAll(any(Pageable.class)))
|
||||
.willReturn(new PageImpl<Vet>(Lists.newArrayList(james(), helen())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShowVetListHtml() throws Exception {
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/vets.html?page=1"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(model().attributeExists("listVets"))
|
||||
.andExpect(view().name("vets/vetList"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShowResourcesVetList() throws Exception {
|
||||
ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
actions.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$.vetList[0].id").value(1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class VetTests {
|
||||
|
||||
@Test
|
||||
void testSerialization() {
|
||||
Vet vet = new Vet();
|
||||
vet.setFirstName("Zaphod");
|
||||
vet.setLastName("Beeblebrox");
|
||||
vet.setId(123);
|
||||
@SuppressWarnings("deprecation")
|
||||
Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
|
||||
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
|
||||
assertThat(other.getLastName()).isEqualTo(vet.getLastName());
|
||||
assertThat(other.getId()).isEqualTo(vet.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
|
||||
<hashTree>
|
||||
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
|
||||
<stringProp name="TestPlan.comments"></stringProp>
|
||||
<boolProp name="TestPlan.functional_mode">false</boolProp>
|
||||
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
|
||||
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="PETCLINIC_HOST" elementType="Argument">
|
||||
<stringProp name="Argument.name">PETCLINIC_HOST</stringProp>
|
||||
<stringProp name="Argument.value">localhost</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="PETCLINIC_PORT" elementType="Argument">
|
||||
<stringProp name="Argument.name">PETCLINIC_PORT</stringProp>
|
||||
<stringProp name="Argument.value">8080</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="CONTEXT_WEB" elementType="Argument">
|
||||
<stringProp name="Argument.name">CONTEXT_WEB</stringProp>
|
||||
<stringProp name="Argument.value"></stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="TestPlan.user_define_classpath"></stringProp>
|
||||
</TestPlan>
|
||||
<hashTree>
|
||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="User threads" enabled="true">
|
||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Contr�leur Boucle" enabled="true">
|
||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||
<stringProp name="LoopController.loops">10</stringProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">500</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
|
||||
<longProp name="ThreadGroup.start_time">1361531541000</longProp>
|
||||
<longProp name="ThreadGroup.end_time">1361531541000</longProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
<stringProp name="ThreadGroup.duration"></stringProp>
|
||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
||||
<boolProp name="ThreadGroup.delayedStart">true</boolProp>
|
||||
<stringProp name="TestPlan.comments">Original : 500 - 10 - 10</stringProp>
|
||||
</ThreadGroup>
|
||||
<hashTree>
|
||||
<ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Fixed time counter" enabled="true">
|
||||
<stringProp name="ConstantTimer.delay">300</stringProp>
|
||||
</ConstantTimer>
|
||||
<hashTree/>
|
||||
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="Default HTTP parameters" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain">${PETCLINIC_HOST}</stringProp>
|
||||
<stringProp name="HTTPSampler.port">${PETCLINIC_PORT}</stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path"></stringProp>
|
||||
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
|
||||
</ConfigTestElement>
|
||||
<hashTree/>
|
||||
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP cookie manager" enabled="true">
|
||||
<collectionProp name="CookieManager.cookies"/>
|
||||
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
|
||||
</CookieManager>
|
||||
<hashTree/>
|
||||
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="User Count" enabled="true">
|
||||
<stringProp name="CounterConfig.start">1</stringProp>
|
||||
<stringProp name="CounterConfig.end">3</stringProp>
|
||||
<stringProp name="CounterConfig.incr">1</stringProp>
|
||||
<stringProp name="CounterConfig.name">count</stringProp>
|
||||
<stringProp name="CounterConfig.format"></stringProp>
|
||||
<boolProp name="CounterConfig.per_user">false</boolProp>
|
||||
</CounterConfig>
|
||||
<hashTree/>
|
||||
<CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Pet Count" enabled="true">
|
||||
<stringProp name="CounterConfig.start">1</stringProp>
|
||||
<stringProp name="CounterConfig.end">3</stringProp>
|
||||
<stringProp name="CounterConfig.incr">1</stringProp>
|
||||
<stringProp name="CounterConfig.name">petCount</stringProp>
|
||||
<stringProp name="CounterConfig.format"></stringProp>
|
||||
<boolProp name="CounterConfig.per_user">false</boolProp>
|
||||
</CounterConfig>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Home page" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CSS" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/resources/css/petclinic.css</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="JS" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/webjars/bootstrap/5.2.3/dist/js/bootstrap.bundle.min.js</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Vets" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/vets.html</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Find owner" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/find</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Find owner with lastname=""" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners?lastName=</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Owner" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Edit Owner" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST Edit Owner" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="firstName" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">Test</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">firstName</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="lastName" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">${count}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">lastName</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="address" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">1234+Test+St.</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">address</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="city" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">TestCity</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">city</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="telephone" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">612345678</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">telephone</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/edit</stringProp>
|
||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="New Pet" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/new</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST new pet" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="name" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">Test+Fluffy+${petCount}</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">name</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="birthDate" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">2020-12-20</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">birthDate</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="type" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">cat</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">type</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/new</stringProp>
|
||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="New visit" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="POST new visit" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="Variables pr�-d�finies" enabled="true">
|
||||
<collectionProp name="Arguments.arguments">
|
||||
<elementProp name="date" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">2013-02-22</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">date</stringProp>
|
||||
</elementProp>
|
||||
<elementProp name="description" elementType="HTTPArgument">
|
||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
||||
<stringProp name="Argument.value">visit</stringProp>
|
||||
<stringProp name="Argument.metadata">=</stringProp>
|
||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
||||
<stringProp name="Argument.name">description</stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
||||
<stringProp name="HTTPSampler.port"></stringProp>
|
||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">${CONTEXT_WEB}/owners/${count}/pets/${petCount}/visits/new</stringProp>
|
||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="Results" enabled="false">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>false</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<threadCounts>true</threadCounts>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregated report" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>false</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<threadCounts>true</threadCounts>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</jmeterTestPlan>
|
||||