Thursday, November 9, 2017

Completable future (java 8) simple example

A long long time ago class Future was introduced in Java. Now, with Java 8 we have an "upgraded" version of it.

1. Regular "Future"

Futures are very useful objects: if some task which will be executed in another thread have to return some value - we can use Callable object which returns Futubre object. Later we will be able to get the value returned by task by executing .get method from returned Future. The problem here: when we are executing .get - we are blocked: if the value is not ready yet(long running task) we will be waiting till the value will finally be returned.

Let's write some code to recall our knowledge of Future:

Function for simulation of long running task:

public int longTask(int delay) {
    try {
        Thread.sleep(delay);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return delay;

}

Function for creation Callable from this long task:
public Callable<Integer> longCallable(int delay) {
    Callable<Integer> result = () -> longTask(delay);
    return result;
}


Function for submitting Callable by Executor and creation of Future:

public Future<Integer> longFuture(int delay) {
    return Executors.newWorkStealingPool().submit(longCallable(delay));
}

Debug function to print our Futures:

public void printFuture(Future<Integer> future) {
    try {
        System.out.println(future.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

Let's create an array with several futures:

public List<Future<Integer>> getTestFutures() {
    return IntStream.rangeClosed(1, 3)
            .<Future<Integer>>mapToObj(i -> longFuture(i))
            .collect(Collectors.toList());
}

Now we have a Futures, so let's take the values from them:  

public void blockingFutureGet() {
    getTestFutures().forEach(f -> printFuture(f));
}


And, as we mentioned before - we are blocked here. 
After next execution:  

System.out.println("Begin");
app.blockingFutureGet();
System.out.println("End");


Result will be: 
Begin
1
2
3
End

So, the main thread was blocked till we finally got ALL VALUES from the Futures. 
Sometimes such behavior is not acceptable: we may need to execute tasks in totally async way. 
For that purpose we may rewrite previous function this way: 


public void nonBlockingFutureGet() {
    new Thread(this::blockingFutureGet).start();
}

We are running additional thread to get values from the Futures.
The result will be:
Begin
End
1
2
3

-  the main thread was not blocked - it continued execution while another thread was getting values from Futures. But this approach little bit complicated: we have to submit our tasks, collect the futures, run additional thread, pass collected futures there and execute all needed manipulations. To make it simpler we can just use CompletableFutures.

2. CompletableFuture

And now let's compare all the code above with CompletableFuture implementation:

public void completableFuture() {
    IntStream.rangeClosed(1, 3).forEach(i ->
        CompletableFuture.supplyAsync(() -> longTask(i))
           .thenAccept(System.out::println)
    );
}

It's now much easier!  Also results can be forwarded further for additional tasks/operations:

public void completableFuture2() {
    IntStream.rangeClosed(1, 3).forEach(i ->
            CompletableFuture.supplyAsync(() -> longTask(i))
                    .thenApply(v->v*10)
                    .thenApply(v->Integer.toString(v)+"!")
                    .thenAccept(System.out::println)
                    .exceptionally(e-> {
                        System.out.println("We have a problem:"+e.getMessage());
                        return null;
                    })
    );
}

3. Running additional asynchronous tasks using thenCompose

In previous example we used thenApply method which executes tasks in synchronous way. But it can be asynchronous - we can execute thenCompose method and return another async task:

public void cfThenCompose() {
    IntStream.rangeClosed(1, 3)
            .mapToObj(i -> CompletableFuture.supplyAsync(() -> longTask(i * 1000)))
            .forEach(f -> f.thenCompose(j -> {
                             System.out.println(j);
                             return CompletableFuture.supplyAsync(() -> longTask(j + 1));
                         }).thenAccept(System.out::println)
                     );
}


Results of execution:
1000
2000
1001
3000
2001
3001

4. Combining results of 2 independent futures.

In a previous example we were running execution based on previous execution.But what if need result of 2 tasks for further computation? We can use thenCombine method or that:

public void cfThenCombine() {
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->longTask(1000));
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(()->longTask(2000));
    f1.thenCombine(f2, (r1, r2)-> r1+r2).thenAccept(System.out::println);
}

The result is, as expected:
3000

5. The end.

CompletableFutures as the big step forward for java in the direction of functional programming. 

Sunday, October 29, 2017

Spring bean lifecycle, postrocessors, profiler

0. intro

When we create object - we have only one entry point to modify somehow object state: constructor.
In Spring this process is much more complicated:


let's create a simple spring application to see how it goes.

1. Project structure

It's a regular gradle-java project.

conten of buid.gradle file:

group 'com.demien.spring'version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.springframework', name: 'spring-core', version: '5.0.0.RELEASE'    
    compile group: 'org.springframework', name: 'spring-beans', version: '5.0.0.RELEASE'    compile group: 'org.springframework', name: 'spring-context', version: '5.0.0.RELEASE'
    testCompile group: 'junit', name: 'junit', version: '4.12'}



directory structure:




2. Annotations

For deep understanding of some life cycle phases let's create 2 annotations:

- Generate name annotation. If field is annotated by this annotation, that means we have to generate the value for this field - simulation of "name". For that we have also settings: minLenght and maxLenght  - length of name which we have to generate.

package com.demien.spring.lifecycle.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedName {
    int minLength();
    int maxLength();
}

- Profiling annotation. If class has such annotation - we have to measure execution time of every class method.
package com.demien.spring.lifecycle.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Profiling {
}


3. Spring bean - messenger

Now let's create a very simple bean which will be printing messages.

Interface is very simple: just 2 methods:

package com.demien.spring.lifecycle.beans;

public interface Messenger {
    void printMessage();
    void setUp(String symbol);
}


Implementation is more complicated:
  - we are using annotations from above
  - we have 3 "state" methods: constructor, init and setup


package com.demien.spring.lifecycle.beans;

import com.demien.spring.lifecycle.annotations.GeneratedName;
import com.demien.spring.lifecycle.annotations.Profiling;

import java.time.LocalTime;

@Profiling
public class SimpleMessenger implements Messenger {

    private String messageText = "NULL";
    private String symbol = "";

    @GeneratedName(minLength = 5, maxLength = 10)
    private String name;

    public SimpleMessenger() {
        System.out.print("Constructor: ");
        printMessage();
    }

    public void init() {
        System.out.print("Init: ");
        printMessage();
    }

    @Override    
    public void setUp(String symbol) {
        this.symbol = symbol;
        System.out.print("Setup: ");
        printMessage();
    }
public void setMessageText(String messageText) { this.messageText = messageText; } @Override
    public void printMessage() {
        System.out.println();
        System.out.println(messageText + ", " + name+symbol+" ["+ LocalTime.now()+"]");
    }


}


4. JMX profiler settings 

For profiler, it's better to have ability to turn on/off when it's needed. We can use JMX for that.
For JMX we need an interface which name ends by MBean and implementation of it:

package com.demien.spring.lifecycle.jmx;

public interface ProfilerSettingsMBean {
    void setEnabled(boolean enabled);
}


package com.demien.spring.lifecycle.jmx;

public class ProfilerSettings implements ProfilerSettingsMBean {

    private boolean enabled;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}


5. AppConfig

It's the most complicated and interesting part of our application.

- definition of message bean. Here we also defining "init-method" which will be executed after bean initialization.

@Bean(initMethod = "init")
Messenger messenger() {
    SimpleMessenger messenger = new SimpleMessenger();
    messenger.setMessageText("Hello");
    return messenger;
}
-

- We want to do some actions(execute messenger.setUp method) when spring created application context - on context refresh event.

@Bean
ApplicationListener<ContextRefreshedEvent> refreshedEventApplicationListener() {
    return new ApplicationListener<ContextRefreshedEvent>() {
        @Override        
        public void onApplicationEvent(ContextRefreshedEvent event) {
            ApplicationContext ctx = event.getApplicationContext();
            Messenger messenger = ctx.getBean(Messenger.class);
            messenger.setUp("!");
        }
    };

}

- we need post processor for dealing with annotation @GeneratedName - we have to generate name and put it into such field.

@Bean
BeanPostProcessor nameGenerationPostProcessor() {

    return new BeanPostProcessor() {
        @Override        
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                GeneratedName annotation = field.getAnnotation(GeneratedName.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    ReflectionUtils.setField(field, bean, generateName(annotation.minLength(), annotation.maxLength()));
                }
            }
            return bean;
        }

    };
}


