package org.ovirt.engine.core.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.ovirt.engine.core.common.utils.IObjectDescriptorContainer;
import org.ovirt.engine.core.compat.backendcompat.PropertyInfo;
import org.ovirt.engine.core.compat.backendcompat.TypeCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ObjectIdentityChecker {
private static final Logger log = LoggerFactory.getLogger(ObjectIdentityChecker.class);
private IObjectDescriptorContainer container;
private static Map<String, Class<?>> aliases = new HashMap<>();
private static Map<Class<?>, ObjectIdentityChecker> identities = new HashMap<>();
private Map<Enum<?>, Set<String>> dictionary = new HashMap<>();
private Set<String> permitted = new HashSet<>();
private Set<String> hotsetAllowedFields = new HashSet<>();
private Set<String> transientFields = new HashSet<>();
private Set<String> permittedForHostedEngine = new HashSet<>();
public ObjectIdentityChecker(Class<?> type) {
identities.put(type, this);
}
public ObjectIdentityChecker(Class<?> type, Iterable<Class<?>> aliases) {
this(type);
for (Class<?> alias : aliases) {
ObjectIdentityChecker.aliases.put(alias.getSimpleName(), type);
}
}
public final void setContainer(IObjectDescriptorContainer value) {
container = value;
}
public static boolean canUpdateField(Object fieldContainer, String fieldName, Enum<?> status) {
return canUpdateField(fieldContainer.getClass().getSimpleName(), fieldName, status, fieldContainer);
}
public static boolean canUpdateField(String objectType, String fieldName, Enum<?> status, Object fieldContainer) {
Class<?> type = aliases.get(objectType);
if (type != null) {
return canUpdateField(type, fieldName, status, fieldContainer);
} else {
throw new RuntimeException("status type [null] not exist");
}
}
public static boolean canUpdateField(Class<?> objectType, String fieldName, Enum<?> status,
Object fieldContainer) {
ObjectIdentityChecker checker = identities.get(objectType);
if (checker != null) {
return checker.isFieldUpdatable(status, fieldName, fieldContainer);
}
return true;
}
public final <T extends Enum<T>> void addField(T status, String fieldName) {
Set<String> values = dictionary.get(status);
if (values == null) {
values = new HashSet<>();
dictionary.put(status, values);
}
values.add(fieldName);
}
public final <T extends Enum<T>> void addField(Iterable<T> statuses, String fieldName) {
for (T status : statuses) {
addField(status, fieldName);
}
}
public final void addHostedEngineFields(String... fieldNames) {
for (String fieldName : fieldNames) {
permittedForHostedEngine.add(fieldName);
}
}
public final void addPermittedFields(String... fieldNames) {
for (String fieldName : fieldNames) {
permitted.add(fieldName);
}
}
public final void addHotsetFields(String... fieldNames) {
for (String fieldName : fieldNames) {
hotsetAllowedFields.add(fieldName);
}
}
public final void addTransientFields(String... fieldNames) {
for (String fieldName : fieldNames) {
transientFields.add(fieldName);
}
}
public final boolean isFieldUpdatable(String name) {
return permitted.contains(name);
}
public final boolean isHostedEngineFieldUpdatable(String name) {
return permittedForHostedEngine.contains(name);
}
public final boolean isHotSetField(String name) {
return hotsetAllowedFields.contains(name);
}
public final boolean isTransientField(String name) {
return transientFields.contains(name);
}
public boolean isFieldUpdatable(Enum<?> status, String name, Object fieldContainer) {
return isFieldUpdatable(status, name, fieldContainer, false);
}
public boolean isFieldUpdatable(Enum<?> status, String name, Object fieldContainer, boolean hotsetEnabled) {
boolean returnValue = true;
if (!isFieldUpdatable(name)) {
if (fieldContainer != null && container != null
&& !container.canUpdateField(fieldContainer, name, status)) {
returnValue = false;
} else {
Set<String> values = dictionary.get(status);
returnValue = values != null ? values.contains(name) : false;
// if field is not updateable in this status, check if hotset request and its an hotset allowed field
if (!returnValue && hotsetEnabled) {
returnValue = isHotSetField(name);
}
}
if (!returnValue) {
log.warn("Field '{}' can not be updated when status is '{}'", name, status);
}
}
return returnValue;
}
/**
* This method will copy all fields that are not @editable from source obj to dest obj
*
* @param source object that has values of non editable fields
* @param destination object to copy the non editable to it
*/
public boolean copyNonEditableFieldsToDestination(Object source, Object destination, boolean hotSetEnabled) {
Class<?> cls = source.getClass();
while (!cls.equals(Object.class)) {
for (Field srcFld : cls.getDeclaredFields()) {
try {
// copy fields that are non final, and not-editable and not a hotset field or it is but this is not hotset case
if (!Modifier.isFinal(srcFld.getModifiers()) &&
!isFieldUpdatable(srcFld.getName()) &&
(!isHotSetField(srcFld.getName()) || !hotSetEnabled)) {
srcFld.setAccessible(true);
Field dstFld = cls.getDeclaredField(srcFld.getName());
dstFld.setAccessible(true);
dstFld.set(destination, srcFld.get(source));
}
} catch (Exception exp) {
log.error("Failed to copy non editable field '{}', error: {}",
srcFld.getName(),
exp.getMessage());
log.debug("Exception", exp);
return false;
}
}
cls = cls.getSuperclass();
}
return true;
}
public final boolean isUpdateValid(Object source, Object destination) {
if (source.getClass() != destination.getClass()) {
return false;
}
for (String fieldName : getChangedFields(source, destination)) {
if (!isFieldUpdatable(fieldName)) {
return false;
}
}
return true;
}
public final boolean isHostedEngineUpdateValid(Object source, Object destination) {
if (source.getClass() != destination.getClass()) {
return false;
}
for (String fieldName : getChangedFields(source, destination)) {
if (!isHostedEngineFieldUpdatable(fieldName)) {
return false;
}
}
return true;
}
public final boolean isUpdateValid(Object source, Object destination, Enum<?> status) {
return isUpdateValid(source, destination, status, false);
}
public final boolean isUpdateValid(Object source, Object destination, Enum<?> status, boolean hotsetEnabled) {
if (source.getClass() != destination.getClass()) {
return false;
}
for (String fieldName : getChangedFields(source, destination)) {
if (!isFieldUpdatable(status, fieldName, null, hotsetEnabled)) {
log.warn("ObjectIdentityChecker.isUpdateValid:: Not updatable field '{}' was updated",
fieldName);
return false;
}
}
return true;
}
public final List<String> getChangedFieldsForStatus(Object source, Object destination, Enum<?> status) {
List<String> fields = new ArrayList<>();
if (source.getClass() != destination.getClass()) {
return fields;
}
for (String fieldName : getChangedFields(source, destination)) {
if (!isFieldUpdatable(status, fieldName, null, false) && !isTransientField(fieldName)) {
fields.add(fieldName);
}
}
return fields;
}
public final boolean isFieldsUpdated(Object source, Object destination, Iterable<String> fields) {
List<String> changedFields = getChangedFields(source, destination);
for (String field : fields) {
if (changedFields.contains(field)) {
return true;
}
}
return false;
}
public static List<String> getChangedFields(Object source, Object destination) {
final List<String> returnValue = new ArrayList<>();
if (source.getClass().isInstance(destination)) {
Class<?> objectType = source.getClass();
List<PropertyInfo> properties = TypeCompat.getProperties(objectType);
for (PropertyInfo property : properties) {
Object sourceValue = property.getValue(source, null);
Object destinationValue = property.getValue(destination, null);
if (property.getCanWrite()&& !Objects.equals(sourceValue, destinationValue)) {
returnValue.add(property.getName());
}
}
}
return returnValue;
}
}