diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..248dbbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +target/ +.idea/ +*.iml +.vscode/ +.classpath +.project +.settings/ diff --git a/.openshiftio/application.yaml b/.openshiftio/application.yaml new file mode 100644 index 0000000..de5c544 --- /dev/null +++ b/.openshiftio/application.yaml @@ -0,0 +1,224 @@ +apiVersion: v1 +kind: Template +metadata: + name: launchpad-builder-fuse-rest-http-booster + annotations: + description: This template creates a Build Configuration using an S2I builder. + tags: instant-app +parameters: +- name: SOURCE_REPOSITORY_URL + description: The source URL for the application + displayName: Source URL + required: true +- name: SOURCE_REPOSITORY_REF + description: The branch name for the application + displayName: Source Branch + value: master + required: true +- name: SOURCE_REPOSITORY_DIR + description: The location within the source repo of the application + displayName: Source Directory + value: . + required: true +- name: ARTIFACT_COPY_ARGS + description: Syntax to be used to copy uberjar files to the target directory + displayName: Copy Args + value: '*-exec.jar' + required: true +- name: GITHUB_WEBHOOK_SECRET + description: A secret string used to configure the GitHub webhook. + displayName: GitHub Webhook Secret + required: true + from: '[a-zA-Z0-9]{40}' + generate: expression +- name: MAVEN_MIRROR_URL + description: Maven Nexus Repository to be used during build phase + displayName: + required: false +objects: +- apiVersion: v1 + kind: ImageStream + metadata: + name: fis-java-openshift + spec: + tags: + - name: "1.0" + annotations: + description: JBoss Fuse Integration Services 1.0 Java S2I images. + iconClass: icon-jboss + supports: 'jboss-fuse:6.2.1,java:8,xpaas:1.2' + tags: 'builder,jboss-fuse,java,xpaas,hidden' + version: '1.0' + from: + kind: DockerImage + name: 'registry.access.redhat.com/jboss-fuse-6/fis-java-openshift:1.0' + - name: "2.0" + annotations: + description: JBoss Fuse Integration Services 2.0 Java S2I images. + iconClass: icon-jboss + supports: 'jboss-fuse:6.3.0,java:8,xpaas:1.2' + tags: 'builder,jboss-fuse,java,xpaas' + version: '2.0' + from: + kind: DockerImage + name: 'registry.access.redhat.com/jboss-fuse-6/fis-java-openshift:2.0' +- apiVersion: v1 + kind: ImageStream + metadata: + name: fuse-rest-http-booster + spec: {} +- apiVersion: v1 + kind: BuildConfig + metadata: + name: fuse-rest-http-booster + labels: + app: fuse-rest-http-booster + group: com.redhat.fuse.boosters + provider: fabric8 + spec: + output: + to: + kind: ImageStreamTag + name: fuse-rest-http-booster:latest + postCommit: {} + resources: {} + runPolicy: Serial + source: + git: + uri: ${SOURCE_REPOSITORY_URL} + ref: ${SOURCE_REPOSITORY_REF} + #contextDir: ${SOURCE_REPOSITORY_DIR} + type: Git + strategy: + sourceStrategy: + env: + - name: BUILD_LOGLEVEL + value: '5' + - name: MAVEN_ARGS_APPEND + value: "-pl ${SOURCE_REPOSITORY_DIR}" + - name: ARTIFACT_DIR + value: "${SOURCE_REPOSITORY_DIR}/target" + - name: MAVEN_MIRROR_URL + value: "${MAVEN_MIRROR_URL}" + - name: ARTIFACT_COPY_ARGS + value: "${ARTIFACT_COPY_ARGS}" + forcePull: true + from: + kind: ImageStreamTag + name: fis-java-openshift:2.0 + incremental: true + type: Source + triggers: + - github: + secret: ${GITHUB_WEBHOOK_SECRET} + type: GitHub + - type: ConfigChange + - imageChange: {} + type: ImageChange + status: + lastVersion: 0 +- apiVersion: v1 + kind: Service + metadata: + annotations: + prometheus.io/port: "9779" + prometheus.io/scrape: "true" + labels: + expose: "true" + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters + name: fuse-rest-http-booster + spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters +- apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters + name: fuse-rest-http-booster + spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters + strategy: + rollingParams: + timeoutSeconds: 3600 + type: Rolling + template: + metadata: + labels: + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters + spec: + containers: + - env: + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: fuse-rest-http-booster:latest + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 180 + name: fuse-rest-http-booster + ports: + - containerPort: 8080 + name: http + protocol: TCP + - containerPort: 9779 + name: prometheus + protocol: TCP + - containerPort: 8778 + name: jolokia + protocol: TCP + readinessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 10 + securityContext: + privileged: false + triggers: + - type: ConfigChange + - imageChangeParams: + automatic: true + containerNames: + - fuse-rest-http-booster + from: + kind: ImageStreamTag + name: fuse-rest-http-booster:latest + type: ImageChange +- apiVersion: v1 + kind: Route + metadata: + labels: + app: fuse-rest-http-booster + provider: fabric8 + group: com.redhat.fuse.boosters + name: fuse-rest-http-booster + spec: + port: + targetPort: 8080 + to: + kind: Service + name: fuse-rest-http-booster diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..08fcf0c --- /dev/null +++ b/README.adoc @@ -0,0 +1,87 @@ +:launchURL: https://developers.redhat.com/launch + += REST API Level 0 - Fuse Booster + +== Overview +The REST API Level 0 mission shows how to map business operations to a remote procedure call endpoint over HTTP using a REST framework. This corresponds to Level 0 in the Richardson Maturity Model. +Creating an HTTP endpoint using REST and its underlying principles to define your API lets you quickly prototype and design the API flexibly. + +This booster introduces the mechanics of interacting with a remote (exposed by Apache Camel) service using the HTTP protocol. It allows you to: +. Execute an HTTP GET request on `api/greetings/{name}`` endpoint; using the url parameter `{name}` and producing a response in JSON format with a payload of Hello, $name! with $name replaced by the value of the url parameter used into the request. +. Browse the api's Swagger page. + +== Deployment options + +This booster can run in the following modes: + +* Standalone on your machine +* Single-node OpenShift cluster +* OpenShift Online at link:{launchURL}[] + +The most effective way to demonstrate the booster is to deploy and run the project on OpenShift. +For more details about running this booster on a single-node OpenShift cluster, CI/CD deployments, as well as the rest of the runtime, see the link:http://appdev.openshift.io/docs/spring-boot-runtime.html[Spring Boot Runtime Guide]. + +IMPORTANT: This booster requires Java 8 JDK or greater and Maven 3.3.x or greater. + +== Running the booster standalone on your machine +You can run this booster as a standalone project on your local machine: + +. Download the project and extract the archive on your local filesystem. +. Build the project: ++ +[source,bash,options="nowrap",subs="attributes+"] +---- +$ cd PROJECT_DIR +$ mvn clean package +---- +. then run the services as follows: ++ +[source,bash,options="nowrap",subs="attributes+"] +---- +$ mvn spring-boot:run +---- +. Visit link:http://localhost:8080[] and follow the instructions on that page. + +== Running the booster on a single-node OpenShift cluster +If you have a single-node OpenShift cluster, such as Minishift or the Red Hat Container Development Kit, link:http://appdev.openshift.io/docs/minishift-installation.html[installed and running], you can deploy your booster there. +A single-node OpenShift cluster provides you with access to a cloud environment that is similar to a production environment. + +To deploy your booster to a running single-node OpenShift cluster: + +. Download the project and extract the archive on your local filesystem. + +. Log in to your OpenShift cluster: ++ +[source,bash,options="nowrap",subs="attributes+"] +---- +$ oc login -u developer -p developer +---- + +. Create a new OpenShift project for the booster: ++ +[source,bash,options="nowrap",subs="attributes+"] +---- +$ oc new-project MY_PROJECT_NAME +---- + +. Build and deploy the project to the OpenShift cluster: ++ +[source,bash,options="nowrap",subs="attributes+"] +---- +$ mvn clean -DskipTests fabric8:deploy -Popenshift +---- + +. In your browser, navigate to the `MY_PROJECT_NAME` project in the OpenShift console. +Wait until you can see that the pod for the `fuse-rest-http-booster` application has started up. + +. Just above the entry for the `fuse-rest-http-booster` application on the `Overview` page, there is a URL of the form `http://fuse-rest-http-booster-MY_PROJECT_NAME.OPENSHIFT_IP_ADDR.nip.io`. +Click on the URL to access the greetings service application and follow the instructions on that page. + +== Running the booster on OpenShift Online +You can deploy the circuit breaker booster directly to OpenShift Online when you create the project at link:{launchURL}[]. + +. Visit link:{launchURL}[]. +. At the *Deployment step*, select *Use OpenShift Online*. +. Follow the on-screen instructions to create a new *REST API Level 0* project using the *Fuse* runtime. + +NOTE: As part of the process of creating this booster, link:{launchURL}[] sets up a project with a CI/CD deployment of this booster. You can see the status of this deployment in your Single-node OpenShift Cluster or OpenShift Online Web Console. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3d9c22e --- /dev/null +++ b/pom.xml @@ -0,0 +1,215 @@ + + + + 4.0.0 + + com.redhat.fuse.boosters + fuse-rest-http-booster + 7.0.0.redhat-SNAPSHOT + + Fuse :: Boosters :: Rest HTTP + + + UTF-8 + UTF-8 + + 1.5.12.RELEASE + 2.21.0 + + + 3.5.33 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.apache.camel + camel-spring-boot-dependencies + ${camel.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.webjars + swagger-ui + 3.13.0 + + + org.webjars + webjars-locator + 0.33 + + + + + org.apache.camel + camel-spring-boot-starter + + + org.apache.camel + camel-http-starter + + + org.apache.camel + camel-servlet-starter + + + org.apache.camel + camel-jackson-starter + + + org.apache.camel + camel-swagger-java-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.camel + camel-test + test + + + + + + + + io.fabric8 + fabric8-maven-plugin + ${fmp.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + exec + + + + + repackage + + + + + + + + + + redhat-ga-repository + https://maven.repository.redhat.com/ga + + true + + + false + + + + redhat-ea-repository + https://maven.repository.redhat.com/earlyaccess/all + + true + + + false + + + + + + + redhat-ga-repository + https://maven.repository.redhat.com/ga + + true + + + false + + + + redhat-ea-repository + https://maven.repository.redhat.com/earlyaccess/all + + true + + + false + + + + + + + openshift + + + + io.fabric8 + fabric8-maven-plugin + + + + resource + build + + + + + + + + + diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/Application.java b/src/main/java/com/redhat/fuse/boosters/rest/http/Application.java new file mode 100644 index 0000000..e85fb7b --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/Application.java @@ -0,0 +1,16 @@ +package com.redhat.fuse.boosters.rest.http; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + /** + * Main method to start the application. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/CamelRouter.java b/src/main/java/com/redhat/fuse/boosters/rest/http/CamelRouter.java new file mode 100644 index 0000000..adc09b9 --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/CamelRouter.java @@ -0,0 +1,43 @@ +package com.redhat.fuse.boosters.rest.http; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.rest.RestBindingMode; +import org.springframework.stereotype.Component; + +/** + * A simple Camel REST DSL route that implement the greetings service. + * + */ +@Component +public class CamelRouter extends RouteBuilder { + + + @Override + public void configure() throws Exception { + + // @formatter:off + restConfiguration() + .apiContextPath("/api-doc") + .apiProperty("api.title", "Greeting REST API") + .apiProperty("api.version", "1.0") + .apiProperty("cors", "true") + .apiProperty("base.path", "camel/") + .apiProperty("api.path", "/") + .apiProperty("host", "") +// .apiProperty("schemes", "") + .apiContextRouteId("doc-api") + .component("servlet") + .bindingMode(RestBindingMode.json); + + rest("/greetings/").description("Greeting to {name}") + .get("/{name}").outType(Greetings.class) + .route().routeId("greeting-api") + .to("direct:greetingsImpl"); + + from("direct:greetingsImpl").description("Greetings REST service implementation route") + .streamCaching() + .to("bean:greetingsService?method=getGreetings"); + // @formatter:on + } + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/Greetings.java b/src/main/java/com/redhat/fuse/boosters/rest/http/Greetings.java new file mode 100644 index 0000000..d005589 --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/Greetings.java @@ -0,0 +1,26 @@ +package com.redhat.fuse.boosters.rest.http; + +/** + * Greetings entity + * + */ +public class Greetings { + + private String greetings; + + public Greetings() { + } + + public Greetings(String greetings) { + this.greetings = greetings; + } + + public String getGreetings() { + return greetings; + } + + public void setGreetings(String greetings) { + this.greetings = greetings; + } + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsService.java b/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsService.java new file mode 100644 index 0000000..17dab29 --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsService.java @@ -0,0 +1,16 @@ +package com.redhat.fuse.boosters.rest.http; + +/** + * Service interface for name service. + * + */ +public interface GreetingsService { + + /** + * Generate Greetings + * + * @return a string greetings + */ + Greetings getGreetings( String name); + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsServiceImpl.java b/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsServiceImpl.java new file mode 100644 index 0000000..fbbde7b --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/GreetingsServiceImpl.java @@ -0,0 +1,16 @@ +package com.redhat.fuse.boosters.rest.http; + +import org.apache.camel.Header; +import org.springframework.stereotype.Service; + +@Service("greetingsService") +public class GreetingsServiceImpl implements GreetingsService { + + private static final String THE_GREETINGS = "Hello, "; + + @Override + public Greetings getGreetings(@Header("name") String name ) { + return new Greetings( THE_GREETINGS + name ); + } + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/fuse/boosters/rest/http/IndexRootMapper.java b/src/main/java/com/redhat/fuse/boosters/rest/http/IndexRootMapper.java new file mode 100644 index 0000000..16a21c4 --- /dev/null +++ b/src/main/java/com/redhat/fuse/boosters/rest/http/IndexRootMapper.java @@ -0,0 +1,21 @@ +package com.redhat.fuse.boosters.rest.http; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@ComponentScan +public class IndexRootMapper extends WebMvcConfigurerAdapter { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("forward:/rest-http-index.html"); + } + + // @Override + // public void addViewControllers(ViewControllerRegistry registry) { + // registry.addViewController("/").setStatusCode(HttpStatus.PERMANENT_REDIRECT).setViewName("redirect:/webjars/swagger-ui/index.html?url=/camel/api-doc&validatorUrl="); + // } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/resources/favicon.ico b/src/main/resources/META-INF/resources/favicon.ico new file mode 100644 index 0000000..145e43a Binary files /dev/null and b/src/main/resources/META-INF/resources/favicon.ico differ diff --git a/src/main/resources/META-INF/resources/rest-http-index.html b/src/main/resources/META-INF/resources/rest-http-index.html new file mode 100644 index 0000000..52dbfb6 --- /dev/null +++ b/src/main/resources/META-INF/resources/rest-http-index.html @@ -0,0 +1,31 @@ + + + + + Rest Http Mission - Red Hat Fuse + + + +
+
+

