FREE

ΕΝΟΤΗΤΑ 4 APACHE CAMEL - COMBINING RESTFUL SERVICE WITH APACHE CAMEL

Στο σημερινό δωρεάν μάθημα Apache Camel θα δούμε πως μπορούμε να στείλουμε δεδομένα από ένα RESTful service σε ένα Apache Camel route. Για να μπορέσουμε να πετύχουμε το επιθυμητό αποτέλεσμα θα πρέπει να μιλήσουμε για το CamelContext, για το ProducerTemplate και για το VM component.

Στην προηγούμενη ενότητα είχαμε δει πως μπορούμε να διαβάζουμε αρχεία, από δύο διαφορετικούς φακέλους, και να στέλνουμε τις πληροφορίες στο ίδιο direct component για να καταλήξει σε ένα queue. Επίσης είχαμε δει πως να κάνουμε παρεμβάσεις στο μήνυμα με τους Processors. Επάνω σε αυτόν τον κώδικα θα κάνουμε κάποιες τροποποιήσεις για να επικοινωνήσει ένα RESTful service που θα δημιουργήσουμε με το direct component. Επίσης, το γεγονός ότι έχουμε αναπτύξει το πρόγραμμα μας στην πλατφόρμα του microprofile, θα μας φανεί χρήσιμο διότι περιέχει ήδη όλες τις απαραίτητες Java ΕΕ 8 βιβλιοθήκες για την δημιουργία RESTful services.

Για αρχή θα χρειαστεί να δημιουργήσουμε ένα απλό RESTful service. Για αυτό το σκοπό θα χρειαστούμε τρεις κλάσεις. Η πρώτη θα ενεργοποιεί το RESTful service, η δεύτερη θα έχει τον κώδικα που θα εκτελείται κάθε φορά που καλούμε το service, ενώ η τρίτη θα δημιουργεί αντικείμενα είδος Customer. Δεν θα μπούμε σε λεπτομέρειες για το πως ακριβώς δουλεύει ένα RESTful service. Μπορείτε να διαβάσετε το Jakarta EE 8 documentation για αυτό. Εδώ απλά χρησιμοποιούμε το RESTful service για να δείξουμε τι επιπλέον κώδικας χρειάζεται για να καλέσουμε ένα Apache Camel route μέσα από αυτό.

Δημιουργούμε μια κλάση με το όνομα JAXRSApplication. Το όνομα δεν έχει σημασία. Αυτό όμως που έχει σημασία είναι ότι για να ενεργοποιηθεί ένα RESTful service θα πρέπει η κλάση να κληρονομεί από την κλάση Application. Επίσης, με το annotation @ApplicationPath δηλώνουμε το root URL στο οποίο μπορούμε να στέλνουμε τα αιτήματα μας. Ο κώδικας της κλάσης JAXRSApplication είναι ο εξής:

JAXRSApplication.java


package com.example;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("resources")
public class JAXRSApplication extends Application {

}

Ας δημιουργήσουμε τώρα την κλάση που περιγράφει έναν Customer. Θα κρατήσουμε τον κώδικα σε πολύ απλή μορφή οπότε έναν Customer τον περιγράφουμε με ένα id, με το όνομα και το επίθετο. Ο κώδικας της κλάσης Customer είναι ο εξής:

Customer.java


package com.example;

public class Customer {
	private int id;

	private String first;

	private String last;

	public String getLastName() {
	return last;
	}

	public void setLastName(String last) {
	this.last = last;
	}


	public int getId() {
	return id;
	}

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

	public String getFirstName() {
	return first;
	}

	public void setFirstName(String first) {
	this.first = first;
	}

	@Override
	public String toString(){
	return this.id+" "+this.first+" "+this.last;
	}

}

Τέλος, ας δημιουργήσουμε το ίδιο το web service το οποίο θα αποτελείται από δύο μεθόδους – μια @GET η οποία θα μας επιστρέφει όλες τις καταχωρήσεις που έχουμε κάνει στην βάση και μια @POST με την οποία θα προσθέτουμε καινούργιους customers. Για να κρατήσουμε τον κώδικα μας απλό, δεν θα ενωθούμε σε μια πραγματική βάση, αλλά θα χρησιμοποιήσουμε ένα Map.

Στην @POST μέθοδο, δεχόμαστε ένα input είδος String το οποίο μετατρέπουμε σε αντικείμενο Customer με την χρήση του JSON-B. Αφού αυξήσουμε το id κατά ένα, προσθέτουμε το αντικείμενο customer στο Map. Με την @GET μέθοδο διαβάζουμε ότι customers υπάρχουν μέσα στο Map και αφού μετατρέψουμε τα αντικείμενα σε JSON (πάλι χρησιμοποιώντας JSON-B) τα επιστρέφουμε σαν αποτέλεσμα του αιτήματος . Ας δούμε τον κώδικα και πως λειτουργεί μεμονωμένα αυτό το web service πριν το ενώσουμε με το Apache Camel.

