Library Service Guide

What you'll build

You will build a service managing books, authors and subscriptions.

What you’ll need

Development requirements

A Javaâ„¢ Development Kit (JDK) installed. We recommend AdoptOpenJDK version 11 or 8.

Apache Maven version 3.1 or later installed.

Optional

An Integrated Developer Environment (IDE)

Popular choices include IntelliJ IDEA, Spring Tools, Visual Studio Code, or Eclipse, and many more.

Testing and running requirements

A FlexiCore based server running locally or on an accessible server.

One-click, no prerequisites installation is available for Linux (AMD64, ARM64) and Windows (AMD64) here 

A Docker image with fully installed FlexiCore and prerequisites is available here

Step 1: People Management

Library Management project is dependent on People Management project , please complete the people management guide. 

Step 2: Library Model

Step 2.a: Design Basic Model

managing entities for this phase:

  1. Book
  2. Author
  3. Subscription – this will model the fact that a person lend a book from our library.

Create a new Maven Project with the following structure:

Library Model Project Structure

update your pom.xml from here

Step 2.b: Create Book Entity

Open up the project in your IDE and create the Book.java file in the src/main/java/com/flexicore/example/library/model folder. Now change the contents of the file by adding the extra method and annotations shown in the code below. You can copy and paste the code or just type it.

package com.flexicore.example.library.model;

import com.flexicore.example.person.Person;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;

import javax.persistence.Entity;
import javax.persistence.ManyToOne;

@Entity
public class Book extends Baseclass {

    public Book() {
    }

    public Book(String name, SecurityContext securityContext) {
        super(name, securityContext);
    }

    @ManyToOne(targetEntity = Author.class)
    private Author author;

    @ManyToOne(targetEntity = Author.class)
    public Author getAuthor() {
        return author;
    }

    public <T extends Book> T setAuthor(Author author) {
        this.author = author;
        return (T) this;
    }
} 
  1. Our Book object inherits from Baseclass since we want to be able to govern permissions for it

Step 2.c: Create Author Entity

Open up the project in your IDE and create the Author.java file in the src/main/java/com/flexicore/example/library/model folder. Now change the contents of the file by adding the extra method and annotations shown in the code below. You can copy and paste the code or just type it.

package com.flexicore.example.library.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.person.Person;
import com.flexicore.security.SecurityContext;

import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Author extends Person {

    public Author(String name, SecurityContext securityContext) {
        super(name, securityContext);
    }

    public Author() {
    }

    @OneToMany(targetEntity = Book.class,mappedBy = "author")
    @JsonIgnore
    private List<Book> books=new ArrayList<>();

    @OneToMany(targetEntity = Book.class,mappedBy = "author")
    @JsonIgnore
    public List<Book> getBooks() {
        return books;
    }

    public <T extends Author> T setBooks(List<Book> books) {
        this.books = books;
        return (T) this;
    }
} 
  1. Our Author object inherits from Person since we want to extend its capabilities.

if you haven’t completed the People Management Project it is required to compile library model.

Step 2.d: Create Subscription Entity

Open up the project in your IDE and create the Subscription.java file in the src/main/java/com/flexicore/example/library/model folder. Now change the contents of the file by adding the extra method and annotations shown in the code below. You can copy and paste the code or just type it.

package com.flexicore.example.library.model;

import com.flexicore.example.person.Person;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import java.time.OffsetDateTime;

@Entity
public class Subscription extends Baseclass {
    @Column(columnDefinition = "timestamp with time zone")
    private OffsetDateTime startTime;
    @Column(columnDefinition = "timestamp with time zone")
    private OffsetDateTime endTime;

    public Subscription() {
    }

    public Subscription(String name, SecurityContext securityContext) {
        super(name, securityContext);
    }

    @ManyToOne(targetEntity = Book.class)
    private Book book;
    @ManyToOne(targetEntity = Person.class)
    private Person person;

    @ManyToOne(targetEntity = Book.class)
    public Book getBook() {
        return book;
    }

    public <T extends Subscription> T setBook(Book book) {
        this.book = book;
        return (T) this;
    }

    @ManyToOne(targetEntity = Person.class)
    public Person getPerson() {
        return person;
    }

    public <T extends Subscription> T setPerson(Person person) {
        this.person = person;
        return (T) this;
    }

    @Column(columnDefinition = "timestamp with time zone")
    public OffsetDateTime getStartTime() {
        return startTime;
    }

    public <T extends Subscription> T setStartTime(OffsetDateTime startTime) {
        this.startTime = startTime;
        return (T) this;
    }

    @Column(columnDefinition = "timestamp with time zone")
    public OffsetDateTime getEndTime() {
        return endTime;
    }

    public <T extends Subscription> T setEndTime(OffsetDateTime endTime) {
        this.endTime = endTime;
        return (T) this;
    }
} 
  1. Our Subscription object inherits from Baseclass since we want to extend its capabilities.
  2. our OffsetDateTime typed fields are annotated by@Column(columnDefinition = "timestamp with time zone") to tell our database to save the date information with  time zone.

Step 2.e: Create Persistence.xml

copy persistence.xml content from here.

this will allow automatic generation of JPA metamodels required to implement Criteria API based queries.

Step 2.f: install library model

./mvn install

./cp target/library-model-1.0.0-jar /home/flexicore/entities

Step 3: Library Service

Step 3.a: create Library service plugin

create a maven project with the following structure:

Library Service Project Structure

update your pom.xml from here

Step 3.b: Create library service request objects

lets define objects that will be consumed by our api:

  1. BookCreate – this object will contain all required details for creating a Book it will also inherit from BaseclassCreate object as we would like to extend Baseclass capabilities
  2. BookUpdate – this object will extend BookCreate object and id of the book to update
  3. BookFilter – this object will be sent by the client when fetching Books containing filtering options on them.
  4. AuthorCreate – this object will contain all required details for creating an Author it will also inherit from PersonCreate object as we would like to extend Person capabilities
  5. AuthorUpdate – this object will extend AuthorCreate object and id of the Author to update
  6. AuthorFilter – this object will be sent by the client when fetching Authors containing filtering options on them.
  7. SubscriptionCreate – this object will contain all required details for creating an Subscription it will also inherit from BaseclassCreate object as we would like to extend Baseclass capabilities
  8. SubscriptionUpdate – this object will extend SubscriptionCreate object and id of the Subscription to update
  9. SubscriptionFilter – this object will be sent by the client when fetching Subscriptions containing filtering options on them.

 

package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Author;

public class BookCreate {
   private String name;
   private String description;
   private String authorId;
   @JsonIgnore
   private Author author;

   public String getName() {
      return name;
   }

   public <T extends BookCreate> T setName(String name) {
      this.name = name;
      return (T) this;
   }

   public String getDescription() {
      return description;
   }

   public <T extends BookCreate> T setDescription(String description) {
      this.description = description;
      return (T) this;
   }

   public String getAuthorId() {
      return authorId;
   }

   public <T extends BookCreate> T setAuthorId(String authorId) {
      this.authorId = authorId;
      return (T) this;
   }

   @JsonIgnore
   public Author getAuthor() {
      return author;
   }

   public <T extends BookCreate> T setAuthor(Author author) {
      this.author = author;
      return (T) this;
   }
} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Book;

public class BookUpdate extends BookCreate {

   private String id;
   @JsonIgnore
   private Book book;

   public String getId() {
      return id;
   }

   public <T extends BookUpdate> T setId(String id) {
      this.id = id;
      return (T) this;
   }

   @JsonIgnore
   public Book getBook() {
      return book;
   }

   public <T extends BookUpdate> T setBook(Book book) {
      this.book = book;
      return (T) this;
   }
} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Author;
import com.flexicore.model.FilteringInformationHolder;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class BookFilter extends FilteringInformationHolder {

   private Set<String> authorIds = new HashSet<>();
   @JsonIgnore
   private List<Author> authors;

   public Set<String> getAuthorIds() {
      return authorIds;
   }

   public <T extends BookFilter> T setAuthorIds(Set<String> authorIds) {
      this.authorIds = authorIds;
      return (T) this;
   }

   @JsonIgnore
   public List<Author> getAuthors() {
      return authors;
   }

   public <T extends BookFilter> T setAuthors(List<Author> authors) {
      this.authors = authors;
      return (T) this;
   }
}
 
package com.flexicore.examples.request;

public class AuthorCreate extends PersonCreate {

} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Author;

public class AuthorUpdate extends AuthorCreate {

   private String id;
   @JsonIgnore
   private Author author;

   public String getId() {
      return id;
   }

   public <T extends AuthorUpdate> T setId(String id) {
      this.id = id;
      return (T) this;
   }

   @JsonIgnore
   public Author getAuthor() {
      return author;
   }

   public <T extends AuthorUpdate> T setAuthor(Author author) {
      this.author = author;
      return (T) this;
   }
} 
package com.flexicore.examples.request;

public class AuthorFilter extends PersonFilter {

} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.person.Person;

import java.time.OffsetDateTime;

public class SubscriptionCreate {

   private String description;
   private OffsetDateTime startTime;
   private OffsetDateTime endTime;
   private String bookId;
   @JsonIgnore
   private Book book;
   private String personId;
   @JsonIgnore
   private Person person;

   public String getDescription() {
      return description;
   }

   public <T extends SubscriptionCreate> T setDescription(String description) {
      this.description = description;
      return (T) this;
   }

   public OffsetDateTime getStartTime() {
      return startTime;
   }

   public <T extends SubscriptionCreate> T setStartTime(OffsetDateTime startTime) {
      this.startTime = startTime;
      return (T) this;
   }

   public OffsetDateTime getEndTime() {
      return endTime;
   }

   public <T extends SubscriptionCreate> T setEndTime(OffsetDateTime endTime) {
      this.endTime = endTime;
      return (T) this;
   }

   public String getBookId() {
      return bookId;
   }

   public <T extends SubscriptionCreate> T setBookId(String bookId) {
      this.bookId = bookId;
      return (T) this;
   }

   @JsonIgnore
   public Book getBook() {
      return book;
   }

   public <T extends SubscriptionCreate> T setBook(Book book) {
      this.book = book;
      return (T) this;
   }

   public String getPersonId() {
      return personId;
   }

   public <T extends SubscriptionCreate> T setPersonId(String personId) {
      this.personId = personId;
      return (T) this;
   }

   @JsonIgnore
   public Person getPerson() {
      return person;
   }

   public <T extends SubscriptionCreate> T setPerson(Person person) {
      this.person = person;
      return (T) this;
   }
} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.person.Person;
import com.flexicore.model.FilteringInformationHolder;

import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class SubscriptionFilter extends FilteringInformationHolder {

   private Set<String> bookIds = new HashSet<>();
   @JsonIgnore
   private List<Book> books;

   private Set<String> personIds = new HashSet<>();
   @JsonIgnore
   private List<Person> persons;

   public Set<String> getBookIds() {
      return bookIds;
   }

   public <T extends SubscriptionFilter> T setBookIds(Set<String> bookIds) {
      this.bookIds = bookIds;
      return (T) this;
   }

   @JsonIgnore
   public List<Book> getBooks() {
      return books;
   }

   public <T extends SubscriptionFilter> T setBooks(List<Book> books) {
      this.books = books;
      return (T) this;
   }

   public Set<String> getPersonIds() {
      return personIds;
   }

   public <T extends SubscriptionFilter> T setPersonIds(Set<String> personIds) {
      this.personIds = personIds;
      return (T) this;
   }

   @JsonIgnore
   public List<Person> getPersons() {
      return persons;
   }

   public <T extends SubscriptionFilter> T setPersons(List<Person> persons) {
      this.persons = persons;
      return (T) this;
   }

} 
package com.flexicore.examples.request;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flexicore.example.library.model.Subscription;

public class SubscriptionUpdate extends SubscriptionCreate {

   private String id;
   @JsonIgnore
   private Subscription subscription;

   public String getId() {
      return id;
   }

   public <T extends SubscriptionUpdate> T setId(String id) {
      this.id = id;
      return (T) this;
   }

   @JsonIgnore
   public Subscription getSubscription() {
      return subscription;
   }

   public <T extends SubscriptionUpdate> T setSubscription(
         Subscription subscription) {
      this.subscription = subscription;
      return (T) this;
   }
} 

Step 3.c: Create Repositories

lets define the repositories that will be used to fetch and save books, authors and subscriptions from the database.

package com.flexicore.examples.data;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.example.library.model.Author;
import com.flexicore.example.library.model.Author_;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.library.model.Book_;
import com.flexicore.examples.request.BookFilter;
import com.flexicore.interfaces.AbstractRepositoryPlugin;
import com.flexicore.model.QueryInformationHolder;
import com.flexicore.security.SecurityContext;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;

@PluginInfo(version = 1)
@Extension
@Component
public class BookRepository extends AbstractRepositoryPlugin {

   public List<Book> listAllBooks(BookFilter filtering,
         SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Book> q = cb.createQuery(Book.class);
      Root<Book> r = q.from(Book.class);
      List<Predicate> preds = new ArrayList<>();
      addBookPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Book> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Book.class, securityContext);
      return getAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

   private void addBookPredicate(BookFilter filtering, CriteriaBuilder cb,
         Root<Book> r, List<Predicate> preds) {
      if (filtering.getAuthors() != null && !filtering.getAuthors().isEmpty()) {
         Set<String> ids = filtering.getAuthors().parallelStream()
               .map(f -> f.getId()).collect(Collectors.toSet());
         Join<Book, Author> join = r.join(Book_.author);
         preds.add(join.get(Author_.id).in(ids));
      }
   }

   public Long countAllBooks(BookFilter filtering,
         SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Long> q = cb.createQuery(Long.class);
      Root<Book> r = q.from(Book.class);
      List<Predicate> preds = new ArrayList<>();
      addBookPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Book> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Book.class, securityContext);
      return countAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

} 
package com.flexicore.examples.data;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.example.library.model.Author;
import com.flexicore.examples.request.AuthorFilter;
import com.flexicore.interfaces.AbstractRepositoryPlugin;
import com.flexicore.model.QueryInformationHolder;
import com.flexicore.security.SecurityContext;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@PluginInfo(version = 1)
@Extension
@Component
public class AuthorRepository extends AbstractRepositoryPlugin {

   public List<Author> listAllAuthors(AuthorFilter filtering,
         SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Author> q = cb.createQuery(Author.class);
      Root<Author> r = q.from(Author.class);
      List<Predicate> preds = new ArrayList<>();
      addAuthorPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Author.class, securityContext);
      return getAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

   private void addAuthorPredicate(AuthorFilter filtering, CriteriaBuilder cb,
         Root<Author> r, List<Predicate> preds) {
      PersonRepository.addPersonPredicate(filtering, cb, r, preds);

   }

   public Long countAllAuthors(AuthorFilter filtering,
         SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Long> q = cb.createQuery(Long.class);
      Root<Author> r = q.from(Author.class);
      List<Predicate> preds = new ArrayList<>();
      addAuthorPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Author> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Author.class, securityContext);
      return countAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

} 
package com.flexicore.examples.data;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.library.model.Book_;
import com.flexicore.example.library.model.Subscription;
import com.flexicore.example.library.model.Subscription_;
import com.flexicore.example.person.Person;
import com.flexicore.example.person.Person_;
import com.flexicore.examples.request.SubscriptionFilter;
import com.flexicore.interfaces.AbstractRepositoryPlugin;
import com.flexicore.model.QueryInformationHolder;
import com.flexicore.security.SecurityContext;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;

@PluginInfo(version = 1)
@Extension
@Component
public class SubscriptionRepository extends AbstractRepositoryPlugin {

   public List<Subscription> listAllSubscriptions(
         SubscriptionFilter filtering, SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Subscription> q = cb.createQuery(Subscription.class);
      Root<Subscription> r = q.from(Subscription.class);
      List<Predicate> preds = new ArrayList<>();
      addSubscriptionPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Subscription> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Subscription.class, securityContext);
      return getAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

   private void addSubscriptionPredicate(SubscriptionFilter filtering,
         CriteriaBuilder cb, Root<Subscription> r, List<Predicate> preds) {
      if (filtering.getBooks() != null && !filtering.getBooks().isEmpty()) {
         Set<String> ids = filtering.getBooks().parallelStream()
               .map(f -> f.getId()).collect(Collectors.toSet());
         Join<Subscription, Book> join = r.join(Subscription_.book);
         preds.add(join.get(Book_.id).in(ids));
      }

      if (filtering.getPersons() != null && !filtering.getPersons().isEmpty()) {
         Set<String> ids = filtering.getPersons().parallelStream()
               .map(f -> f.getId()).collect(Collectors.toSet());
         Join<Subscription, Person> join = r.join(Subscription_.person);
         preds.add(join.get(Person_.id).in(ids));
      }

   }

   public Long countAllSubscriptions(SubscriptionFilter filtering,
         SecurityContext securityContext) {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<Long> q = cb.createQuery(Long.class);
      Root<Subscription> r = q.from(Subscription.class);
      List<Predicate> preds = new ArrayList<>();
      addSubscriptionPredicate(filtering, cb, r, preds);
      QueryInformationHolder<Subscription> queryInformationHolder = new QueryInformationHolder<>(
            filtering, Subscription.class, securityContext);
      return countAllFiltered(queryInformationHolder, preds, cb, q, r);
   }

} 
  1. the repository is annotated by
    •  @Extension annotation to allow FlexiCore to load it as a plugin 
    • @PluginInfo annotation to allow future versioning support
    • @Component annotation to let spring know it is a bean.
  2. the repository class extends AbstractRepositoryPlugin which provides easy method for access control and out of the box methods for persisting objects
  3. the repository exposes methods for listing and counting Book/Author/Subscription both are calling the relevant addBookPredicates/addAuthorPredicates/addSubscriptionPredicates which adds the required predicates , all access control predicates are automatically added when countAllFiltered and getAllFiltered are called
  4. addBookPredicates/addAuthorPredicates/addSubscriptionPredicates uses JPA Criteria Api to filter data based on BookFilter/AuthorFilter/SubscriptionFilter objects we have created in previous phase.

Step 3.d: Create Services

lets define the services that will be used by other plugins and REST api (or any other API implementations for that matter)

package com.flexicore.examples.service;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Author;
import com.flexicore.example.library.model.Book;
import com.flexicore.examples.data.BookRepository;
import com.flexicore.examples.request.BookCreate;
import com.flexicore.examples.request.BookFilter;
import com.flexicore.examples.request.BookUpdate;
import com.flexicore.interfaces.ServicePlugin;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;

import javax.ws.rs.BadRequestException;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@PluginInfo(version = 1)
@Extension
@Component
public class BookService implements ServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private BookRepository repository;

   public Book createBook(BookCreate bookCreate,
         SecurityContext securityContext) {
      Book book = createBookNoMerge(bookCreate, securityContext);
      repository.merge(book);
      return book;
   }

   public Book createBookNoMerge(BookCreate bookCreate,
         SecurityContext securityContext) {
      Book book = new Book(bookCreate.getName(),securityContext);
      updateBookNoMerge(book, bookCreate);
      return book;
   }

   public boolean updateBookNoMerge(Book book, BookCreate bookCreate) {
      boolean update = false;
      if (bookCreate.getName() != null
            && !bookCreate.getName().equals(book.getName())) {
         book.setName(bookCreate.getName());
         update = true;
      }

      if (bookCreate.getDescription() != null
            && !bookCreate.getDescription().equals(book.getDescription())) {
         book.setDescription(bookCreate.getDescription());
         update = true;
      }

      if (bookCreate.getAuthor() != null
            && (book.getAuthor() == null || !bookCreate.getAuthor().getId()
                  .equals(book.getAuthor().getId()))) {
         book.setAuthor(bookCreate.getAuthor());
         update = true;
      }

      return update;
   }

   public Book updateBook(BookUpdate bookUpdate,
         SecurityContext securityContext) {
      Book book = bookUpdate.getBook();
      if (updateBookNoMerge(book, bookUpdate)) {
         repository.merge(book);
      }
      return book;
   }

   public <T extends Baseclass> T getByIdOrNull(String id, Class<T> c,
         List<String> batchString, SecurityContext securityContext) {
      return repository.getByIdOrNull(id, c, batchString, securityContext);
   }

   public PaginationResponse<Book> getAllBooks(BookFilter bookFilter,
         SecurityContext securityContext) {
      List<Book> list = listAllBooks(bookFilter, securityContext);
      long count = repository.countAllBooks(bookFilter, securityContext);
      return new PaginationResponse<>(list, bookFilter, count);
   }

   public List<Book> listAllBooks(BookFilter bookFilter,
         SecurityContext securityContext) {
      return repository.listAllBooks(bookFilter, securityContext);
   }

   public void validate(BookFilter bookFilter, SecurityContext securityContext) {
      Set<String> authorIds = bookFilter.getAuthorIds();
      Map<String, Author> authorMap = authorIds.isEmpty()
            ? new HashMap<>()
            : repository
                  .listByIds(Author.class, authorIds, securityContext)
                  .parallelStream()
                  .collect(Collectors.toMap(f -> f.getId(), f -> f));
      authorIds.removeAll(authorMap.keySet());
      if (!authorIds.isEmpty()) {
         throw new BadRequestException("No Authors with ids " + authorIds);
      }
      bookFilter.setAuthors(new ArrayList<>(authorMap.values()));
   }

   public void validate(BookCreate bookCreate, SecurityContext securityContext) {
      String authorId = bookCreate.getAuthorId();
      Author author = authorId != null ? getByIdOrNull(authorId,
            Author.class, null, securityContext) : null;
      if (author == null) {
         throw new BadRequestException("No Author with id " + authorId);
      }
      bookCreate.setAuthor(author);
   }
} 
package com.flexicore.examples.service;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Author;
import com.flexicore.examples.data.AuthorRepository;
import com.flexicore.examples.request.AuthorCreate;
import com.flexicore.examples.request.AuthorFilter;
import com.flexicore.examples.request.AuthorUpdate;
import com.flexicore.interfaces.ServicePlugin;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;
import org.pf4j.Extension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.logging.Logger;

@PluginInfo(version = 1)
@Component
@Extension
@Primary
public class AuthorService implements ServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private AuthorRepository repository;

   @PluginInfo(version = 1)
   @Autowired
   private PersonService personService;

   public Author createAuthor(AuthorCreate authorCreate,
         SecurityContext securityContext) {
      Author author = createAuthorNoMerge(authorCreate, securityContext);
      repository.merge(author);
      return author;
   }

   public Author createAuthorNoMerge(AuthorCreate authorCreate,
         SecurityContext securityContext) {
      Author author = new Author(authorCreate.getFirstName(),securityContext);
      updateAuthorNoMerge(author, authorCreate);
      return author;
   }

   public boolean updateAuthorNoMerge(Author author, AuthorCreate authorCreate) {
      boolean update =personService.updatePersonNoMerge(author,authorCreate);

      return update;
   }

   public Author updateAuthor(AuthorUpdate authorUpdate,
         SecurityContext securityContext) {
      Author author = authorUpdate.getAuthor();
      if (updateAuthorNoMerge(author, authorUpdate)) {
         repository.merge(author);
      }
      return author;
   }

   public <T extends Baseclass> T getByIdOrNull(String id, Class<T> c,
         List<String> batchString, SecurityContext securityContext) {
      return repository.getByIdOrNull(id, c, batchString, securityContext);
   }

   public PaginationResponse<Author> getAllAuthors(AuthorFilter authorFilter,
         SecurityContext securityContext) {
      List<Author> list = listAllAuthors(authorFilter, securityContext);
      long count = repository.countAllAuthors(authorFilter, securityContext);
      return new PaginationResponse<>(list, authorFilter, count);
   }

   public List<Author> listAllAuthors(AuthorFilter authorFilter,
         SecurityContext securityContext) {
      return repository.listAllAuthors(authorFilter, securityContext);
   }

   public void validate(AuthorFilter authorFilter,
         SecurityContext securityContext) {

   }

   public void validate(AuthorCreate authorCreate,
         SecurityContext securityContext) {

   }
} 
package com.flexicore.examples.service;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Author;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.library.model.Subscription;
import com.flexicore.example.person.Person;
import com.flexicore.examples.data.SubscriptionRepository;
import com.flexicore.examples.request.SubscriptionCreate;
import com.flexicore.examples.request.SubscriptionFilter;
import com.flexicore.examples.request.SubscriptionUpdate;
import com.flexicore.interfaces.ServicePlugin;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;

import javax.ws.rs.BadRequestException;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@PluginInfo(version = 1)
@Extension
@Component
public class SubscriptionService implements ServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private SubscriptionRepository repository;

   public Subscription createSubscription(
         SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      Subscription subscription = createSubscriptionNoMerge(
            subscriptionCreate, securityContext);
      repository.merge(subscription);
      return subscription;
   }

   public Subscription createSubscriptionNoMerge(
         SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      Subscription subscription = new Subscription("subscription",securityContext);
      updateSubscriptionNoMerge(subscription, subscriptionCreate);
      return subscription;
   }

   public boolean updateSubscriptionNoMerge(Subscription subscription,
         SubscriptionCreate subscriptionCreate) {
      boolean update = false;

      if (subscriptionCreate.getDescription() != null
            && !subscriptionCreate.getDescription().equals(
                  subscription.getDescription())) {
         subscription.setDescription(subscriptionCreate.getDescription());
         update = true;
      }

      if (subscriptionCreate.getEndTime() != null
            && !subscriptionCreate.getEndTime().equals(
                  subscription.getEndTime())) {
         subscription.setEndTime(subscriptionCreate.getEndTime());
         update = true;
      }

      if (subscriptionCreate.getStartTime() != null
            && !subscriptionCreate.getStartTime().equals(
                  subscription.getStartTime())) {
         subscription.setStartTime(subscriptionCreate.getStartTime());
         update = true;
      }
      if (subscriptionCreate.getPerson() != null
            && (subscription.getPerson() == null || !subscriptionCreate
                  .getPerson().getId()
                  .equals(subscription.getPerson().getId()))) {
         subscription.setPerson(subscriptionCreate.getPerson());
         update = true;
      }

      if (subscriptionCreate.getBook() != null
            && (subscription.getBook() == null || !subscriptionCreate
                  .getBook().getId()
                  .equals(subscription.getBook().getId()))) {
         subscription.setBook(subscriptionCreate.getBook());
         update = true;
      }

      return update;
   }

   public Subscription updateSubscription(
         SubscriptionUpdate subscriptionUpdate,
         SecurityContext securityContext) {
      Subscription subscription = subscriptionUpdate.getSubscription();
      if (updateSubscriptionNoMerge(subscription, subscriptionUpdate)) {
         repository.merge(subscription);
      }
      return subscription;
   }

   public <T extends Baseclass> T getByIdOrNull(String id, Class<T> c,
         List<String> batchString, SecurityContext securityContext) {
      return repository.getByIdOrNull(id, c, batchString, securityContext);
   }

   public PaginationResponse<Subscription> getAllSubscriptions(
         SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      List<Subscription> list = listAllSubscriptions(subscriptionFilter,
            securityContext);
      long count = repository.countAllSubscriptions(subscriptionFilter,
            securityContext);
      return new PaginationResponse<>(list, subscriptionFilter, count);
   }

   public List<Subscription> listAllSubscriptions(
         SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      return repository.listAllSubscriptions(subscriptionFilter,
            securityContext);
   }

   public void validate(SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      Set<String> bookIds = subscriptionFilter.getBookIds();
      Map<String, Book> bookMap = bookIds.isEmpty()
            ? new HashMap<>()
            : repository.listByIds(Book.class, bookIds, securityContext)
                  .parallelStream()
                  .collect(Collectors.toMap(f -> f.getId(), f -> f));
      bookIds.removeAll(bookMap.keySet());
      if (!bookIds.isEmpty()) {
         throw new BadRequestException("No Books with ids " + bookIds);
      }
      subscriptionFilter.setBooks(new ArrayList<>(bookMap.values()));

      Set<String> personIds = subscriptionFilter.getPersonIds();
      Map<String, Person> personMap = personIds.isEmpty()
            ? new HashMap<>()
            : repository
                  .listByIds(Person.class, personIds, securityContext)
                  .parallelStream()
                  .collect(Collectors.toMap(f -> f.getId(), f -> f));
      personIds.removeAll(personMap.keySet());
      if (!personIds.isEmpty()) {
         throw new BadRequestException("No Person with ids " + personIds);
      }
      subscriptionFilter.setPersons(new ArrayList<>(personMap.values()));
   }

   public void validate(SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      String bookId = subscriptionCreate.getBookId();
      Book book = bookId != null ? getByIdOrNull(bookId, Book.class, null,
            securityContext) : null;
      if (bookId!=null&&book == null) {
         throw new BadRequestException("No Book with id " + bookId);
      }
      subscriptionCreate.setBook(book);

      String personId = subscriptionCreate.getPersonId();
      Person person = personId != null ? getByIdOrNull(personId,
            Person.class, null, securityContext) : null;
      if (personId!=null&&person == null) {
         throw new BadRequestException("No Person with id " + personId);
      }
      subscriptionCreate.setPerson(person);
   }
} 

Step 3.e: Create REST Services

lets define the REST services that will expose REST API that our clients will use:

package com.flexicore.examples.rest;

