io.github.joselion.springr2dbcrelationships.R2dbcRelationshipsCallbacks Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-r2dbc-relationships Show documentation
Show all versions of spring-r2dbc-relationships Show documentation
Spring R2DBC annotations to handle entity relationships
The newest version!
package io.github.joselion.springr2dbcrelationships;
import static java.util.function.Predicate.not;
import java.util.List;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.context.ApplicationContext;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback;
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import io.github.joselion.springr2dbcrelationships.annotations.ManyToMany;
import io.github.joselion.springr2dbcrelationships.annotations.ManyToOne;
import io.github.joselion.springr2dbcrelationships.annotations.OneToMany;
import io.github.joselion.springr2dbcrelationships.annotations.OneToOne;
import io.github.joselion.springr2dbcrelationships.helpers.Commons;
import io.github.joselion.springr2dbcrelationships.helpers.Reflect;
import io.github.joselion.springr2dbcrelationships.processors.ManyToManyProcessor;
import io.github.joselion.springr2dbcrelationships.processors.ManyToOneProcessor;
import io.github.joselion.springr2dbcrelationships.processors.OneToManyProcessor;
import io.github.joselion.springr2dbcrelationships.processors.OneToOneProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples;
/**
* Implementation of all the callback that process the R2DBC Relationship
* annotations.
*
* @param the entity type
* @param template the r2dbc entity template
* @param context the Spring application context
*/
public record R2dbcRelationshipsCallbacks(
R2dbcEntityTemplate template,
ApplicationContext context
) implements AfterConvertCallback, AfterSaveCallback, BeforeConvertCallback {
@Override
public Publisher onAfterConvert(final T entity, final SqlIdentifier table) {
final var oneToOneProcessor = new OneToOneProcessor(this.template, entity, table, this.context);
final var oneToManyProcessor = new OneToManyProcessor(this.template, entity, table, this.context);
final var manyToOneProcessor = new ManyToOneProcessor(this.template, entity, table, this.context);
final var manyToManyProcessor = new ManyToManyProcessor(this.template, entity, table, this.context);
return Mono.just(entity)
.map(T::getClass)
.map(Class::getDeclaredFields)
.flatMapMany(Flux::fromArray)
.parallel()
.runOn(Schedulers.parallel())
.flatMap(field ->
Mono.just(OneToOne.class)
.mapNotNull(field::getAnnotation)
.flatMap(oneToOneProcessor.populate(field))
.switchIfEmpty(
Mono.just(OneToMany.class)
.mapNotNull(field::getAnnotation)
.flatMap(oneToManyProcessor.populate(field))
)
.switchIfEmpty(
Mono.just(ManyToOne.class)
.mapNotNull(field::getAnnotation)
.flatMap(manyToOneProcessor.populate(field))
)
.switchIfEmpty(
Mono.just(ManyToMany.class)
.mapNotNull(field::getAnnotation)
.flatMap(manyToManyProcessor.populate(field))
)
.map(value -> Tuples.of(field, value))
)
.sequential()
.reduce(entity, (acc, tuple) -> {
final var field = tuple.getT1();
final var value = tuple.getT2();
return Reflect.update(acc, field, value);
})
.defaultIfEmpty(entity);
}
@Override
public Publisher onAfterSave(final T entity, final OutboundRow outboundRow, final SqlIdentifier table) {
final var oneToOneProcessor = new OneToOneProcessor(this.template, entity, table, this.context);
final var oneToManyProcessor = new OneToManyProcessor(this.template, entity, table, this.context);
final var manyToManyProcessor = new ManyToManyProcessor(this.template, entity, table, this.context);
return Mono.just(entity)
.map(T::getClass)
.map(Class::getDeclaredFields)
.flatMapIterable(List::of)
.parallel()
.runOn(Schedulers.parallel())
.flatMap(field ->
Mono.just(OneToOne.class)
.mapNotNull(field::getAnnotation)
.filter(not(OneToOne::readonly))
.filter(not(oneToOneProcessor.isBackReference(field)))
.flatMap(oneToOneProcessor.persist(field))
.switchIfEmpty(
Mono.just(OneToMany.class)
.mapNotNull(field::getAnnotation)
.filter(not(OneToMany::readonly))
.flatMap(oneToManyProcessor.persist(field))
)
.switchIfEmpty(
Mono.just(ManyToMany.class)
.mapNotNull(field::getAnnotation)
.filter(not(ManyToMany::readonly))
.flatMap(manyToManyProcessor.persist(field))
)
.map(value -> Tuples.of(field, value))
)
.sequential()
.reduce(entity, (acc, tuple) -> {
final var field = tuple.getT1();
final var value = tuple.getT2();
return Reflect.update(acc, field, value);
})
.defaultIfEmpty(entity);
}
@Override
public Publisher onBeforeConvert(final T entity, final SqlIdentifier table) {
final var annotations = List.of(OneToOne.class, ManyToOne.class);
return Mono.just(entity)
.map(T::getClass)
.map(Class::getDeclaredFields)
.flatMapIterable(List::of)
.filter(field -> annotations.stream().anyMatch(field::isAnnotationPresent))
.reduce(Mono.just(entity), (acc, field) ->
acc.flatMap(nextEntity -> {
final var oneToOneProcessor = new OneToOneProcessor(this.template, nextEntity, table, this.context);
final var manyToOneProcessor = new ManyToOneProcessor(this.template, nextEntity, table, this.context);
return Mono.just(OneToOne.class)
.mapNotNull(field::getAnnotation)
.filter(not(OneToOne::readonly))
.filter(oneToOneProcessor.isBackReference(field))
.flatMap(oneToOneProcessor.persist(field))
.switchIfEmpty(
Mono.just(ManyToOne.class)
.mapNotNull(field::getAnnotation)
.filter(ManyToOne::persist)
.flatMap(manyToOneProcessor.persist(field))
)
.switchIfEmpty(acc);
})
.map(Commons::cast)
)
.flatMap(Function.identity())
.defaultIfEmpty(entity);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy