Guice integartion for OrientDB
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

Guice integration for OrientDB

License Build Status Appveyor build status codecov

guice-persist-orient wins 4th place in Software Quality Award 2015

Examples repository

Support: Gitter chat

About

OrientDB is document, graph and object database (see intro and starter course). Underlying format is almost the same for all database types, which allows us to use single database in any way. For example, schema creation and updates may be performed as object database (jpa style) and graph api may be used for creating relations.

Features:

  • For orient 2.2
  • Integration through guice-persist (UnitOfWork, PersistService, @Transactional)
  • Support for document, object and graph databases
  • Database types support according to classpath (object and graph db support activated by adding jars to classpath)
  • All three database types may be used in single transaction (changes will be visible between different apis)
  • Hooks for schema migration and data initialization extensions
  • Extension for orient object to scheme mapper with plugins support
  • Auto mapping entities in package to db scheme or using classpath scanning to map annotated entities
  • Auto db creation (for memory, local and plocal)
  • Different db users may be used (for example, for schema initialization or to use orient security model), including support for user change inside transaction
  • Support method retry on ONeedRetryException
  • Spring-data like repositories with advanced features (e.g. generics usage in query). Great abilities for creating reusable parts (mixins). Support plugins.
  • Basic crud mixins with ability to use object api for graphs
  • Compatible with Play framework

Thanks to

Setup

Releases are published to bintray jcenter (package appear immediately after release) and then to maven central (require few days after release to be published).

JCenter Maven Central

Maven:

<dependency>
<groupId>ru.vyarus</groupId>
<artifactId>guice-persist-orient</artifactId>
<version>3.3.2</version>
<exclusions>
  <exclusion>
      <groupId>com.orientechnologies</groupId>
      <artifactId>orientdb-graphdb</artifactId>
  </exclusion>
  <exclusion>
      <groupId>com.orientechnologies</groupId>
      <artifactId>orientdb-object</artifactId>
  </exclusion>
</exclusions>
</dependency>

Gradle:

compile ('ru.vyarus:guice-persist-orient:3.3.2'){
    exclude module: 'orientdb-graphdb'
    exclude module: 'orientdb-object'       
}

Remove exclusions to enable object and graph db support.

3.3.2 and above require guice 4.2.0

  • For orient 2.1.x use 3.2.0 (see old docs)
  • For orient 2.0.x use 3.1.1 (see old docs)
  • For orient 1.x use 2.1.0 (see old docs)

NOTE: It's very important for object db to use exact javassist version it depends on. If other libraries in your classpath use javassist, check that newer or older version not appear in classpath.

Snapshots

You can use snapshot versions through JitPack:

  • Go to JitPack project page
  • Select Commits section and click Get it on commit you want to use (top one - the most recent)
  • Follow displayed instruction: add repository and change dependency (NOTE: due to JitPack convention artifact group will be different)

Install the Guice module

install(new OrientModule(url, user, password));

See orient documentation for supported db types. In short:

  • 'memory:dbname' to use in-memory database
  • 'plocal:dbname' to use embedded database (no server required, local fs folder will be used); db name must be local fs path
  • 'remote:dbname' to use remote db (you need to start server to use it)

By default use 'admin/admin' user.

Auto database creation for local types (all except 'remote') is enabled by default, but you can switch it off. Auto creation is nice for playing/developing/testing phase, but most likely will not be useful for production.

install(new OrientModule(url, user, password)
                .autoCreateLocalDatabase(false));

Default transactions configuration may be specified as additional module parameter. By default, OPTIMISTIC transactions used (use optimistic locking based on object version, same way as hibernate optimistic locking). NOTX mode disables transactions. Read more about transactions in orient

For example, to switch off transactions use:

install(new OrientModule(url, user, password)
                .defaultTransactionConfig(new TxConfig(OTransaction.TXTYPE.NOTX));
Custom types support

If you need to use orient custom types (custom converter to/from object, used only in object connection)

To register custom type:

install(new OrientModule(url, user, password)
                .withCustomTypes(MyTypeSerializer.class));

where MySerializer implements OObjectSerializer

Serializers are assumed to be guice beans: declare it in guice module if required, otherwise guice JIT will be used to obtain converter instance.

Custom converters are collected using guice multibinder feature, so you can also install custom converter by using multibinder directly:

Multibinder.newSetBinder(binder(), OObjectSerializer.class).addBinding().to(serializerClass);

Usage

Lifecycle

You need to manually start/stop persist service in your code (because only you can control application lifecycle). On start connection pools will be initialized, database created (if required) and scheme/data initializers called. Stop will shutdown all connection pools.

@Inject
PersistService orientService

public void onAppStartup(){
    orientService.start()
}

public void onAppShutdown(){
    orientService.stop()
}

Unit of work (transaction)

Unit of work defines transaction scope. Actual orient transaction will start only on first connection acquire (so basically, unit of work may not contain actual orient transaction, but for simplicity both may be considered equal).

Unit of work may be defined by:

  • @Transactional annotation on guice bean or single method (additional @TxType annotation allows to define different transaction type for specific unit of work)
  • Inject PersistentContext bean into your service and use its methods
  • Using TransactionManager begin() and end() methods.

First two options are better, because they automatically manage rollbacks and avoid not closed (forgot to call end) transactions. Read more about orient transactions

NOTE: orient 2 is more strict about transactions: now ODocument could be created only inside transaction and object proxy can't be used outside of transaction.

When you get error:

Database instance is not set in current thread. Assure to set it with: ODatabaseRecordThreadLocal.instance().set(db)

It almost certainly means that you perform transactional operation outside of transaction. Simply enlarge transaction scope.

When you inside transaction, activateOnCurrentThread() is called each time you obtain connection from raw connection provider or PersistentContext and you will always have correctly bound connection.

Examples

Defining transaction for all methods in bean (both method1 and method2 will be executed in transaction):

@Transactional
public class MyBean {
    public void method1() ...
    public void method2() ...
}

Defining no tx transaction on method:

@Transactional
@TxType(OTransaction.TXTYPE.NOTX)
public void method()

Notx usually used for scheme updates, but in some cases may be used to speed up execution (but in most cases its better to use transaction for consistency).

Using PersistentContext:

@Inject PersistentContext<OObjectDatabaseTx> context;

...
context.doInTransaction(new TxAction<Void>() {
        @Override
        public Void execute() throws Throwable {
            // something
            return null;
        }
    });

Using TransactionManager:

@Inject TransactionManager manager;
...
manager.begin();
try {
// do something
    manager.end();
} catch (Exception ex) {
    manager.rollback()
}

NOTE: in contrast to spring default proxies, in guice when you call bean method inside the same bean, annotation interceptor will still work. So it's possible to define few units of work withing single bean using annotations:

public void nonTxMethod(){
    doTx1();
    doTx2();
}

// methods can't be private (should be at least package private)
@Transactional
void doTx1() {..}

@Transactional
void doTx2() {..}
Manual transaction

In some rear cases it is required to use already created database object (use thread-bound connection inside guice). This is possible by using special TxConfig.external() config.

// transaction opened manually
ODatabaseDocumentTx db = new ODatabaseDocumentTx();
// database object must be bound on current thread (orient did this automatically 
// in most cases, so direct call is not required)
db.activateOnCurrentThread();

// context is PersistentContext, but TxTemplate may be called directly
context.doInTransaction(TxConfig.external(), () -> {
    // here we can use external transaction          
})

// connection closed manually
db.close();

Important difference with normal transaction is that guice will not manage commits and rollbacks: it is assumed that connection lifecycle is correctly managed manually.

There are intentionally no shortcuts for starting external unit of work because its not supposed to be used often and must be applied only in cases where other behaviour is impossible,

Connections

Document is the core connection type. Object and graph apis use document connection internally. Connection object mainly defines result of queries: document connection will always return ODocument, object connection returns mapped pojos (actually proxies) and ODocument for fields selections and graph api returns Vertex and Edge types. And of course connections provide specific apis for types.

You can use any connections within single transaction and changes made in one connection type will be visible in other connections. This allows you, for example to update data using object api and create relations using graph api.

To access connection object inside transaction use PersistentContext generified with the type of required connection.

  • PersistentContext<OObjectDatabaseTx> for object database connection
  • PersistentContext<ODatabaseDocumentTx> for document database connection
  • PersistentContext<OrientBaseGraph> for graph database connection (transactional or not)
  • PersistentContext<OrientGraph> for transactional graph database connection (will fail if notx transaction type)
  • PersistentContext<OrientGraphNoTx> for non transactional graph database connection (will provide only for notx transaction type, otherwise fail)

Note: you can't use both OrientGraph and OrientGraphNoTx in the same transaction (type must be used according to transaction type). OrientBaseGraph may be used in places where both types are possible (its the base class for both).

PersistentContext methods are shortcuts for low level api (simplifies usage). You can extend it to add more shortcut methods or make your own: it is not used internally and exists only for public usage.

See full api description

Scheme initialization

Module will automatically create database if it's not exist and perform default graph initialization if graph database support enabled (jar available).

To initialize (or migrate) scheme register implementation of

ru.vyarus.guice.persist.orient.db.scheme.SchemeInitializer

Example:

install(new OrientModule(url, user, password));
bind(SchemeInitializer.class).to(MySchemeInitializer.class);

Scheme initializer is called in notx unit of work (orient requires database scheme updates to be performed without transaction).

By default, no-op implementation enabled.

Object scheme mapping

Database scheme may be initialized from classes (jpa like). See orient object mapping documentation for object mapping (and general object database page).

Default orient object scheme mapper is limited in some cases. Special scheme mapper implementation provided (extension to default orient mapper).

It provides additional mapping annotations:

New annotation could be easily implemented as extensions (and all existing annotation are extensions and may be replaced).

Default modules provided with custom initializers (which use extended scheme mapping mechanism):

  • PackageSchemeModule - register all model classes in specific package (or packages). Useful when all model classes located in one package
  • AutoScanSchemeModule - registers all model classes annotated with @Persistent annotation. Useful for package by feature approach, when model classes contained within related logic

Module must be registered together with main OrientModule, for example:

install(new AutoScanSchemeModule("my.model.root.package"));

For example, to create vertex scheme:

@VertexType
public class MyVertex {..}

Now MyVertex could be used with graph api and with object api. Graph relations may be created dynamically (even if they are not mapped in object).

Remember that scheme created from objects maintain same hierarchy as your objects. E.g. if you use provided VersionedEntity class as base class for entities, it will be also registered in scheme (nothing bad, you may not notice it). But for graphs hierarchies more important: both vertex and edge objects can't extend same class (root class in hierarchy must extend V or E). So if you use @VertexType or @EdgeType annotations make sure their hierarchy not intersect.

Data initialization

To initialize (or migrate) data register implementation of

ru.vyarus.guice.persist.orient.db.data.DataInitializer

By default, no-op implementation enabled.

Example:

bind(DataInitializer.class).to(YourDataInitializerImpl.class);

Initializer is called WITHOUT predefined unit of work, because of different possible requirements. You should define unit of work (maybe more than one) yourself (with annotation or manual).

Change user

To use different db user for one or more transactions use UserManager:

@Inject UserManager userManager;
...
userManager.executeWithUser('user', 'password', new SpecificUserAction<Void>() {
    @Override
    public Void execute() throws Throwable {
        // do work
    }
})

SpecificUserAction defines scope of user overriding. This will not implicitly start transaction, but simply binds different user to current thread.

Overriding may be used in scheme initialization to use more powerful user or to use orient security model (in this case overriding may be done, for example in servlet filter).

Nested user override is not allowed (to avoid confusion). But you can use user overriding inside transaction.

Change tx user

User may be overridden inside transaction:

// context is PersistentContext
context.doWithUser('user', new SpecificUserAction<Void>() {
    @Override
    public Void execute() throws Throwable {
        // do work
    }
})

This changes current connection object user. As a result orient security checks will work for overridden user. Nested user override is not allowed (to avoid confusion).

More about user override

Retry

Due to orient implementation specifics, you may face OConcurrentModificationException When you was trying to save object its expected - optimistic locking (object contains version and if db version is different then your object considered stale, and you cant save it).

But, for example, even query like this may fail:

update Model set name = 'updated'

In concurrent environment this query also may cause OConcurrentModificationException. There is a special base class for such exceptions: ONeedRetryException. So by design some operations may fail initially, but succeed on repeated execution.

To fix such places you can use @Retry annotation. It catches exception and if its ONeedRetryException (or any cause is retry exception) it will repeat method execution.

@Retry(100)
@Transactional
public void update()

Annotation must be defined outside of transaction, because retry exception is casted on commit and it's impossible to catch it inside transaction. If @Retry annotated method will be executed under transaction, it will fail (catches redundant definition and avid error because of "expected behaviour". So be careful using it: be sure not to use annotated methods in transaction.

@Retry may be used with @Transactional on the same method (retry applied before).

In some cases using script instead of query solves concurrent update problem (even without retry):

begin
update Model set name='updated'
commit

Anyway, always write concurrent tests to be sure.

Repository

Repository annotations simplify writing dao or repository objects. Repositories are very close to spring-data repositories and following description will follow this approach. But repository methods may be used in any way (like dao or as additional methods for beans).

Repositories mainly cover query definitions (removing all boilerplate code). If you need something like spring-data specifications, you can use orientqb

Example repository query method:

@Query("select from Model where name=? and nick=?")
List<Model> find(String name, String nick);

Repositories implementation is based on extensions (every annotation you'll see is an extension). Custom extensions supported, so you can change almost everything.

Setup

To use repository features register repository module in guice context:

install(new RepositoryModule());

Guice abstract types support

Repository methods defined with annotations, so interface and abstract methods are ideal candidates to use them. Guice doesn't allow using abstract types, but it's possible with a bit of magic.

Abstract types (abstract class or interface containing repository methods) could be registered directly in guice module:

bind(MyInterfaceRepository.class).to(DynamicClassGenerator.generate(MyInterfaceRepository.class)).in(Singleton.class)

Or dynamic resolution could be used (guice JIT resolution):

@ProvidedBy(DynamicSingletonProvider.class)
public interface MyRepository

When some bean require this dao as dependency, guice will call provider, which will generate proper class for guice. (dynamic resolution completely replaces classpath scanning: only actually used repositories will be created) Note, this will automatically make bean singleton, which should be desirable in most cases. If you need custom scope use DynamicClassProvider with @ScopeAnnotation annotation (see details in guice-ext-annotations)

Note: Intellij IDEA will warn you that ProvidedBy annotation is incorrectly typed, but it's ok, because provider is too generic. There is nothing I can do with it and it's the best (the simplest) way I know (without explicit classpath scanning, which is redundant).

IMPORTANT: guice will control instance creation, so guice AOP features will completely work! @Transactional annotation may be used (generally not the best idea to limit transaction to repository method, but in some cases could be suitable). You can think of repository interface or abstract class as of usual guice bean (no limitations).

Repository methods are applied using aop (that's why they could be used everywhere).

Repositories overview

Method annotations:

All except delegate are command methods (build around orient command objects).

Command methods parameters annotations:

  • @Param - named parameter
  • @ElVar - query variable value (substituted in string before query execution)
  • @RidElVar - extract rid from provided object, document, vertex, string orid and insert into query as el var
  • @Var - orient command variable ($var), may be used by query during execution
  • @Skip and @Limit - orient pagination
  • @FetchPlan - defines fetch plan for query
  • @Listen - to provide query listener (required for async queries)
  • @DynamicParams - map dynamic count of parameters from array/collection/map

Delegate method parameters annotations:

  • @Generic - generic type value of caller repository (now exact class could be specified where to search generic)
  • @Repository - caller repository instance
  • @Connection - db connection object, selected by repository method

Command methods amend annotations:

  • @Timeout - defines query timeout and timeout strategy

Result converter annotations:

  • @NoConversion - disable default result conversion logic
  • @DetachResult - detaches result objects (list or simple object): returned result will contain simple objects instead of proxies

Defining repository

@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository {

    @Query("select from Model")
    List<Model> selectAll();

    @Query("update Model set name = ? where name = ?")
    int updateName(String newName, String oldName);

    @Query("insert into Model (name) values(:name)")
    Model create(@Param("name") String name);
}

Note: also, repository methods could be used to supplement existing bean, but suggest to use pure interface repositories.

@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public abstract class MyDao {

    @Query("select from Model")
    public abstract List<Model> selectAll();

    // normal method
    public void doSomething() {
        ...
    }
}

Note: @Transactional is not required (annotation usage depends on your service architecture, but repository method must be used inside transaction).

Usage examples

Function call:

@Function("function1")
List<Model> function();

Positional parameters:

@Query("select from Model where name=? and nick=?")
List<Model> parametersPositional(String name, String nick)

Named parameters:

@Query( "select from Model where name=:name and nick=:nick")
List<Model> parametersNamed(@Param("name") String name, @Param("nick") String nick)

Pagination:

@Query("select from Model where name=? and nick=?")
List<Model> parametersPaged(String name, String nick, @Skip int skip, @Limit int limit)

El variable:

@Query("select from Model where ${prop}=?")
List<Model> findBy(@ElVar("prop") String prop, String value)

Fetch plan parameter:

@Query("select from Model")
List<Model> selectAll(@FetchPlan("*:0") String plan);

Sql script:

@Script("begin" +
  "let account = create vertex Account set name = :name" +
  "let city = select from City where name = :city" +
  "let edge = create edge Lives from $account to $city" +
  "commit retry 100" +
  "return $edge")
Edge linkCity(@Param("name") String name, @Param("city") String city)

Js script:

@Script(language = "javascript", value =
 "for( i = 0; i < 1000; i++ ){" +
     "db.command('insert into Model(name) values (\"test'+i+'\")');" +
 "}")
void jsScript()

Async query:

@AsyncQuery("select from Model")
void select(@Listen OCommandResultListener listener)

Type safe listener (with conversion):

@AsyncQuery("select from Model")
void select(@Listen AsyncQueryListener<Model> listener)

Or with projection:

@AsyncQuery("select name from Model")
void select(@Listen AsyncQueryListener<String> listener)

Dynamic parameters:

@Query('select from Model where ${cond}')
List<ODocument> findWhere(@ElVar("cond") String cond, @DynamicParams Object... params);

Non blocking (listener execute in different thread):

@AsyncQuery(value = "select from Model", blocking = false)
Future<List<Model>> select(@Listen AsyncQueryListener<Model> listener)

Delegate example:

public class SomeBean {
   public List getAll() {
      ...
   }
}

@Delegate(SomeBean.class)
List getAll();

Live query:

@LiveQuery("select from Model")
int subscribe(@Listen OLiveResultListener listener)

Type safe listener (with conversion):

@LiveQuery("select from Model")
int subscribe(@Listen QueryResultListener<Model> listener)

Or vertex conversion:

@LiveQuery("select from Model")
int subscribe(@Listen QueryResultListener<Vertex> listener)

Unsubscription (usual command call):

@Query("live unsubscribe ${token}")
void unsubscribe(@ElVar("token") int token)

Read more about method usage in wiki:

For more examples see repository definition examples

Writing extensions:

Return types

You can use Iterable, Collection, List, Set, any collection implementation, array, single element or Iterator as return type. Conversion between types will be applied automatically.

@Query("select from Model")
List<Model> selectAll();

@Query("select from Model")
Set<Model> selectAll();

@Query("select from Model")
Model[] selectAll();

@Query("select from Model")
Iterable<Model> selectAll();

@Query("select from Model")
Iterator<Model> selectAll();

If you define single result, when query produce multiple results, first result would be automatically taken:

@Query("select from Model limit 1")
Model selectAll();

Note: limit is not required, but preferred, as soon as you don't need other results

Projection

In some cases single value is preferred, for example:

@Query("select count(@rid) from Model)
int count();

Orient returns ODocument from query with single field (count). Default result converter could recognize when document or vertex contain just one property and return only simple value.

Another case is when you select single field:

@Query("select name from Model")
String[] selectNames()

Read more about projection

Result type definition

It is very important to always define exact return type. Connection type defines type of result object: document connection always return ODocument, object return mapped objects (but ODocument for field calls) and graph - Vertex and Edge.

Result type is used internally to detect connection type for query.

For example, if you write:

@Query("select from Model")
List selectAll();

You will actually receive List<ODocument>, because without generic it's impossible to detect required return type and document connection used for query.

For example, in this case graph connection would be selected:

@Query("select from Model")
List<Vertex> selectAll();
Result conversion

Every repository method result is converted with default converter (as described above).

You can use more specific result conversion extension, for example:

@Query("select from Model")
@NoConversion
List<Model> selectAll();

NoConversion disables conversion mechanism and you receive result object as is.

With object connection, orient always return proxy objects, which usage outside of transaction is limited (same as in jpa). You can use detach converter to receive pure pojos:

@Query("select from Model")
@DetachResult
List<Model> selectAll();

Read more about converter mechanism and writing custom converters.

Mixins

Java support multiple inheritance for interfaces and you can inherit multiple interfaces in classes. So interfaces are ideal for writing small reusable parts (mixins).

Command mixins

El variables in commands support references to class generics, so you can use it for generic repository logic:

public interface MyMixin<T> {

    @Query("select from ${T}")
    List<T> selectAll()
}

@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository extends MyMixin<Model> {}

When you call mixin method from repository instance

repository.selectAll()

Generic value Model will be used for command select from Model and return type will be resolved as List<Model>, which will allow to select proper connection (object if Model is mapped entity).

You may use as many generics as you need. Any repository hierarchy depth will be correctly resolved, so you can even use composition mixins, which wil simply combine commonly used mixins:

public interface RepositoryBase<T> extends Mixin1<T>, Mixin2<T> {}

Note that you don't need to set ProvidedBy annotation on mixins, because it's just interfaces and they are not used as repository instances.

Delegate mixins

Delegates are also support generalization through extensions:

public class DelegateBean {
    public List selectAll(@Generic("T") Class model) {

    }
}

public interface MyMixin<T> {
    @Delegate(DelegateBean.class)
    List<T> selectAll()
}

When delegate bean called from mixin, it will receive generic value (of calling mixin) as parameter.

Read more about mixins usage

Bundled mixins

Few mixins provided out of the box:

Crud mixins are the most common thing: commonly these methods are implemented in AbstractDao or something like this.

DocumentCrud mixin provides base crud methods for document repository.

public interface MyEntityDao extends DocumentCrud<MyEntity> {}

Set mixin generic value only if you have reference entity class. Generic affects only getAll and create methods: if generic not set you will not be able to use only this method.

ObjectCrud mixin provides base crud methods for object repository:

public interface MyEntityRepository extends ObjectCrud<MyEntity> {}

Now MyEntityRepository has all basic crud methods (create, get, delete etc).

Pagination provides simple pagination for your entity or document (but document should have reference type, at least to specify schema type name (may be empty class))

public interface MyEntityRepository extends ObjectCrud<MyEntity>, Pagination<MyEntity, MyEntity> {}

...
// return page
Page page = repository.getPage(1, 20);

In order to use pagination mixin, crud mixin is not required (used in example just to mention one more time that mixins could be combined). Pagination mixin is the most complex one and good place to inspire how to write more complex reusable logic.

ObjectVertexCrud, EdgesSupport and EdgeTypeSupport mixins allows using graph features from object api.

@vertexType
public class Model {}

@EdgeType
public class ModelConnection {}

@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository extends ObjectVertexCrud<Model>, 
                       EdgeTypeSupport<ModelConnection, Model, Model> {}
                       
@Inject ModelRepository repository;
...
Model from = repository.save(new Model(..));
Model to = repository.save(new Model(..));
ModelConnection edge = repository.createEdge(from, to);

Validation

You can use guice-validator to apply runtime validation (jsr 303) for repository methods:

@Query("select from Model where name = ?")
@Size(min = 1)
List<Model> select(@NotNull String name)

Now this query throw ConstraintViolationException if null provided as parameter or no results returned.

Important: register validator module before guice-persist-orient modules! This way validation will be checked before @Transactional or repository methods logic.

Advanced topics

Orient configuration

Configuration could be done on instance:

db.getStorage().getConfiguration()

Or globally:

OGlobalConfiguration.MVRBTREE_NODE_PAGE_SIZE.setValue(2048);

Read about all configuration options

Might also like

  • generics-resolver - extracted library, used for generics resolution during finders analysis
  • dropwizard-orient-server - embedded orientdb server for dropwizard
  • guice-validator - hibernate validator integration for guice (objects validation, method arguments and return type runtime validation)
  • guice-ext-annotations - @Log, @PostConstruct, @PreDestroy and utilities for adding new annotations support

Contribution

Contributions are always welcome, but please check before patch submission:

$ gradlew check

java lib generator