Skip to content
Snippets Groups Projects

Update to support multi-tenant DB connections

Files
3
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.persistence.dao.impl;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
import org.eclipsefoundation.core.exception.MaintenanceException;
import org.eclipsefoundation.core.response.PaginatedResultsFilter;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement.Clause;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of the DB DAO, persisting via the Hibernate framework.
*
* @author Martin Lowe
*/
public abstract class BaseHibernateDao implements PersistenceDao {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseHibernateDao.class);
@Inject
EntityManager inner;
@ConfigProperty(name = "eclipse.db.default.limit")
int defaultLimit;
@ConfigProperty(name = "eclipse.db.default.limit.max")
int defaultMax;
@ConfigProperty(name = "eclipse.db.maintenance", defaultValue = "false")
boolean maintenanceFlag;
@Override
public <T extends BareNode> List<T> get(RDBMSQuery<T> q) {
if (maintenanceFlag) {
throw new MaintenanceException();
}
// handle root request parameters (setting headers to track limit and max count)
if (q.isRoot()) {
handleRoot(q);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Querying DB using the following query: {}", q.getFilter().getSelectSql());
}
// build base query
TypedQuery<T> query = getEntityManager().createQuery(q.getFilter().getSelectSql(),
q.getDocType());
// add ordinal parameters
int ord = 1;
for (Clause c : q.getFilter().getClauses()) {
for (Object param : c.getParams()) {
query.setParameter(ord++, param);
}
}
// check if result set should be limited
if (q.getDTOFilter().useLimit()) {
query = query.setFirstResult(getOffset(q)).setMaxResults(getLimit(q));
}
// run the query
return query.getResultList();
}
@Transactional
@Override
public <T extends BareNode> List<T> add(RDBMSQuery<T> q, List<T> documents) {
if (maintenanceFlag) {
throw new MaintenanceException();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Adding {} documents to DB of type {}", documents.size(), q.getDocType().getSimpleName());
}
EntityManager em = getEntityManager();
// for each doc, check if update or create
List<T> updatedDocs = new ArrayList<>(documents.size());
for (T doc : documents) {
T ref = doc;
if (doc.getId() != null) {
// ensure this object exists before merging on it
if (em.find(q.getDocType(), doc.getId()) != null) {
LOGGER.debug("Merging document with existing document with id '{}'", doc.getId());
ref = em.merge(doc);
} else {
LOGGER.debug("Persisting new document with id '{}'", doc.getId());
em.persist(doc);
}
} else {
LOGGER.debug("Persisting new document with generated UUID ID");
em.persist(doc);
}
// add the ref to the output list
updatedDocs.add(ref);
}
return updatedDocs;
}
@Transactional
@Override
public <T extends BareNode> void delete(RDBMSQuery<T> q) {
if (maintenanceFlag) {
throw new MaintenanceException();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Removing documents from DB using the following query: {}", q);
}
// retrieve results for the given deletion query to delete using entity manager
EntityManager em = getEntityManager();
List<T> results = get(q);
if (results != null) {
// remove all matched documents
results.forEach(em::remove);
}
}
@Transactional
@Override
public Long count(RDBMSQuery<?> q) {
if (maintenanceFlag) {
throw new MaintenanceException();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Counting documents in DB that match the following query: {}", q.getFilter().getCountSql());
}
EntityManager em = getEntityManager();
// build base query
TypedQuery<Long> query = em.createQuery(q.getFilter().getCountSql(), Long.class);
// add ordinal parameters
int ord = 1;
for (Clause c : q.getFilter().getClauses()) {
for (Object param : c.getParams()) {
query.setParameter(ord++, param);
}
}
return query.getSingleResult();
}
@Override
public <T extends BareNode> T getReference(Object id, Class<T> type) {
if (maintenanceFlag) {
throw new MaintenanceException();
}
return getEntityManager().getReference(type, id);
}
/**
* Handles operations that should happen on a "root" or main DB request for a
* fulfilled request. This is done to ensure that values such as DB max are
* abided.
*/
private void handleRoot(RDBMSQuery<?> q) {
// check if count has been performed, if not do so and set it to the response
q.getWrapper().setHeader(PaginatedResultsFilter.MAX_RESULTS_SIZE_HEADER, Long.toString(count(q)));
q.getWrapper().setHeader(PaginatedResultsFilter.MAX_PAGE_SIZE_HEADER, Integer.toString(getLimit(q)));
}
private int getLimit(RDBMSQuery<?> q) {
return q.getLimit() > 0 ? Math.min(q.getLimit(), defaultMax) : defaultLimit;
}
private int getOffset(RDBMSQuery<?> q) {
// allow for manual offsetting
int manualOffset = q.getManualOffset();
if (manualOffset > 0) {
return manualOffset;
}
// if first page, no offset
if (q.getPage() <= 1) {
return 0;
}
int limit = getLimit(q);
return (limit * q.getPage()) - limit;
}
@Override
public HealthCheckResponse call() {
HealthCheckResponseBuilder b = HealthCheckResponse.named("DB readiness");
if (maintenanceFlag) {
return b.down().withData("error", "Maintenance flag is set").build();
}
return b.up().build();
}
/**
* Allows for retrieval of multi-tenant persistence sessions. This is needed as sessions are generated based on
* annotated PersistenceUnit values.
*
* @param persistenceUnit the persistence unit targeted for the transaction
* @return entity manager object linked to current transaction
*/
protected EntityManager getEntityManager() {
return inner;
}
}
Loading