/*
* Copyright 2014 Effektif GmbH.
*
* 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 com.effektif.workflow.impl.data;
import com.effektif.workflow.api.Configuration;
import com.effektif.workflow.api.types.*;
import com.effektif.workflow.impl.configuration.Brewery;
import com.effektif.workflow.impl.configuration.Startable;
import com.effektif.workflow.impl.data.types.*;
import com.effektif.workflow.impl.util.Reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Factory methods for the Effektif type system data types.
*
* @author Tom Baeyens
*/
public class DataTypeService implements Startable {
// private static final Logger log = LoggerFactory.getLogger(DataTypeService.class);
protected Configuration configuration;
protected Map<Class<? extends DataType>,DataTypeImpl> singletons = new ConcurrentHashMap<>();
protected Map<Class<? extends DataType>,Constructor<?>> dataTypeConstructors = new ConcurrentHashMap<>();
protected Map<Class<?>, JavaBeanTypeImpl> javaBeanTypes = new HashMap<>();
protected Map<Type, DataTypeImpl> dataTypesByValueClass = new HashMap<>();
public DataTypeService() {
ServiceLoader<DataTypeImpl> dataTypeLoader = ServiceLoader.load(DataTypeImpl.class);
for (DataTypeImpl dataType: dataTypeLoader) {
// log.debug("Registering dynamically loaded data type "+dataType.getClass().getSimpleName());
registerDataType(dataType);
}
// For undeclared variables a new variable instance
// will be created on the fly when a value is set.
// dataType.getValueClass(); is used. Since more
// dataTypes have String as a value type,
// we need to register the types we want to use
// during auto-creation afterwards.
// See (*) in registerDataType(DataTypeImpl dataTypeImpl) below
BooleanTypeImpl booleanTypeImpl = new BooleanTypeImpl();
booleanTypeImpl.setConfiguration(configuration);
registerDataType(booleanTypeImpl);
DateTypeImpl dateTypeImpl = new DateTypeImpl();
dateTypeImpl.setConfiguration(configuration);
registerDataType(dateTypeImpl);
NumberTypeImpl numberTypeImpl = new NumberTypeImpl();
numberTypeImpl.setConfiguration(configuration);
registerDataType(numberTypeImpl);
registerValueClasses(numberTypeImpl, int.class, short.class, byte.class, long.class, float.class, double.class,
Integer.class, Short.class, Byte.class, Long.class, Float.class, Double.class);
TextTypeImpl textTypeImpl = new TextTypeImpl();
textTypeImpl.setConfiguration(configuration);
registerDataType(textTypeImpl);
ObjectTypeImpl objectTypeImpl = new ObjectTypeImpl();
objectTypeImpl.setConfiguration(configuration);
registerDataType(objectTypeImpl);
}
@Override
public void start(Brewery brewery) {
this.configuration = brewery.get(Configuration.class);
for (DataTypeImpl dataType: singletons.values()) {
dataType.setConfiguration(configuration);
}
}
public void registerDataType(DataTypeImpl dataTypeImpl) {
Class apiClass = dataTypeImpl.getApiClass();
if (apiClass==null) {
return;
}
if (dataTypeImpl.isStatic()) {
singletons.put(apiClass, dataTypeImpl);
} else {
Constructor< ? > constructor = findDataTypeConstructor(dataTypeImpl.getClass());
dataTypeConstructors.put(apiClass, constructor);
}
try {
DataType dataType = (DataType) apiClass.newInstance();
Type valueType = dataType.getValueType();
if (valueType!=null) {
// (*) If multiple datatypes have the same valueType (like string, date etc),
// the last one wins
dataTypesByValueClass.put(valueType, dataTypeImpl);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void registerValueClasses(DataTypeImpl dataTypeImpl, Type... valueTypes) {
if (valueTypes!=null) {
for (Type valueType: valueTypes) {
dataTypesByValueClass.put(valueType, dataTypeImpl);
}
}
}
protected Constructor< ? > findDataTypeConstructor(Class< ? extends DataTypeImpl> dataTypeClass) {
for (Constructor<?> constructor: dataTypeClass.getDeclaredConstructors()) {
Class< ? >[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length==1
&& DataType.class.isAssignableFrom(parameterTypes[0])) {
return constructor;
}
}
throw new RuntimeException("Constructor not found "+dataTypeClass.getName()+"("+DataType.class.getName()+")");
}
public void registerJavaBeanType(Class<?> javaBeanClass) {
JavaBeanType javaBeanTypeApi = new JavaBeanType().javaClass(javaBeanClass);
JavaBeanTypeImpl javaBeanTypeImpl = new JavaBeanTypeImpl(javaBeanTypeApi);
javaBeanTypeImpl.setConfiguration(configuration);
javaBeanTypes.put(javaBeanClass, javaBeanTypeImpl);
registerDataType(javaBeanTypeImpl);
}
public DataTypeImpl getDataTypeByValue(Type type) {
Class< ? > rawClass = Reflection.getRawClass(type);
if (rawClass==List.class) {
Type elementType = Reflection.getTypeArg(type, 0);
DataTypeImpl elementDataType = getDataTypeByValue(elementType);
return new ListTypeImpl(elementDataType);
}
return getDataTypeByValue(rawClass);
}
public DataTypeImpl getDataTypeByValue(Class<?> valueClass) {
DataTypeImpl dataType = null;
if (valueClass!=null) {
dataType = dataTypesByValueClass.get(valueClass);
}
if (dataType==null) {
if (valueClass != null && Map.class.isAssignableFrom(valueClass)) {
dataType = new ObjectTypeImpl();
} else {
dataType = new AnyTypeImpl();
}
dataType.setConfiguration(configuration);
}
return dataType;
}
public DataType getTypeByValue(Object value) {
if (value==null) {
return null;
}
Class<?> valueClass = value.getClass();
if (String.class.isAssignableFrom(valueClass)) {
return new TextType();
}
if (Number.class.isAssignableFrom(valueClass)) {
return new NumberType();
}
if (Collection.class.isAssignableFrom(valueClass)) {
ListType listType = new ListType();
Iterator iterator = ((Collection)value).iterator();
if (iterator.hasNext()) {
Object elementValue = iterator.next();
DataType elementType = getTypeByValue(elementValue);
listType.elementType(elementType);
}
return listType;
} else if (javaBeanTypes.containsKey(valueClass)) {
return new JavaBeanType(valueClass);
}
throw new RuntimeException("No data type found for value "+value+" ("+valueClass.getName()+")");
}
public DataTypeImpl createDataType(DataType type) {
if (type==null) {
return null;
}
DataTypeImpl singleton = singletons.get(type.getClass());
if (singleton!=null) {
return singleton;
}
Constructor<?> constructor = dataTypeConstructors.get(type.getClass());
if (constructor!=null) {
try {
DataTypeImpl dataType = (DataTypeImpl) constructor.newInstance(new Object[]{type});
dataType.setConfiguration(configuration);
return dataType;
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException("Couldn't instantiate data type "+constructor.getDeclaringClass()+": "+e.getMessage(), e);
}
}
throw new RuntimeException("No DataType defined for "+type.getClass().getName());
}
}