- and also we have to process @Profiling annotation.
For that we can on "beforeInitalization" phase store classes which have such annotation. And on "afterInilialization" phase we can return "proxy" object which will be printing execution time after method invocation if such settings is turned on.

@Bean
BeanPostProcessor profilingPostProcessor() throws Exception {
    Map<String, Class> map = new HashMap<>();
    ProfilerSettings profilerSettings = new ProfilerSettings();
    MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
    beanServer.registerMBean(profilerSettings, new ObjectName("Profiling", "name", "settings"));

    return new BeanPostProcessor() {

        @Override        
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            Class<?> beanClass = bean.getClass();
            if (beanClass.isAnnotationPresent(Profiling.class)) {
                map.put(beanName, beanClass);
            }
            return null;
        }

        @Override        
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            Class beanClass = map.get(beanName);
            if (beanClass != null) {
                return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), new InvocationHandler() {
                    @Override                    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
                        long before = System.nanoTime();

                        Object retval = method.invoke(bean, objects);
                        if (profilerSettings.isEnabled()) {
                            System.out.println("exec time:" + (System.nanoTime() - before));
                        }
                        return retval;
                    }
                });
            }
            return null;
        }
    };
}

6. Main app class

Here we just creating sprint context and executing in a loop printMessage method of our messenger.

package com.demien.spring.lifecycle;

import com.demien.spring.lifecycle.beans.Messenger;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {

    public static void main(String[] args) throws InterruptedException {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class);
        ctx.refresh();
        Messenger messenger = ctx.getBean(Messenger.class);

        System.out.println();
        System.out.println("Execution started");
        while (true) {
            Thread.sleep(3000);
            messenger.printMessage();
        }

    }
}


7. Execution

Now we can run our application:

Constructor: 
NULL, null [21:27:53.542]
Init: 
Hello, Mosxrak [21:27:53.553]
Setup: 
Hello, Mosxrak! [21:27:53.678]

Execution started

Hello, Mosxrak! [21:27:56.682]

Hello, Mosxrak! [21:27:59.683]

Hello, Mosxrak! [21:28:02.683]

Here we can see all phases of bean lifecycle in spring:
- first of all, of course, constructor is executed. On this phase nothing was injected into our bean, that is why we have nulls.
- init method was executed when spring created the bean. So bean has message and also post processor generated name
- the last method is "setup" - it was executed when the whole context was created and refreshed. 

8. Turning on profiling 

Now let's test how profiling works. We have to connect JMX client (for example JConsole, or VisualVM) and connect it to our application.



In Profiling/Settings we can see our flag "enaled" - here we have to enter "true" and press "enter".
After this operation output will be changed:


Hello, Cswwjksic! [21:37:28.926]

Hello, Cswwjksic! [21:37:31.926]

Hello, Cswwjksic! [21:37:34.926]

Hello, Cswwjksic! [21:37:37.928]
exec time:1931750

Hello, Cswwjksic! [21:37:40.929]
exec time:235493

Hello, Cswwjksic! [21:37:43.929]
exec time:184623

Hello, Cswwjksic! [21:37:46.930]
exec time:340191

Hello, Cswwjksic! [21:37:49.930]
exec time:358737


After turning on our profiler prints execution time - so everything works as expected.


9. The end

Full source code can be downloaded from here

Wednesday, September 27, 2017

Swagger with SpringBoot

From Wiki:
Swagger is an open source software framework backed by a large ecosystem of tools that helps developers design, build, document, and consume RESTful Web services. While most users identify Swagger by the Swagger UI tool, the Swagger toolset includes support for automated documentation, code generation, and test case generation.

Official page: https://swagger.io/


1. What is Swagger?

