├── addition
├── src
│ ├── test
│ │ └── resources
│ │ │ └── input.json
│ └── main
│ │ ├── webapp
│ │ └── WEB-INF
│ │ │ └── beans.xml
│ │ ├── java
│ │ └── com
│ │ │ └── airhacks
│ │ │ ├── JAXRSConfiguration.java
│ │ │ └── calculator
│ │ │ ├── addition
│ │ │ ├── control
│ │ │ │ └── CalculationsArchiver.java
│ │ │ ├── entity
│ │ │ │ └── Calculation.java
│ │ │ └── boundary
│ │ │ │ ├── LackingParameterException.java
│ │ │ │ ├── Addition.java
│ │ │ │ └── AdditionResource.java
│ │ │ └── monitoring
│ │ │ └── boundary
│ │ │ └── AdditionTimeoutResource.java
│ │ └── resources
│ │ └── META-INF
│ │ └── persistence.xml
├── run.sh
├── Dockerfile
├── ab.sh
└── pom.xml
├── calculator
├── Dockerfile
├── service.config
├── src
│ ├── main
│ │ ├── webapp
│ │ │ └── WEB-INF
│ │ │ │ └── beans.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── airhacks
│ │ │ ├── JAXRSConfiguration.java
│ │ │ └── calculator
│ │ │ ├── operations
│ │ │ └── boundary
│ │ │ │ ├── ArithmeticWebException.java
│ │ │ │ ├── OperationsResource.java
│ │ │ │ └── OperationService.java
│ │ │ ├── configuration
│ │ │ └── boundary
│ │ │ │ ├── Configurator.java
│ │ │ │ └── ConfigurationResource.java
│ │ │ ├── breaker
│ │ │ ├── entity
│ │ │ │ └── SubsequentInvocationsFailure.java
│ │ │ └── boundary
│ │ │ │ ├── UnstableExternalServiceException.java
│ │ │ │ ├── CircuitBreaker.java
│ │ │ │ └── CircuitBreakersAdmin.java
│ │ │ └── kpi
│ │ │ └── boundary
│ │ │ └── MetricsCounter.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── airhacks
│ │ └── calculator
│ │ ├── operations
│ │ └── boundary
│ │ │ └── OperationServiceTest.java
│ │ └── kpi
│ │ └── boundary
│ │ └── MetricsCounterTest.java
└── pom.xml
├── problematic
├── src
│ └── main
│ │ ├── docker
│ │ └── prometheus
│ │ │ ├── Dockerfile
│ │ │ └── prometheus.yml
│ │ ├── webapp
│ │ └── WEB-INF
│ │ │ └── beans.xml
│ │ ├── resources
│ │ ├── problematic503.js
│ │ ├── problematicRollbacks.js
│ │ ├── echoExecutionTime.js
│ │ ├── avgRequestProcessingTime.js
│ │ ├── usedHeapSize.js
│ │ ├── currentThreadsBusy.js
│ │ └── countqueued1minuteaverage.js
│ │ ├── java
│ │ └── com
│ │ │ └── airhacks
│ │ │ ├── JAXRSConfiguration.java
│ │ │ ├── ping
│ │ │ └── boundary
│ │ │ │ ├── PingResource.java
│ │ │ │ └── Pings.java
│ │ │ └── setup
│ │ │ └── control
│ │ │ └── SetupMonitoring.java
│ │ └── stress
│ │ └── problematic_torture.jmx
├── Dockerfile
├── README.md
├── buildAndRun.sh
└── pom.xml
├── .gitignore
├── README.md
├── addition-st
├── pom.xml
└── src
│ └── test
│ └── java
│ └── com
│ └── airhacks
│ └── calculator
│ ├── monitoring
│ └── boundary
│ │ └── AdditionTimeoutResourceIT.java
│ └── addition
│ └── boundary
│ └── AdditionResourceIT.java
├── calculator-st
├── pom.xml
└── src
│ └── test
│ └── java
│ └── com
│ └── airhacks
│ └── calculator
│ └── operations
│ └── boundary
│ └── OperationsResourceIT.java
└── LICENSE
/addition/src/test/resources/input.json:
--------------------------------------------------------------------------------
1 | {
2 | "a": 2,
3 | "b": 40
4 | }
--------------------------------------------------------------------------------
/addition/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | docker run -d -p 8383:8080 --name addition airhacks/addition
--------------------------------------------------------------------------------
/addition/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM airhacks/wildfly
2 | COPY ./target/addition.war ${DEPLOYMENT_DIR}
3 |
--------------------------------------------------------------------------------
/calculator/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM airhacks/tomee
2 | COPY ./target/calculator.war ${DEPLOYMENT_DIR}
3 |
--------------------------------------------------------------------------------
/problematic/src/main/docker/prometheus/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM airhacks/prometheus
2 | COPY prometheus.yml .
3 |
--------------------------------------------------------------------------------
/problematic/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM airhacks/payara-firehose
2 | COPY ./target/problematic.war ${DEPLOYMENT_DIR}
3 |
4 |
--------------------------------------------------------------------------------
/calculator/service.config:
--------------------------------------------------------------------------------
1 | # timeouts
2 | connectionTimeout=200
3 | readTimeout=500
4 | # circuit breaker configuration
5 | maxExceptionsThreashold=2
--------------------------------------------------------------------------------
/addition/ab.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | ab -p src/test/resources/input.json -T"application/json" -c 10 -n 100 http://localhost:8080/addition/resources/addition/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | pom.xml.tag
3 | pom.xml.releaseBackup
4 | pom.xml.versionsBackup
5 | pom.xml.next
6 | release.properties
7 | dependency-reduced-pom.xml
8 | buildNumber.properties
9 | .mvn/timing.properties
10 |
--------------------------------------------------------------------------------
/problematic/README.md:
--------------------------------------------------------------------------------
1 | # Build
2 | mvn clean package && docker build -t com.airhacks/erroneous .
3 |
4 | # RUN
5 |
6 | docker rm -f erroneous || true && docker run -d -p 8080:8080 -p 4848:4848 --name erroneous com.airhacks/erroneous
--------------------------------------------------------------------------------
/problematic/buildAndRun.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | mvn clean package && docker build -t airhacks/problematic .
3 | docker rm -f problematic || true && docker run --net prometheus -d -p 8010:8080 --name problematic airhacks/problematic
4 |
--------------------------------------------------------------------------------
/addition/src/main/webapp/WEB-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/calculator/src/main/webapp/WEB-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/problematic/src/main/webapp/WEB-INF/beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/problematic/src/main/docker/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval: 15s
3 |
4 | external_labels:
5 | monitor: 'problematic-monitor'
6 |
7 | scrape_configs:
8 | - job_name: 'problematic-exporter'
9 | scrape_interval: 5s
10 | metrics_path: '/firehose/resources/metrics'
11 | static_configs:
12 | - targets: ['problematic:8090']
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # javaee-calculator
2 | Java EE Microservices Example
3 |
4 | Prometheus integration see [payara-firehose](http://github.com/AdamBien/docklands). Prometheus and Grafana images are also available from: [docklands](http://github.com/AdamBien/docklands)
5 |
6 | See you at [Munich's Airport](http://workshops.adam-bien.com) or at least online: [Java EE Microservices](http://javaeemicro.services)
7 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/problematic503.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.count503.count;
4 | var output = {
5 | suffix: "count",
6 | units: "total",
7 | component: "unavailable503",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/problematicRollbacks.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.rolledbackcount.count;
4 | var output = {
5 | suffix: "count",
6 | units: "total",
7 | component: "rollbacks",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/echoExecutionTime.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.executiontime.count;
4 | var output = {
5 | suffix: "time",
6 | units: "ms",
7 | component: "methodecho",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/avgRequestProcessingTime.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.processingtime.count;
4 | var output = {
5 | suffix: "avg",
6 | units: "ms",
7 | component: "requestprocessingtime",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/JAXRSConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.airhacks;
2 |
3 | import javax.ws.rs.ApplicationPath;
4 | import javax.ws.rs.core.Application;
5 |
6 | /**
7 | * Configures a JAX-RS endpoint. Delete this class, if you are not exposing
8 | * JAX-RS resources in your application.
9 | *
10 | * @author airhacks.com
11 | */
12 | @ApplicationPath("resources")
13 | public class JAXRSConfiguration extends Application {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/JAXRSConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.airhacks;
2 |
3 | import javax.ws.rs.ApplicationPath;
4 | import javax.ws.rs.core.Application;
5 |
6 | /**
7 | * Configures a JAX-RS endpoint. Delete this class, if you are not exposing
8 | * JAX-RS resources in your application.
9 | *
10 | * @author airhacks.com
11 | */
12 | @ApplicationPath("resources")
13 | public class JAXRSConfiguration extends Application {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/usedHeapSize.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity["usedheapsize-count"].count;
4 | var output = {
5 | suffix: "size",
6 | units: "bytes",
7 | component: "jvmusedheapsize",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/problematic/src/main/java/com/airhacks/JAXRSConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.airhacks;
2 |
3 | import javax.ws.rs.ApplicationPath;
4 | import javax.ws.rs.core.Application;
5 |
6 | /**
7 | * Configures a JAX-RS endpoint. Delete this class, if you are not exposing
8 | * JAX-RS resources in your application.
9 | *
10 | * @author airhacks.com
11 | */
12 | @ApplicationPath("resources")
13 | public class JAXRSConfiguration extends Application {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/currentThreadsBusy.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.currentthreadsbusy.count;
4 | var output = {
5 | suffix: "total",
6 | units: "count",
7 | component: "http1currentthreadsbusy",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/problematic/src/main/resources/countqueued1minuteaverage.js:
--------------------------------------------------------------------------------
1 | function apply(input) {
2 | var metric = JSON.parse(input);
3 | var value = metric.extraProperties.entity.countqueued1minuteaverage.count;
4 | var output = {
5 | suffix: "avg",
6 | units: "count",
7 | component: "countqueued1minuteaverage",
8 | application: "problematic",
9 | value: value
10 | };
11 | return JSON.stringify(output);
12 | }
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/addition/src/main/resources/META-INF/persistence.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/addition/control/CalculationsArchiver.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.addition.control;
3 |
4 | import com.airhacks.calculator.addition.entity.Calculation;
5 | import javax.persistence.EntityManager;
6 | import javax.persistence.PersistenceContext;
7 |
8 | /**
9 | *
10 | * @author airhacks.com
11 | */
12 | public class CalculationsArchiver {
13 |
14 | @PersistenceContext
15 | EntityManager em;
16 |
17 | public void archive(int a, int b, int result) {
18 | this.em.persist(new Calculation(a, b, result));
19 | }
20 |
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/operations/boundary/ArithmeticWebException.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.calculator.operations.boundary;
2 |
3 | import javax.ejb.ApplicationException;
4 | import javax.ws.rs.ApplicationPath;
5 | import javax.ws.rs.WebApplicationException;
6 | import javax.ws.rs.core.Response;
7 |
8 | @ApplicationException(rollback = false)
9 | public class ArithmeticWebException extends WebApplicationException {
10 | public ArithmeticWebException(String message) {
11 | super(Response.status(Response.Status.BAD_REQUEST).header("reason",message).build());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/configuration/boundary/Configurator.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.configuration.boundary;
3 |
4 | import javax.enterprise.inject.Produces;
5 | import javax.enterprise.inject.spi.InjectionPoint;
6 |
7 | /**
8 | *
9 | * @author airhacks.com
10 | */
11 | public class Configurator {
12 |
13 | @Produces
14 | public long expose(InjectionPoint ip) {
15 | String fieldName = ip.getMember().getName();
16 | String configuredValue = System.getenv().getOrDefault(fieldName, "0");
17 | return Long.parseLong(configuredValue);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/breaker/entity/SubsequentInvocationsFailure.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.breaker.entity;
3 |
4 | /**
5 | *
6 | * @author airhacks.com
7 | */
8 | public class SubsequentInvocationsFailure {
9 |
10 | private String methodName;
11 | private int failuresCount;
12 |
13 | public SubsequentInvocationsFailure(String methodName, int failuresCount) {
14 | this.methodName = methodName;
15 | this.failuresCount = failuresCount;
16 | }
17 |
18 | public String getMethodName() {
19 | return methodName;
20 | }
21 |
22 | public int getFailuresCount() {
23 | return failuresCount;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/configuration/boundary/ConfigurationResource.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.configuration.boundary;
3 |
4 | import javax.json.Json;
5 | import javax.json.JsonObject;
6 | import javax.json.JsonObjectBuilder;
7 | import javax.ws.rs.GET;
8 | import javax.ws.rs.Path;
9 |
10 | @Path("configuration")
11 | public class ConfigurationResource {
12 |
13 | @GET
14 | public JsonObject all() {
15 | JsonObjectBuilder result = Json.createObjectBuilder();
16 | System.getenv().
17 | entrySet().
18 | forEach(e -> result.add(e.getKey(), e.getValue()));
19 | return result.build();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/breaker/boundary/UnstableExternalServiceException.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.breaker.boundary;
3 |
4 | import javax.ejb.ApplicationException;
5 | import javax.ws.rs.WebApplicationException;
6 | import javax.ws.rs.core.Response;
7 |
8 | /**
9 | *
10 | * @author airhacks.com
11 | */
12 | @ApplicationException(rollback = true)
13 | public class UnstableExternalServiceException extends WebApplicationException {
14 |
15 | public UnstableExternalServiceException(String message) {
16 | super(Response.status(Response.Status.SERVICE_UNAVAILABLE).
17 | header("info", message).
18 | build());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/addition/entity/Calculation.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.addition.entity;
3 |
4 | import javax.persistence.Entity;
5 | import javax.persistence.GeneratedValue;
6 | import javax.persistence.Id;
7 |
8 | /**
9 | *
10 | * @author airhacks.com
11 | */
12 | @Entity
13 | public class Calculation {
14 |
15 | @Id
16 | @GeneratedValue
17 | private long id;
18 | private int a;
19 | private int b;
20 | private int result;
21 |
22 | Calculation() {
23 | //jpa
24 | }
25 |
26 | public Calculation(int a, int b, int result) {
27 | this.a = a;
28 | this.b = b;
29 | this.result = result;
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/addition/boundary/LackingParameterException.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.addition.boundary;
3 |
4 | import javax.ejb.ApplicationException;
5 | import javax.ws.rs.WebApplicationException;
6 | import javax.ws.rs.core.Response;
7 | import javax.ws.rs.core.Response.Status;
8 |
9 | /**
10 | *
11 | * @author airhacks.com
12 | */
13 | @ApplicationException(rollback = true)
14 | public class LackingParameterException extends WebApplicationException {
15 |
16 | public LackingParameterException(String parameterName) {
17 | super(Response.status(Status.BAD_REQUEST).
18 | header("cause", "Parameter: " + parameterName + " is lacking").
19 | build());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/calculator/src/test/java/com/airhacks/calculator/operations/boundary/OperationServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.calculator.operations.boundary;
2 |
3 | import com.airhacks.calculator.kpi.boundary.MetricsCounter;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import static org.junit.Assert.*;
8 | import static org.mockito.Mockito.mock;
9 |
10 | public class OperationServiceTest {
11 |
12 | OperationService cut;
13 |
14 | @Before
15 | public void init(){
16 | this.cut = new OperationService();
17 | this.cut.counter = mock(MetricsCounter.class);
18 | }
19 |
20 | @Test(expected = ArithmeticWebException.class)
21 | public void divideByZero(){
22 | this.cut.divide(2,0);
23 |
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/calculator/src/test/java/com/airhacks/calculator/kpi/boundary/MetricsCounterTest.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.calculator.kpi.boundary;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import static org.hamcrest.CoreMatchers.is;
7 | import static org.junit.Assert.*;
8 |
9 | public class MetricsCounterTest {
10 |
11 | MetricsCounter cut;
12 |
13 | @Before
14 | public void init() {
15 | this.cut = new MetricsCounter();
16 | this.cut.init();
17 | }
18 |
19 | @Test
20 | public void calculateMetrics() {
21 | this.cut.lastSuccess = 10;
22 | this.cut.success.set(40);
23 | this.cut.calculateMetrics();
24 | assertThat(this.cut.successesPerSecond,is(3d));
25 | assertThat(this.cut.lastSuccess,is(this.cut.success.longValue()));
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/addition/boundary/Addition.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.addition.boundary;
3 |
4 | import com.airhacks.calculator.addition.control.CalculationsArchiver;
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 | import javax.ejb.Stateless;
8 | import javax.inject.Inject;
9 |
10 | @Stateless
11 | public class Addition {
12 |
13 | @Inject
14 | CalculationsArchiver archiver;
15 |
16 | public int add(int a, int b) {
17 | int result = a + b;
18 | if (result == 42) {
19 | thinkLonger();
20 | }
21 | this.archiver.archive(a, b, result);
22 | return result;
23 | }
24 |
25 | void thinkLonger() {
26 | try {
27 | Thread.sleep(1000);
28 | } catch (InterruptedException ex) {
29 | Logger.getLogger(Addition.class.getName()).log(Level.SEVERE, null, ex);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/addition/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.airhacks
5 | addition
6 | 1.0-SNAPSHOT
7 | war
8 |
9 |
10 | javax
11 | javaee-api
12 | 7.0
13 | provided
14 |
15 |
16 |
17 | addition
18 |
19 |
20 | 1.8
21 | 1.8
22 | false
23 |
24 |
--------------------------------------------------------------------------------
/problematic/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.airhacks
5 | problematic
6 | 1.0-SNAPSHOT
7 | war
8 |
9 |
10 | javax
11 | javaee-api
12 | 7.0
13 | provided
14 |
15 |
16 |
17 | problematic
18 |
19 |
20 | 1.8
21 | 1.8
22 | false
23 |
24 |
--------------------------------------------------------------------------------
/problematic/src/main/java/com/airhacks/ping/boundary/PingResource.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.ping.boundary;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import javax.ejb.Stateless;
5 | import javax.inject.Inject;
6 | import javax.ws.rs.GET;
7 | import javax.ws.rs.Path;
8 | import javax.ws.rs.PathParam;
9 | import javax.ws.rs.container.AsyncResponse;
10 | import javax.ws.rs.container.Suspended;
11 |
12 | /**
13 | *
14 | * @author airhacks.com
15 | */
16 | @Stateless
17 | @Path("ping")
18 | public class PingResource {
19 |
20 | @Inject
21 | Pings ping;
22 |
23 | @GET
24 | public void ping(@Suspended AsyncResponse response) {
25 | response.setTimeout(1, TimeUnit.NANOSECONDS);
26 | System.out.println(".");
27 | response.resume("Java EE 8 is crazy fast!");
28 | }
29 |
30 | @GET
31 | @Path("heavy/{blocking}-{echo}")
32 | public String heavyPing(@PathParam("blocking") long blocking, @PathParam("echo") String echo) {
33 | return this.ping.echo(blocking, echo);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/monitoring/boundary/AdditionTimeoutResource.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.monitoring.boundary;
3 |
4 | import java.util.concurrent.atomic.AtomicInteger;
5 | import javax.annotation.PostConstruct;
6 | import javax.enterprise.context.ApplicationScoped;
7 | import javax.enterprise.event.Observes;
8 | import javax.json.Json;
9 | import javax.json.JsonObject;
10 | import javax.ws.rs.GET;
11 | import javax.ws.rs.Path;
12 |
13 | /**
14 | *
15 | * @author airhacks.com
16 | */
17 | @Path("timeouts")
18 | @ApplicationScoped
19 | public class AdditionTimeoutResource {
20 |
21 | private AtomicInteger timeoutCounter;
22 |
23 | @PostConstruct
24 | public void init() {
25 | this.timeoutCounter = new AtomicInteger();
26 | }
27 |
28 | @GET
29 | public JsonObject timeouts() {
30 | return Json.createObjectBuilder().
31 | add("addition-timeouts", this.timeoutCounter.intValue()).
32 | build();
33 | }
34 |
35 | public void onNewTimeout(@Observes String timeout) {
36 | this.timeoutCounter.incrementAndGet();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/breaker/boundary/CircuitBreaker.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.breaker.boundary;
3 |
4 | import com.airhacks.calculator.breaker.entity.SubsequentInvocationsFailure;
5 |
6 | import javax.enterprise.event.Event;
7 | import javax.inject.Inject;
8 | import javax.interceptor.AroundInvoke;
9 | import javax.interceptor.InvocationContext;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 |
12 | /**
13 | *
14 | * @author airhacks.com
15 | */
16 | public class CircuitBreaker {
17 |
18 | @Inject
19 | private CircuitBreakersAdmin admin;
20 |
21 | @Inject
22 | long maxExceptionsThreashold;
23 |
24 | @Inject
25 | Event unstableServiceEvent;
26 |
27 | @AroundInvoke
28 | public Object guard(InvocationContext context) throws Exception {
29 | Object target = context.getTarget();
30 | AtomicInteger counter = admin.getCounter(target);
31 | try {
32 | int failureCount = counter.get();
33 | if (failureCount >= this.maxExceptionsThreashold) {
34 | String methodName = context.getMethod().toString();
35 | this.unstableServiceEvent.fire(new SubsequentInvocationsFailure(methodName, failureCount));
36 | throw new UnstableExternalServiceException(methodName);
37 | }
38 | return context.proceed();
39 | } catch (Exception ex) {
40 | counter.incrementAndGet();
41 | throw ex;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/addition-st/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.airhacks
5 | addition-st
6 | 1.0-SNAPSHOT
7 | jar
8 |
9 |
10 | org.glassfish.jersey.core
11 | jersey-client
12 | 2.25.1
13 |
14 |
15 | org.glassfish.jersey.media
16 | jersey-media-json-processing
17 | 2.25.1
18 |
19 |
20 | org.glassfish
21 | javax.json
22 | 1.0.4
23 |
24 |
25 | junit
26 | junit
27 | 4.12
28 | test
29 |
30 |
31 |
32 | UTF-8
33 | 1.8
34 | 1.8
35 |
36 |
--------------------------------------------------------------------------------
/calculator-st/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.airhacks
5 | calculator-st
6 | 1.0-SNAPSHOT
7 | jar
8 |
9 |
10 | org.glassfish.jersey.core
11 | jersey-client
12 | 2.25.1
13 |
14 |
15 | org.glassfish.jersey.media
16 | jersey-media-json-processing
17 | 2.25.1
18 |
19 |
20 | org.glassfish
21 | javax.json
22 | 1.0.4
23 |
24 |
25 | junit
26 | junit
27 | 4.12
28 | test
29 |
30 |
31 |
32 | UTF-8
33 | 1.8
34 | 1.8
35 |
36 |
--------------------------------------------------------------------------------
/problematic/src/main/java/com/airhacks/ping/boundary/Pings.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.ping.boundary;
3 |
4 | import javax.annotation.Resource;
5 | import javax.ejb.SessionContext;
6 | import javax.ejb.Stateless;
7 | import javax.ejb.TransactionManagement;
8 | import javax.ejb.TransactionManagementType;
9 | import javax.transaction.HeuristicMixedException;
10 | import javax.transaction.HeuristicRollbackException;
11 | import javax.transaction.NotSupportedException;
12 | import javax.transaction.RollbackException;
13 | import javax.transaction.SystemException;
14 | import javax.transaction.UserTransaction;
15 |
16 | /**
17 | *
18 | * @author airhacks.com
19 | */
20 | @Stateless
21 | @TransactionManagement(TransactionManagementType.BEAN)
22 | public class Pings {
23 |
24 | @Resource
25 | UserTransaction ut;
26 |
27 | @Resource
28 | SessionContext sc;
29 |
30 | public String echo(long blockTime, String input) {
31 | try {
32 | ut.begin();
33 | ut.setTransactionTimeout(1);
34 | } catch (NotSupportedException | SystemException ex) {
35 | }
36 | heavyProcessing(blockTime);
37 | try {
38 | if ("rollback".equalsIgnoreCase(input)) {
39 | ut.rollback();
40 | } else {
41 | ut.commit();
42 | }
43 | } catch (RollbackException | HeuristicMixedException
44 | | HeuristicRollbackException | SecurityException
45 | | IllegalStateException | SystemException ex) {
46 | }
47 | return "echo" + input;
48 | }
49 |
50 | void heavyProcessing(long blockTime) {
51 | try {
52 | Thread.sleep(blockTime);
53 | } catch (InterruptedException ex) {
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/calculator/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.airhacks
5 | calculator
6 | 1.0-SNAPSHOT
7 | war
8 |
9 |
10 | javax
11 | javaee-api
12 | 7.0
13 | provided
14 |
15 |
16 | com.airhacks
17 | perceptor-spy
18 | 0.0.1
19 |
20 |
21 | junit
22 | junit
23 | 4.12
24 | test
25 |
26 |
27 | org.mockito
28 | mockito-all
29 | 1.10.8
30 | test
31 |
32 |
33 | org.glassfish.jersey.core
34 | jersey-client
35 | 2.25
36 | test
37 |
38 |
39 |
40 | calculator
41 |
42 |
43 | 1.8
44 | 1.8
45 | false
46 |
47 |
--------------------------------------------------------------------------------
/addition-st/src/test/java/com/airhacks/calculator/monitoring/boundary/AdditionTimeoutResourceIT.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.monitoring.boundary;
3 |
4 | import com.airhacks.calculator.addition.boundary.AdditionResourceIT;
5 | import javax.json.JsonObject;
6 | import javax.ws.rs.client.Client;
7 | import javax.ws.rs.client.ClientBuilder;
8 | import javax.ws.rs.client.WebTarget;
9 | import javax.ws.rs.core.MediaType;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.junit.Assert.assertNotNull;
12 | import static org.junit.Assert.assertThat;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 |
16 | /**
17 | *
18 | * @author airhacks.com
19 | */
20 | public class AdditionTimeoutResourceIT {
21 |
22 | private Client client;
23 | private WebTarget tut;
24 | static final String ADDITION_URI = "http://localhost:8383/addition/resources/timeouts";
25 |
26 | @Before
27 | public void init() {
28 | this.client = ClientBuilder.newClient();
29 | this.tut = this.client.target(ADDITION_URI);
30 | }
31 |
32 | @Test
33 | public void increaseCounterOnTimeout() {
34 | JsonObject response = this.tut.
35 | request(MediaType.APPLICATION_JSON).
36 | get(JsonObject.class);
37 | int initial = numberOfTimeouts(response);
38 |
39 | causeTimeout();
40 |
41 | response = this.tut.
42 | request(MediaType.APPLICATION_JSON).
43 | get(JsonObject.class);
44 | int afterTimeout = numberOfTimeouts(response);
45 |
46 | assertThat(afterTimeout, is(initial + 1));
47 |
48 | numberOfTimeouts(response);
49 |
50 | }
51 |
52 | public void causeTimeout() {
53 | AdditionResourceIT additionTest = new AdditionResourceIT();
54 | additionTest.init();
55 | additionTest.timeoutWith42();
56 | }
57 |
58 | static int numberOfTimeouts(JsonObject input) {
59 | assertNotNull(input);
60 | return input.getInt("addition-timeouts");
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/breaker/boundary/CircuitBreakersAdmin.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.calculator.breaker.boundary;
2 |
3 |
4 | import javax.annotation.PostConstruct;
5 | import javax.enterprise.context.ApplicationScoped;
6 | import javax.json.Json;
7 | import javax.json.JsonObject;
8 | import javax.json.JsonObjectBuilder;
9 | import javax.ws.rs.DELETE;
10 | import javax.ws.rs.GET;
11 | import javax.ws.rs.Path;
12 | import javax.ws.rs.PathParam;
13 | import javax.ws.rs.core.Response;
14 | import java.util.concurrent.ConcurrentHashMap;
15 | import java.util.concurrent.atomic.AtomicInteger;
16 |
17 | @ApplicationScoped
18 | @Path("circuit-breakers")
19 | public class CircuitBreakersAdmin {
20 |
21 | ConcurrentHashMap counters;
22 |
23 | @PostConstruct
24 | public void init() {
25 | this.counters = new ConcurrentHashMap<>();
26 | }
27 |
28 | public AtomicInteger getCounter(Object target) {
29 | String name = target.getClass().getName();
30 | this.counters.putIfAbsent(name, new AtomicInteger());
31 | return this.counters.get(name);
32 | }
33 |
34 | @GET
35 | public JsonObject all() {
36 | JsonObjectBuilder retVal = Json.createObjectBuilder();
37 | this.counters.entrySet().forEach(e-> retVal.add(e.getKey(),e.getValue().longValue()));
38 | return retVal.build();
39 | }
40 |
41 | @GET
42 | @Path("{name}")
43 | public Response find(@PathParam("name") String name) {
44 | if(!this.counters.containsKey(name))
45 | return Response.noContent().build();
46 | AtomicInteger atomicLong = this.counters.get(name);
47 | return Response.ok(atomicLong.longValue()).build();
48 | }
49 |
50 | @DELETE
51 | @Path("{name}")
52 | public void reset(@PathParam("name") String name) {
53 | AtomicInteger atomicLong = this.counters.get(name);
54 | if(atomicLong != null)
55 | atomicLong.set(0);
56 | }
57 |
58 | @DELETE
59 | public void resetAll() {
60 | this.counters.values().forEach(v -> v.set(0));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/operations/boundary/OperationsResource.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.operations.boundary;
3 |
4 | import javax.ejb.Stateless;
5 | import javax.inject.Inject;
6 | import javax.json.Json;
7 | import javax.json.JsonObject;
8 | import javax.ws.rs.POST;
9 | import javax.ws.rs.Path;
10 |
11 | @Stateless
12 | @Path("operations")
13 | public class OperationsResource {
14 |
15 | @Inject
16 | OperationService operations;
17 |
18 | @POST
19 | @Path("addition")
20 | public JsonObject addition(JsonObject input) {
21 | int a = input.getJsonNumber("a").intValue();
22 | int b = input.getJsonNumber("b").intValue();
23 | int result = this.operations.add(a, b);
24 | return Json.createObjectBuilder().
25 | add("result", result).
26 | build();
27 | }
28 |
29 | @POST
30 | @Path("multiplication")
31 | public JsonObject multiplication(JsonObject input){
32 | int a = input.getJsonNumber("a").intValue();
33 | int b = input.getJsonNumber("b").intValue();
34 | int result = this.operations.multiply(a, b);
35 | return Json.createObjectBuilder().
36 | add("result", result).
37 | build();
38 |
39 | }
40 | @POST
41 | @Path("division")
42 | public JsonObject division(JsonObject input){
43 | int a = input.getJsonNumber("a").intValue();
44 | int b = input.getJsonNumber("b").intValue();
45 | double result = this.operations.divide(a, b);
46 | return Json.createObjectBuilder().
47 | add("result", result).
48 | build();
49 |
50 | }
51 |
52 | @POST
53 | @Path("subtraction")
54 | public JsonObject subtraction(JsonObject input){
55 | int a = input.getJsonNumber("a").intValue();
56 | int b = input.getJsonNumber("b").intValue();
57 | double result = this.operations.substract(a, b);
58 | return Json.createObjectBuilder().
59 | add("result", result).
60 | build();
61 |
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/addition/src/main/java/com/airhacks/calculator/addition/boundary/AdditionResource.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.addition.boundary;
3 |
4 | import static java.util.Optional.ofNullable;
5 | import static java.util.concurrent.CompletableFuture.supplyAsync;
6 | import java.util.concurrent.TimeUnit;
7 | import javax.annotation.Resource;
8 | import javax.ejb.Stateless;
9 | import javax.enterprise.concurrent.ManagedExecutorService;
10 | import javax.enterprise.event.Event;
11 | import javax.inject.Inject;
12 | import javax.json.Json;
13 | import javax.json.JsonNumber;
14 | import javax.json.JsonObject;
15 | import javax.ws.rs.POST;
16 | import javax.ws.rs.Path;
17 | import javax.ws.rs.container.AsyncResponse;
18 | import javax.ws.rs.container.Suspended;
19 | import javax.ws.rs.core.Response;
20 |
21 | /**
22 | *
23 | * @author airhacks.com
24 | */
25 | @Stateless
26 | @Path("addition")
27 | public class AdditionResource {
28 |
29 | @Inject
30 | Addition addition;
31 |
32 | @Inject
33 | Event timeoutEscalations;
34 |
35 | @Resource
36 | ManagedExecutorService mes;
37 |
38 | @POST
39 | public void addition(@Suspended AsyncResponse response, JsonObject input) {
40 | response.setTimeout(500, TimeUnit.MILLISECONDS);
41 | response.setTimeoutHandler(this::handleTimeout);
42 | int a = ofNullable(input.getJsonNumber("a")).
43 | map(JsonNumber::intValue).
44 | orElseThrow(() -> new LackingParameterException("a"));
45 | int b = ofNullable(input.getJsonNumber("b")).
46 | map(JsonNumber::intValue).
47 | orElseThrow(() -> new LackingParameterException("b"));
48 | supplyAsync(() -> computeResult(a, b), mes).thenAccept(response::resume);
49 | }
50 |
51 | JsonObject computeResult(int a, int b) {
52 | int result = addition.add(a, b);
53 | JsonObject payload = Json.createObjectBuilder().
54 | add("result", result).
55 | build();
56 | return payload;
57 | }
58 |
59 | void handleTimeout(AsyncResponse response) {
60 | timeoutEscalations.fire("addition is too lazy today");
61 | Response info = Response.
62 | status(Response.Status.SERVICE_UNAVAILABLE).
63 | header("reason", "too lazy").
64 | build();
65 | response.resume(info);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/kpi/boundary/MetricsCounter.java:
--------------------------------------------------------------------------------
1 | package com.airhacks.calculator.kpi.boundary;
2 |
3 | import com.airhacks.calculator.breaker.entity.SubsequentInvocationsFailure;
4 |
5 | import javax.annotation.PostConstruct;
6 | import javax.ejb.*;
7 | import javax.enterprise.event.Observes;
8 | import javax.json.Json;
9 | import javax.json.JsonObject;
10 | import javax.json.JsonObjectBuilder;
11 | import javax.ws.rs.GET;
12 | import javax.ws.rs.Path;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.concurrent.atomic.AtomicLong;
15 |
16 | @Startup
17 | @Singleton
18 | @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
19 | @Path("metrics")
20 | public class MetricsCounter {
21 |
22 | ConcurrentHashMap failedInvocations;
23 | AtomicLong fortyTwo;
24 | AtomicLong overflow;
25 | AtomicLong success;
26 |
27 |
28 | double successesPerSecond = 0;
29 | double overflowsPerSecond = 0;
30 |
31 | long lastSuccess = 0;
32 | long lastOverflow = 0;
33 |
34 | @PostConstruct
35 | public void init() {
36 | this.failedInvocations = new ConcurrentHashMap<>();
37 | this.fortyTwo = new AtomicLong();
38 | this.overflow = new AtomicLong();
39 | this.success = new AtomicLong();
40 | }
41 |
42 | @GET
43 | @Path("kpis")
44 | public JsonObject metrics() {
45 | return Json.createObjectBuilder().
46 | add("42count", fortyTwo.longValue()).
47 | add("overflows", overflow.longValue()).
48 | add("success", success.longValue()).
49 | add("successesPerSecond",this.successesPerSecond).
50 | add("overflowsPerSecond",this.overflowsPerSecond).
51 | build();
52 | }
53 |
54 | @GET
55 | @Path("open-circuits")
56 | public JsonObject openCircuits() {
57 | JsonObjectBuilder retVal = Json.createObjectBuilder();
58 | this.failedInvocations.
59 | entrySet().
60 | forEach(entry -> retVal.add(entry.getKey(),entry.getValue()));
61 | return retVal.build();
62 | }
63 |
64 |
65 |
66 | public void onOpenCircuitInvocation(@Observes SubsequentInvocationsFailure failure) {
67 | String methodName = failure.getMethodName();
68 | this.failedInvocations.put(methodName,failure.getFailuresCount());
69 | }
70 |
71 | public void fortyTwo() {
72 | fortyTwo.incrementAndGet();
73 | }
74 |
75 | public void overflow() {
76 | this.overflow.incrementAndGet();
77 | }
78 |
79 | public void success() {
80 | this.success.incrementAndGet();
81 | }
82 |
83 | @Schedule(minute = "*",second = "*/10",hour = "*")
84 | public void calculateMetrics() {
85 | System.out.println(".");
86 | this.successesPerSecond = ((double)(success.longValue() - this.lastSuccess))/10;
87 | this.lastSuccess = success.longValue();
88 |
89 | this.overflowsPerSecond = ((double)(overflow.longValue() - this.lastOverflow))/10;
90 | this.lastOverflow = overflow.longValue();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/addition-st/src/test/java/com/airhacks/calculator/addition/boundary/AdditionResourceIT.java:
--------------------------------------------------------------------------------
1 | /*
2 | */
3 | package com.airhacks.calculator.addition.boundary;
4 |
5 | import javax.json.Json;
6 | import javax.json.JsonObject;
7 | import javax.ws.rs.client.Client;
8 | import javax.ws.rs.client.ClientBuilder;
9 | import static javax.ws.rs.client.Entity.json;
10 | import javax.ws.rs.client.WebTarget;
11 | import javax.ws.rs.core.MediaType;
12 | import javax.ws.rs.core.Response;
13 | import static org.hamcrest.CoreMatchers.containsString;
14 | import static org.hamcrest.CoreMatchers.is;
15 | import static org.junit.Assert.assertThat;
16 | import org.junit.Before;
17 | import org.junit.Test;
18 |
19 | /**
20 | *
21 | * @author airhacks.com
22 | */
23 | public class AdditionResourceIT {
24 |
25 | private Client client;
26 | private WebTarget tut;
27 | static final String ADDITION_URI = "http://localhost:8383/addition/resources/addition/";
28 |
29 | @Before
30 | public void init() {
31 | this.client = ClientBuilder.newClient();
32 | this.tut = this.client.target(ADDITION_URI);
33 | }
34 |
35 | @Test
36 | public void addition() {
37 | JsonObject input = Json.createObjectBuilder().
38 | add("a", 2).
39 | add("b", 21).
40 | build();
41 | Response response = this.tut.
42 | request(MediaType.APPLICATION_JSON).
43 | post(json(input));
44 | assertThat(response.getStatus(), is(200));
45 | JsonObject jsonResult = response.readEntity(JsonObject.class);
46 | int result = jsonResult.getJsonNumber("result").intValue();
47 | assertThat(result, is(23));
48 | }
49 |
50 | @Test
51 | public void additionWithIncompleteInput() {
52 | JsonObject input = Json.createObjectBuilder().
53 | add("b", 2).
54 | build();
55 | Response response = this.tut.
56 | request(MediaType.APPLICATION_JSON).
57 | post(json(input));
58 | assertThat(response.getStatus(), is(400));
59 | String cause = response.getHeaderString("cause");
60 | assertThat(cause, containsString(" a "));
61 |
62 | input = Json.createObjectBuilder().
63 | add("a", 2).
64 | build();
65 | response = this.tut.
66 | request(MediaType.APPLICATION_JSON).
67 | post(json(input));
68 | assertThat(response.getStatus(), is(400));
69 | cause = response.getHeaderString("cause");
70 | assertThat(cause, containsString(" b "));
71 | }
72 |
73 | @Test
74 | public void timeoutWith42() {
75 | JsonObject input = Json.createObjectBuilder().
76 | add("a", 40).
77 | add("b", 2).
78 | build();
79 | Response response = this.tut.
80 | request(MediaType.APPLICATION_JSON).
81 | post(json(input));
82 | assertThat(response.getStatus(), is(503));
83 | String reason = response.getHeaderString("reason");
84 | assertThat(reason, containsString("lazy"));
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/calculator/src/main/java/com/airhacks/calculator/operations/boundary/OperationService.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.calculator.operations.boundary;
3 |
4 | import com.airhacks.calculator.breaker.boundary.CircuitBreaker;
5 | import com.airhacks.calculator.kpi.boundary.MetricsCounter;
6 | import com.airhacks.interceptor.monitoring.boundary.PerformanceSensor;
7 |
8 | import javax.annotation.PostConstruct;
9 | import javax.enterprise.context.ApplicationScoped;
10 | import javax.inject.Inject;
11 | import javax.interceptor.Interceptors;
12 | import javax.json.Json;
13 | import javax.json.JsonObject;
14 | import javax.ws.rs.WebApplicationException;
15 | import javax.ws.rs.client.Client;
16 | import javax.ws.rs.client.ClientBuilder;
17 |
18 | import static java.lang.Math.multiplyExact;
19 | import static java.lang.Math.subtractExact;
20 | import static javax.ws.rs.client.Entity.json;
21 | import javax.ws.rs.client.WebTarget;
22 | import javax.ws.rs.core.MediaType;
23 | import javax.ws.rs.core.Response;
24 |
25 | @ApplicationScoped
26 | @Interceptors(PerformanceSensor.class)
27 | public class OperationService {
28 |
29 | private Client client;
30 | private WebTarget tut;
31 | static final String ADDITION_URI = "http://addition:8080/addition/resources/addition/";
32 |
33 | @Inject
34 | private long readTimeout;
35 | @Inject
36 | private long connectionTimeout;
37 |
38 | @Inject
39 | MetricsCounter counter;
40 |
41 | @PostConstruct
42 | public void init() {
43 | this.client = ClientBuilder.newClient();
44 | this.client.property("http.connection.timeout", this.connectionTimeout);
45 | this.client.property("http.receive.timeout", this.readTimeout);
46 | this.tut = this.client.target(ADDITION_URI);
47 | }
48 |
49 | @Interceptors(CircuitBreaker.class)
50 | public int add(int a, int b) {
51 | JsonObject input = Json.createObjectBuilder().
52 | add("a", a).
53 | add("b", b).
54 | build();
55 | Response response = this.tut.
56 | request(MediaType.APPLICATION_JSON).
57 | post(json(input));
58 | JsonObject jsonResult = response.readEntity(JsonObject.class);
59 | this.counter.success();
60 | return jsonResult.getJsonNumber("result").intValue();
61 | }
62 |
63 | public double divide(int a,int b){
64 | try {
65 | return verifyResult(a / b);
66 | }catch(ArithmeticException ex){
67 | this.counter.overflow();
68 | throw new ArithmeticWebException(ex.getMessage());
69 | }
70 | }
71 |
72 | public int multiply(int a,int b){
73 | try {
74 | return verifyResult(multiplyExact(a,b));
75 | } catch (ArithmeticException e) {
76 | this.counter.overflow();
77 | throw new ArithmeticWebException(e.getMessage());
78 | }
79 | }
80 |
81 | public int substract(int a,int b){
82 | try {
83 | return verifyResult(subtractExact(a,b));
84 | } catch (ArithmeticException e) {
85 | this.counter.overflow();
86 | throw new ArithmeticWebException(e.getMessage());
87 | }
88 | }
89 |
90 | T verifyResult(T number){
91 | if(number.intValue() == 42){
92 | this.counter.fortyTwo();
93 | }
94 | this.counter.success();
95 | return number;
96 | }
97 |
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/problematic/src/main/java/com/airhacks/setup/control/SetupMonitoring.java:
--------------------------------------------------------------------------------
1 |
2 | package com.airhacks.setup.control;
3 |
4 | import java.io.BufferedReader;
5 | import java.io.IOException;
6 | import java.io.InputStreamReader;
7 | import java.util.stream.Collectors;
8 | import javax.annotation.PostConstruct;
9 | import javax.annotation.Resource;
10 | import javax.ejb.Singleton;
11 | import javax.ejb.Startup;
12 | import javax.ejb.Timeout;
13 | import javax.ejb.Timer;
14 | import javax.ejb.TimerConfig;
15 | import javax.ejb.TimerService;
16 | import javax.json.Json;
17 | import javax.json.JsonObject;
18 | import javax.ws.rs.client.ClientBuilder;
19 | import javax.ws.rs.client.Entity;
20 | import javax.ws.rs.client.WebTarget;
21 | import javax.ws.rs.core.Response;
22 |
23 | /**
24 | *
25 | * @author airhacks.com
26 | */
27 | @Singleton
28 | @Startup
29 | public class SetupMonitoring {
30 |
31 | private WebTarget monitoring;
32 |
33 | @Resource
34 | TimerService ts;
35 | private Timer timer;
36 |
37 |
38 | @PostConstruct
39 | public void init() {
40 | this.reconfigure();
41 | }
42 |
43 | void setupTimer() {
44 | this.timer = ts.createSingleActionTimer(30 * 1000, new TimerConfig(null, false));
45 |
46 | }
47 |
48 | @Timeout
49 | public void reconfigure() {
50 | this.monitoring = ClientBuilder.
51 | newClient().
52 | target("http://localhost:8080/firehose/resources/configurations/");
53 | this.configure("problematic503", "http://localhost:4848/monitoring/domain/server/http-service/server/request.json");
54 | this.configure("problematicRollbacks", "http://localhost:4848/monitoring/domain/server/transaction-service/rolledbackcount.json");
55 | this.configure("avgRequestProcessingTime", "http://localhost:4848/monitoring/domain/server/http-service/server/request/processingtime.json");
56 | this.configure("countqueued1minuteaverage", "http://localhost:4848/monitoring/domain/server/network/connection-queue/countqueued1minuteaverage.json");
57 | this.configure("echoExecutionTime", "http://localhost:4848/monitoring/domain/server/applications/problematic/Pings/bean-methods/echo-long-java.lang.String.json");
58 | this.configure("currentThreadsBusy", "http://localhost:4848/monitoring/domain/server/network/http-listener-1/thread-pool/currentthreadsbusy.json");
59 | this.configure("usedHeapSize", "http://localhost:4848/monitoring/domain/server/jvm/memory/usedheapsize-count.json");
60 | }
61 |
62 | public void configure(String name, String uri) {
63 | String extractor = null;
64 | try (BufferedReader buffer = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/" + name + ".js")))) {
65 | extractor = buffer.lines().collect(Collectors.joining("\n"));
66 | System.out.println("registering extractor = " + extractor);
67 | } catch (IOException ex) {
68 | throw new IllegalStateException("Initialization error", ex);
69 | }
70 | JsonObject configuration = Json.createObjectBuilder().
71 | add("uri", uri).
72 | add("extractor", extractor).
73 | build();
74 |
75 | Response response = monitoring.path(name).request().put(Entity.json(configuration));
76 | System.out.println("Registration status for " + name + " is: " + response.getStatus());
77 | if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
78 | System.out.println("Configuration was not successful, retrying later");
79 | this.setupTimer();
80 | }
81 |
82 | }
83 |
84 |
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/calculator-st/src/test/java/com/airhacks/calculator/operations/boundary/OperationsResourceIT.java:
--------------------------------------------------------------------------------
1 | /*
2 | */
3 | package com.airhacks.calculator.operations.boundary;
4 |
5 | import javax.json.Json;
6 | import javax.json.JsonObject;
7 | import javax.ws.rs.client.Client;
8 | import javax.ws.rs.client.ClientBuilder;
9 | import static javax.ws.rs.client.Entity.json;
10 | import javax.ws.rs.client.WebTarget;
11 | import javax.ws.rs.core.MediaType;
12 | import javax.ws.rs.core.Response;
13 | import static org.hamcrest.CoreMatchers.is;
14 | import static org.junit.Assert.assertThat;
15 | import org.junit.Before;
16 | import org.junit.Test;
17 |
18 | /**
19 | *
20 | * @author airhacks.com
21 | */
22 | public class OperationsResourceIT {
23 |
24 | private Client client;
25 | private WebTarget tut;
26 | static final String ADDITION_URI = "http://localhost:8282/calculator/resources/operations/";
27 |
28 | @Before
29 | public void init() {
30 | this.client = ClientBuilder.newClient();
31 | this.tut = this.client.target(ADDITION_URI);
32 | }
33 |
34 | @Test
35 | public void addition() {
36 | JsonObject input = Json.createObjectBuilder().
37 | add("a", 2).
38 | add("b", 21).
39 | build();
40 | Response response = this.tut.path("addition").
41 | request(MediaType.APPLICATION_JSON).
42 | post(json(input));
43 | assertThat(response.getStatus(), is(200));
44 | JsonObject jsonResult = response.readEntity(JsonObject.class);
45 | int result = jsonResult.getJsonNumber("result").intValue();
46 | assertThat(result, is(23));
47 | }
48 |
49 |
50 |
51 | @Test
52 | public void slowAddition() {
53 | JsonObject input = Json.createObjectBuilder().
54 | add("a", 2).
55 | add("b", 40).
56 | build();
57 | Response response = this.tut.path("addition").
58 | request(MediaType.APPLICATION_JSON).
59 | post(json(input));
60 | assertThat(response.getStatus(), is(200));
61 | JsonObject jsonResult = response.readEntity(JsonObject.class);
62 | int result = jsonResult.getJsonNumber("result").intValue();
63 | assertThat(result, is(23));
64 | }
65 |
66 | @Test
67 | public void multiplication() {
68 | JsonObject input = Json.createObjectBuilder().
69 | add("a", 2).
70 | add("b", 21).
71 | build();
72 | Response response = this.tut.path("multiplication").
73 | request(MediaType.APPLICATION_JSON).
74 | post(json(input));
75 | assertThat(response.getStatus(), is(200));
76 | JsonObject jsonResult = response.readEntity(JsonObject.class);
77 | int result = jsonResult.getJsonNumber("result").intValue();
78 | assertThat(result, is(42));
79 | }
80 |
81 | @Test
82 | public void multiplicationWithOverflow() {
83 | JsonObject input = Json.createObjectBuilder().
84 | add("a", Integer.MAX_VALUE).
85 | add("b", Integer.MAX_VALUE).
86 | build();
87 | Response response = this.tut.path("multiplication").
88 | request(MediaType.APPLICATION_JSON).
89 | post(json(input));
90 | assertThat(response.getStatus(), is(400));
91 | }
92 |
93 | @Test
94 | public void division() {
95 | JsonObject input = Json.createObjectBuilder().
96 | add("a", 22).
97 | add("b", 2).
98 | build();
99 | Response response = this.tut.path("division").
100 | request(MediaType.APPLICATION_JSON).
101 | post(json(input));
102 | assertThat(response.getStatus(), is(200));
103 | JsonObject jsonResult = response.readEntity(JsonObject.class);
104 | int result = jsonResult.getJsonNumber("result").intValue();
105 | assertThat(result, is(11));
106 | }
107 | @Test
108 | public void subtraction() {
109 | JsonObject input = Json.createObjectBuilder().
110 | add("a", 22).
111 | add("b", 2).
112 | build();
113 | Response response = this.tut.path("subtraction").
114 | request(MediaType.APPLICATION_JSON).
115 | post(json(input));
116 | assertThat(response.getStatus(), is(200));
117 | JsonObject jsonResult = response.readEntity(JsonObject.class);
118 | int result = jsonResult.getJsonNumber("result").intValue();
119 | assertThat(result, is(20));
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/problematic/src/main/stress/problematic_torture.jmx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | continue
16 |
17 | false
18 | -1
19 |
20 | 50
21 | 1
22 | 1511456117000
23 | 1511456117000
24 | false
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | localhost
34 | 8010
35 |
36 |
37 |
38 |
39 | /problematic/resources/ping/heavy/10-rollback
40 | GET
41 | true
42 | false
43 | true
44 | false
45 | false
46 |
47 |
48 |
49 |
50 | false
51 |
52 | saveConfig
53 |
54 |
55 | true
56 | true
57 | true
58 |
59 | true
60 | true
61 | true
62 | true
63 | false
64 | true
65 | true
66 | false
67 | false
68 | false
69 | true
70 | false
71 | false
72 | false
73 | true
74 | 0
75 | true
76 | true
77 | true
78 | true
79 | true
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------