import com.flexicore.annotations.OperationsInside;
import com.flexicore.annotations.ProtectedREST;
import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Book;
import com.flexicore.examples.request.BookCreate;
import com.flexicore.examples.request.BookFilter;
import com.flexicore.examples.request.BookUpdate;
import com.flexicore.examples.service.BookService;
import com.flexicore.interfaces.RestServicePlugin;
import com.flexicore.security.SecurityContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

import javax.interceptor.Interceptors;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by Asaf on 04/06/2017.
 */
@PluginInfo(version = 1)
@OperationsInside
@ProtectedREST
@Path("plugins/Book")
@Tag(name = "Book")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Extension
@Component
public class BookRESTService implements RestServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private BookService service;

   @POST
   @Path("createBook")
   @Operation(summary = "createBook", description = "Creates Book")
   public Book createBook(
         @HeaderParam("authenticationKey") String authenticationKey,
         BookCreate bookCreate, @Context SecurityContext securityContext) {
      service.validate(bookCreate, securityContext);
      return service.createBook(bookCreate, securityContext);
   }

   @PUT
   @Operation(summary = "updateBook", description = "Updates Book")
   @Path("updateBook")
   public Book updateBook(
         @HeaderParam("authenticationKey") String authenticationKey,
         BookUpdate bookUpdate, @Context SecurityContext securityContext) {
      String bookId = bookUpdate.getId();
      Book book = service.getByIdOrNull(bookId, Book.class, null,
            securityContext);
      if (book == null) {
         throw new BadRequestException("No Book with id " + bookId);
      }
      bookUpdate.setBook(book);
      service.validate(bookUpdate, securityContext);
      return service.updateBook(bookUpdate, securityContext);
   }

   @POST
   @Operation(summary = "getAllBooks", description = "Gets All Books Filtered")
   @Path("getAllBooks")
   public PaginationResponse<Book> getAllBooks(
         @HeaderParam("authenticationKey") String authenticationKey,
         BookFilter bookFilter, @Context SecurityContext securityContext) {
      service.validate(bookFilter, securityContext);
      return service.getAllBooks(bookFilter, securityContext);
   }
} 
package com.flexicore.examples.rest;

import com.flexicore.annotations.OperationsInside;
import com.flexicore.annotations.ProtectedREST;
import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Author;
import com.flexicore.examples.request.AuthorCreate;
import com.flexicore.examples.request.AuthorFilter;
import com.flexicore.examples.request.AuthorUpdate;
import com.flexicore.examples.service.AuthorService;
import com.flexicore.interfaces.RestServicePlugin;
import com.flexicore.security.SecurityContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.pf4j.Extension;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by Asaf on 04/06/2017.
 */
@PluginInfo(version = 1)
@OperationsInside
@ProtectedREST
@Path("plugins/Author")
@Tag(name = "Author")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Component
@Extension
@Primary
public class AuthorRESTService implements RestServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private AuthorService authorService;

   @POST
   @Path("createAuthor")
   @Operation(summary = "createAuthor", description = "Creates Author")
   public Author createAuthor(
         @HeaderParam("authenticationKey") String authenticationKey,
         AuthorCreate authorCreate, @Context SecurityContext securityContext) {
      authorService.validate(authorCreate, securityContext);
      return authorService.createAuthor(authorCreate, securityContext);
   }

   @PUT
   @Operation(summary = "updateAuthor", description = "Updates Author")
   @Path("updateAuthor")
   public void updateAuthor(
         @HeaderParam("authenticationKey") String authenticationKey,
         AuthorUpdate authorUpdate, @Context SecurityContext securityContext) {
      String authorId = authorUpdate.getId();
      Author author = authorService.getByIdOrNull(authorId, Author.class, null,
            securityContext);
      if (author == null) {
         throw new BadRequestException("No Author with id " + authorId);
      }
      authorUpdate.setAuthor(author);
      authorService.validate(authorUpdate, securityContext);
      authorService.updateAuthor(authorUpdate, securityContext);
   }

   @POST
   @Operation(summary = "getAllAuthors", description = "Gets All Authors Filtered")
   @Path("getAllAuthors")
   public PaginationResponse<Author> getAllAuthors(
         @HeaderParam("authenticationKey") String authenticationKey,
         AuthorFilter authorFilter, @Context SecurityContext securityContext) {
      authorService.validate(authorFilter, securityContext);
      return authorService.getAllAuthors(authorFilter, securityContext);
   }
} 
package com.flexicore.examples.service;

import com.flexicore.annotations.plugins.PluginInfo;
import com.flexicore.data.jsoncontainers.PaginationResponse;
import com.flexicore.example.library.model.Author;
import com.flexicore.example.library.model.Book;
import com.flexicore.example.library.model.Subscription;
import com.flexicore.example.person.Person;
import com.flexicore.examples.data.SubscriptionRepository;
import com.flexicore.examples.request.SubscriptionCreate;
import com.flexicore.examples.request.SubscriptionFilter;
import com.flexicore.examples.request.SubscriptionUpdate;
import com.flexicore.interfaces.ServicePlugin;
import com.flexicore.model.Baseclass;
import com.flexicore.security.SecurityContext;

import javax.ws.rs.BadRequestException;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.pf4j.Extension;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@PluginInfo(version = 1)
@Extension
@Component
public class SubscriptionService implements ServicePlugin {

   @PluginInfo(version = 1)
   @Autowired
   private SubscriptionRepository repository;

   public Subscription createSubscription(
         SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      Subscription subscription = createSubscriptionNoMerge(
            subscriptionCreate, securityContext);
      repository.merge(subscription);
      return subscription;
   }

   public Subscription createSubscriptionNoMerge(
         SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      Subscription subscription = new Subscription("subscription",securityContext);
      updateSubscriptionNoMerge(subscription, subscriptionCreate);
      return subscription;
   }

   public boolean updateSubscriptionNoMerge(Subscription subscription,
         SubscriptionCreate subscriptionCreate) {
      boolean update = false;

      if (subscriptionCreate.getDescription() != null
            && !subscriptionCreate.getDescription().equals(
                  subscription.getDescription())) {
         subscription.setDescription(subscriptionCreate.getDescription());
         update = true;
      }

      if (subscriptionCreate.getEndTime() != null
            && !subscriptionCreate.getEndTime().equals(
                  subscription.getEndTime())) {
         subscription.setEndTime(subscriptionCreate.getEndTime());
         update = true;
      }

      if (subscriptionCreate.getStartTime() != null
            && !subscriptionCreate.getStartTime().equals(
                  subscription.getStartTime())) {
         subscription.setStartTime(subscriptionCreate.getStartTime());
         update = true;
      }
      if (subscriptionCreate.getPerson() != null
            && (subscription.getPerson() == null || !subscriptionCreate
                  .getPerson().getId()
                  .equals(subscription.getPerson().getId()))) {
         subscription.setPerson(subscriptionCreate.getPerson());
         update = true;
      }

      if (subscriptionCreate.getBook() != null
            && (subscription.getBook() == null || !subscriptionCreate
                  .getBook().getId()
                  .equals(subscription.getBook().getId()))) {
         subscription.setBook(subscriptionCreate.getBook());
         update = true;
      }

      return update;
   }

   public Subscription updateSubscription(
         SubscriptionUpdate subscriptionUpdate,
         SecurityContext securityContext) {
      Subscription subscription = subscriptionUpdate.getSubscription();
      if (updateSubscriptionNoMerge(subscription, subscriptionUpdate)) {
         repository.merge(subscription);
      }
      return subscription;
   }

   public <T extends Baseclass> T getByIdOrNull(String id, Class<T> c,
         List<String> batchString, SecurityContext securityContext) {
      return repository.getByIdOrNull(id, c, batchString, securityContext);
   }

   public PaginationResponse<Subscription> getAllSubscriptions(
         SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      List<Subscription> list = listAllSubscriptions(subscriptionFilter,
            securityContext);
      long count = repository.countAllSubscriptions(subscriptionFilter,
            securityContext);
      return new PaginationResponse<>(list, subscriptionFilter, count);
   }

   public List<Subscription> listAllSubscriptions(
         SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      return repository.listAllSubscriptions(subscriptionFilter,
            securityContext);
   }

   public void validate(SubscriptionFilter subscriptionFilter,
         SecurityContext securityContext) {
      Set<String> bookIds = subscriptionFilter.getBookIds();
      Map<String, Book> bookMap = bookIds.isEmpty()
            ? new HashMap<>()
            : repository.listByIds(Book.class, bookIds, securityContext)
                  .parallelStream()
                  .collect(Collectors.toMap(f -> f.getId(), f -> f));
      bookIds.removeAll(bookMap.keySet());
      if (!bookIds.isEmpty()) {
         throw new BadRequestException("No Books with ids " + bookIds);
      }
      subscriptionFilter.setBooks(new ArrayList<>(bookMap.values()));

      Set<String> personIds = subscriptionFilter.getPersonIds();
      Map<String, Person> personMap = personIds.isEmpty()
            ? new HashMap<>()
            : repository
                  .listByIds(Person.class, personIds, securityContext)
                  .parallelStream()
                  .collect(Collectors.toMap(f -> f.getId(), f -> f));
      personIds.removeAll(personMap.keySet());
      if (!personIds.isEmpty()) {
         throw new BadRequestException("No Person with ids " + personIds);
      }
      subscriptionFilter.setPersons(new ArrayList<>(personMap.values()));
   }

   public void validate(SubscriptionCreate subscriptionCreate,
         SecurityContext securityContext) {
      String bookId = subscriptionCreate.getBookId();
      Book book = bookId != null ? getByIdOrNull(bookId, Book.class, null,
            securityContext) : null;
      if (bookId!=null&&book == null) {
         throw new BadRequestException("No Book with id " + bookId);
      }
      subscriptionCreate.setBook(book);

      String personId = subscriptionCreate.getPersonId();
      Person person = personId != null ? getByIdOrNull(personId,
            Person.class, null, securityContext) : null;
      if (personId!=null&&person == null) {
         throw new BadRequestException("No Person with id " + personId);
      }
      subscriptionCreate.setPerson(person);
   }
}