In this post I'll show 2 components of Swagger: 
   - set of annotations which help us to "describe" REST - related stuff: rest endpoints and DTO-objects.
   - Swagger UI, which can be used for calling this endpoints for testing purposes.

Example of "description" of DTO-object filed:   
@ApiModelProperty(notes = "Group Name")
private String name;

Example of "description" of REST-endpoint:
@GET@Path("/{id}")
@ApiOperation(value = "Get group by id resource.", response = Group.class)
@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Group resource found"),
        @ApiResponse(code = 404, message = "Group resource not found")
})
public Response getGroup(@ApiParam @PathParam("id") Long id) {

Example of Swagger UI, using which we can call just listed above method: 




2. build.gradle 

I just generated the SpringBoot project from start.spring.io and added swagger dependency into it:
buildscript {
   ext {
      springBootVersion = '1.5.7.RELEASE'   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'apply plugin: 'eclipse'apply plugin: 'org.springframework.boot'
jar.archiveName = "SwaggerTestApp.jar"group = 'com.demien'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8
repositories {
   mavenCentral()
}


dependencies {
   compile('org.springframework.boot:spring-boot-starter-jersey')
   compile('org.springframework.boot:spring-boot-starter-web')

    compile group: 'io.swagger', name: 'swagger-jersey2-jaxrs', version: '1.5.16'       testCompile('org.springframework.boot:spring-boot-starter-test')
}



3. Main start class


Nothing special here, just  adding several packages for scanning

package com.demien.swtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(
    scanBasePackages = {
         "com.demien.swtest.config", 
         "com.demien.swtest.rest", 
         "com.demien.swtest.service"         }
)
public class SwtestApplication  {

   public static void main(String[] args) {
      SpringApplication.run(SwtestApplication.class, args);
   }
}



4. Jersey config


It's the most complicated part of application - we have to configure swagger here with metha-data of our application. 


package com.demien.swtest.config;

import com.demien.swtest.rest.GroupResource;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.wadl.internal.WadlResource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

    @Component    public class JerseyConfig extends ResourceConfig {

        @Value("${spring.jersey.application-path:/}")
        private String apiPath;

        public JerseyConfig() {
            // Register endpoints, providers, ...            this.registerEndpoints();
        }

        @PostConstruct        public void init() {
            // Register components where DI is needed            this.configureSwagger();
            //this.registerEndpoints();        }

        private void registerEndpoints() {
            this.register(GroupResource.class);
            // Access through /<Jersey's servlet path>/application.wadl            this.register(WadlResource.class);
        }

        private void configureSwagger() {
            // Available at localhost:port/api/swagger.json            this.register(ApiListingResource.class);
            this.register(SwaggerSerializers.class);

            BeanConfig config = new BeanConfig();
            config.setConfigId("springboot-jersey-swagger-test-app");
            config.setTitle("Spring Boot, Jersey, Swagger Test Application");
            config.setVersion("v1");
            config.setContact("Dmitry Kovalsky");
            config.setSchemes(new String[] { "http", "https" });
            config.setBasePath(this.apiPath);
            config.setResourcePackage("com.demien.swtest.rest");
            config.setPrettyPrint(true);
            config.setScan(true);
        }
}


5. Dto and Model classes

UI is not sending ID - it will be generated on server side, what is why I need 2 classes: one for data which will be sent from UI and the second one  - for response. Fields in these classes are swagger-annotated.


package com.demien.swtest.dto;

import io.swagger.annotations.ApiModelProperty;

public class GroupDTO {

    @ApiModelProperty(notes = "Group Name")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.demien.swtest.model;

import com.demien.swtest.dto.GroupDTO;
import io.swagger.annotations.ApiModelProperty;

public class Group extends GroupDTO{
    @ApiModelProperty(notes = "Generated Group ID")
    private Long id;

    public Long getId() {
        return id;
    }

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

    public Group() {
    }

    public Group(GroupDTO dto) {
        setName(dto.getName());
    }

}



6. Rest controller(resource)

Here, all rest methods are swagger-annotated with description and errors which may be raised by it. 


package com.demien.swtest.rest;

import com.demien.swtest.dto.GroupDTO;
import com.demien.swtest.model.Group;
import com.demien.swtest.service.GroupService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

@Component@Path("/groups")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Api(value = "Group resource", produces = "application/json")
public class GroupResource {

    @Autowired    private GroupService groupService;

    public Response OkResponse(Object entity) {
        return Response.status(Response.Status.OK).entity(entity).build();
    }

    public Response NotFoundResponse() {
        return Response.status(Response.Status.NOT_FOUND).build();
    }

    @POST    @ApiOperation(value = "Create group.", response = Group.class)
    @ApiResponses(value = {
            @ApiResponse(code = 201, message = "group resource ", responseHeaders = {
                    @ResponseHeader(name = "Location", description = "The URL to retrieve created resource", response = String.class)
            })
    })
    public Response createGroup(GroupDTO groupDTO, @Context UriInfo uriInfo) {
        Group result = groupService.add(new Group(groupDTO));
        return OkResponse(result);
    }

    @GET    @Path("/{id}")
    @ApiOperation(value = "Get group by id resource.", response = Group.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Group resource found"),
            @ApiResponse(code = 404, message = "Group resource not found")
    })
    public Response getGroup(@ApiParam @PathParam("id") Long id) {
        Group result = groupService.get(id);
        return result == null ? NotFoundResponse() : OkResponse(result);
    }

    @PUT    @ApiOperation(value = "Update group.", response = Group.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Group resource found")
    })
    public Response updateGroup(Group group, @Context UriInfo uriInfo) {
        groupService.update(group.getId(), group);
        Group result = groupService.get(group.getId());
        return OkResponse(result);
    }

    @DELETE    @Path("/{id}")
    @ApiOperation(value = "Delete group by id resource.")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Group resource found"),
            @ApiResponse(code = 404, message = "Group resource not found")
    })
    public Response deleteGroup(@ApiParam @PathParam("id") Long id) {
        Group result = groupService.get(id);
        if (result == null) return NotFoundResponse();
        groupService.delete(id);
        return OkResponse(id);
    }


}      

7. Imitation of service
I made imitation of generic service and concrete subclass. 


package com.demien.swtest.service;

import com.demien.swtest.model.Group;
import org.springframework.stereotype.Service;

import java.util.function.UnaryOperator;

@Servicepublic class GroupService extends AbstractService<Group> {

    public GroupService() {
        super((e, id) -> {
            e.setId(id);
            return e;
        });
    }
}



package com.demien.swtest.service;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;

public abstract class AbstractService<T> {

    private long id;
    private Map<Long,T> storage = new HashMap<>();
    private BiFunction<T, Long, T> idSetter;

    public AbstractService(BiFunction<T, Long, T> idSetter) {
        this.idSetter = idSetter;
    }


    public T add(T entity) {
        id++;
        T result =  idSetter.apply(entity, id);
        storage.put(id, result);
        return result;

    }

    public T get(Long id) {
        return storage.get(id);
    }

    public void update(Long id, T entity) {
        storage.put(id, entity);
    }

    public void delete(Long id) {
        storage.put(id, null);
    }
}



8. swagger.json

Now we can run our application and open this address: http://localhost:8080/api/swagger.json
The result should be - json generated by swagger which "explains" our rest endpoints with 
detailed description: 

{
   "swagger":"2.0",
   "info":{
      "version":"v1",
      "title":"Spring Boot, Jersey, Swagger Test Application",
      "contact":{
         "name":"Dmitry Kovalsky"
      }
   },
   "basePath":"/api",
   "tags":[
      {
         "name":"Group resource"
      }
   ],
   "schemes":[
      "http",
      "https"
   ],
   "paths":{
      "/groups/{id}":{
         "get":{
            "tags":[
               "Group resource"
            ],
            "summary":"Get group by id resource.",
            "description":"",
            "operationId":"getGroup",
            "consumes":[
               "application/json"
            ],
            "produces":[
               "application/json"
            ],
            "parameters":[
               {
                  "name":"id",
                  "in":"path",
                  "required":true,
                  "type":"integer",
                  "format":"int64"
               }
            ],
            "responses":{
               "200":{
                  "description":"Group resource found"
               },
               "404":{
                  "description":"Group resource not found"
               }
            }
         },
         "delete":{
            "tags":[
               "Group resource"
            ],
            "summary":"Delete group by id resource.",
            "description":"",
            "operationId":"deleteGroup",
            "consumes":[
               "application/json"
            ],
            "produces":[
               "application/json"
            ],
            "parameters":[
               {
                  "name":"id",
                  "in":"path",
                  "required":true,
                  "type":"integer",
                  "format":"int64"
               }
            ],
            "responses":{
               "200":{
                  "description":"Group resource found"
               },
               "404":{
                  "description":"Group resource not found"
               }
            }
         }
      },
      "/groups":{
         "post":{
            "tags":[
               "Group resource"
            ],
            "summary":"Create group.",
            "description":"",
            "operationId":"createGroup",
            "consumes":[
               "application/json"
            ],
            "produces":[
               "application/json"
            ],
            "parameters":[
               {
                  "in":"body",
                  "name":"body",
                  "required":false,
                  "schema":{
                     "$ref":"#/definitions/GroupDTO"
                  }
               }
            ],
            "responses":{
               "200":{
                  "description":"successful operation",
                  "schema":{
                     "$ref":"#/definitions/Group"
                  }
               },
               "201":{
                  "description":"group resource ",
                  "headers":{
                     "Location":{
                        "type":"string",
                        "description":"The URL to retrieve created resource"
                     }
                  }
               }
            }
         },
         "put":{
            "tags":[
               "Group resource"
            ],
            "summary":"Update group.",
            "description":"",
            "operationId":"updateGroup",
            "consumes":[
               "application/json"
            ],
            "produces":[
               "application/json"
            ],
            "parameters":[
               {
                  "in":"body",
                  "name":"body",
                  "required":false,
                  "schema":{
                     "$ref":"#/definitions/Group"
                  }
               }
            ],
            "responses":{
               "200":{
                  "description":"Group resource found"
               }
            }
         }
      }
   },
   "definitions":{
      "Group":{
         "type":"object",
         "properties":{
            "name":{
               "type":"string"
            },
            "id":{
               "type":"integer",
               "format":"int64"
            }
         }
      },
      "GroupDTO":{
         "type":"object",
         "properties":{
            "name":{
               "type":"string"
            }
         }
      }
   }
}


9. Swagger UI 

JSON with endpoint description - is great! But swagger can even more: based on this JSON, it can provide the UI to call these endpoints. We just have to download it from https://swagger.io/swagger-ui/ and put into src/main/resource/static. In will be available by address: http://localhost:8080/index.html.

Example of trying POST method on GROUP resource:


And after pressing "Try it out!" we will have: 



10. The end


Full source code can be downloaded from here

Friday, July 28, 2017

Debug for Mocha tests in Visual Studio Code

I was surprised  when I found how it's hard to set up such simple thing as "debug" in Visual Studio Code. It's unclear at all! For debug configuration we have just one json file and what exactly should be placed there - nobody knows. Additional problem were caused by using Babel ES2015 in code.

So, that exactly have to be done:

1. launch.json

Create file if it doesn't exists:
.vscode\launch.json

and place there:

{
    "version": "0.2.0",
    "configurations": [
        {
            "request": "launch",
            "name": "Debug Mocha Test",
            "type": "node",
            "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
            "stopOnEntry": true,
            "args": [
                "--compilers",
                "js:babel-core/register",               
                "--recursive"
            ],
            "cwd": "${workspaceRoot}/.",
            "runtimeExecutable": null,
            "env": {}
        }
    ]
}


2. Put breackpoint and run debug by pressing F5

And this is basically it - debug should work now: 




3. The end

As a result, it was very simple, but it took several  hours of time to understand what exactly has to be done :)

