splitted database from account

This commit is contained in:
Joern Muehlencord
2019-06-05 14:32:12 +02:00
parent d50f21f869
commit 212e4dad5d
20 changed files with 361 additions and 290 deletions

View File

@ -0,0 +1,299 @@
/*
* Copyright 2016 Joern Muehlencord <joern at muehlencord.de>.
*
* 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
*
* http://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 de.muehlencord.shared.db;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
* @param <T>
*/
public abstract class AbstractController<T> {
@Inject
@ApplicationPU
protected EntityManager em;
private final Class<T> entityClass;
public AbstractController(Class<T> clazz) {
this.entityClass = clazz;
}
protected Predicate getFilterCondition(CriteriaBuilder cb, Root<T> root, Map<String, Object> filters) {
return getFilterCondition(cb, root, filters, null);
}
protected Predicate getFilterCondition(CriteriaBuilder cb, Root<T> root, Map<String, Object> filters, Map<String, Object> excludeFilters) {
Predicate filterCondition = null;
filterCondition = getFilterCondition(filterCondition, cb, root, filters, true);
filterCondition = getFilterCondition(filterCondition, cb, root, excludeFilters, false);
return filterCondition;
}
protected Predicate addFilterCondition(CriteriaBuilder cb, Predicate filterCondition, Predicate addCondition) {
if (addCondition == null) {
return filterCondition;
}
if (filterCondition == null) {
filterCondition = addCondition;
} else {
filterCondition = cb.and(filterCondition, addCondition);
}
return filterCondition;
}
/**
* extends the given filterCondition by the addtional filters
*
* @param filterCondition the current filter condition
* @param cb the criteria builder to use
* @param root the root of the object to search for
* @param filters the filters to apply
* @param include if set to true, the filter is used as include filter
* (equals, in). If set to false, the filter is inverted and used as exclude
* filter (not equals, not in etc)
* @return
*/
protected Predicate getFilterCondition(Predicate filterCondition, CriteriaBuilder cb, Root<T> root, Map<String, Object> filters, boolean include) {
String wildCard = "%";
if (filters != null) {
for (Map.Entry<String, Object> filter : filters.entrySet()) {
if (!"".equals(filter.getValue())) {
Path<String> path = getPathElement(root, filter);
// check for differnt types
// 1st String, either from Enum Status or from other free text string
if (String.class.equals(filter.getValue().getClass())) {
switch (filter.getKey()) {
default:
String filterValue = filter.getValue().toString();
Predicate predicate;
if (filterValue.equals("{NULL}")) {
if (include) {
predicate = cb.isNull(path);
} else {
predicate = cb.isNotNull(path);
}
} else {
filterValue = filterValue.trim();
filterValue = filterValue.replace("?", "_");
filterValue = filterValue.replace("*", "%");
String[] values = filterValue.split("\\s+"); // split by whitespaces
Predicate[] partSearchPredicates = new Predicate[values.length];
for (int i = 0; i < values.length; i++) {
String value = wildCard + values[i] + wildCard;
if (include) {
partSearchPredicates[i] = cb.like(cb.upper(path), value.toUpperCase(Locale.US), '\\');
} else {
partSearchPredicates[i] = cb.notLike(cb.upper(path), value.toUpperCase(Locale.US), '\\');
}
}
predicate = cb.and(partSearchPredicates); // all parts must be available
}
filterCondition = addFilterCondition(cb, filterCondition, predicate);
}
} // 2nd for arrays, received from e.g. project selections
else if (filter.getValue().getClass().isArray()) {
Predicate condition = null;
Object[] values = (Object[]) filter.getValue();
if (values.length > 0) {
for (Object value : values) {
if (include) {
Predicate equalPredicate = cb.equal(path, value);
if (condition == null) {
condition = equalPredicate;
} else {
condition = cb.or(condition, equalPredicate);
}
} else {
Predicate equalPredicate = cb.notEqual(path, value);
if (condition == null) {
condition = equalPredicate;
} else {
condition = cb.and(condition, equalPredicate);
}
}
}
filterCondition = addFilterCondition(cb, filterCondition, condition);
}
} else { // at last object comparison
if (include) {
filterCondition = addFilterCondition(cb, filterCondition, cb.equal(path, filter.getValue()));
} else {
filterCondition = addFilterCondition(cb, filterCondition, cb.notEqual(path, filter.getValue()));
}
}
}
}
}
return filterCondition;
}
private Path<String> getPathElement(Root<T> root, Map.Entry<String, Object> filter) {
String[] pathElements = StringUtils.split(filter.getKey(), '.');
Path<String> path = null;
for (String element : pathElements) {
if (path == null) {
path = root.get(element);
} else {
path = path.get(element);
}
}
return path;
}
public void applyUpdateableChanges(Updateable updateable, boolean onCreate, String updatedBy) throws ControllerException {
if (onCreate) {
updateable.setCreatedBy(updatedBy);
updateable.setCreatedOn(new Date());
}
updateable.setLastUpdatedBy(updatedBy);
updateable.setLastUpdatedOn(new Date());
}
public T attach(T entity) {
return em.merge(entity);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
public T create(T entity, String createdBy) throws ControllerException {
if (Updateable.class.isAssignableFrom(entity.getClass())) {
Updateable updateable = (Updateable) entity;
applyUpdateableChanges(updateable, true, createdBy);
}
em.persist(entity);
return entity;
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
public T update(T entity, String updatedBy) throws ControllerException {
if (Updateable.class.isAssignableFrom(entity.getClass())) {
Updateable updateable = (Updateable) entity;
applyUpdateableChanges(updateable, false, updatedBy);
}
return em.merge(entity);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
public void delete(T entity, String deletedBy) throws ControllerException {
em.remove(attach(entity));
}
@Lock(LockType.READ)
public T find(Object id) {
return em.find(entityClass, id);
}
@Lock(LockType.READ)
public List<T> findAll() {
return findAll(new ArrayList<>());
}
@Lock(LockType.READ)
public List<T> findAll(String... orderFields) {
return findAll(Arrays.asList(orderFields));
}
@Lock(LockType.READ)
public List<T> findAll(List<String> orderFields) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> criteria = cb.createQuery(entityClass);
final Root<T> r = criteria.from(entityClass);
List<Order> orderList = new ArrayList<>();
orderFields.stream().forEachOrdered(field -> orderList.add(cb.asc(r.get(field))));
final TypedQuery<T> query = em.createQuery(criteria.orderBy(orderList));
return query.getResultList();
}
@Lock(LockType.READ)
public List<T> find(Map<String, Object> filters, List<String> orderFields) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> criteria = cb.createQuery(entityClass);
final Root<T> r = criteria.from(entityClass);
Predicate filterCondition = getFilterCondition(cb, r, filters);
if (filterCondition != null) {
criteria.where(filterCondition);
}
List<Order> orderList = new ArrayList<>();
orderFields.stream().forEachOrdered(field -> orderList.add(cb.asc(r.get(field))));
final TypedQuery<T> query = em.createQuery(criteria.orderBy(orderList));
return query.getResultList();
}
/**
* returns null, if the list is empty or null itself. Returns the one
* element if there is exactly one element in the list. Otherwise an
* exception is thrown
*
* @param resultList
* @return
* @throws ControllerException
*/
public T ensureSingleElement(List<T> resultList) throws ControllerException {
if ((resultList == null) || (resultList.isEmpty())) {
return null;
}
if (resultList.size() > 1) {
throw new ControllerException(ControllerException.CAUSE_TOO_MANY_ROWS, "More than one element found in list - expected exactly one");
}
return resultList.get(0);
}
private <T> SingularAttribute<? super T, ?> getIdAttribute() {
Metamodel m = em.getEntityManagerFactory().getMetamodel();
IdentifiableType<T> of = (IdentifiableType<T>) m.managedType(entityClass);
// of.getDeclaredId(entityClass).getJavaMember().
return of.getId(of.getIdType().getJavaType());
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2019 Joern Muehlencord <joern at muehlencord.de>.
*
* 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
*
* http://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 de.muehlencord.shared.db;
import de.muehlencord.shared.util.DateUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
* @param <T> an entity which needs to extend EndDateable
*/
public abstract class AbstractEnddateableController<T extends EndDateable<T>> extends AbstractController<T> {
private final Class<T> endDateableClass;
public AbstractEnddateableController(Class<T> clazz) {
super(clazz);
this.endDateableClass = clazz;
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
@Override
public void delete(T entity, String deletedBy) throws ControllerException {
T entityToUpdate = attach(entity);
if (Updateable.class.isAssignableFrom(entityToUpdate.getClass())) {
Updateable updateable = (Updateable) entityToUpdate;
applyUpdateableChanges(updateable, false, deletedBy);
}
entityToUpdate.setValidTo(DateUtil.getCurrentTimeInUTC());
em.merge(entityToUpdate);
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
public T create(T entity, String createdBy) throws ControllerException {
entity.setValidFrom(DateUtil.getCurrentTimeInUTC());
return super.create(entity, createdBy);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Transactional
@Lock(LockType.WRITE)
@Override
public T update(T entity, String createdBy) throws ControllerException {
T newEntity = entity.cloneEndDateable();
delete(entity, createdBy);
return create(newEntity, createdBy);
}
@Lock(LockType.READ)
@Override
public List<T> findAll(List<String> orderFields) {
Date now = DateUtil.getCurrentTimeInUTC();
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> criteria = cb.createQuery(endDateableClass);
final Root<T> root = criteria.from(endDateableClass);
Predicate alreadyValid = cb.lessThanOrEqualTo(root.get("validFrom"), now);
Predicate validToNotSet = cb.isNull(root.get("validTo"));
Predicate isBeforeValidTo = cb.greaterThanOrEqualTo(root.get("validTo"), now);
Predicate stillValid = cb.or(isBeforeValidTo, validToNotSet);
Predicate isValid = cb.and(alreadyValid, stillValid);
criteria.where(isValid);
List<Order> orderList = new ArrayList<>();
orderFields.stream().forEachOrdered(field -> orderList.add(cb.asc(root.get(field))));
final TypedQuery<T> query = em.createQuery(criteria.orderBy(orderList));
return query.getResultList();
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2018 joern.muehlencord.
*
* 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
*
* http://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 de.muehlencord.shared.db;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ApplicationPU {
}

View File

@ -0,0 +1,46 @@
package de.muehlencord.shared.db;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import static javax.transaction.Transactional.TxType.REQUIRED;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Transactional(value = REQUIRED)
@Interceptor
@Priority(value = ApplicationTransactionJoinInterceptor.PRIORITY)
public class ApplicationTransactionJoinInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationTransactionJoinInterceptor.class);
// attach behind the interceptor of the container
public static final int PRIORITY = Interceptor.Priority.PLATFORM_BEFORE + 250;
@Inject
@ApplicationPU
private EntityManager em;
@AroundInvoke
public Object joinTransaction(InvocationContext context) throws Exception {
if (em == null) {
return context.proceed();
} else {
if (em.isJoinedToTransaction()) {
LOGGER.trace("transaction already joined");
} else {
LOGGER.trace("joining transaction");
em.joinTransaction();
}
}
return context.proceed();
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019 joern.muehlencord.
*
* 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
*
* http://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 de.muehlencord.shared.db;
import javax.ejb.ApplicationException;
/**
*
* @author joern.muehlencord
*/
@ApplicationException(rollback=true)
public class ControllerException extends Exception {
private static final long serialVersionUID = 5190280225284514859L;
public static final int CAUSE_ALREADY_EXISTS = 1;
public static final int CAUSE_NOT_FOUND = 2;
public static final int CAUSE_CANNOT_PERSIST = 3;
public static final int CAUSE_TOO_MANY_ROWS = 4;
public static final int CAUSE_CANNOT_DELETE = 5;
private final int causeCode;
/**
* Creates a new instance of <code>ControllerException</code> without detail
* message.
*
* @param cause the reason code
* @param message an explanation
*/
public ControllerException(int cause, String message) {
super(message);
this.causeCode = cause;
}
/**
*
* @param causeCode
* @param message
* @param cause
*/
public ControllerException(int causeCode, String message, Throwable cause) {
super(message, cause);
this.causeCode = causeCode;
}
public int getCauseCode() {
return causeCode;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2019 Joern Muehlencord <joern at muehlencord.de>.
*
* 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
*
* http://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 de.muehlencord.shared.db;
import java.util.Date;
/**
* Enddateable entities are not deleted but an enddate is set to "now"
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public interface EndDateable<T> {
T cloneEndDateable();
Date getValidFrom();
Date getValidTo();
void setValidFrom(Date validFrom);
void setValidTo(Date validTo);
}

View File

@ -0,0 +1,29 @@
package de.muehlencord.shared.db;
import java.util.Date;
/**
* This interface is used for Entities which provide createdOn / createdBy
* lastUpatedBy / lastUpdatedOn fields. The AbstractController uses this interface
* to automatically update the fields on creation / update.
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public interface Updateable {
void setCreatedBy(String createdBy);
String getCreatedBy();
void setCreatedOn(Date createdOn);
Date getCreatedOn();
void setLastUpdatedBy(String lastUpdatedBy);
String getLastUpdatedBy();
void setLastUpdatedOn(Date lastUpdatedOn);
Date getLastUpdatedOn();
}