<file_sep># ase-assignment2

To run all tests, use `mvn verify` in the `endpoint` folder. To run the application, use `mvn thorntail:run`.

To regenerate pact file, use `mvn verify` in the `endpoint-client` folder.

To package as uber jar, use `mvn clean install package` in `endpoint` folder (+ `verify` to run pact tests while packaging), then `java -jar target/endpoint-thorntail.jar`).
<file_sep>package edu.baylor.ecs.endpoint.model;

import java.time.LocalDateTime;

public class TestPersonObject extends Person {
    public TestPersonObject(Integer id) {
        this.id = id;
    }

    TestPersonObject(Integer id,
                     String name,
                     Integer age,
                     String email,
                     Integer version) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.version = version;
    }
}
<file_sep>-- id, age, email, name, version
INSERT INTO person VALUES(1, 30, '<EMAIL>', '<NAME>', 1);
INSERT INTO person VALUES(2, 24, '<EMAIL>', '<NAME>', 1);
INSERT INTO person VALUES(3, 56, '<EMAIL>', '<NAME>', 1);
INSERT INTO person VALUES(4, 19, '<EMAIL>', '<NAME>', 1);
INSERT INTO person VALUES(5, 41, '<EMAIL>', '<NAME>', 1);
-- id, brand, license plate, owner, type, version
INSERT INTO car VALUES(1, 'Chevrolet', 'ABC 123', 'Silverado', 1, 1);
INSERT INTO car VALUES(2, 'Chevrolet', 'PRD 845', 'Suburban', 1, 1);
INSERT INTO car VALUES(3, 'Nissan', 'HKJ3456', 'Ultima', 1, 2);
INSERT INTO car VALUES(4, 'Toyota', 'BTN4377', 'Highlander', 1, 3);
INSERT INTO car VALUES(5, 'Ford', 'FGR5591', 'Focus', 1, 3);
INSERT INTO car VALUES(6, 'Ford', 'OOP9191', 'F-150', 1, 3);
INSERT INTO car VALUES(7, 'Lamborghini', 'LKL0987', 'Murcielago', 1, 4);
INSERT INTO car VALUES(8, 'Ford', 'MBT2048', 'Taurus', 1, 4);
INSERT INTO car VALUES(9, 'Hyundai', 'BVB6655', 'Sonata', 1, 5);
INSERT INTO car VALUES(10, 'Chevrolet', 'OMG4242', 'Blazer', 1, 5);
INSERT INTO car VALUES(11, 'Chevrolet', 'LNB1234', 'Traverse', 1, 5);
<file_sep>package edu.baylor.ecs.endpoint;

import edu.baylor.ecs.endpoint.model.Car;
import edu.baylor.ecs.endpoint.model.Person;

import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.validation.ConstraintViolationException;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Collection;
import java.util.List;

@Path("/person")
@ApplicationScoped
public class PersonResource {

    @PersistenceContext(unitName = "EndpointPU")
    private EntityManager em;

    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public Collection<Person> findAll() throws Exception {
        return em.createNamedQuery("Person.findAll", Person.class)
                .getResultList();
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{personId}")
    public Response get(@PathParam("personId") Integer personId) {
        Person person = em.find(Person.class, personId);
        if (person == null) {
            return Response
                    .status(Response.Status.NOT_FOUND)
                    .entity(null)
                    .build();
        }
        return Response
                .status(Response.Status.OK)
                .entity(person)
                .build();
    }

    @POST
    @Path("/")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Transactional
    public Response create(Person person) throws Exception {
        if (person.getId() != null) {
            return Response
                    .status(Response.Status.CONFLICT)
                    .entity("Unable to create Person, id was already set.")
                    .build();
        }

        try {
            em.persist(person);
            em.flush();
        } catch (ConstraintViolationException e) {
            return Response
                    .status(Response.Status.BAD_REQUEST)
                    .entity("Bad entity!")
                    .build();
        } catch (Exception e) {
            return Response
                    .serverError()
                    .entity(e.getMessage())
                    .build();
        }
        return Response
                .created(new URI("person/" + person.getId().toString()))
                .build();
    }

    @DELETE
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{personId}")
    @Transactional
    public Response remove(@PathParam("personId") Integer personId) throws Exception {
        try {
            Person person = em.find(Person.class, personId);
            List<Car> cars = em.createNamedQuery("Car.findAllByPerson")
                    .setParameter("owner", person)
                    .getResultList();

            // get rid of ownership for this person, but don't delete the cars
            for (Car car : cars) {
                car.setOwner(null);
                em.merge(car);
            }

            em.remove(person);
        } catch (Exception e) {
            return Response
                    .serverError()
                    .entity(e.getMessage())
                    .build();
        }

        return Response
                .noContent()
                .build();
    }

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{personId}")
    @Transactional
    public Response update(@PathParam("personId") Integer personId, Person person) throws Exception {
        try {
            Person entity = em.find(Person.class, personId);

            if (null == entity) {
                return Response
                        .status(Response.Status.NOT_FOUND)
                        .entity("Person with id of " + personId + " does not exist.")
                        .build();
            }

            if (person.getId() == null || !person.getId().equals(personId)) {
                return Response
                        .status(Response.Status.BAD_REQUEST)
                        .entity("Person id expected to be " + personId + ", but was " + person.getId() + ".")
                        .build();
            }

            // all this setter logic would go in a service method, if there was a service layer
            entity.setName(person.getName());
            entity.setEmail(person.getEmail());
            entity.setAge(person.getAge());
            try {
                Person merged = em.merge(entity);
                em.flush();
                return Response
                        .ok(merged)
                        .build();
            } catch (ConstraintViolationException e) {
                return Response.status(Response.Status.BAD_REQUEST)
                        .entity("Bad entity!")
                        .build();
            }

        } catch (Exception e) {
            return Response
                    .serverError()
                    .entity(e.getMessage())
                    .build();
        }
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{personId}/cars")
    public Response getCars(@PathParam("personId") Integer personId) {
        Person person = em.find(Person.class, personId);

        if (person == null) {
            return Response
                    .status(Response.Status.NOT_FOUND)
                    .entity("Person with id of " + personId + " does not exist.")
                    .build();
        }

        List<Car> cars = em.createNamedQuery("Car.findAllByPerson")
                .setParameter("owner", person)
                .getResultList();

        return Response
                .status(Response.Status.OK)
                .entity(cars)
                .build();
    }
}
<file_sep>package edu.baylor.ecs.endpoint;

import java.util.List;
import java.util.Map;

import edu.baylor.ecs.endpoint.model.Person;
import edu.baylor.ecs.endpoint.model.TestPersonObject;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.wildfly.swarm.arquillian.DefaultDeployment;

import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.fest.assertions.Assertions.assertThat;

@RunWith(Arquillian.class)
@DefaultDeployment
@RunAsClient
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PersonResourceTest {

    @BeforeClass
    public static void setup() throws Exception {
        RestAssured.baseURI = "http://localhost:8080";
    }

    @Test
    public void testGetAllPersons() throws Exception {
        Response response =
                when()
                        .get("/person")
                .then()
                        .extract().response();

        String jsonAsString = response.asString();
        List<Map<String, ?>> jsonAsList = JsonPath.from(jsonAsString).getList("");

        // size of list
        assertThat(jsonAsList.size()).isEqualTo(5);

        Map<String, ?> record1 = jsonAsList.get(0);

        // spot checking values
        assertThat(record1.get("id")).isEqualTo(1);
        assertThat(record1.get("name")).isEqualTo("<NAME>");
        assertThat(record1.get("age")).isEqualTo(30);

        Map<String, ?> record2 = jsonAsList.get(2);

        assertThat(record2.get("id")).isEqualTo(3);
        assertThat(record2.get("name")).isEqualTo("<NAME>");
        assertThat(record2.get("age")).isEqualTo(56);
    }

    @Test
    public void testGetPerson() throws Exception {
        Response response =
                given()
                        .pathParam("personId", 2)
                .when()
                        .get("/person/{personId}")
                .then()
                        .extract().response();

        String jsonAsString = response.asString();

        Person person = JsonPath.from(jsonAsString).getObject("", Person.class);

        assertThat(person.getId()).isEqualTo(2);
        assertThat(person.getName()).isEqualTo("<NAME>");
        assertThat(person.getAge()).isEqualTo(24);
    }

    @Test
    public void testCreatePerson() throws Exception {
        Person newPerson = new Person();
        newPerson.setName("<NAME>");
        newPerson.setAge(92);
        newPerson.setEmail("<EMAIL>");

        Response response =
                given()
                        .contentType(ContentType.JSON)
                        .body(newPerson)
                .when()
                        .post("/person");

        assertThat(response).isNotNull();
        assertThat(response.getStatusCode()).isEqualTo(201);
        String locationUrl = response.getHeader("Location");
        Integer personId = Integer.valueOf(locationUrl.substring(locationUrl.lastIndexOf('/') + 1));

        response =
                when()
                        .get("/person")
                .then()
                        .extract().response();

        String jsonAsString = response.asString();
        List<Map<String, ?>> jsonAsList = JsonPath.from(jsonAsString).getList("");

        assertThat(jsonAsList.size()).isEqualTo(6);

        response =
                given()
                        .pathParam("personId", personId)
                .when()
                        .get("/person/{personId}")
                .then()
                        .extract().response();

        jsonAsString = response.asString();

        Person person = JsonPath.from(jsonAsString).getObject("", Person.class);

        assertThat(person.getId()).isEqualTo(personId);
        assertThat(person.getName()).isEqualTo("<NAME>");
        assertThat(person.getAge()).isEqualTo(92);
        assertThat(person.getEmail()).isEqualTo("<EMAIL>");
    }

    @Test
    public void testCreatePersonNullField() throws Exception {
        Person sirNoName = new Person();
        sirNoName.setEmail("<EMAIL>");
        sirNoName.setAge(49);

        Response response =
                given()
                        .contentType(ContentType.JSON)
                        .body(sirNoName)
                .when()
                        .post("/person");

        assertThat(response).isNotNull();
        assertThat(response.getStatusCode()).isEqualTo(400);
    }

    @Test
    public void testUpdatePerson() {
        Person rob = new TestPersonObject(4);
        rob.setName("<NAME>");
        rob.setAge(19);
        rob.setEmail("<EMAIL>"); // he ditched yahoo

        Response response =
                given()
                        .contentType(ContentType.JSON)
                        .pathParam("personId", 4)
                        .body(rob)
                        .when()
                        .put("/person/{personId}");

        assertThat(response).isNotNull();
        assertThat(response.getStatusCode()).isEqualTo(200);

        response =
                given()
                        .pathParam("personId", 4)
                        .when()
                        .get("/person/{personId}")
                        .then()
                        .extract().response();

        String jsonAsString = response.asString();
        Person person = JsonPath.from(jsonAsString).getObject("", Person.class);

        assertThat(person.getId()).isEqualTo(4);
        assertThat(person.getEmail()).isEqualTo("<EMAIL>");
    }

    @Test
    public void testUpdatePersonNullId() {
        Person rob = new Person(); // no ID
        rob.setName("<NAME>");
        rob.setAge(19);
        rob.setEmail("<EMAIL>");

        Response response =
                given()
                        .contentType(ContentType.JSON)
                        .pathParam("personId", 4)
                        .body(rob)
                        .when()
                        .put("/person/{personId}");

        assertThat(response).isNotNull();
        assertThat(response.getStatusCode()).isEqualTo(400);

        response =
                given()
                        .pathParam("personId", 4)
                        .when()
                        .get("/person/{personId}")
                        .then()
                        .extract().response();

        String jsonAsString = response.asString();
        Person person = JsonPath.from(jsonAsString).getObject("", Person.class);

        assertThat(person.getId()).isEqualTo(4);
        // assert email didn't change
        assertThat(person.getName()).isEqualTo("<NAME>");
    }

    @Test
    public void testDeletePerson() {
        // get list as is
        Response response =
                when()
                        .get("/person")
                        .then()
                        .extract().response();

        String oldJsonString = response.asString();
        List<Map<String, ?>> oldList = JsonPath.from(oldJsonString).getList("");

        response =
                given()
                        .pathParam("personId", 5)
                        .when()
                        .delete("/person/{personId}")
                        .then()
                        .extract().response();

        assertThat(response.getStatusCode()).isEqualTo(204);

        // get the new list
        response =
                when()
                        .get("/person")
                        .then()
                        .extract().response();

        String newJsonString = response.asString();
        List<Map<String, ?>> newList = JsonPath.from(newJsonString).getList("");

        assertThat(newList.size()).isEqualTo(oldList.size() - 1);

        // try to get the deleted person
        response =
                given()
                        .pathParam("personId", 5)
                        .when()
                        .get("/person/{personId}")
                        .then()
                        .extract().response();

        assertThat(response.getStatusCode()).isEqualTo(404);

    }
}
<file_sep>package edu.baylor.ecs.endpoint.model;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.Objects;

@Entity
@Table(name = "person")
@NamedQueries({
        @NamedQuery(name = "Person.findAll", query = "select p from Person p")
})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Person.class)
public class Person {

    @Id
    @SequenceGenerator(
            name = "person_sequence",
            allocationSize = 1,
            initialValue = 6
    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_sequence")
    @Column(name = "id", updatable = false, nullable = false)
    protected Integer id;

    @NotNull
    @Size(min = 3, max = 50)
    @Column(nullable = false)
    protected String name;

    @NotNull
    @Column(nullable = false)
    @Pattern(regexp = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", flags = Pattern.Flag.CASE_INSENSITIVE)
    protected String email;

    @Column(nullable = false)
    protected int age;

    @Version
    protected Integer version = 0;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person other = (Person) o;
        // person ID not included in equality check
        return Objects.equals(name, other.name) &&
                Objects.equals(email, other.email) &&
                Objects.equals(age, other.age) &&
                Objects.equals(version, other.version);
    }

    @Override
    public int hashCode() {
        // person ID not included in hash code
        return Objects.hash(age, email, name, version);
    }
}