CustomerResource.java


package com.example;

import javax.enterprise.context.ApplicationScoped;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@ApplicationScoped
@Path("/customers")
public class CustomerResource {
private Map<Integer, Customer> customerDB = new ConcurrentHashMap<Integer, Customer>();
		private AtomicInteger idCounter = new AtomicInteger();
		String customerString;

		public CustomerResource() {
		}

		@POST
		@Consumes(MediaType.APPLICATION_JSON)
		public Response createCustomer(String input) {

		Jsonb jsonb = JsonbBuilder.create();
		Customer customer = new Customer();
		customer = jsonb.fromJson(input, Customer.class);
		customer.setId(idCounter.incrementAndGet());
		System.out.println(customer.toString());
		customerDB.put(customer.getId(), customer);
		System.out.println("Created customer " + customer.getId());
		return Response.created(URI.create("/customers/" + customer.getId())).build();

		}

		@GET
		@Produces(MediaType.APPLICATION_JSON)
		public Response getCustomer() {
		Jsonb jsonb = JsonbBuilder.create();
		String result = jsonb.toJson(customerDB);
		System.out.println(result);
		return Response.ok(result).build();
		}
	}

Δωρεάν Μαθήματα και Σεμινάρια Apache Camel

Τρέχουμε λοιπόν την εντολή mvn clean install για να χτιστεί το νέο jar με τον προστιθέμενο κώδικα και ξεκινάμε την εφαρμογή. Όταν ο Tomee Application Server είναι σε πλήρη λειτουργία, τότε χρησιμοποιώντας ένα εργαλείο όπως το Postman μπορούμε να καλέσουμε τα web services.

Δωρεάν Μαθήματα και Σεμινάρια Apache Camel

Δωρεάν Μαθήματα και Σεμινάρια Apache Camel

Αφού λοιπόν έχουμε το Web Service σε λειτουργία, τώρα επιθυμούμε να στείλουμε το JSON input και στο Apache Camel route για να αποθηκευτεί στο queue. Ένας από τους λόγους που θέλουμε να το κάνουμε αυτό ίσως είναι γιατί αυτή την πληροφορία την περιμένει ένα άλλο σύστημα που ασύγχρονα μέσα από το queue ενεργοποιείται κάθε φορά που υπάρχει καινούργιο εισερχόμενο μήνυμα.

Για να κατορθώσουμε να επικοινωνήσουμε από την εφαρμογή μας με το Apache Camel γενικότερα, θα χρειαστεί να κάνουμε inject ένα αντικείμενο είδος CamelContext. Μην ξεχνάτε, ότι και σε προηγούμενη ενότητα, όπου θέλαμε να μιλήσουμε με έναν εξωτερικό broker, το component sjms που είχαμε δημιουργήσει το δηλώσαμε πρώτα στο CamelContext πριν το χρησιμοποιήσουμε. Ο λόγος είναι γιατί το CamelContext είναι ο κεντρικός μηχανισμός που ελέγχει όλη την λειτουργία του Apache Camel. Οτιδήποτε και αν προσπαθήσουμε να δημιουργήσουμε θα πρέπει να είναι δηλωμένο πρώτα στο CamelContext και μετά να χρησιμοποιηθεί στον κώδικα.

Μια από τις πολλές ιδιότητες του CamelContext, από την στιγμή που το κάνουμε inject στον κώδικα μας, είναι και η δημιουργία ενός ProducerTemplate. Το ProducerTemplate μας δίνει την δυνατότητα να στέλνουμε μηνύματα σε endpoints. Οπότε είναι ακριβώς αυτό που αναζητάμε. Το μήνυμα που θέλουμε να στείλουμε είναι το JSON input από τον χρήστη, ενώ το endpoint το έχουμε ήδη και είναι το direct:start.

https://camel.apache.org/manual/latest/producertemplate.html

Δωρεάν Μαθήματα και Σεμινάρια Apache Camel

Ας δούμε λοιπόν πως αλλάζει ο κώδικας στην κλάση CustomerResource.

CustomerResource.java


package com.example;

import javax.enterprise.context.ApplicationScoped;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;

@ApplicationScoped
@Path("/customers")
public class CustomerResource {
private Map<Integer, Customer> customerDB = new ConcurrentHashMap<Integer, Customer>();
		private AtomicInteger idCounter = new AtomicInteger();
		String customerString;

