added EntityUtil, ensured cloned entities are updated to represent a new entity

This commit is contained in:
Joern Muehlencord
2019-06-21 09:45:16 +02:00
parent a8e0f9bd5f
commit ab2a0e2301
9 changed files with 633 additions and 86 deletions

View File

@ -41,7 +41,6 @@ import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.transaction.Transactional;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
/**
@ -91,8 +90,9 @@ public abstract class AbstractController<T extends Serializable> {
* @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)
* @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) {
@ -231,7 +231,7 @@ public abstract class AbstractController<T extends Serializable> {
@Lock(LockType.WRITE)
public T update(T entity, String updatedBy) throws ControllerException {
T currentEntity = attach(entity);
T newEntity = getClone(currentEntity);
T newEntity = EntityUtil.cloneToNewEntity(currentEntity);
if (Auditable.class.isAssignableFrom(entity.getClass())) {
Audit audit = ((Auditable) entity).getAudit();
((Auditable) entity).setAudit(applyAuditChanges(audit, false, updatedBy));
@ -243,7 +243,7 @@ public abstract class AbstractController<T extends Serializable> {
endDateable.setValidTo(DateUtil.getCurrentTimeInUTC());
em.merge(entity);
// and create new entity instead
return create(newEntity, updatedBy);
} else {
// if it is not enddatable, just update it (already done above)
@ -325,8 +325,9 @@ public abstract class AbstractController<T extends Serializable> {
}
/**
* 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
* 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
@ -348,19 +349,4 @@ public abstract class AbstractController<T extends Serializable> {
// of.getDeclaredId(entityClass).getJavaMember().
return of.getId(of.getIdType().getJavaType());
}
private T getClone(T entity) {
T newEntity = SerializationUtils.clone(entity);
// remove audit if class is auditable - it is a new clone
if (Auditable.class.isAssignableFrom(newEntity.getClass())) {
((Auditable) newEntity).setAudit(null);
}
// set new valid dates if class is enddateable
if (EndDateable.class.isAssignableFrom(newEntity.getClass())) {
((EndDateable) newEntity).setValidFrom(DateUtil.getCurrentTimeInUTC());
((EndDateable) newEntity).setValidTo(null);
}
return newEntity;
}
}

View File

@ -1,62 +1,63 @@
/*
* 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;
}
}
/*
* 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 INTERNAL_ERROR = 0;
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,116 @@
/*
* 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 de.muehlencord.shared.util.DateUtil;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import javax.persistence.Id;
import org.apache.commons.lang3.SerializationUtils;
import org.slf4j.LoggerFactory;
/**
*
* @author joern.muehlencord
*/
public class EntityUtil {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(EntityUtil.class);
public static Field getIdField(Class<?> entity) {
if (entity == null) {
return null;
}
for (var f : entity.getDeclaredFields()) {
Id id = null;
Annotation[] as = f.getAnnotations();
for (Annotation a : as) {
if (a.annotationType() == Id.class) {
id = (Id) a;
}
}
if (id != null) {
return f;
}
}
// iterated over all fields, not found
return null;
}
public static <T> T setIdValue(T entity, Object fieldValue) throws ControllerException {
Field field = getIdField(entity.getClass());
if (field == null) {
return entity;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("id column of {} is {}", entity.getClass().getSimpleName(), field.getName());
}
try {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), entity.getClass());
pd.getWriteMethod().invoke(entity, fieldValue);
return entity;
} catch (IllegalAccessException | InvocationTargetException | IntrospectionException ex) {
String hint = "Error setting value of field " + field.getName() + " to " + fieldValue;
if (fieldValue != null) {
hint += " of type " + fieldValue.getClass().getSimpleName();
}
throw new ControllerException(ControllerException.INTERNAL_ERROR, hint, ex);
}
}
/**
* clones the given entity and updates related fields so the entity appears as new.The following changes are executed
* <ul>
* <li>the Id field of the entity is set to null</li>
* <li>if the entity is auditable, the audit is set to null.</li>
* <li>if the entity is enddatable, validTo is set to null and validFrom is set to current sysdate (in UTC)
* </ul>
*
* @param <T> the entity to be cloned - must implement serializeable to be cloned
* @param entity the entity to be cloned - must implement serializeable to be cloned
* @return the cloned entity with updated fields as described above
* @throws de.muehlencord.shared.db.ControllerException if the id value cannot be set to null
*/
public static <T extends Serializable> T cloneToNewEntity(T entity) throws ControllerException {
T newEntity = SerializationUtils.clone(entity);
// ensure id column is set to null
newEntity = setIdValue(newEntity, null);
// remove audit if class is auditable - it is a new clone
if (Auditable.class.isAssignableFrom(newEntity.getClass())) {
((Auditable) newEntity).setAudit(null);
}
// set new valid dates if class is enddateable
if (EndDateable.class.isAssignableFrom(newEntity.getClass())) {
((EndDateable) newEntity).setValidFrom(DateUtil.getCurrentTimeInUTC());
((EndDateable) newEntity).setValidTo(null);
}
return newEntity;
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %-5p [%c] %m%n" />
</layout>
</appender>
<category name="de.muehlencord">
<priority value="DEBUG"/>
</category>
<category name="com.sun">
<priority value="WARN"/>
</category>
<category name="javax.xml">
<priority value="WARN"/>
</category>
<logger name="org.hibernate">
<level value="info"/>
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="consoleAppender" />
</root>
</log4j:configuration>