/** * Copyright 2008-2016 Qualogy Solutions B.V. * * 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.qualogy.qafe.business.integration.adapter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.map.AbstractHashedMap; import org.apache.commons.collections.map.CaseInsensitiveMap; import com.qualogy.qafe.bind.commons.type.AdapterMapping; import com.qualogy.qafe.bind.commons.type.In; import com.qualogy.qafe.bind.commons.type.Parameter; import com.qualogy.qafe.bind.commons.type.Reference; import com.qualogy.qafe.bind.commons.type.TypeDefinition; import com.qualogy.qafe.bind.core.application.ApplicationContext; import com.qualogy.qafe.business.integration.builder.PredefinedClassTypeConverter; import com.qualogy.qafe.core.datastore.DataIdentifier; import com.qualogy.qafe.core.datastore.DataStore; import com.qualogy.qafe.core.datastore.ParameterValueHandler; import com.qualogy.qafe.core.datastore.DataMap; /** * Integration service adpater that can adapt parameters to and from a service call * to an actual servicecall, done by a processor * @author mvanderwurff * */ public class Adapter{ /** * Method to adapt an integration service result to one ore more business domain objects. If service result * is a list, the list will be recursively adapted. * * This 'controlling' method for adapting the output controls in 2 steps: * 1. map according to specified service attribute name (regarding the dot notation within the reference key) * 2. map according to mapping or type * * The order in which the mapping upon the outcome will be tried is: * 1. MappingAdapter when mapping not null, * 2. PredefinedMapper when type is a known (for this business project) type * 3. BestEffortAdapter will try to adapt Type(business domain) rules to the given object * * Notes: * - Empty mapping leaves a null object * - A null paramname on an outputmapping doesn't leave an entry * * @param id * @param serviceOutcome - the object from a service to adapt * @param outputMapping - the mapping the object needs to be adapted to */ public static void adaptOut(DataIdentifier id, Object serviceOutcome, List outputMapping){ if(serviceOutcome instanceof Object[]){ serviceOutcome = Arrays.asList((Object[])serviceOutcome); } if(outputMapping!=null){ for (Iterator iter1 = outputMapping.iterator(); iter1.hasNext();) { Parameter param = (Parameter) iter1.next(); if(param.getName() == null) continue; Object dsResult = null; if(serviceOutcome instanceof List){ dsResult = prepareList((List)serviceOutcome, param); }else{ dsResult = prepare(serviceOutcome, param); } if(DataStore.findValue(id, DataStore.KEY_WORD_COUNT) != null){ dsResult = ((Map)((ArrayList)dsResult).get(0)).get("count(*)"); } DataStore.store(id, param.getName(), dsResult); } } retrieveQafeBuiltInList(id, serviceOutcome); } private static void retrieveQafeBuiltInList(DataIdentifier id, Object serviceOutcome) { // A call statement can return a variable containing the json representation of the builtins to execute. // This method is to collect that value to use it in business action without user mentioning it in the out param. if(serviceOutcome instanceof Map) { // Call statement always returns a Map.(In case of java if the same functionality needed it should also return Map) // If we do not do this check excpetion can happen in case of simple value return from Java. Reference ref = new Reference(DataStore.KEY_WORD_QAFE_BUILT_IN_LIST, Reference.SOURCE_DATASTORE_ID); Parameter builtInParameter = new Parameter(ref); builtInParameter.setName(DataStore.KEY_WORD_QAFE_BUILT_IN_LIST); Object dsResult = prepare(serviceOutcome, builtInParameter); if(dsResult != null) { DataStore.store(id, builtInParameter.getName(), dsResult); } } } /** * Method to prepare objects for adaption and the actual adapt action * Method checks if a reference key is supplied to get a nested value from * the serviceoutcome. * * The serviceOutcome must be of type Map, so will be converted to a Map if necessary. * * @param serviceOutcome * @param param * @return */ private static Object prepare(Object serviceOutcome, Parameter param){ Object toBeMapped = null; Object converted = ObjectMapConverter.convert(serviceOutcome); if(converted!=null)//if cannot be converted serviceOutcome = converted; if(param.getRef()==null || param.getRef().denotesRoot()){ //need root? toBeMapped = serviceOutcome; }else{ //serviceoutcome must be a map if not root is wanted if(!(serviceOutcome instanceof Map)) throw new UnableToAdaptException("cannot get attributes like '" + param.getRef() + "' from a non-complex type (" + (serviceOutcome!=null?""+serviceOutcome.getClass():"null") + ")"); toBeMapped = (serviceOutcome==null || ((Map)serviceOutcome).isEmpty())?null:((Map)serviceOutcome).get(param.getRef().toString()); } return adaptFromService(toBeMapped, param.getAdapter(), param.getType()); } /** * Method to prepare a list of objects for actual adaption to do's, * uses Adapter.prepare(Object, Parameter), so actually a convinience * method that loops through the list and calls prepare. * * upon the objects * @param serviceOutcome * @param param * @return */ private static Object prepareList(List serviceOutcome, Parameter param){ List result = null; if(!serviceOutcome.isEmpty()){ result = new ArrayList(); for (Iterator iter = serviceOutcome.iterator(); iter.hasNext();) { result.add(prepare(iter.next(), param)); } } return result; } /** * Method to adapt a (part of a) serviceOutcome to a type or mapping specified in the configuration. * * If List or array, this method is called recursively. * * @param toAdapt * @param mapping * @param type * @return */ private static Object adaptFromService(Object toAdapt, AdapterMapping mapping, TypeDefinition type) { Object adapted = null; if(toAdapt instanceof Object[]){ toAdapt = Arrays.asList((Object[]) toAdapt); } if(toAdapt instanceof Collection){ List result = new ArrayList(); for (Iterator iter = ((List)toAdapt).iterator(); iter.hasNext();){ Object tmpResult = adaptFromService(iter.next(),mapping, type); result.add(tmpResult); } adapted = result; }else{ if(toAdapt!=null){ if(mapping!=null){//map according to adapter, it's always a map adapted = MappingAdapter.adaptFromService(toAdapt, mapping); }else if(PredefinedAdapterFactory.canObjectBeConverted(type, toAdapt)){//simple type adapted = PredefinedAdapterFactory.create(type).convert(toAdapt); }else if(type!=null){//either create mapping based upon typedefinition otherwise see if we have some conversion todo adapted = BestEffortAdapter.adapt((Map)toAdapt, type); }else{ adapted = toAdapt; } } } return adapted; } // public static Map adaptIn(ApplicationContext context, DataIdentifier id, List inputMapping) { // return Adapter.adaptIn(context, id, inputMapping, null); // } /** * Method adapts the data stored under the uniqueidentifier to the types and adapters * supplied through the mapping. Afterwards the method wil create adapted objects * for processing. * * First there needs to be determined if the in parameter has an adaptermapping supplied. * If so, the MappingAdapter will handle accordingly and adapts the object structure * to the mapping. Else the single value is retrieved and converted if necessary. * Secondly the data is prepared for the service call. * - If the adapted value is not null, this will be the adpated object * - Otherwise the type will be inspected and taken as being adapted * - If the type is also not supplied, the adapter is checked for an outputclass, * which will than be the adapted object * - If none of above applies the method will throw an exception because later on the * method can not be determined because of the missing argument. * * @param id * @param inputMapping * @return */ @SuppressWarnings("unchecked") public static Map<String, AdaptedToService> adaptIn(ClassLoader classLoader, ApplicationContext context, DataIdentifier id, List<In> inputMapping) { // Map<String, AdaptedToService> adaptedMap = new CaseInsensitiveMap(); Map<String, AdaptedToService> adaptedMap = new DataMap<String, AdaptedToService>(); for (int i = 0; inputMapping!=null && i < inputMapping.size(); i++) { In in = (In)inputMapping.get(i); Object adapted = null;//conversion-/adapter-result if(in.getAdapter()!=null && in.getAdapter().getOutputClass()!=null){ adapted = MappingAdapter.adaptToService(classLoader, id, in); }else{ adapted = ParameterValueHandler.get(context, id, in); if(in.getOutputClass()!=null && adapted instanceof Map){ adapted = ObjectMapConverter.convert(in.getOutputClass(), (Map)adapted); }else{ if(in.getType()!=null && PredefinedAdapterFactory.canObjectBeConverted(in.getType(), adapted)) adapted = PredefinedAdapterFactory.create(in.getType()).convert(adapted); } } if(adapted!=null || (in.getUseWhenNotSet()!=null && in.getUseWhenNotSet()==true)){ adaptedMap = addAdaptedToServiceMap(adaptedMap, in, adapted); } } return adaptedMap; } private static Map<String, AdaptedToService> addAdaptedToServiceMap(Map<String, AdaptedToService> adaptedMap, In in, Object adapted) { AdaptedToService ats = null; if(adapted!=null){ ats = AdaptedToService.create(adapted); }else if(in.getUseWhenNotSet()!=null && in.getUseWhenNotSet()==true){ if(in.getType()!=null && PredefinedClassTypeConverter.isPredefined(in.getType())){//if value is null set type if not null, so method can be determined and called with null param ats = AdaptedToService.create(PredefinedClassTypeConverter.convert(in.getType())); }else if(in.getOutputClass()!=null){ ats = AdaptedToService.create(in.getOutputClass()); }else if(in.getAdapter()!=null && in.getAdapter().getOutputClass()!=null){ ats = AdaptedToService.create(in.getAdapter().getOutputClass()); } } adaptedMap.put(in.getName(), ats); return adaptedMap; } }