		public CustomerResource() {
		}

		@Inject
		private CamelContext camelctx;

		@POST
		@Consumes(MediaType.APPLICATION_JSON)
		public Response createCustomer(String input) {

		ProducerTemplate producer = camelctx.createProducerTemplate();
		producer.sendBody("direct:start", input);

		Jsonb jsonb = JsonbBuilder.create();
		Customer customer = new Customer();
		customer = jsonb.fromJson(input, Customer.class);
		customer.setId(idCounter.incrementAndGet());
		System.out.println(customer.toString());
		customerDB.put(customer.getId(), customer);
		System.out.println("Created customer " + customer.getId());
		return Response.created(URI.create("/customers/" + customer.getId())).build();

		}

		@GET
		@Produces(MediaType.APPLICATION_JSON)
		public Response getCustomer() {
		Jsonb jsonb = JsonbBuilder.create();
		String result = jsonb.toJson(customerDB);
		System.out.println(result);
		return Response.ok(result).build();
		}
	}

Πριν όμως ξεκινήσουμε την εφαρμογή, ας κάνουμε ακόμα μια αλλαγή αλλά αυτή την φορά στο Apache Camel route. Μέχρι τώρα, σε όλα μας τα παραδείγματα χρησιμοποιούσαμε έναν εξωτερικό broker στου οποίου το queue στέλναμε τα μηνύματα μας. Κατά την διάρκεια ανάπτυξης κώδικα, ίσως δεν επιθυμείτε να σηκώσετε ένα εξωτερικό σύστημα, όπως έναν broker, για να δοκιμάσετε τον κώδικα σας. Το Apache Camel μας προσφέρει ένα εσωτερικό component που κάνει προσομοίωση ενός broker και ονομάζεται vm.

https://camel.apache.org/components/latest/vm-component.html

Δωρεάν Μαθήματα και Σεμινάρια Apache Camel

Μπορούμε λοιπόν να αντικαταστήσουμε την εξωτερική σύνδεση με τον broker με ένα vm component. Για να αναγνωρίσει το συγκεκριμένο component η εφαρμογή μας θα πρέπει να δηλώσουμε το dependency στο pom.xml αρχείο.

vm dependency


	<dependency>
		<groupId>org.apache.camel</groupId>
		<artifactId>camel-vm</artifactId>
		<version>3.4.0</version>
	</dependency>

Για να μπορέσουμε να βεβαιωθούμε ότι το μήνυμα μας έφτασε στο vm component, μπορούμε να χρησιμοποιήσουμε έναν processor που απλά τυπώνει στο terminal ότι μήνυμα έχει φτάσει στο vm component. Θα μπορούσαμε επίσης μέσα στον Processor να γράψουμε κώδικα που μετατρέπει το JSON σε αντικείμενο Customer και να κάναμε κάποια ενέργεια με αυτό. Ας δούμε λοιπόν πως αλλάζει ο κώδικας της MyRouteBuilder κλάσης.

MyRouteBuilder.java


package com.example;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilder extends RouteBuilder {


@Override
public void configure() {


from("file:C:\\testdata1?delete=true").
		process(new Processor() {
		public void process(Exchange exchange) throws Exception {
		String inBody = exchange.getIn().getBody(String.class);
		inBody = "Message from testdata1 : " + inBody;
		exchange.getIn().setBody(inBody);
	}
}).
to("direct:start");

from("file:C:\\testdata2?delete=true").process(new Processor() {
		public void process(Exchange exchange) throws Exception {
		String inBody = exchange.getIn().getBody(String.class);
		inBody = "Message from testdata2 : " + inBody;
		exchange.getIn().setBody(inBody);
	}
}).to("direct:start");

from("direct:start").to("vm:test").process(new Processor() {
		public void process(Exchange exchange) throws Exception {
		String inBody = exchange.getIn().getBody(String.class);
		System.out.println(inBody);
		}
	});
}
}

Τώρα μπορούμε να τρέξουμε την εφαρμογή μας. Δοκιμάστε πρώτα να τοποθετήσετε ένα απλό txt αρχείο μέσα στους φακέλλους testdata1 και testdata2. Μετά στείλτε ένα JSON μήνυμα καλώντας το web service. Και στις τρεις περιπτώσεις θα δείτε ότι τα μηνύματα έφτασαν στο vm και ο Processor μας τυπώνει στο terminal το κείμενο του κάθε μηνύματος.

Output


Message from testdata1 : michail



Message from testdata2 : michail



{
"firstName": "Michail",
"lastName": "Kassapoglou"
}

1 Michail Kassapoglou
Created customer 1