Rick

Rick
Rick

Sunday, April 12, 2015

Using QBit to create Java RESTful microservices

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

Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training