added EntityUtil, ensured cloned entities are updated to represent a new entity
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
116
db/src/main/java/de/muehlencord/shared/db/EntityUtil.java
Normal file
116
db/src/main/java/de/muehlencord/shared/db/EntityUtil.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
32
db/src/main/resources/log4j.xml
Normal file
32
db/src/main/resources/log4j.xml
Normal 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>
|
||||
Reference in New Issue
Block a user