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

net.lecousin.reactive.data.relational.LcReactiveDataRelationalClient Maven / Gradle / Ivy

There is a newer version: 0.10.2
Show newest version
package net.lecousin.reactive.data.relational;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Component;

import net.lecousin.reactive.data.relational.enhance.EntityState;
import net.lecousin.reactive.data.relational.mapping.LcEntityReader;
import net.lecousin.reactive.data.relational.mapping.LcMappingR2dbcConverter;
import net.lecousin.reactive.data.relational.mapping.LcReactiveDataAccessStrategy;
import net.lecousin.reactive.data.relational.model.EntityCache;
import net.lecousin.reactive.data.relational.model.LcEntityTypeInfo;
import net.lecousin.reactive.data.relational.model.ModelUtils;
import net.lecousin.reactive.data.relational.query.SelectExecution;
import net.lecousin.reactive.data.relational.query.SelectQuery;
import net.lecousin.reactive.data.relational.query.criteria.Criteria;
import net.lecousin.reactive.data.relational.query.operation.Operation;
import net.lecousin.reactive.data.relational.schema.RelationalDatabaseSchema;
import net.lecousin.reactive.data.relational.schema.SchemaBuilderFromEntities;
import net.lecousin.reactive.data.relational.schema.dialect.RelationalDatabaseSchemaDialect;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class LcReactiveDataRelationalClient {

	public static final Log logger = LogFactory.getLog(LcReactiveDataRelationalClient.class);
	
	private static final String QUERY_ENTITY_NAME = "entity";
	
	private DatabaseClient client;
	private MappingContext, ? extends RelationalPersistentProperty> mappingContext;
	private RelationalDatabaseSchemaDialect schemaDialect;
	private LcReactiveDataAccessStrategy dataAccess;
	private LcMappingR2dbcConverter mapper;
	
	public LcReactiveDataRelationalClient(
		DatabaseClient client,
		MappingContext, ? extends RelationalPersistentProperty> mappingContext,
		RelationalDatabaseSchemaDialect schemaDialect,
		LcReactiveDataAccessStrategy dataAccess,
		LcMappingR2dbcConverter mapper
	) {
		this.client = client;
		this.mappingContext = mappingContext;
		this.schemaDialect = schemaDialect;
		this.dataAccess = dataAccess;
		this.mapper = mapper;
		this.mapper.setLcClient(this);
		// ensure all declared entities have been detected by Spring
		for (Class type : LcEntityTypeInfo.getClasses())
			mappingContext.getPersistentEntity(type);
	}

	public DatabaseClient getSpringClient() {
		return client;
	}
	
	public LcMappingR2dbcConverter getMapper() {
		return mapper;
	}
	
	@SuppressWarnings("java:S1452") // usage of generic wildcard type
	public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() {
		return mappingContext;
	}
	
	public LcReactiveDataAccessStrategy getDataAccess() {
		return dataAccess;
	}
	
	public RelationalDatabaseSchemaDialect getSchemaDialect() {
		return schemaDialect;
	}
	
	public R2dbcDialect getDialect() {
		return dataAccess.getDialect();
	}

	public Mono dropSchemaContent(RelationalDatabaseSchema schema) {
		return schemaDialect.dropSchemaContent(schema).execute(this);
	}
	
	public Mono createSchemaContent(RelationalDatabaseSchema schema) {
		return schemaDialect.createSchemaContent(schema).execute(this);
	}
	
	public Mono dropCreateSchemaContent(RelationalDatabaseSchema schema) {
		return dropSchemaContent(schema).then(createSchemaContent(schema));
	}
	
	public RelationalDatabaseSchema buildSchemaFromEntities() {
		return buildSchemaFromEntities(LcEntityTypeInfo.getClasses());
	}
	
	public RelationalDatabaseSchema buildSchemaFromEntities(Collection> classes) {
		return new SchemaBuilderFromEntities(this).build(classes);
	}
	
	public  Mono save(T entity) {
		try {
			@SuppressWarnings("unchecked")
			RelationalPersistentEntity entityType = (RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(entity.getClass());
			Operation op = new Operation(this);
			op.addToSave(entity, entityType, null, null);
			return op.execute().thenReturn(entity);
		} catch (Exception e) {
			return Mono.error(e);
		}
	}
	
	public  Flux save(Iterable entities) {
		try {
			Iterator it = entities.iterator();
			if (!it.hasNext())
				return Flux.empty();
			T instance = it.next();
			@SuppressWarnings("unchecked")
			RelationalPersistentEntity entityType = (RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(instance.getClass());
			Operation op = new Operation(this);
			op.addToSave(instance, entityType, null, null);
			while (it.hasNext())
				op.addToSave(it.next(), entityType, null, null);
			return op.execute().thenMany(Flux.fromIterable(entities));
		} catch (Exception e) {
			return Flux.error(e);
		}
	}
	
	public  Flux save(Publisher publisher) {
		Operation op = new Operation(this);
		List list = new LinkedList<>();
		return Flux.from(publisher)
			.doOnNext(instance -> {
				op.addToSave(instance, null, null, null);
				list.add(instance);
			})
			.then(Mono.fromCallable(op::execute))
			.flatMap(m -> m)
			.thenReturn(list)
			.flatMapMany(Flux::fromIterable);
	}
	
	public Mono saveAll(Iterable entities) {
		Iterator it = entities.iterator();
		if (!it.hasNext())
			return Mono.empty();
		Operation op = new Operation(this);
		do {
			op.addToSave(it.next(), null, null, null);
		} while (it.hasNext());
		return op.execute();
	}
	
	public Mono saveAll(Object... entities) {
		return saveAll(Arrays.asList(entities));
	}
	
	public  Mono lazyLoad(T entity) {
		return lazyLoad(entity, mappingContext.getRequiredPersistentEntity(entity.getClass()));
	}
	
	public  Mono lazyLoad(T entity, RelationalPersistentEntity entityType) {
		return lazyLoad(entity, EntityState.get(entity, this, entityType), entityType);
	}
	
	public  Mono lazyLoad(T entity, EntityState state, RelationalPersistentEntity entityType) {
		return Mono.fromCallable(() -> state.loading(() -> doLoading(entity, entityType))).flatMap(result -> result);
	}
	
	@SuppressWarnings("unchecked")
	private  Mono doLoading(T entity, RelationalPersistentEntity entityType) {
		PersistentPropertyAccessor accessor = entityType.getPropertyAccessor(entity);
		Object id = ModelUtils.getId(entityType, accessor, mappingContext);
		EntityCache cache = new EntityCache();
		cache.setById((Class) entity.getClass(), id, entity);
		return SelectQuery.from((Class) entity.getClass(), QUERY_ENTITY_NAME)
			.where(ModelUtils.getCriteriaOnId(QUERY_ENTITY_NAME, entityType, accessor, mappingContext))
			.limit(0, 1)
			.execute(this, new LcEntityReader(cache, getMapper()))
			.next()
			;
	}

	public  Flux lazyLoad(Iterable entities, RelationalPersistentEntity entityType) {
		List> alreadyLoading = new LinkedList<>();
		List toLoad = new LinkedList<>();
		for (T entity : entities) {
			EntityState state = EntityState.get(entity, this, entityType);
			Mono loading = state.getLoading();
			if (loading != null)
				alreadyLoading.add(loading);
			else
				toLoad.add(entity);
		}
		Flux loading = doLoading(toLoad, entityType).cache();
		for (T entity : toLoad) {
			EntityState state = EntityState.get(entity, this, entityType);
			alreadyLoading.add(state.loading(() -> loading.filter(e -> e == entity).next()));
		}
		return Flux.merge(alreadyLoading);
	}
	
	@SuppressWarnings("unchecked")
	private  Flux doLoading(Iterable entities, RelationalPersistentEntity entityType) {
		Iterator it = entities.iterator();
		if (!it.hasNext())
			return Flux.empty();
		T entity = it.next();
		if (!it.hasNext())
			return Flux.from(doLoading(entity, entityType));
		EntityCache cache = new EntityCache();
		Criteria criteria = null;
		do {
			PersistentPropertyAccessor accessor = entityType.getPropertyAccessor(entity);
			Object id = ModelUtils.getId(entityType, accessor, mappingContext);
			cache.setById((Class) entity.getClass(), id, entity);
			Criteria entityCriteria = ModelUtils.getCriteriaOnId(QUERY_ENTITY_NAME, entityType, accessor, mappingContext);
			criteria = criteria != null ? criteria.or(entityCriteria) : entityCriteria;
			if (!it.hasNext())
				break;
			entity = it.next();
		} while (true);
		return SelectQuery.from((Class) entity.getClass(), QUERY_ENTITY_NAME)
			.where(criteria)
			.execute(this, new LcEntityReader(cache, getMapper()))
			;
	}
	
	public  Flux execute(SelectQuery query, @Nullable LcEntityReader reader) {
		return new SelectExecution(query, this, reader).execute();
	}
	
	public  Mono delete(T entity) {
		try {
			@SuppressWarnings("unchecked")
			RelationalPersistentEntity entityType = (RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(entity.getClass());
			Operation op = new Operation(this);
			op.addToDelete(entity, entityType, null, null);
			return op.execute();
		} catch (Exception e) {
			return Mono.error(e);
		}
	}
	
	public  Mono delete(Iterable entities) {
		try {
			Iterator it = entities.iterator();
			if (!it.hasNext())
				return Mono.empty();
			T instance = it.next();
			@SuppressWarnings("unchecked")
			RelationalPersistentEntity entityType = (RelationalPersistentEntity) mappingContext.getRequiredPersistentEntity(instance.getClass());
			Operation op = new Operation(this);
			op.addToDelete(instance, entityType, null, null);
			while (it.hasNext())
				op.addToDelete(it.next(), entityType, null, null);
			return op.execute();
		} catch (Exception e) {
			return Mono.error(e);
		}
	}
	
	public  Mono delete(Publisher publisher) {
		Operation op = new Operation(this);
		return Flux.from(publisher)
			.doOnNext(instance -> op.addToDelete(instance, null, null, null))
			.then(Mono.fromCallable(op::execute))
			.flatMap(m -> m);
	}
	
}