Рубрики
Без рубрики

Введение в данные с помощью Oligo

Узнайте, как реализовать простую службу OData с помощью библиотеки Apache Olingo.

Автор оригинала: Philippe Sevestre.

1. введение

Этот учебник является продолжением нашего руководства по протоколу OData , в котором мы изучили основы протокола OData .

Теперь мы посмотрим, как реализовать простой сервис OData с помощью библиотеки Apache Olingo //.

Эта библиотека предоставляет платформу для предоставления данных с использованием протокола OData, что позволяет легко и на основе стандартов получить доступ к информации, которая в противном случае была бы заблокирована во внутренних базах данных.

2. Что Такое Олинго?

Olingo-это одна из “рекомендуемых” реализаций OData, доступных для среды Java , а другая – платформа SDL OData . Он поддерживается Фондом Apache и состоит из трех основных модулей:

  • Java V2 – клиентские и серверные библиотеки, поддерживающие OData V2
  • Java V4 – серверные библиотеки, поддерживающие OData V4
  • Javascript V4 – Javascript, клиентская библиотека, поддерживающая OData V4

В этой статье мы рассмотрим только библиотеки Java версии 2 на стороне сервера, которые поддерживают прямую интеграцию с JPA . Полученная служба поддерживает операции CRUD и другие функции протокола OData, включая упорядочение, подкачку и фильтрацию.

С другой стороны, Olingo V4 обрабатывает только аспекты протокола более низкого уровня, такие как согласование типа контента и анализ URL-адресов. Это означает, что нам, разработчикам, придется кодировать все мелкие детали, касающиеся таких вещей, как генерация метаданных, генерация внутренних запросов на основе параметров URL-адресов и т. Д.

Что касается клиентской библиотеки JavaScript, мы пока оставляем ее, потому что, поскольку OData-это протокол на основе HTTP, мы можем использовать любую библиотеку REST для доступа к ней.

3. Служба Olingo Java V2

Давайте создадим простую службу OData с двумя наборами сущностей , которые мы использовали в нашем кратком введении в сам протокол . По своей сути Olingo V2-это просто набор ресурсов JAX-RS, и поэтому нам необходимо предоставить необходимую инфраструктуру для его использования. А именно, нам нужна реализация JAX-RS и совместимый контейнер сервлетов.

В этом примере мы решили использовать Spring Boot – поскольку он обеспечивает быстрый способ создания подходящей среды для размещения нашего сервиса. Мы также будем использовать адаптер Olingo JPA, который “разговаривает” непосредственно с предоставленным пользователем EntityManager , чтобы собрать все данные, необходимые для создания модели EntityDataModel OData.

Хотя это и не является строгим требованием, включение JPAadapter значительно упрощает задачу создания нашего сервиса.

Помимо стандартных зависимостей Spring Boot, нам нужно добавить пару банок Oligos:


    org.apache.olingo
    olingo-odata2-core
    2.0.11
    
        
            javax.ws.rs
            javax.ws.rs-api
         
    


    org.apache.olingo
    olingo-odata2-jpa-processor-core
    2.0.11


    org.apache.olingo
    olingo-odata2-jpa-processor-ref
    2.0.11
    
        
            org.eclipse.persistence
            eclipselink
        
    

Последняя версия этих библиотек доступна в центральном репозитории Maven:

Нам нужны эти исключения в этом списке, потому что Olingo имеет зависимости от EclipseLink в качестве поставщика JPA, а также использует другую версию JAX-RS, чем Spring Boot.

3.1. Классы доменов

Первым шагом для внедрения сервиса OData на основе JPA с Olingo является создание наших доменных сущностей. В этом простом примере мы создадим только два класса – CarMaker и CarModel – с одним отношением “один ко многим”.:

@Entity
@Table(name="car_maker")
public class CarMaker {    
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)    
    private Long id;
    @NotNull
    private String name;
    @OneToMany(mappedBy="maker",orphanRemoval = true,cascade=CascadeType.ALL)
    private List models;
    // ... getters, setters and hashcode omitted 
}

