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

com.github.alex1304.ultimategdbot.api.Database Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
package com.github.alex1304.ultimategdbot.api;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.reactivestreams.Publisher;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

/**
 * Manages interactions with the database.
 */
public class Database {
	
	private SessionFactory sessionFactory = null;
	private final Set resourceNames = new HashSet<>();
	private final Scheduler databaseScheduler = Schedulers.newElastic("database-elastic");

	/**
	 * Initializes the database.
	 */
	public void configure() {
		var config = new Configuration();
		for (var resource : resourceNames) {
			config.addResource(resource);
		}
		if (sessionFactory != null) {
			sessionFactory.close();
		}
		sessionFactory = config.buildSessionFactory();
	}

	/**
	 * Allows to find a database entity by its ID.
	 * 
	 * @param entityClass class of the entity
	 * @param key         the ID
	 * @param              The entity type
	 * @param              the ID type
	 * @return a Mono emitting the entity, if found
	 */
	public  Mono findByID(Class entityClass, K key) {
		Objects.requireNonNull(entityClass);
		Objects.requireNonNull(key);
		return Mono.fromCallable(() -> {
			try (var s = newSession()) {
				return s.get(entityClass, key);
			}
		}).subscribeOn(databaseScheduler)
				.onErrorMap(DatabaseException::new);
	}

	/**
	 * Makes a simple query to the database.
	 * 
	 * @param entityClass the entity type to fetch
	 * @param query       the HQL query
	 * @param params      the query params
	 * @param              the entity type
	 * @return a Flux emitting the results of the query
	 */
	public  Flux query(Class entityClass, String query, Object... params) {
		Objects.requireNonNull(entityClass);
		Objects.requireNonNull(query);
		Objects.requireNonNull(params);
		return Mono.fromCallable(() -> {
			var list = new ArrayList();
			try (var s = newSession()) {
				var q = s.createQuery(query, entityClass);
				for (int i = 0; i < params.length; i++) {
					q.setParameter(i, params[i]);
				}
				list.addAll(q.getResultList());
			}
			return list;
		}).subscribeOn(databaseScheduler)
				.flatMapMany(Flux::fromIterable)
				.onErrorMap(DatabaseException::new);
	}

	/**
	 * Saves an object in database
	 * 
	 * @param obj the object to save
	 * @return a Mono that completes when it has saved
	 */
	public Mono save(Object obj) {
		return performEmptyTransaction(session -> session.saveOrUpdate(obj));
	}

	/**
	 * Deletes an object from database
	 * 
	 * @param obj the object to save
	 * @return a Mono that completes when it has deleted
	 */
	public Mono delete(Object obj) {
		return performEmptyTransaction(session -> session.delete(obj));
	}

	/**
	 * Allows to perform more complex actions with the database, by having full
	 * control on the current transaction. This method does not return a value. If
	 * you need to perform a transaction that returns a value upon completion, the
	 * variant {@link #performTransaction(Function)} is preferred.
	 * 
	 * @param txConsumer the transaction consuming a Session object
	 * @return a Mono completing when the transaction terminates successfully
	 */
	public Mono performEmptyTransaction(Consumer txConsumer) {
		return Mono.fromCallable(() -> {
			Transaction tx = null;
			try (var s = newSession()) {
				tx = s.beginTransaction();
				txConsumer.accept(s);
				tx.commit();
			} catch (RuntimeException e) {
				if (tx != null && tx.isActive()) {
					try {
						tx.rollback();
					} catch (RuntimeException e0) {
						e.addSuppressed(e0);
						throw e;
					}
				}
				throw e;
			}
			return null;
		}).subscribeOn(databaseScheduler)
				.onErrorMap(DatabaseException::new);
	}
	
	/**
	 * Allows to perform more complex actions with the database, by having full
	 * control on the current transaction. This method can return a value upon
	 * completion. If you don't need a return value for the transaction, the variant
	 * {@link #performEmptyTransaction(Consumer)} is preferred.
	 * 
	 * @param             the type of the returned value
	 * @param txFunction the transaction accepting a Session object and returning a
	 *                   value
	 * @return a Mono completing when the transaction terminates successfully and
	 *         emitting a value.
	 */
	public  Mono performTransaction(Function txFunction) {
		return Mono.fromCallable(() -> {
			V returnVal;
			Transaction tx = null;
			try (var s = newSession()) {
				tx = s.beginTransaction();
				returnVal = txFunction.apply(s);
				tx.commit();
			} catch (RuntimeException e) {
				e.printStackTrace();
				if (tx != null && tx.isActive()) {
					try {
						tx.rollback();
					} catch (RuntimeException e0) {
						e.addSuppressed(e0);
						throw e;
					}
				}
				throw e;
			}
			return returnVal;
		}).subscribeOn(databaseScheduler)
				.onErrorMap(DatabaseException::new);
	}

	/**
	 * Allows to manipulate a Session in an asynchronous context. The session
	 * provides a Publisher which completion indicates that the transaction can be
	 * committed and the session closed.
	 * 
	 * @param                  the type of value that the transaction may produce
	 * @param txAsyncFunction a function that manipulates a Session and returns a
	 *                        Publisher completing when the transaction is ready to
	 *                        be committed
	 * @return a Flux completing when the transaction terminates successfully and
	 *         may emit values that constitute the result of the transaction
	 */
	public  Flux performTransactionWhen(Function> txAsyncFunction) {
		return Flux.usingWhen(
						Mono.fromCallable(this::newSession).doOnNext(Session::beginTransaction),
						txAsyncFunction,
						this::commitAndClose,
						this::rollbackAndClose,
						this::rollbackAndClose)
				.subscribeOn(databaseScheduler);
	}

	void addAllMappingResources(Set resourceNames) {
		this.resourceNames.addAll(Objects.requireNonNull(resourceNames));
	}
	
	private Mono commitAndClose(Session session) {
		return Mono.fromRunnable(() -> {
			var tx = session.getTransaction();
			if (tx != null && tx.isActive()) {
				try {
					tx.commit();
				} finally {
					session.close();
				}
			}
		}).onErrorMap(DatabaseException::new);
	}
	
	private Mono rollbackAndClose(Session session) {
		return Mono.fromRunnable(() -> {
			var tx = session.getTransaction();
			if (tx != null && tx.isActive()) {
				try {
					tx.rollback();
				} finally {
					session.close();
				}
			}
		}).onErrorMap(DatabaseException::new);
	}

	private Session newSession() {
		if (sessionFactory == null || sessionFactory.isClosed())
			throw new IllegalStateException("Database not configured");

		return sessionFactory.openSession();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy