io.atlasmap.core.DefaultAtlasFieldActionService Maven / Gradle / Ivy
package io.atlasmap.core;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.atlasmap.api.AtlasConversionException;
import io.atlasmap.api.AtlasException;
import io.atlasmap.spi.AtlasFieldAction;
import io.atlasmap.spi.AtlasActionProcessor;
import io.atlasmap.spi.AtlasConversionService;
import io.atlasmap.spi.AtlasFieldActionInfo;
import io.atlasmap.spi.AtlasFieldActionService;
import io.atlasmap.spi.AtlasInternalSession;
import io.atlasmap.v2.Action;
import io.atlasmap.v2.ActionDetail;
import io.atlasmap.v2.ActionParameter;
import io.atlasmap.v2.ActionParameters;
import io.atlasmap.v2.ActionResolver;
import io.atlasmap.v2.AtlasModelFactory;
import io.atlasmap.v2.AuditStatus;
import io.atlasmap.v2.CollectionType;
import io.atlasmap.v2.CustomAction;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.FieldGroup;
import io.atlasmap.v2.FieldType;
import io.atlasmap.v2.Multiplicity;
import io.atlasmap.v2.SimpleField;
public class DefaultAtlasFieldActionService implements AtlasFieldActionService {
private static final Logger LOG = LoggerFactory.getLogger(DefaultAtlasFieldActionService.class);
private List actionProcessors = new ArrayList<>();
private ReadWriteLock actionProcessorsLock = new ReentrantReadWriteLock();
private AtlasConversionService conversionService = null;
private ActionResolver actionResolver = null;
public DefaultAtlasFieldActionService(AtlasConversionService conversionService) {
this.conversionService = conversionService;
}
public void init() {
// TODO load custom field actions in application bundles
// on hierarchical class loader environment
init(this.getClass().getClassLoader());
}
private JavaType javaType(Type type) {
return TypeFactory.defaultInstance().constructType(type);
}
public void init(ClassLoader classLoader) {
Lock writeLock = actionProcessorsLock.writeLock();
try {
writeLock.lock();
actionProcessors.clear();
this.actionResolver = ActionResolver.getInstance(classLoader);
actionProcessors.addAll(loadFieldActions(classLoader));
} finally {
writeLock.unlock();
}
}
public List loadFieldActions() {
return loadFieldActions(this.getClass().getClassLoader());
}
interface ActionProcessor {
ActionDetail getActionDetail();
Class extends Action> getActionClass();
Object process(Action action, Object sourceObject) throws AtlasException;
}
public List loadFieldActions(ClassLoader classLoader) {
final ServiceLoader fieldActionServiceLoader = ServiceLoader.load(AtlasFieldAction.class,
classLoader);
final ServiceLoader compat = ServiceLoader.load(io.atlasmap.api.AtlasFieldAction.class,
classLoader);
List answer = new ArrayList<>();
fieldActionServiceLoader.forEach(atlasFieldAction -> createActionProcessor(atlasFieldAction, answer));
compat.forEach(atlasFieldAction -> createActionProcessor(atlasFieldAction, answer));
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Loaded %s Field Actions", answer.size()));
}
return answer;
}
private void createActionProcessor(AtlasFieldAction atlasFieldAction, List answer) {
if (LOG.isDebugEnabled()) {
LOG.debug("Loading FieldAction class: " + atlasFieldAction.getClass().getCanonicalName());
}
Class> clazz = atlasFieldAction.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// Continue supporting creating details from @AtlasFieldActionInfo
ActionProcessor det = createDetailFromFieldActionInfo(clazz, method);
if (det != null) {
answer.add(det);
}
// Also support using new simpler @AtlasActionProcessor
det = createDetailFromProcessor(clazz, method);
if (det != null) {
answer.add(det);
}
}
}
private ActionProcessor createDetailFromFieldActionInfo(final Class> clazz, final Method method) {
AtlasFieldActionInfo annotation = method.getAnnotation(AtlasFieldActionInfo.class);
if (annotation == null) {
return null;
}
final ActionDetail det = new ActionDetail();
det.setClassName(clazz.getName());
det.setMethod(method.getName());
det.setName(annotation.name());
det.setSourceType(annotation.sourceType());
det.setTargetType(annotation.targetType());
CollectionType sourceCollection = annotation.sourceCollectionType();
CollectionType targetCollection = annotation.sourceCollectionType();
if (sourceCollection != null && sourceCollection != CollectionType.NONE) {
det.setMultiplicity(Multiplicity.MANY_TO_ONE);
} else if (targetCollection != null && targetCollection != CollectionType.NONE) {
det.setMultiplicity(Multiplicity.ONE_TO_MANY);
} else {
det.setMultiplicity(Multiplicity.ONE_TO_ONE);
}
Class extends Action> actionClazz;
try {
actionClazz = (Class extends Action>) Class.forName("io.atlasmap.v2." + annotation.name());
} catch (Exception e) {
actionClazz = null;
det.setCustom(true);
}
try {
det.setActionSchema(actionClazz);
} catch (Exception e) {
LOG.error(String.format("Could not get json schema for action=%s msg=%s", annotation.name(), e.getMessage()), e);
}
try {
// TODO https://github.com/atlasmap/atlasmap/issues/538
if (det.isCustom() == null || !det.isCustom()) {
det.setParameters(detectFieldActionParameters(actionClazz));
}
} catch (ClassNotFoundException e) {
LOG.error(String.format("Error detecting parameters for field action=%s msg=%s", annotation.name(), e.getMessage()), e);
}
if (LOG.isTraceEnabled()) {
LOG.trace("Loaded FieldAction: " + det.getName());
}
Class extends Action> finalActionClazz = actionClazz;
return new ActionProcessor() {
@Override
public ActionDetail getActionDetail() {
return det;
}
@Override
public Class extends Action> getActionClass() {
return finalActionClazz;
}
@Override
public Object process(Action action, Object sourceObject) throws AtlasException {
if (det == null) {
return sourceObject;
}
Object targetObject = null;
try {
Object convertedSourceObject = convertSourceObject(sourceObject);
if (Modifier.isStatic(method.getModifiers())) {
// TODO eliminate Action parameter even for OOTB
// we can use annotation also for the parameters instead
// cf. https://github.com/atlasmap/atlasmap/issues/536
if (det.isCustom() != null && det.isCustom()) {
targetObject = det.getMultiplicity() == Multiplicity.ZERO_TO_ONE
? method.invoke(null) : method.invoke(null, convertedSourceObject);
} else {
targetObject = det.getMultiplicity() == Multiplicity.ZERO_TO_ONE
? method.invoke(null, action) : method.invoke(null, action, convertedSourceObject);
}
} else {
Object object = clazz.newInstance();
if (det.isCustom() != null && det.isCustom()) {
targetObject = det.getMultiplicity() == Multiplicity.ZERO_TO_ONE
? method.invoke(object) : method.invoke(object, convertedSourceObject);
} else {
targetObject = det.getMultiplicity() == Multiplicity.ZERO_TO_ONE
? method.invoke(object, action) : method.invoke(object, action, convertedSourceObject);
}
}
} catch (Throwable e) {
throw new AtlasException(String.format("Error processing action %s", det.getName()), e);
}
return targetObject;
}
private Object convertSourceObject(Object sourceObject) throws AtlasConversionException {
Class> paramType;
int paramCount = method.getParameterCount();
if (paramCount < 2) {
return null;
}
paramType = method.getParameterTypes()[1];
if (paramType.isInstance(sourceObject)) {
return sourceObject;
}
return conversionService.convertType(sourceObject, null, paramType, null);
}
};
}
private ActionProcessor createDetailFromProcessor(Class> clazz, Method method) {
AtlasActionProcessor annotation = method.getAnnotation(AtlasActionProcessor.class);
if (annotation == null) {
return null;
}
if (method.getParameterCount() < 1) {
LOG.debug("Invalid @AtlasActionProcessor method. Expected at least 1 parameter: " + method);
}
Class extends Action> actionClazz = null;
if (Action.class.isAssignableFrom(method.getParameterTypes()[0])) {
actionClazz = (Class extends Action>) method.getParameterTypes()[0];
} else {
LOG.debug("Invalid @AtlasActionProcessor method. 1st parameter does not subclass " + Action.class.getName() + ": " + method);
}
final Class> targetClass = method.getReturnType();
String name = actionResolver.toId(actionClazz);
ActionDetail det = new ActionDetail();
det.setClassName(clazz.getName());
det.setMethod(method.getName());
det.setName(name);
det.setTargetType(toFieldType(targetClass, method.getGenericReturnType()));
if (!clazz.getPackage().getName().equals("io.atlasmap.actions")) {
det.setCustom(true);
}
Type[] genericParameterTypes = method.getGenericParameterTypes();
if (genericParameterTypes.length >= 2) {
Class> sourceClass = method.getParameterTypes()[1];
if (annotation.sourceType() != FieldType.NONE) {
det.setSourceType(annotation.sourceType());
} else {
det.setSourceType(toFieldType(sourceClass, method.getGenericParameterTypes()[1]));
}
CollectionType sourceCollection = toFieldCollectionType(sourceClass);
CollectionType targetCollection = toFieldCollectionType(targetClass);
if (sourceCollection != CollectionType.NONE) {
det.setMultiplicity(Multiplicity.MANY_TO_ONE);
} else if (targetCollection != CollectionType.NONE) {
det.setMultiplicity(Multiplicity.ONE_TO_MANY);
} else {
det.setMultiplicity(Multiplicity.ONE_TO_ONE);
}
} else if (genericParameterTypes.length == 1) {
det.setMultiplicity(Multiplicity.ZERO_TO_ONE);
}
try {
det.setActionSchema(actionClazz);
} catch (Exception e) {
LOG.error(String.format("Could not get json schema for action=%s msg=%s", clazz.getName(), e.getMessage()), e);
}
try {
det.setParameters(detectFieldActionParameters(actionClazz));
} catch (ClassNotFoundException e) {
LOG.error(String.format("Error detecting parameters for field action=%s msg=%s", det.getName(), e.getMessage()), e);
}
Object o = null;
try {
o = Modifier.isStatic(method.getModifiers()) ? clazz.newInstance() : null;
} catch (Throwable e) {
LOG.error(String.format("Error creating object instance for action=%s msg=%s", det.getName(), e.getMessage()), e);
}
final Object object = o;
Class extends Action> finalActionClazz = actionClazz;
return new ActionProcessor() {
@Override
public ActionDetail getActionDetail() {
return det;
}
@Override
public Class extends Action> getActionClass() {
return finalActionClazz;
}
@Override
public Object process(Action action, Object sourceObject) throws AtlasException {
try {
if( det.getMultiplicity() == Multiplicity.ZERO_TO_ONE ) {
return method.invoke(object, action);
} else {
sourceObject = convertSourceObject(sourceObject);
return method.invoke(object, action, sourceObject);
}
} catch (Throwable e) {
throw new AtlasException(String.format("Error processing action %s", det.getName()), e);
}
}
private Object convertSourceObject(Object sourceObject) throws AtlasConversionException {
if (sourceObject == null) {
return null;
}
Class> paramType;
paramType = method.getParameterTypes()[1];
CollectionType paramCollectionType = toFieldCollectionType(paramType);
CollectionType sourceCollectionType = toFieldCollectionType(sourceObject.getClass()) ;
if (paramCollectionType != CollectionType.NONE) {
List © 2015 - 2025 Weber Informatics LLC | Privacy Policy