@Entity
@Table(name="car_model")
public class CarModel {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    @NotNull
    private String name;
    
    @NotNull
    private Integer year;
    
    @NotNull
    private String sku;
    
    @ManyToOne(optional=false,fetch=FetchType.LAZY) @JoinColumn(name="maker_fk")
    private CarMaker maker;
    
    // ... getters, setters and hashcode omitted
}

3.2. Реализация ODataJPAServiceFactory

Ключевым компонентом, необходимым для предоставления инструментария o для обслуживания данных из домена JPA, является конкретная реализация абстрактного класса под названием ODataJPAServiceFactory. Этот класс должен расширять ODataServiceFactory и работать как адаптер между JPA и данными. Мы назовем эту фабрику Cars ODataJPAServiceFactory в честь основной темы нашего домена:

@Component
public class CarsODataJPAServiceFactory extends ODataJPAServiceFactory {
    // other methods omitted...

    @Override
    public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
        ODataJPAContext ctx = getODataJPAContext();
        ODataContext octx = ctx.getODataContext();
        HttpServletRequest request = (HttpServletRequest) octx.getParameter(
          ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
        EntityManager em = (EntityManager) request
          .getAttribute(EntityManagerFilter.EM_REQUEST_ATTRIBUTE);
        
        ctx.setEntityManager(em);
        ctx.setPersistenceUnitName("default");
        ctx.setContainerManaged(true);                
        return ctx;
    }
}

Olingo вызывает метод initialize JPA Context () , если этот класс получает новый ODataJPAContext используется для обработки каждого запроса данных. Здесь мы используем метод getODataJPAContext() из базового класса, чтобы получить “простой” экземпляр, который мы затем немного настраиваем.

Этот процесс несколько запутан, поэтому давайте нарисуем последовательность UML, чтобы визуализировать, как все это происходит:

Обратите внимание, что мы намеренно используем setEntityManager() вместо setEntityManagerFactory(). Мы могли бы получить его от Spring, но если мы передадим его Olingo, это будет противоречить тому, как Spring Boot обрабатывает свой жизненный цикл, особенно при работе с транзакциями.

По этой причине мы прибегнем к передаче уже существующего экземпляра EntityManager и сообщим ему, что его жизненный цикл управляется извне. Введенный экземпляр EntityManager исходит из атрибута, доступного по текущему запросу. Позже мы увидим, как установить этот атрибут.

3.3. Регистрация ресурсов Джерси

Следующим шагом является регистрация нашей Фабрики обслуживания в среде выполнения Oligos и регистрация точки входа Oligos в среде выполнения JAX-RS. Мы сделаем это в производном классе ResourceConfig , где мы также определим путь к данным для нашего сервиса /odata :

@Component
@ApplicationPath("/odata")
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig(CarsODataJPAServiceFactory serviceFactory, EntityManagerFactory emf) {        
        ODataApplication app = new ODataApplication();        
        app
          .getClasses()
          .forEach( c -> {
              if ( !ODataRootLocator.class.isAssignableFrom(c)) {
                  register(c);
              }
          });
        
        register(new CarsRootLocator(serviceFactory)); 
        register(new EntityManagerFilter(emf));
    }
    
    // ... other methods omitted
}

Oligos provided OData Application -это обычный класс JAX-RS Application , который регистрирует несколько поставщиков, используя стандартный обратный вызов getClasses() .

Мы можем использовать все, кроме класса ODataRootLocator как есть. Этот конкретный отвечает за создание экземпляра нашей реализации ODataJPAServiceFactory с использованием метода Java newInstance () . Но, поскольку мы хотим, чтобы Spring управлял им для нас, нам нужно заменить его пользовательским локатором.

Этот локатор является очень простым ресурсом JAX-RS, который расширяет запас Oligos ODataRootLocator и возвращает наш управляемый весной ServiceFactory при необходимости:

@Path("/")
public class CarsRootLocator extends ODataRootLocator {
    private CarsODataJPAServiceFactory serviceFactory;
    public CarsRootLocator(CarsODataJPAServiceFactory serviceFactory) {
        this.serviceFactory = serviceFactory;
    }