Tuesday, July 11, 2017

Selenium Java getting started

1. Intro

What is selenium?

From wiki:
Selenium is a portable software-testing framework for web applications. Selenium provides a record/playback tool for authoring tests without the need to learn a test scripting language (Selenium IDE). It also provides a test domain-specific language (Selenese) to write tests in a number of popular programming languages, including C#GroovyJavaPerlPHPPythonRuby and Scala. The tests can then run against most modern web browsers. Selenium deploys on WindowsLinux, and OS X platforms. It is open-source software, released under the Apache 2.0 license: web developers can download and use it without charge.

Let's try to do some simple tasks using this library. I want to use if for testing my simple ReactJS application, which I created week ago.


2. How it works. 

With selenium we can "emulate" all user actions we need: entering text into input fields, clicking the buttons, checking checkboxes and many others. But first of all, we need to find somehow element for which we want to perform such actions. In selenium there are a lot of options for doing this:

  • by id
  • by name
  • by tag name 
  • ....




In this post I'm using the simplest way: by element id.
Example: driver.findElement(By.id("submit-department").click();

Here is a list of html elements which I will be accessing by theirs ids:



3. SetUp

There are just few things to start working with selenium:

  • first of all we have to add to our project selenium dependency. Example for  gradle:

      compile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '2.41.0'
  • also we need the driver for your favorite browser. For example for ChromeBrowser you can download it from here. You have have to unpack it and put it somewhere in file system. 
  • And finally, driver binary from previous step, have to be set up as system property in your code this way:
System.setProperty("webdriver.chrome.driver", "d:/dev/selenium/chromedriver.exe");

After that we can try to create the instance of driver and open something with the browser:
WebDriver driver = new ChromeDriver();
driver.get("http://localhost:3000");

-it should run the browser and navigate it to page from code above.



4. Testing 

I wrote very simple application which creates several departments and after that several users. And users creation goes using departments created before. At the end I'm checking if all departments and users were created.


package com.demien.seltest;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.Select;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class App {

    static WebDriver driver;

    public static void clearAndSet(String id, String value) {
        driver.findElement(By.id(id)).clear();
        driver.findElement(By.id(id)).sendKeys(value);
    }

    public static void click(String id) {
        driver.findElement(By.id(id)).click();
    }

    public static void select(String id, String option) {
        Select select = new Select(driver.findElement(By.id(id)));
        select.selectByVisibleText(option);
    }

    public static void addDepartment(String id, String name) {
        clearAndSet("input-departments-id", id);
        clearAndSet("input-departments-name", name);
        click("submit-departments");
    }

    public static void addUser(String id, String name, String email, String department) {
        clearAndSet("input-users-id", id);
        clearAndSet("input-users-name", name);
        clearAndSet("input-users-email", email);
        select("select-users-department", department);
        click("submit-users");
    }

    public static void checkTable(String id, List<String> values) {
        WebElement table = driver.findElement(By.id(id));

        List<WebElement> allRows = table.findElements(By.tagName("tr"));
        for (WebElement row : allRows) {
            List<WebElement> cells = row.findElements(By.tagName("td"));
            for (WebElement cell : cells) {
                values.remove(cell.getText());
            }
        }

        if (values.size() > 0) {
            throw new RuntimeException("Some elements are absent in table:" + Arrays.toString(values.toArray()));
        }
    }

    public static void checkTable(String id, String... values) {
        checkTable(id, new LinkedList<>(Arrays.asList(values)));
    }

    public static void main(String[] args) {
        System.setProperty("webdriver.chrome.driver", "d:/dev/selenium/chromedriver.exe");
        driver = new ChromeDriver();

        driver.get("http://localhost:3000");

        addDepartment("IT", "Informational technologies");
        addDepartment("ADM", "Administration");
        addDepartment("SEQ", "Security");

        checkTable("table-departments", "IT", "ADM", "SEQ", "Informational technologies", "Administration", "Security");

        addUser("JBL", "Joe Black", "joe.black@email.com", "Security");
        addUser("HSEB", "Huan Sebastyan", "huan.sebastyan@email.com", "Administration");

        checkTable("table-users", "JBL", "Joe Black", "joe.black@email.com", "Security", "HSEB", "Huan Sebastyan", "huan.sebastyan@email.com", "Administration");

    }
}

5. The end 

As a result, using just code from above, I created 3 departments and 2 users.




Source code can be downloaded from here