All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.croz.nrich.registry.data.service.DefaultRegistryDataService Maven / Gradle / Ivy

Go to download

Provides a representation of JPA entities in a format that can be used on the client side to build dynamic forms and tables, as well as a REST API for querying, updating and deleting those entities

The newest version!
/*
 *  Copyright 2020-2023 CROZ d.o.o, the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package net.croz.nrich.registry.data.service;

import net.croz.nrich.registry.api.core.service.RegistryEntityFinderService;
import net.croz.nrich.registry.api.data.interceptor.RegistryDataInterceptor;
import net.croz.nrich.registry.api.data.request.ListBulkRegistryRequest;
import net.croz.nrich.registry.api.data.request.ListRegistryRequest;
import net.croz.nrich.registry.api.data.service.RegistryDataService;
import net.croz.nrich.registry.core.model.RegistryDataConfiguration;
import net.croz.nrich.registry.core.model.RegistryDataConfigurationHolder;
import net.croz.nrich.registry.core.support.ManagedTypeWrapper;
import net.croz.nrich.registry.data.util.HibernateUtil;
import net.croz.nrich.search.api.converter.StringToEntityPropertyMapConverter;
import net.croz.nrich.search.api.model.SearchConfiguration;
import net.croz.nrich.search.api.model.property.SearchPropertyConfiguration;
import net.croz.nrich.search.api.model.sort.SortDirection;
import net.croz.nrich.search.api.model.sort.SortProperty;
import net.croz.nrich.search.api.util.PageableUtil;
import net.croz.nrich.search.support.JpaQueryBuilder;
import org.modelmapper.ModelMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaQuery;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

// TODO better error handling and maybe versioning
public class DefaultRegistryDataService implements RegistryDataService {

    private final EntityManager entityManager;

    private final ModelMapper modelMapper;

    private final StringToEntityPropertyMapConverter stringToEntityPropertyMapConverter;

    private final RegistryDataConfigurationHolder registryDataConfigurationHolder;

    private final List registryDataInterceptorList;

    private final Map> classNameQueryBuilderMap;

    private final RegistryEntityFinderService registryEntityFinderService;

    public DefaultRegistryDataService(EntityManager entityManager, ModelMapper modelMapper, StringToEntityPropertyMapConverter stringToEntityPropertyMapConverter,
                                      RegistryDataConfigurationHolder registryDataConfigurationHolder, List registryDataInterceptorList,
                                      RegistryEntityFinderService registryEntityFinderService) {
        this.entityManager = entityManager;
        this.modelMapper = modelMapper;
        this.stringToEntityPropertyMapConverter = stringToEntityPropertyMapConverter;
        this.registryDataConfigurationHolder = registryDataConfigurationHolder;
        this.registryDataInterceptorList = registryDataInterceptorList;
        this.classNameQueryBuilderMap = initializeQueryBuilderMap(registryDataConfigurationHolder);
        this.registryEntityFinderService = registryEntityFinderService;
    }

    @Transactional(readOnly = true)
    @Override
    public Map> listBulk(ListBulkRegistryRequest request) {
        return request.registryRequestList().stream()
            .collect(Collectors.toMap(ListRegistryRequest::classFullName, this::list));
    }

    @Transactional(readOnly = true)
    @Override
    public 

Page

list(ListRegistryRequest request) { interceptorList().forEach(registryDataInterceptor -> registryDataInterceptor.beforeRegistryList(request)); return registryListInternal(request); } @Transactional @Override public T create(String classFullName, Object entityData) { interceptorList().forEach(registryDataInterceptor -> registryDataInterceptor.beforeRegistryCreate(classFullName, entityData)); @SuppressWarnings("unchecked") RegistryDataConfiguration registryDataConfiguration = (RegistryDataConfiguration) registryDataConfigurationHolder.findRegistryConfigurationForClass(classFullName); T instance = resolveEntityInstance(registryDataConfiguration.registryType(), entityData); modelMapper.map(entityData, instance); return mergeAndInitializeEntity(instance); } @Transactional @Override public T update(String classFullName, Object id, Object entityData) { interceptorList().forEach(registryDataInterceptor -> registryDataInterceptor.beforeRegistryUpdate(classFullName, id, entityData)); @SuppressWarnings("unchecked") RegistryDataConfiguration registryDataConfiguration = (RegistryDataConfiguration) registryDataConfigurationHolder.findRegistryConfigurationForClass(classFullName); ManagedTypeWrapper wrapper = registryDataConfigurationHolder.resolveManagedTypeWrapper(classFullName); T instance = registryEntityFinderService.findEntityInstance(registryDataConfiguration.registryType(), id); if (wrapper.isIdClassIdentifier() || wrapper.isEmbeddedIdentifier()) { entityManager.remove(instance); instance = resolveEntityInstance(registryDataConfiguration.registryType(), entityData); } else { setIdFieldToOriginalValue(wrapper, entityData, id); } modelMapper.map(entityData, instance); return mergeAndInitializeEntity(instance); } @Transactional @Override public T delete(String classFullName, Object id) { interceptorList().forEach(registryDataInterceptor -> registryDataInterceptor.beforeRegistryDelete(classFullName, id)); @SuppressWarnings("unchecked") RegistryDataConfiguration registryDataConfiguration = (RegistryDataConfiguration) registryDataConfigurationHolder.findRegistryConfigurationForClass(classFullName); T instance = registryEntityFinderService.findEntityInstance(registryDataConfiguration.registryType(), id); entityManager.remove(instance); return instance; } private Map> initializeQueryBuilderMap(RegistryDataConfigurationHolder registryDataConfigurationHolder) { return registryDataConfigurationHolder.registryDataConfigurationList().stream() .collect(Collectors.toMap(registryDataConfiguration -> registryDataConfiguration.registryType().getName(), registryDataConfiguration -> new JpaQueryBuilder<>(entityManager, registryDataConfiguration.registryType())) ); } private List interceptorList() { return Optional.ofNullable(registryDataInterceptorList).orElse(Collections.emptyList()); } private Page

registryListInternal(ListRegistryRequest request) { @SuppressWarnings("unchecked") RegistryDataConfiguration registryDataConfiguration = (RegistryDataConfiguration) registryDataConfigurationHolder.findRegistryConfigurationForClass(request.classFullName()); SearchConfiguration> searchConfiguration = registryDataConfiguration.searchConfiguration(); @SuppressWarnings("unchecked") JpaQueryBuilder queryBuilder = (JpaQueryBuilder) classNameQueryBuilderMap.get(request.classFullName()); ManagedTypeWrapper managedTypeWrapper = registryDataConfigurationHolder.resolveManagedTypeWrapper(request.classFullName()); String idAttributeName = Optional.ofNullable(managedTypeWrapper.getIdAttributeName()).orElseGet(() -> managedTypeWrapper.getIdClassPropertyNameList().get(0)); Pageable pageable = PageableUtil.convertToPageable(request.pageNumber(), request.pageSize(), new SortProperty(idAttributeName, SortDirection.ASC), request.sortPropertyList()); Map searchRequestMap = resolveSearchRequestMap(managedTypeWrapper, request, searchConfiguration.getSearchPropertyConfiguration()); CriteriaQuery

query = queryBuilder.buildQuery(searchRequestMap, searchConfiguration, pageable.getSort()); TypedQuery

typedQuery = entityManager.createQuery(query); typedQuery.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize()); return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable, () -> executeCountQuery(queryBuilder, searchRequestMap, searchConfiguration)); } private Map resolveSearchRequestMap(ManagedTypeWrapper managedTypeWrapper, ListRegistryRequest request, SearchPropertyConfiguration searchPropertyConfiguration) { Map searchRequestMap = Collections.emptyMap(); if (request.searchParameter() != null) { searchRequestMap = stringToEntityPropertyMapConverter.convert( request.searchParameter().query(), request.searchParameter().propertyNameList(), managedTypeWrapper.getIdentifiableType(), searchPropertyConfiguration ); } return searchRequestMap; } private long executeCountQuery(JpaQueryBuilder queryBuilder, Map searchRequestMap, SearchConfiguration> searchConfiguration) { CriteriaQuery countQuery = queryBuilder.buildCountQuery(searchRequestMap, searchConfiguration); List totals = entityManager.createQuery(countQuery).getResultList(); return totals.stream().mapToLong(value -> value == null ? 0L : value).sum(); } @SuppressWarnings("unchecked") private T resolveEntityInstance(Class type, Object entityData) { if (entityData != null && type.equals(entityData.getClass())) { return (T) entityData; } return BeanUtils.instantiateClass(type); } // in case users submit a null id value inside entity data it should be reset, this seems to be the simplest way of doing that private void setIdFieldToOriginalValue(ManagedTypeWrapper managedTypeWrapper, Object entityData, Object id) { Map idValueMap = Collections.singletonMap(managedTypeWrapper.getIdAttributeName(), id); modelMapper.map(idValueMap, entityData); } private T mergeAndInitializeEntity(T instance) { T mergedInstance = entityManager.merge(instance); HibernateUtil.initialize(mergedInstance); return mergedInstance; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy