QBit Restful Microservices
Before we delve into QBit restful services, let's cover what we get from gradle's application plugin. In order to be a microservice, a service needs to run in a standalone process or a related group of standalone processes.
Gradle application plugin
Building a standalone application with gradle is quite easy. You use the gradle application plug-in.
Gradle build using java and application plugin
apply plugin: 'java'
apply plugin:'application'
sourceCompatibility = 1.8
version = '1.0'
mainClassName = "io.advantageous.examples.Main"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
}
To round out this example, let's create a simple Main java class.
Simple main Java to demonstrate Gradle application plugin
package io.advantageous.examples;
public class Main {
public static void main(String... args) {
System.out.println("Hello World!");
}
}
The project structure is as follows:
Project structure
$ tree
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── restful-qbit.iml
├── settings.gradle
└── src
└── main
└── java
└── io
└── advantageous
└── examples
└── Main.java
We use a standard maven style structure.
To build this application we use the following commands:
Building our application
$ gradle clean build
Output
:clean UP-TO-DATE
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
BUILD SUCCESSFUL
Total time: 2.474 secs
To run our application we use the following:
Running our application
$ gradle run
Output
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
Hello World!
BUILD SUCCESSFUL
Total time: 2.202 secs
There are three more commands that we care about:
- installDist Installs the application into a specified directory
- distZip Creates ZIP archive including libs and start scripts
- distTar Creates TAR archive including libs and start scripts
The application plug-in allows you to create scripts to start a process. These scripts work on all operating systems. Microservices run as standalone processes. The gradle application plug-in is a good fit for microservice development.
Let's use the application plugin to create a dist zip file.
Using gradle to create a distribution zip
$ gradle distZip
Let's see where gradle put the zip.
Using find to see where the zip went
$ find . -name "*.zip"
./build/distributions/restful-qbit-1.0.zip
Let's unzip to a directory.
Unzipping to an install directory
$ mkdir /opt/example
$ unzip ./build/distributions/restful-qbit-1.0.zip -d /opt/example/
Archive: ./build/distributions/restful-qbit-1.0.zip
creating: /opt/example/restful-qbit-1.0/
creating: /opt/example/restful-qbit-1.0/lib/
inflating: /opt/example/restful-qbit-1.0/lib/restful-qbit-1.0.jar
creating: /opt/example/restful-qbit-1.0/bin/
inflating: /opt/example/restful-qbit-1.0/bin/restful-qbit
inflating: /opt/example/restful-qbit-1.0/bin/restful-qbit.bat
Now we can run it from the install directory.
Running from install directory
$ /opt/example/restful-qbit-1.0/bin/restful-qbit
Hello World!
Contents of restful-qbit startup script.
$ cat /opt/example/restful-qbit-1.0/bin/restful-qbit
#!/usr/bin/env bash
##############################################################################
##
## restful-qbit start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and RESTFUL_QBIT_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="restful-qbit"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/.." >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/lib/restful-qbit-1.0.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And RESTFUL_QBIT_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $RESTFUL_QBIT_OPTS
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" io.advantageous.examples.Main "$@"
Creating a simple RestFul Microservice
Let's create a simple HTTP service that responsds with pong when we send it a ping as follows.
Service that responds to this curl command
$ curl http://localhost:9090/services/pongservice/ping
"pong"
First let's import the qbit lib. We are using the SNAPSHOT but hopefully by the time you read this the release will be available.
Add the following to your gradle build.
Adding QBit to your gradle build
apply plugin: 'java'
apply plugin:'application'
sourceCompatibility = 1.8
version = '1.0'
mainClassName = "io.advantageous.examples.Main"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile group: 'io.advantageous.qbit', name: 'qbit-vertx', version: '0.7.3-SNAPSHOT'
}
Define a service as follows:
QBit service
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
@RequestMapping
public class PongService {
@RequestMapping
public String ping() {
return "pong";
}
}
The @RequestMapping defines the service as one that responsds to an HTTP call. If you do not specify the path, then the lower case name of the class and the lower case name of the method becomes the path. Thus PongService.ping() becomes /pongservice/ping. To bind this service to a port we use a service server. A service server is a server that hosts services like our pong service.
Change the Main class to use the ServiceServer as follows:
Using main class to bind in service to a port
package io.advantageous.examples;
import io.advantageous.qbit.server.ServiceServer;
import io.advantageous.qbit.server.ServiceServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceServer serviceServer = ServiceServerBuilder
.serviceServerBuilder()
.setPort(9090).build();
serviceServer.initServices(new PongService());
serviceServer.startServer();
}
}
Notice we pass an instance of the PongService to the initServices of the service server. If we want to change the root address from "services" to something else we could do this:
Changing the root URI of the service server
final ServiceServer serviceServer = ServiceServerBuilder
.serviceServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(new PongService());
serviceServer.startServer();
Now we can call this using curl as follows:
Use the curl command to invoke service via /main/pongservice/ping
$ curl http://localhost:9090/main/pongservice/ping
"pong"
QBit uses builders to make it easy to integrate QBit with frameworks like Spring or Guice or to just use standalone.
Adding a service that takes request params
Taking request parameters
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
@RequestMapping("/add")
public int add(@RequestParam("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
Notice the above uses @RequestParam this allows you to pull the requests params as arguments to the method. If we pass a URL like: http://localhost:9090/main/my/service/add?a=1&b=2. QBit will use 1 for argument a and 2 for argument b.
Adding the new service to Main
package io.advantageous.examples;
import io.advantageous.qbit.server.ServiceServer;
import io.advantageous.qbit.server.ServiceServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceServer serviceServer = ServiceServerBuilder
.serviceServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(
new PongService(),
new SimpleService());
serviceServer.startServer();
}
}
When we load this URL:
http://localhost:9090/main/my/service/add?a=1&b=2
We get this response.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
3
Working with URI params
Many think that URLs without request parameters are more search engine friendly or you can list things under a context which make it more RESTful. This is open to debate. I don't care about debate, but here is an example of using URI params.
Working with URI params example
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
...
...
@RequestMapping("/add2/{a}/{b}")
public int add2( @PathVariable("a") int a,
@PathVariable("b") int b) {
return a + b;
}
}
Now we can pass arguments which are part of the URL path. We do this by using the
@PathVariable
annotation. Thus the following URL:http://localhost:9090/main/my/service/add2/1/4
The 1 is correlates to the "a" argument to the method and the 4 correlates to the "b" arguments.
We would get the following response when we load this URL.
Working with URI params output
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
5
You can mix and match URI params and request params.
Working with URI params and request params example
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
...
...
@RequestMapping("/add3/{a}/")
public int add3( @PathVariable("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
This allows us to mix URI params and request params as follows:
http://localhost:9090/main/my/service/add3/1?b=8
Now we get this response:
Working with URI params and request params example
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
9
Fuller RESTful example
Let's create a simple Employee / Department listing application.
RESTful Employee and Department listing
package io.advantageous.examples.employees;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestMethod;
import io.advantageous.qbit.annotation.RequestParam;
import java.util.*;
import java.util.function.Predicate;
@RequestMapping("/dir")
public class EmployeeDirectoryService {
private final List<Department> departmentList = new ArrayList<>();
@RequestMapping("/employee/{employeeId}/")
public Employee listEmployee(@PathVariable("employeeId") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get().employeeList()
.stream().filter(employee -> employee.getId() == employeeId)
.findFirst().get();
} else {
return null;
}
}
@RequestMapping("/department/{departmentId}/")
public Department listDepartment(@PathVariable("departmentId") final long departmentId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get();
} else {
return null;
}
}
@RequestMapping(value = "/department/", method = RequestMethod.POST)
public boolean addDepartment( @RequestParam("departmentId") final long departmentId,
@RequestParam("name") final String name) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department " + departmentId + " already exists");
}
departmentList.add(new Department(departmentId, name));
return true;
}
@RequestMapping(value = "/department/employee/", method = RequestMethod.POST)
public boolean addEmployee( @RequestParam("departmentId") final long departmentId,
final Employee employee) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (!departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department not found");
}
final boolean alreadyExists = departmentOptional.get().employeeList().stream()
.anyMatch(employeeItem -> employeeItem.getId() == employee.getId());
if (alreadyExists) {
throw new IllegalArgumentException("Employee with id already exists " + employee.getId());
}
departmentOptional.get().addEmployee(employee);
return true;
}
}
To add three departments Engineering, HR and Sales:
Add Engineering, HR and Sales department with REST
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=1&name=Engineering
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=2&name=HR
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=3&name=Sales
Now let's add some employees into those departments.
Add some employees to the departments
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Rick","lastName":"Hightower", "id": 1}' /
http://localhost:9090/main/dir/departme\
nt/employee/?departmentId=1
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Diana","lastName":"Hightower", "id": 2}' /
http://localhost:9090/main/dir/departm\
ent/employee/?departmentId=2
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Maya","lastName":"Hightower", "id": 3}' /
http://localhost:9090/main/dir/departme\
nt/employee/?departmentId=3
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Paul","lastName":"Hightower", "id": 4}' /
http://localhost:9090/main/dir/departmen\
t/employee/?departmentId=3
Now let's list the employees. We can get the employees with the following curl.
Listing employees with curl
$ curl http://localhost:9090/main/dir/employee/1
{"firstName":"Rick","lastName":"Hightower","id":1}
$ curl http://localhost:9090/main/dir/employee/2
{"firstName":"Diana","lastName":"Hightower","id":2}
$ curl http://localhost:9090/main/dir/employee/3
{"firstName":"Maya","lastName":"Hightower","id":3}
$ curl http://localhost:9090/main/dir/employee/4
{"firstName":"Paul","lastName":"Hightower","id":4}
Now we can list departments with our RESTful API:
Listing departments with curl
$ curl http://localhost:9090/main/dir/department/1
{"id":1,"employees":[{"firstName":"Rick","lastName":"Hightower","id":1}]}
$ curl http://localhost:9090/main/dir/department/2
{"id":2,"employees":[{"firstName":"Diana","lastName":"Hightower","id":2}]}
$ curl http://localhost:9090/main/dir/department/3
{
"id": 3,
"employees": [
{
"firstName": "Maya",
"lastName": "Hightower",
"id": 3
},
{
"firstName": "Paul",
"lastName": "Hightower",
"id": 4
}
]
}
Some feel that is a search engine friendly RESTful interface although it is not. A true RESTful interface would have hyperlinks, but let's leave that off for another discussion lest we bring on the debate akin to vi vs. emacs or Scala vs. Groovy.
Some theory
Generally speaking people prefer the following (subject to much debate):
- POST to add to a resource
- PUT to update a resource
- GET to read a resource
- DELETE to remove an item from a list
- End a group in the singular form with a slash as in department/
- Use the name or id in the URI path to address a resource department/1/ would address the department with id 1.
Adding DELETE verb
You can use any of the HTTP verbs. Typically as mentioned before you use the DELETE verb to delete a resource as follows:
Mapping methods to DELETE verb
@RequestMapping(value = "/employee", method = RequestMethod.DELETE)
public boolean removeEmployee(@RequestParam("id") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
@RequestMapping(value = "/department", method = RequestMethod.DELETE)
public boolean removeDepartment(@RequestParam("id") final long departmentId) {
return departmentList.removeIf(department -> departmentId == department.getId());
}
Now let's delete somebody.
Using DELETE from command line
curl -H "Content-Type: application/json" -X DELETE /
http://localhost:9090/main/dir/employee?id=3
Mixing and matching request params and path variables
You can mix and match
@PathVariable
and @RequestParam
which is a quite common case and one that QBit just started supporitng this last release.Mixing and matching request params and path variables example
@RequestMapping(value = "/department/{departmentId}/employee", method = RequestMethod.DELETE)
public boolean removeEmployeeFromDepartment(
@PathVariable("departmentId") final long departmentId,
@RequestParam("id") final long employeeId) {
/* Find the department by id. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
In this very common case, you can use the PathVariable to address the department resource and then ask for a specific employee to be deleted only from this department.
Using curl to delete an employee from a specific department
curl -H "Content-Type: application/json" -X DELETE \
http://localhost:9090/main/dir/department/3/employee?id=4
Full examples:
Listing
$ tree
.
├── addDepartments.sh
├── addEmployees.sh
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── removeEmployee.sh
├── restful-qbit.iml
├── settings.gradle
├── showEmployees.sh
└── src
├── main
│ └── java
│ └── io
│ └── advantageous
│ └── examples
│ ├── Main.java
│ ├── PongService.java
│ ├── SimpleService.java
│ └── employees
│ ├── Department.java
│ ├── Employee.java
│ └── EmployeeDirectoryService.java
└── test
└── java
└── io
└── advantageous
└── examples
└── employees
└── EmployeeDirectoryServiceTest.java
Department.java
package io.advantageous.examples.employees;
import java.util.ArrayList;
import java.util.List;
public class Department {
private String name;
private final long id;
private final List<Employee> employees = new ArrayList();
public Department(long id, String name) {
this.id = id;
this.name = name;
}
public void addEmployee(final Employee employee) {
employees.add(employee);
}
public boolean removeEmployee(final long id) {
return employees.removeIf(employee -> employee.getId() == id);
}
public List<Employee> employeeList() {
return employees;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Employee
package io.advantageous.examples.employees;
public class Employee {
private String firstName;
private String lastName;
private final long id;
public Employee(String firstName, String lastName, long id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public long getId() {
return id;
}
}
EmployeeServiceDirectory
package io.advantageous.examples.employees;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestMethod;
import io.advantageous.qbit.annotation.RequestParam;
import java.util.*;
import java.util.function.Predicate;
@RequestMapping("/dir")
public class EmployeeDirectoryService {
private final List<Department> departmentList = new ArrayList<>();
@RequestMapping("/employee/{employeeId}/")
public Employee listEmployee(@PathVariable("employeeId") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get().employeeList()
.stream().filter(employee -> employee.getId() == employeeId)
.findFirst().get();
} else {
return null;
}
}
@RequestMapping("/department/{departmentId}/")
public Department listDepartment(@PathVariable("departmentId") final long departmentId) {
return departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst().get();
}
@RequestMapping(value = "/department/", method = RequestMethod.POST)
public boolean addDepartment( @RequestParam("departmentId") final long departmentId,
@RequestParam("name") final String name) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department " + departmentId + " already exists");
}
departmentList.add(new Department(departmentId, name));
return true;
}
@RequestMapping(value = "/department/employee/", method = RequestMethod.POST)
public boolean addEmployee( @RequestParam("departmentId") final long departmentId,
final Employee employee) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (!departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department not found");
}
final boolean alreadyExists = departmentOptional.get().employeeList().stream()
.anyMatch(employeeItem -> employeeItem.getId() == employee.getId());
if (alreadyExists) {
throw new IllegalArgumentException("Employee with id already exists " + employee.getId());
}
departmentOptional.get().addEmployee(employee);
return true;
}
@RequestMapping(value = "/employee", method = RequestMethod.DELETE)
public boolean removeEmployee(@RequestParam("id") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
@RequestMapping(value = "/department", method = RequestMethod.DELETE)
public boolean removeDepartment(@RequestParam("id") final long departmentId) {
return departmentList.removeIf(department -> departmentId == department.getId());
}
@RequestMapping(value = "/department/{departmentId}/employee", method = RequestMethod.DELETE)
public boolean removeEmployeeFromDepartment(
@PathVariable("departmentId") final long departmentId,
@RequestParam("id") final long employeeId) {
/* Find the department by id. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
}
PongService.java
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
@RequestMapping
public class PongService {
@RequestMapping
public String ping() {
return "pong";
}
}
SimpleService.java
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
@RequestMapping("/add")
public int add(@RequestParam("a") int a,
@RequestParam("b") int b) {
return a + b;
}
@RequestMapping("/add2/{a}/{b}")
public int add2( @PathVariable("a") int a,
@PathVariable("b") int b) {
return a + b;
}
@RequestMapping("/add3/{a}/")
public int add3( @PathVariable("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
Main.java
package io.advantageous.examples;
import io.advantageous.examples.employees.EmployeeDirectoryService;
import io.advantageous.qbit.server.ServiceServer;
import io.advantageous.qbit.server.ServiceServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceServer serviceServer = ServiceServerBuilder
.serviceServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(
new PongService(),
new SimpleService(),
new EmployeeDirectoryService());
serviceServer.startServer();
}
}
No comments:
Post a Comment