@Entity
public class Application {

    @Column(nullable = false, unique = true)
    private UUID applicationId;

    @Column(nullable = false)
    @GeneratedValue
    @Id
    @JsonIgnore
    private Long id;

    /**
     * Create a new instance
     *
     * @param applicationId the Cloud Foundry application id
     */
    public Application(UUID applicationId) {
        this.applicationId = applicationId;
    }

    Application() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Application that = (Application) o;
        return Objects.equals(this.applicationId, that.applicationId) &&
            Objects.equals(this.id, that.id);
    }

    /**
     * Returns the application id
     *
     * @return the application id
     */
    public UUID getApplicationId() {
        return this.applicationId;
    }

    /**
     * Returns the id
     *
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id
     *
     * @param id the id
     */
    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.applicationId, this.id);
    }

    @Override
    public String toString() {
        return "Application{" +
            "applicationId=" + this.applicationId +
            ", id=" + this.id +
            '}';
    }

} @Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"application_id", "schedule_id"}))
public class Chaos {

    @JoinColumn(nullable = false)
    @JsonIgnore
    @ManyToOne
    private Application application;

    @Column(nullable = false)
    @GeneratedValue
    @Id
    @JsonIgnore
    private Long id;

    @Column(nullable = false)
    private Double probability;

    @JoinColumn(nullable = false)
    @JsonIgnore
    @ManyToOne
    private Schedule schedule;

    /**
     * Create a new instance
     *
     * @param application the application to apply chaos to
     * @param probability the probability of an instance being destroyed
     * @param schedule    the schedule to apply chaos on
     */
    public Chaos(Application application, Double probability, Schedule schedule) {
        this.application = application;
        this.probability = probability;
        this.schedule = schedule;
    }

    Chaos() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Chaos chaos = (Chaos) o;
        return Objects.equals(this.application, chaos.application) &&
            Objects.equals(this.id, chaos.id) &&
            Objects.equals(this.probability, chaos.probability) &&
            Objects.equals(this.schedule, chaos.schedule);
    }

    /**
     * Returns the application
     *
     * @return the application
     */
    public Application getApplication() {
        return this.application;
    }

    /**
     * Returns the id
     *
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id
     *
     * @param id the id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns the probability
     *
     * @return the probability
     */
    public Double getProbability() {
        return this.probability;
    }

    /**
     * Sets the probability
     *
     * @param probability the probability
     */
    public void setProbability(Double probability) {
        this.probability = probability;
    }

    /**
     * Returns the schedule
     *
     * @return the schedule
     */
    public Schedule getSchedule() {
        return this.schedule;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.application, this.id, this.probability, this.schedule);
    }

    @Override
    public String toString() {
        return "Chaos{" +
            "application=" + this.application +
            ", id=" + this.id +
            ", probability=" + this.probability +
            ", schedule=" + this.schedule +
            '}';
    }

} @Entity
public class Event {

    @JoinColumn(nullable = false)
    @ManyToOne
    @JsonIgnore
    private Chaos chaos;

    @Column(nullable = false)
    private Instant executedAt;

    @Column(nullable = false)
    @GeneratedValue
    @Id
    @JsonIgnore
    private Long id;

    @Column(nullable = false)
    @ElementCollection
    @OrderBy
    private List<Integer> terminatedInstances;

    @Column(nullable = false)
    private Integer totalInstanceCount;

    public Event(Chaos chaos, Instant executedAt, List<Integer> terminatedInstances, Integer totalInstanceCount) {
        this.chaos = chaos;
        this.executedAt = executedAt;
        this.terminatedInstances = terminatedInstances;
        this.totalInstanceCount = totalInstanceCount;
    }

    Event() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Event event = (Event) o;
        return Objects.equals(this.chaos, event.chaos) &&
            Objects.equals(this.executedAt, event.executedAt) &&
            Objects.equals(this.id, event.id) &&
            Objects.equals(this.terminatedInstances, event.terminatedInstances) &&
            Objects.equals(this.totalInstanceCount, event.totalInstanceCount);
    }

    /**
     * Returns the chaos
     *
     * @return the chaos
     */
    public Chaos getChaos() {
        return this.chaos;
    }

    /**
     * Returns when the event was executed
     *
     * @return when the event was executed
     */
    public Instant getExecutedAt() {
        return this.executedAt;
    }

    /**
     * Returns the id
     *
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id
     *
     * @param id the id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns the number of instances terminated
     *
     * @return the number of instances terminated
     */
    public int getTerminatedInstanceCount() {
        return this.getTerminatedInstances().size();
    }

    /**
     * Returns the terminated instances
     *
     * @return the terminated instances
     */
    public List<Integer> getTerminatedInstances() {
        return this.terminatedInstances;
    }

    /**
     * Returns the total instance count
     *
     * @return the total instance count
     */
    public Integer getTotalInstanceCount() {
        return this.totalInstanceCount;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.chaos, this.executedAt, this.id, this.terminatedInstances, this.totalInstanceCount);
    }

    @Override
    public String toString() {
        return "Event{" +
            "chaos=" + this.chaos +
            ", executedAt=" + this.executedAt +
            ", id=" + this.id +
            ", terminatedInstances=" + this.terminatedInstances +
            ", totalInstanceCount=" + this.totalInstanceCount +
            '}';
    }

} @Repository
public interface EventRepository extends JpaRepository<Event, Long> {

    /**
     * Find all of the {@link Event}s related to a {@link Chaos}
     *
     * @param chaos the {@link Chaos} that {@link Event}s are related to
     * @return a collection of {@link Event}s related to the {@link Chaos}
     */
    @Transactional(readOnly = true)
    List<Event> findByChaos(Chaos chaos);

    /**
     * Find all of the {@link Event}s that occurred before an {@link Instant}
     *
     * @param instant the {@link Instant} to find {@link Event}s before
     * @return a collection {@link Event}s that occurred before the {@link Instant}
     */
    @Transactional(readOnly = true)
    List<Event> findByExecutedAtBefore(Instant instant);

} @Entity
public class Schedule {

    @Column(nullable = false)
    private String expression;

    @Column(nullable = false)
    @GeneratedValue
    @Id
    @JsonIgnore
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    /**
     * Create a new instance
     *
     * @param expression the cron expression of the schedule
     * @param name       the name of the schedule
     */
    public Schedule(String expression, String name) {
        this.name = name;
        this.expression = expression;
    }

    Schedule() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Schedule schedule = (Schedule) o;
        return Objects.equals(this.expression, schedule.expression) &&
            Objects.equals(this.id, schedule.id) &&
            Objects.equals(this.name, schedule.name);
    }

    /**
     * Returns the expression
     *
     * @return the expression
     */
    public String getExpression() {
        return this.expression;
    }

    /**
     * Sets the expression
     *
     * @param expression the expression
     */
    public void setExpression(String expression) {
        this.expression = expression;
    }

    /**
     * Returns the id
     *
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id
     *
     * @param id the id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns the name
     *
     * @return the name
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets the name
     *
     * @param name the name
     */
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.expression, this.id, this.name);
    }

    @Override
    public String toString() {
        return "Schedule{" +
            "expression='" + this.expression + '\'' +
            ", id=" + this.id +
            ", name='" + this.name + '\'' +
            '}';
    }

} @RestController
@RequestMapping("/events")
public class EventController {

    private final EventRepository eventRepository;

    private final EventResourceAssembler eventResourceAssembler;

    @Autowired
    EventController(EventRepository eventRepository, EventResourceAssembler eventResourceAssembler) {
        this.eventRepository = eventRepository;
        this.eventResourceAssembler = eventResourceAssembler;
    }

    @Transactional
    @RequestMapping(method = DELETE, value = "/{id}")
    public ResponseEntity delete(@PathVariable Long id) {
        this.eventRepository.delete(id);
        return ResponseEntity.noContent().build();
    }

    @Transactional(readOnly = true)
    @RequestMapping(method = GET, value = "", produces = HAL_JSON_VALUE)
    public ResponseEntity list(Pageable pageable, PagedResourcesAssembler<Event> pagedResourcesAssembler) {
        Page<Event> events = this.eventRepository.findAll(pageable);
        return ResponseEntity.ok(pagedResourcesAssembler.toResource(events, this.eventResourceAssembler));
    }

    @Transactional(readOnly = true)
    @RequestMapping(method = GET, value = "/{id}", produces = HAL_JSON_VALUE)
    public ResponseEntity read(@PathVariable Long id) {
        Event event = this.eventRepository.getOne(id);
        return ResponseEntity.ok(this.eventResourceAssembler.toResource(event));
    }

} @Component
public final class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResourceAssembler.EventResource> {

    public EventResourceAssembler() {
        super(EventController.class, EventResource.class);
    }

    @Override
    public EventResource toResource(Event event) {
        EventResource resource = createResourceWithId(event.getId(), event);

        resource.add(linkTo(methodOn(ChaosController.class).read(event.getChaos().getId())).withRel("chaos"));

        return resource;
    }

    @Override
    protected EventResource instantiateResource(Event event) {
        return new EventResource(event);
    }

    static final class EventResource extends Resource<Event> {

        private EventResource(Event event) {
            super(event);
        }

    }

} static RestDocumentationResultHandler document(Snippet... snippets) {
    return MockMvcRestDocumentation.document("{method-name}",
        preprocessRequest(prettyPrint()),
        preprocessResponse(prettyPrint()),
        snippets);
} static LinksSnippet links(LinkDescriptor... descriptors) {
    return HypermediaDocumentation.links(
        linkWithRel("self").ignored())
        .and(descriptors);
} static LinksSnippet listLinks() {
    return HypermediaDocumentation.links(
        linkWithRel("first").description("The first page of results").optional(),
        linkWithRel("last").description("The last page of results").optional(),
        linkWithRel("next").description("The next page of results").optional(),
        linkWithRel("prev").description("The previous page of results").optional(),
        linkWithRel("self").ignored().optional());
} static RequestParametersSnippet listRequestParameters() {
    return RequestDocumentation.requestParameters(
        parameterWithName("page").description("Page to retrieve").optional(),
        parameterWithName("size").description("Size of the page to retrieve").optional());
} static ResponseFieldsSnippet listResponseFields(FieldDescriptor descriptor) {
    return PayloadDocumentation.responseFields(
        fieldWithPath("page.number").type(JsonFieldType.NUMBER).description("The number of this page of results"),
        fieldWithPath("page.size").type(JsonFieldType.NUMBER).description("The size of this page of results"),
        fieldWithPath("page.totalPages").type(JsonFieldType.NUMBER).description("The total number of pages of results"),
        fieldWithPath("page.totalElements").type(JsonFieldType.NUMBER).description("The total number of results"),
        fieldWithPath("_links").ignored())
        .and(descriptor);
} static <T> Page<T> page(Pageable pageable, LongFunction<T> function) {
    return new PageImpl<>(LongStream.range(pageable.getOffset(), pageable.getOffset() + pageable.getPageSize())
        .mapToObj(function)
        .collect(Collectors.toList()), pageable, 12);
} static String query(Pageable pageable) {
    return String.format("?page=%d&size=%d", pageable.getPageNumber(), pageable.getPageSize());
} static ResponseFieldsSnippet responseFields(FieldDescriptor... descriptors) {
    return PayloadDocumentation.responseFields(
        fieldWithPath("_links").ignored())
        .and(descriptors);
}
import io.pivotal.strepsirrhini.chaosloris.data.Application;
import io.pivotal.strepsirrhini.chaosloris.data.Chaos;
import io.pivotal.strepsirrhini.chaosloris.data.Event;
import io.pivotal.strepsirrhini.chaosloris.data.EventRepository;
import io.pivotal.strepsirrhini.chaosloris.data.Schedule;
import io.pivotal.strepsirrhini.chaosloris.web.EventController;
import io.pivotal.strepsirrhini.chaosloris.web.EventResourceAssembler;
import org.cloudfoundry.client.CloudFoundryClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.document;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.links;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.listLinks;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.listRequestParameters;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.listResponseFields;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.page;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.query;
import static io.pivotal.strepsirrhini.chaosloris.docs.DocumentationUtilities.responseFields;
import static org.mockito.Mockito.when;
import static org.springframework.hateoas.MediaTypes.HAL_JSON;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
/*
 * Copyright 2015-2018 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
 *
 *      http://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 io.pivotal.strepsirrhini.chaosloris.docs;




@AutoConfigureRestDocs(outputDir = "target/generated-snippets", uriScheme = "https", uriHost = "chaos-loris", uriPort = 443)
@RunWith(SpringRunner.class)
@WebMvcTest(EventController.class)
public class EventDocumentation {

    @MockBean(answer = Answers.RETURNS_SMART_NULLS)
    private CloudFoundryClient cloudFoundryClient;

    @MockBean(answer = Answers.RETURNS_SMART_NULLS)
    private EventRepository eventRepository;

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void eventDelete() throws Exception {
        this.mockMvc.perform(delete("/events/{id}", 1L))
            .andDo(document(
                pathParameters(parameterWithName("id").description("The event's id"))));
    }

    @Test
    public void eventList() throws Exception {
        Schedule schedule = new Schedule("0 0 * * * *", "hourly");
        schedule.setId(1L);

        Application application = new Application(UUID.randomUUID());
        application.setId(2L);

        Chaos chaos = new Chaos(application, 0.1, schedule);
        chaos.setId(3L);

        PageRequest pageable = new PageRequest(2, 3);

        when(this.eventRepository.findAll(pageable))
            .thenReturn(page(pageable, item -> {
                Event event = new Event(chaos, Instant.now(), Collections.singletonList((int) item % 10), 10);
                event.setId(item);
                return event;
            }));

        this.mockMvc.perform(get("/events" + query(pageable)).accept(HAL_JSON))
            .andDo(document(
                listRequestParameters(),
                listResponseFields(fieldWithPath("_embedded.events").description("A collection of Events as described in [Read an Event](#read-an-event)")),
                listLinks()));
    }

    @Test
    public void eventRead() throws Exception {
        Application application = new Application(UUID.randomUUID());
        application.setId(-1L);

        Schedule schedule = new Schedule("0 0 * * * *", "hourly");
        schedule.setId(-2L);

        Chaos chaos = new Chaos(application, 0.2, schedule);
        chaos.setId(-3L);

        Event event = new Event(chaos, Instant.EPOCH, Arrays.asList(0, 1), Integer.MIN_VALUE);
        event.setId(-4L);

        when(this.eventRepository.getOne(schedule.getId()))
            .thenReturn(event);

        this.mockMvc.perform(get("/events/{id}", schedule.getId()).accept(HAL_JSON))
            .andDo(document(
                pathParameters(parameterWithName("id").description("The event's id")),
                responseFields(
                    fieldWithPath("executedAt").description("An ISO-8601 timestamp for when the event occurred"),
                    fieldWithPath("terminatedInstances").description("The instances terminated during the event"),
                    fieldWithPath("totalInstanceCount").description("The total number of instances that were candidates for termination during the event"),
                    fieldWithPath("terminatedInstanceCount").description("The total number of instances terminated during the event")),
                links(linkWithRel("chaos").description("The [Chaos](#chaoses) that triggered the event"))));