    @Override
    public ODataServiceFactory getServiceFactory() {
       return this.serviceFactory;
    } 
}

3.4. Фильтр EntityManager

Последняя оставшаяся часть для нашего сервиса OData-это EntityManagerFilter . Этот фильтр вводит EntityManager в текущий запрос, поэтому он доступен для Фабрики обслуживания . Это простой класс JAX-RS @Provider , который реализует оба интерфейса ContainerRequestFilter и ContainerResponseFilter , поэтому он может правильно обрабатывать транзакции:

@Provider
public static class EntityManagerFilter implements ContainerRequestFilter, 
  ContainerResponseFilter {

    public static final String EM_REQUEST_ATTRIBUTE = 
      EntityManagerFilter.class.getName() + "_ENTITY_MANAGER";
    private final EntityManagerFactory emf;

    @Context
    private HttpServletRequest httpRequest;

    public EntityManagerFilter(EntityManagerFactory emf) {
        this.emf = emf;
    }

    @Override
    public void filter(ContainerRequestContext ctx) throws IOException {
        EntityManager em = this.emf.createEntityManager();
        httpRequest.setAttribute(EM_REQUEST_ATTRIBUTE, em);
        if (!"GET".equalsIgnoreCase(ctx.getMethod())) {
            em.getTransaction().begin();
        }
    }

    @Override
    public void filter(ContainerRequestContext requestContext, 
      ContainerResponseContext responseContext) throws IOException {
        EntityManager em = (EntityManager) httpRequest.getAttribute(EM_REQUEST_ATTRIBUTE);
        if (!"GET".equalsIgnoreCase(requestContext.getMethod())) {
            EntityTransaction t = em.getTransaction();
            if (t.isActive() && !t.getRollbackOnly()) {
                t.commit();
            }
        }
        
        em.close();
    }
}

Первый метод filter () , вызываемый в начале запроса ресурса, использует предоставленный EntityManagerFactory для создания нового экземпляра EntityManager , который затем помещается под атрибут, чтобы впоследствии он мог быть восстановлен ServiceFactory . Мы также пропускаем запросы GET, так как они не должны иметь никаких побочных эффектов, и поэтому нам не понадобится транзакция.

Второй метод filter() вызывается после того, как Olingo завершит обработку запроса. Здесь мы также проверяем метод запроса и при необходимости фиксируем транзакцию.

3.5. Тестирование

Давайте протестируем нашу реализацию с помощью простых команд curl . Первое, что мы можем сделать, это получить сервисы $metadata document:

curl http://localhost:8080/odata/$metadata

Как и ожидалось, документ содержит два типа – CarMaker и CarModel – и ассоциацию . Теперь давайте еще немного поиграем с нашим сервисом, извлекая коллекции и сущности верхнего уровня:

curl http://localhost:8080/odata/CarMakers
curl http://localhost:8080/odata/CarModels
curl http://localhost:8080/odata/CarMakers(1)
curl http://localhost:8080/odata/CarModels(1)
curl http://localhost:8080/odata/CarModels(1)/CarMakerDetails

Теперь давайте протестируем простой запрос, возвращающий все Автопроизводители , где его имя начинается с “B”:

curl http://localhost:8080/odata/CarMakers?$filter=startswith(Name,'B')

Более полный список примеров URL-адресов доступен в нашей статье Руководства по протоколу OData .

5. Заключение

В этой статье мы рассмотрели, как создать простой сервис OData, поддерживаемый доменом JPA, с помощью Olingo V2.

На момент написания этой статьи существует открытая проблема с Olingo JIRA отслеживание работ над модулем JPA для V4, но последний комментарий датируется 2016 годом. Существует также сторонний JPAadapter с открытым исходным кодом , размещенный в репозитории GitHub SAP , который, хотя и не выпущен, на данный момент кажется более функциональным, чем у Olingo.

Как обычно, весь код для этой статьи доступен на GitHub .