Rest Http Mission - Red Hat Fuse

+

+ The REST API Level 0 mission shows how to map business operations to a remote procedure call endpoint over HTTP using a REST framework. This corresponds to Level 0 in the Richardson Maturity Model. + Creating an HTTP endpoint using REST and its underlying principles to define your API lets you quickly prototype and design the API flexibly. +

+

+ This booster introduces the mechanics of interacting with a remote (exposed by Apache Camel) service using the HTTP protocol. It allows you to: +

+
Execute an HTTP GET request on api/greetings/{name} endpoint:
+
api/greetings/{name} this uses the url parameter {name} and produce a response in JSON format with a payload of Hello, $name! with $name replaced by the value of the url parameter used into the request.
+
Browse the api's Swagger page:
+
Api Swagger page this displays the swagger-ui for the rest api.
+
+

+
+
+ + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..57ab22d --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,9 @@ +server.port=8080 + +# disable all management enpoints except health +endpoints.enabled = false +endpoints.health.enabled = true +management.health.defaults.enabled=false + +camel.health.enabled=false +camel.health.indicator.enabled=true diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 0000000..ca73e8a --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,8 @@ +appender.stdout.type = Console +appender.stdout.name = stdout +appender.stdout.layout.type = PatternLayout +appender.stdout.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +logger.zipkin.name = org.apache.camel.zipkin +logger.zipkin.level = DEBUG +rootLogger.level = DEBUG +rootLogger.appenderRef.stdout.ref = stdout diff --git a/src/test/java/com/redhat/fuse/boosters/rest/http/HttpRequestTest.java b/src/test/java/com/redhat/fuse/boosters/rest/http/HttpRequestTest.java new file mode 100644 index 0000000..aadab43 --- /dev/null +++ b/src/test/java/com/redhat/fuse/boosters/rest/http/HttpRequestTest.java @@ -0,0 +1,31 @@ +package com.redhat.fuse.boosters.rest.http; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class HttpRequestTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void greetingsShouldReturnFallbackMessage() throws Exception { + Assert.assertEquals( "Hello, jacopo", this.restTemplate.getForObject("http://localhost:" + port + "/camel/greetings/jacopo", Greetings.class).getGreetings()); + } + + @Test + public void healthShouldReturnOkMessage() throws Exception { + Assert.assertEquals( "{\"status\":\"UP\"}", this.restTemplate.getForObject("http://localhost:" + port + "/health", String.class)); + } +} \ No newline at end of file