/* Copyright (c) 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.json; import com.effektif.workflow.api.condition.Condition; import com.effektif.workflow.api.condition.Unspecified; import com.effektif.workflow.api.model.RelativeTime; import com.effektif.workflow.api.types.BooleanType; import com.effektif.workflow.api.types.DataType; import com.effektif.workflow.api.types.NumberType; import com.effektif.workflow.api.types.TextType; import com.effektif.workflow.api.workflow.Activity; import com.effektif.workflow.api.workflow.Extensible; import com.effektif.workflow.api.workflow.Timer; import com.effektif.workflow.api.workflow.Trigger; import com.effektif.workflow.impl.activity.AbstractTriggerImpl; import com.effektif.workflow.impl.activity.ActivityType; import com.effektif.workflow.impl.conditions.ConditionImpl; import com.effektif.workflow.impl.data.DataTypeImpl; import com.effektif.workflow.impl.job.JobType; import com.effektif.workflow.impl.job.TimerType; import com.effektif.workflow.impl.json.types.*; import com.effektif.workflow.impl.workflow.boundary.BoundaryEventTimerImpl; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; /** * @author Tom Baeyens */ public class MappingsBuilder { Map<Class, String> baseClasses = new HashMap<>(); List<Class> subClasses = new ArrayList<>(); Set<Field> inlineFields = new HashSet<>(); Set<Field> ignoredFields = new HashSet<>(); Map<Field,FieldMapping> fieldsMappings = new HashMap<>(); Map<Field,String> fieldNames = new HashMap<>(); List<JsonTypeMapperFactory> typeMapperFactories = new ArrayList<>(); Map<Type,DataType> dataTypesByValueClass = new HashMap<>(); public MappingsBuilder configureDefaults() { inline(Extensible.class, "properties"); baseClass(Trigger.class); baseClass(JobType.class); subClasses(BoundaryEventTimerImpl.class); //todo: make this dynamic baseClass(Activity.class); baseClass(Condition.class); // needs to be added explicitly as it has no impl: subClass(Unspecified.class); inline(Unspecified.class, "properties"); baseClass(DataType.class, "name"); baseClass(RelativeTime.class); subClasses(RelativeTime.SUBCLASSES); baseClass(Timer.class); typeMapperFactory(new ValueMapper()); typeMapperFactory(new StringMapper()); typeMapperFactory(new BooleanMapper()); typeMapperFactory(new ClassMapper()); typeMapperFactory(new NumberMapperFactory()); typeMapperFactory(new VariableMapperFactory()); typeMapperFactory(new VariableInstanceMapperFactory()); typeMapperFactory(new TypedValueMapperFactory()); typeMapperFactory(new EnumMapperFactory()); typeMapperFactory(new ArrayMapperFactory()); typeMapperFactory(new CollectionMapperFactory()); typeMapperFactory(new EnumSetMapperFactory()); typeMapperFactory(new MapMapperFactory()); typeMapperFactory(new BindingMapperFactory()); loadPlugins(); return this; } public MappingsBuilder baseClass(Class baseClass) { return baseClass(baseClass, "type"); } public MappingsBuilder baseClass(Class baseClass, String typeField) { baseClasses.put(baseClass, typeField); return this; } public MappingsBuilder subClass(Class subClass) { subClasses.add(subClass); return this; } public MappingsBuilder subClasses(Class... subClasses) { if (subClasses!=null) { for (Class subClass: subClasses) { this.subClasses.add(subClass); } } return this; } public void removeSubClass(Class subClass) { subClasses.remove(subClass); } public MappingsBuilder inline(Class clazz, String fieldName) { inlineFields.add(getField(clazz, fieldName)); return this; } public MappingsBuilder ignore(Class clazz, String fieldName) { ignoredFields.add(getField(clazz, fieldName)); return this; } public MappingsBuilder fieldMapping(Class clazz, String fieldName, FieldMapping fieldMapping) { Field field = getField(clazz, fieldName); fieldsMappings.put(field, fieldMapping); return this; } public MappingsBuilder fieldMapper(Class clazz, String fieldName, JsonTypeMapper fieldMapper) { Field field = getField(clazz, fieldName); fieldsMappings.put(field, new FieldMapping(field, fieldMapper)); return this; } public MappingsBuilder jsonFieldName(Class clazz, String fieldName, String jsonFieldName) { fieldNames.put(getField(clazz, fieldName), jsonFieldName); return this; } public MappingsBuilder typeMapperFactory(JsonTypeMapperFactory mapperFactory) { typeMapperFactories.add(mapperFactory); return this; } public MappingsBuilder loadPlugins() { ServiceLoader<ActivityType> activityTypeLoader = ServiceLoader.load(ActivityType.class); for (ActivityType activityType: activityTypeLoader) { subClass(activityType.getActivityApiClass()); } ServiceLoader<ConditionImpl> conditionLoader = ServiceLoader.load(ConditionImpl.class); for (ConditionImpl condition: conditionLoader) { subClass(condition.getApiType()); } ServiceLoader<AbstractTriggerImpl> triggerLoader = ServiceLoader.load(AbstractTriggerImpl.class); for (AbstractTriggerImpl trigger: triggerLoader) { subClass(trigger.getTriggerApiClass()); } ServiceLoader<DataTypeImpl> dataTypeLoader = ServiceLoader.load(DataTypeImpl.class); for (DataTypeImpl dataTypeImpl: dataTypeLoader) { try { Class<? extends DataType> apiClass = dataTypeImpl.getApiClass(); if (apiClass!=null) { subClass(apiClass); DataType dataType = apiClass.newInstance(); dataTypesByValueClass.put(dataType.getValueType(), dataType); } } catch (Exception e) { throw new RuntimeException(e); } } ServiceLoader<TimerType> timerTypeLoader = ServiceLoader.load(TimerType.class); for (TimerType timerType: timerTypeLoader) { subClass(timerType.getTimerApiClass()); } // potentially multiple datatypes may map to eg String. // by re-putting these datatypes, we ensure that these basic // data types are used when looking up a datatype by value dataTypesByValueClass.put(String.class, TextType.INSTANCE); dataTypesByValueClass.put(Boolean.class, BooleanType.INSTANCE); dataTypesByValueClass.put(Byte.class, NumberType.INSTANCE); dataTypesByValueClass.put(Short.class, NumberType.INSTANCE); dataTypesByValueClass.put(Integer.class, NumberType.INSTANCE); dataTypesByValueClass.put(Long.class, NumberType.INSTANCE); dataTypesByValueClass.put(Float.class, NumberType.INSTANCE); dataTypesByValueClass.put(Double.class, NumberType.INSTANCE); dataTypesByValueClass.put(BigInteger.class, NumberType.INSTANCE); dataTypesByValueClass.put(BigDecimal.class, NumberType.INSTANCE); return this; } public Mappings getMappings() { return new Mappings(this); } protected Field getField(Class clazz, String fieldName) { try { return clazz.getDeclaredField(fieldName); } catch (Exception e) { throw new RuntimeException(e); } } }