Coverage Report - net.sf.sapjcosupport.SapJcoInterface
 
Classes in this File Line Coverage Branch Coverage Complexity
SapJcoInterface
0% 
0% 
6,458
 
 1  
 package net.sf.sapjcosupport;
 2  
 
 3  
 import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
 4  
 import com.sap.mw.jco.IFunctionTemplate;
 5  
 import com.sap.mw.jco.JCO;
 6  
 import org.apache.commons.beanutils.BeanUtils;
 7  
 import org.apache.log4j.Logger;
 8  
 import org.springframework.beans.factory.InitializingBean;
 9  
 import org.springframework.dao.DataRetrievalFailureException;
 10  
 import org.springframework.util.MethodInvoker;
 11  
 
 12  
 import java.lang.reflect.Constructor;
 13  
 import java.lang.reflect.Field;
 14  
 import java.lang.reflect.InvocationTargetException;
 15  
 import java.util.*;
 16  
 
 17  
 /**
 18  
  * Abstract superclass for all SAP data access objects.
 19  
  *
 20  
  * @author Niki Driessen
 21  
  * @since Jan 18, 2006 - 3:07:41 PM
 22  
  */
 23  0
 public abstract class SapJcoInterface {
 24  0
     private static final Logger logger = Logger.getLogger(SapJcoInterface.class);
 25  0
     private static final Logger sapJcoXmlLogger = Logger.getLogger("SapJcoXML");
 26  0
     private static SimpleReflectionCache reflectionCache = new SimpleReflectionCache();
 27  
     private static Map cache;
 28  
     private static final String FUNCTIONTEMPLATE_CACHE_KEY = "_functiontemplate";
 29  
     private static final char SELECTED = 'X';
 30  
 
 31  
     static {
 32  0
         cache = Collections.synchronizedMap(new HashMap());
 33  0
     }
 34  
 
 35  
     protected SapDataSource dataSource;
 36  0
     protected int maxParallelCalls = 5;
 37  
 
 38  
     /**
 39  
      * Sets the maximum number of parallel calls to SAP.
 40  
      *
 41  
      * @param calls maximum number of parallel SAP calls
 42  
      */
 43  
     public void setMaxParallelCalls(int calls) {
 44  0
         this.maxParallelCalls = calls;
 45  0
     }
 46  
 
 47  
     /**
 48  
      * Sets the SAP data source this data access object is working on.
 49  
      *
 50  
      * @param source SAP data source
 51  
      */
 52  
     public void setDataSource(SapDataSource source) {
 53  0
         this.dataSource = source;
 54  0
     }
 55  
 
 56  
     /**
 57  
      * This method will resolve the FunctionTemplate for the given name.
 58  
      * It will search the local cache and if not found, retrieve it from SAP
 59  
      * via the repository. It then caches that and returns it.
 60  
      * <p/>
 61  
      * This method NEVER returns null! An exception is thrown when the function can not be resolved.
 62  
      *
 63  
      * @param name the name of the function to find
 64  
      * @return the function template
 65  
      * @throws SapJcoMappingException when the requested function can not be found in the configured repositories
 66  
      */
 67  
     protected JCO.Function getFunction(String name) {
 68  0
         String cacheKey = name + FUNCTIONTEMPLATE_CACHE_KEY;
 69  0
         IFunctionTemplate template = (IFunctionTemplate) cache.get(cacheKey);
 70  0
         if (template == null) {
 71  0
             template = dataSource.getRepository().getFunctionTemplate(name);
 72  0
             if (template == null) {
 73  0
                 throw new SapJcoMappingException(new StringBuffer("Could not find function '").append(name).append(
 74  
                         "'").toString());
 75  
             }
 76  0
             cache.put(cacheKey, template);
 77  
         }
 78  0
         return template.getFunction();
 79  
     }
 80  
 
 81  0
     private class AsyncRetrieveCall extends AsyncMethodCall {
 82  
         private Class persistentObject;
 83  
         private Map input;
 84  
         private Object result;
 85  
         private String profile;
 86  
 
 87  0
         public AsyncRetrieveCall(Class persistentObject, Map input, String profile) {
 88  0
             this.persistentObject = persistentObject;
 89  0
             this.input = input;
 90  0
             this.profile = profile;
 91  0
         }
 92  
 
 93  
         protected void executeMethod() {
 94  0
             result = retrieve(persistentObject, input, profile);
 95  0
         }
 96  
 
 97  
         protected Object getMethodReturnValue() {
 98  0
             return result;
 99  
         }
 100  
     }
 101  
 
 102  
     /**
 103  
      * Actually calls a BAPI.
 104  
      */
 105  
     public Object performSapCall(SapMapping sapMapping, Map input, Class persistentObject, String profile) {
 106  0
         List inputListMappings = sapMapping.getInputLists();
 107  0
         SapListMapping outputListMapping = sapMapping.getOutputList();
 108  0
         SapListMapping fieldSelection = sapMapping.getFieldSelection();
 109  
 
 110  0
         SapBapiMapping sapBapiMapping = sapMapping.getBapi();
 111  0
         JCO.Function function = getFunction(sapBapiMapping.getName());
 112  0
         JCO.Client connection = dataSource.getConnection();
 113  
 
 114  0
         if (logger.isInfoEnabled()) {
 115  0
             logger.info("Starting SAP backend call: " + sapBapiMapping.getName());
 116  
         }
 117  
 
 118  
         //map normal inported parameters
 119  0
         prepareFunctionInput(sapBapiMapping, input, function);
 120  
         //handle input lists
 121  0
         prepareFunctionInputList(inputListMappings, function, input);
 122  
         //handle field selections (profiles)
 123  0
         handleFieldSelection(fieldSelection, function, sapBapiMapping, profile);
 124  
 
 125  
         try {
 126  
 
 127  0
             long currTime = System.currentTimeMillis();
 128  0
             if (sapJcoXmlLogger.isDebugEnabled()) {
 129  0
                 String tempFile = System.getProperty("java.io.tmpdir") + function
 130  
                         .getName() + "_Request-" + currTime + ".xml";
 131  0
                 function.writeXML(tempFile);
 132  0
                 sapJcoXmlLogger.debug("BAPI function input written to location: " + tempFile);
 133  
             }
 134  0
             connection.execute(function);
 135  0
             if (sapJcoXmlLogger.isDebugEnabled()) {
 136  0
                 String tempFile = System.getProperty("java.io.tmpdir") + function
 137  
                         .getName() + "_Response-" + currTime + ".xml";
 138  0
                 function.writeXML(tempFile);
 139  0
                 sapJcoXmlLogger.debug("BAPI function output written to location: " + tempFile);
 140  
             }
 141  
             try {
 142  0
                 checkReturnStructure(function);
 143  0
             } catch (DataRetrievalFailureException e) {
 144  0
                 logger.error("The backend call returned no data.", e);
 145  0
                 return null; //no data found, so nothing do do...
 146  0
             }
 147  0
             JCO.ParameterList output = function.getExportParameterList();
 148  0
             if (output == null) {
 149  0
                 output = function.getTableParameterList();
 150  0
                 if (output == null) {
 151  0
                     throw new DataRetrievalFailureException("No results returned");
 152  
                 }
 153  
             }
 154  
 
 155  0
             if (outputListMapping != null) {    // multiple results
 156  0
                 return parseOutputList(function, outputListMapping, sapBapiMapping, fieldSelection, persistentObject);
 157  
             } else {    // single result
 158  
                 //now map to persistent object
 159  0
                 return parseOutput(sapBapiMapping, fieldSelection, function, persistentObject);
 160  
             }
 161  0
         } catch (JCO.Exception e) {
 162  0
             throw new DataRetrievalFailureException("Underlying JCO bridge threw an exception", e);
 163  0
         } catch (IllegalAccessException e) {
 164  0
             throw new DataRetrievalFailureException("Instantiation of new value object instance failed", e);
 165  0
         } catch (InstantiationException e) {
 166  0
             throw new DataRetrievalFailureException("Instantiation of new value object instance failed", e);
 167  
         } finally {
 168  0
             dataSource.release(connection);
 169  
         }
 170  
     }
 171  
 
 172  
     private Object parseOutput(SapBapiMapping sapBapiMapping, SapListMapping fieldSelection, JCO.Function function,
 173  
                                Class persistentObject) throws IllegalAccessException, InstantiationException {
 174  0
         Object entity = persistentObject.newInstance();
 175  0
         for (int j = 0; j < sapBapiMapping.getStructures().size(); j++) {
 176  0
             SapStructureMapping sapStructureMapping = (SapStructureMapping) sapBapiMapping.getStructures().get(j);
 177  0
             if (sapStructureMapping.isExcluded() || (fieldSelection != null && sapStructureMapping.getName().equals(
 178  
                     fieldSelection.getName()))) {
 179  
                 // discard excluded lists and the field selection structure when processing output
 180  0
                 continue;
 181  
             }
 182  0
             JCO.Field field = getField(function, sapStructureMapping.getName());
 183  0
             if (field == null) {
 184  0
                 throw new DataRetrievalFailureException(new StringBuffer("Field ").append(sapStructureMapping.getName())
 185  
                         .append(" not found in resultset").toString());
 186  
             }
 187  0
             if (sapStructureMapping.isMap()) {
 188  0
                 if (!field.isTable()) {
 189  0
                     throw new SapJcoMappingException(new StringBuffer("Field ").append(sapStructureMapping.getName())
 190  
                             .append(" is mapped as table but ist one").toString());
 191  
                 }
 192  0
                 JCO.Table table = field.getTable();
 193  0
                 if (table.getNumRows() > 0) {
 194  0
                     convertMap(table, entity, sapStructureMapping, persistentObject);
 195  
                 } else {
 196  0
                     if (logger.isDebugEnabled()) {
 197  0
                         logger.debug("Single result table contains no rows: " + table.getName());
 198  
                     }
 199  
                 }
 200  0
             } else if (sapStructureMapping.isEntity()) {
 201  0
                 if (!field.isTable()) {
 202  0
                     throw new SapJcoMappingException(new StringBuffer("Field ").append(sapStructureMapping.getName())
 203  
                             .append(" is mapped as table but ist one").toString());
 204  
                 }
 205  0
                 JCO.Table table = field.getTable();
 206  0
                 if (table.getNumRows() > 0) {
 207  0
                     if (sapStructureMapping.getCollectionName() == null) {
 208  0
                         throw new SapJcoMappingException(new StringBuffer("Entity mapping defined for structure ")
 209  
                                 .append(sapStructureMapping.getName()).append(
 210  
                                 " but no collection specified in parent entity").toString());
 211  
                     }
 212  0
                     List collection = new ArrayList(table.getNumRows());
 213  
                     try {
 214  0
                         Class entityClass = Class.forName(sapStructureMapping.getEntityClass());
 215  0
                         table.firstRow();
 216  
                         do {
 217  0
                             Object childEntity = instantiate(entityClass);
 218  0
                             mapRecordToEntity(entityClass, childEntity, table, sapStructureMapping);
 219  0
                             collection.add(childEntity);
 220  0
                         } while (table.nextRow());
 221  0
                         BeanUtils.setProperty(entity, sapStructureMapping.getCollectionName(), collection);
 222  0
                     } catch (InvocationTargetException e) {
 223  0
                         throw new SapJcoMappingException(
 224  
                                 "Error mapping entity collection " + sapStructureMapping.getCollectionName(), e);
 225  0
                     } catch (ClassNotFoundException e) {
 226  0
                         throw new SapJcoMappingException(
 227  
                                 "Can not find entity class " + sapStructureMapping.getEntityClass(), e);
 228  0
                     }
 229  
                 } else {
 230  0
                     logger.info("No rows for entity collection " + sapStructureMapping.getName());
 231  
                 }
 232  
             } else {
 233  
                 JCO.Record record;
 234  0
                 if (!field.isStructure()) {
 235  0
                     field.getTable().firstRow();
 236  0
                     record = field.getTable();
 237  
                 } else {
 238  0
                     record = field.getStructure();
 239  
                 }
 240  0
                 mapRecordToEntity(persistentObject, entity, record, sapStructureMapping);
 241  
             }
 242  
         }
 243  0
         if (entity != null && entity instanceof InitializingBean) {
 244  
             try {
 245  0
                 ((InitializingBean) entity).afterPropertiesSet();
 246  0
             } catch (Exception e) {
 247  0
                 throw new DataRetrievalFailureException(
 248  
                         "Encountered problems while executing 'afterPropertiesSet' on value bean", e);
 249  0
             }
 250  
         }
 251  0
         return entity;
 252  
     }
 253  
 
 254  
     private List parseOutputList(JCO.Function function, SapListMapping outputListMapping,
 255  
                                  SapBapiMapping sapBapiMapping, SapListMapping fieldSelection, Class persistentObject)
 256  
             throws IllegalAccessException, InstantiationException {
 257  0
         JCO.Field outputField = getField(function, outputListMapping.getName());
 258  0
         JCO.Table outputList = checkAndGetTable(outputField);
 259  0
         outputList.firstRow();
 260  0
         List results = new ArrayList();
 261  
         do {
 262  0
             Object entity = persistentObject.newInstance();
 263  0
             String pkField = outputListMapping.getPrimaryKey();
 264  0
             if (outputList.getNumRows() == 0) {
 265  0
                 return results;
 266  
             }
 267  0
             String pkValue = outputList.getString(pkField);
 268  0
             if (pkValue == null) {
 269  0
                 continue;
 270  
             }
 271  0
             for (int j = 0; j < sapBapiMapping.getStructures().size(); j++) {
 272  0
                 SapStructureMapping sapStructureMapping = (SapStructureMapping) sapBapiMapping.getStructures().get(j);
 273  0
                 if (sapStructureMapping.isExcluded() || (fieldSelection != null && sapStructureMapping.getName().equals(
 274  
                         fieldSelection.getName()))) {
 275  
                     // discard excluded lists and the field selection structure when processing output
 276  0
                     continue;
 277  
                 }
 278  0
                 JCO.Field field = getField(function, sapStructureMapping.getName());
 279  0
                 if (field == null) {
 280  0
                     throw new DataRetrievalFailureException(new StringBuffer("Field ").append(
 281  
                             sapStructureMapping.getName()).append(" not found in resultset").toString());
 282  
                 }
 283  
 
 284  0
                 if (sapStructureMapping.getName().equalsIgnoreCase(outputListMapping.getName())) {
 285  
                     //map the current row
 286  0
                     mapRecordToEntity(persistentObject, entity, outputList, sapStructureMapping);
 287  
                 } else {
 288  
                     //handle map
 289  0
                     if (sapStructureMapping.isListOfMaps()) {
 290  0
                         JCO.Table table = checkAndGetTable(field);
 291  0
                         if (table.getNumRows() == 0) {
 292  0
                             if (logger.isDebugEnabled()) {
 293  0
                                 logger.debug("Multiple result table contains no rows: " + table.getName());
 294  
                             }
 295  
                             continue; // process next row
 296  
                         }
 297  
 
 298  
                         //filter
 299  0
                         JCO.Table filteredTable = filterTable(table, pkField, pkValue);
 300  0
                         if (filteredTable.getNumRows() == 0) {
 301  0
                             if (logger.isDebugEnabled()) {
 302  0
                                 logger.debug("Filtered result table contains no rows: " + table
 303  
                                         .getName() + "; filtered on key: " + pkField + "=" + pkValue);
 304  
                             }
 305  
                         } else {
 306  
                             //map
 307  0
                             convertMap(filteredTable, entity, sapStructureMapping, persistentObject);
 308  
                         }
 309  0
                     } else if (sapStructureMapping.isList()) {
 310  
                         //advance pointer
 311  0
                         JCO.Table record = checkAndGetTable(field);
 312  0
                         record.setRow(outputList.getRow());
 313  0
                         mapRecordToEntity(persistentObject, entity, record, sapStructureMapping);
 314  
                     } else {
 315  0
                         throw new SapJcoMappingException(
 316  
                                 "Output list defined, structure type must be 'list' or 'list' with 'key-field' and 'pk' attribute defined");
 317  
                     }
 318  
                 }
 319  
             }
 320  0
             if (entity != null && entity instanceof InitializingBean) {
 321  
                 try {
 322  0
                     ((InitializingBean) entity).afterPropertiesSet();
 323  0
                 } catch (Exception e) {
 324  0
                     throw new DataRetrievalFailureException(
 325  
                             "Encountered problems while executing 'afterPropertiesSet' on value bean", e);
 326  0
                 }
 327  
             }
 328  0
             results.add(entity);
 329  0
         } while (outputList.nextRow());
 330  0
         return results;
 331  
     }
 332  
 
 333  
     private void handleFieldSelection(SapListMapping fieldSelection, JCO.Function function,
 334  
                                       SapBapiMapping sapBapiMapping, String profile) {
 335  0
         if (fieldSelection != null) {
 336  0
             JCO.Structure fieldSelectionStructure = function.getImportParameterList().getStructure(
 337  
                     fieldSelection.getName());
 338  0
             Collection selectedFields = sapBapiMapping.getFieldSelection(profile);
 339  0
             if (selectedFields == null) {
 340  
                 //select ALL
 341  0
                 JCO.FieldIterator iterator = fieldSelectionStructure.fields();
 342  0
                 while (iterator.hasNextFields()) {
 343  0
                     JCO.Field field = iterator.nextField();
 344  0
                     field.setValue(SELECTED);
 345  
                 }
 346  
             } else {
 347  0
                 for (Iterator i = selectedFields.iterator(); i.hasNext();) {
 348  0
                     Object value = i.next();
 349  0
                     fieldSelectionStructure.setValue(SELECTED, String.valueOf(value));
 350  
                 }
 351  
             }
 352  
         }
 353  0
     }
 354  
 
 355  
     private void prepareFunctionInputList(List inputListMappings, JCO.Function function, Map input) {
 356  0
         if (inputListMappings != null) {
 357  0
             JCO.ParameterList tableList = function.getTableParameterList();
 358  0
             for (Iterator inputListIterator = inputListMappings.iterator(); inputListIterator.hasNext();) {
 359  0
                 SapListMapping inputListMapping = (SapListMapping) inputListIterator.next();
 360  0
                 JCO.Table inputTable = tableList.getTable(inputListMapping.getName());
 361  0
                 if (inputTable == null) {
 362  0
                     throw new SapJcoMappingException("Input table '" + inputListMapping.getName() + "' doesn't exist");
 363  
                 }
 364  0
                 Collection inputValues = (Collection) input.get(inputListMapping.getName());
 365  0
                 if (inputValues != null) {
 366  0
                     for (Iterator valueIterator = inputValues.iterator(); valueIterator.hasNext();) {
 367  0
                         Object value = valueIterator.next();
 368  0
                         inputTable.appendRow();
 369  0
                         if (value instanceof Map) {
 370  
                             // This approach was introduced to enable multiple fields to be set, not just the primary key field.
 371  
                             // The keys of the Map.Entry should map exactly to the SAP field names (*NOT* the name of the corresponding java fields)
 372  
                             // The use of the primary key field in the input list mapping is bypassed..
 373  0
                             Map fields = (Map) value;
 374  0
                             for (Iterator entrySetIterator = fields.entrySet().iterator(); entrySetIterator.hasNext();)
 375  
                             {
 376  0
                                 Map.Entry entry = (Map.Entry) entrySetIterator.next();
 377  0
                                 inputTable.setValue(String.valueOf(entry.getValue()), String.valueOf(entry.getKey()));
 378  
                             }
 379  
                         } else {
 380  
                             // assign each value in the input collection to the primary key field of a new JCO.Table row
 381  
                             // (the primary key field is defined in the input list mapping)
 382  0
                             inputTable.setValue(String.valueOf(value), inputListMapping.getPrimaryKey());
 383  
                         }
 384  
                     }
 385  
                 }
 386  
             }
 387  
         }
 388  0
     }
 389  
 
 390  
     private void prepareFunctionInput(SapBapiMapping sapBapiMapping, Map input, JCO.Function function) {
 391  
         // Prepare input parameters for SAP function call
 392  0
         for (int j = 0; j < sapBapiMapping.getInput().size(); j++) {
 393  0
             SapInput sapInput = (SapInput) sapBapiMapping.getInput().get(j);
 394  0
             String value = (String) input.get(sapInput.getName());
 395  0
             if ((value == null || value.trim().length() == 0) && sapInput.isRequired()) {
 396  0
                 if (sapInput.getDefaultValue() != null && sapInput.getDefaultValue().length() > 0) {
 397  0
                     value = sapInput.getDefaultValue();
 398  
                 } else {
 399  0
                     throw new SapJcoMappingException("Missing input parameter '" + sapInput
 400  
                             .getName() + "' (required and no default value supplied)");
 401  
                 }
 402  
             }
 403  0
             if (logger.isDebugEnabled()) {
 404  0
                 logger.debug(new StringBuffer("Setting import parameter '").append(sapInput.getName()).append("' to '")
 405  
                         .append(value).append("'"));
 406  
             }
 407  0
             function.getImportParameterList().setValue(value, sapInput.getName());
 408  
         }
 409  0
     }
 410  
 
 411  
 
 412  
     /**
 413  
      * This helper method creates a new <code>JCO.Table</code> instance that contains
 414  
      * all rows of the parent table which have a value of <code>pkValue</code> for field <code>pkField</code>.
 415  
      *
 416  
      * @param table   table to sort
 417  
      * @param pkField name of the primary key field
 418  
      * @param pkValue value against which rows are filtered
 419  
      * @return new instance of JCO.Table containing all but the filtered rows
 420  
      */
 421  
     private JCO.Table filterTable(JCO.Table table, String pkField, String pkValue) {
 422  0
         JCO.Table subTable = new JCO.Table(table.getMetaData());
 423  0
         table.firstRow();
 424  
         do {
 425  0
             if (pkValue.equals(table.getString(pkField))) {
 426  
                 // copy current row
 427  0
                 subTable.appendRow();
 428  0
                 for (int counter = 0; counter < table.getNumColumns(); counter++) {
 429  0
                     subTable.setValue(table.getValue(counter), counter);
 430  
                 }
 431  
             }
 432  0
         } while (table.nextRow());
 433  0
         return subTable;
 434  
     }
 435  
 
 436  
     /**
 437  
      * This helper method checks if a JCO.Field instance that is mapped as a table is actually a JCO.Table
 438  
      *
 439  
      * @param field field to check
 440  
      * @return JCO.Field cast to JCO.Table
 441  
      * @throws SapJcoMappingException if the <code>JCO.Field</code> is not a <code>JCO.Table</code>
 442  
      */
 443  
     private JCO.Table checkAndGetTable(JCO.Field field) throws SapJcoMappingException {
 444  0
         if (!field.isTable()) {
 445  0
             throw new SapJcoMappingException(new StringBuffer("Field ").append(field.getName()).append(
 446  
                     " is mapped as table but isn't one").toString());
 447  
         }
 448  0
         return field.getTable();
 449  
     }
 450  
 
 451  
     private JCO.Field getField(JCO.Function function, String name) {
 452  0
         JCO.ParameterList output = function.getExportParameterList();
 453  0
         if (output != null && output.hasField(name)) {
 454  0
             return output.getField(name);
 455  
         }
 456  0
         output = function.getTableParameterList();
 457  0
         if (output != null && output.hasField(name)) {
 458  0
             return output.getField(name);
 459  
         }
 460  0
         return null;
 461  
     }
 462  
 
 463  
     protected List retrieve(Class persistentObject, List inputMaps) {
 464  0
         return retrieve(persistentObject, inputMaps, null);
 465  
     }
 466  
 
 467  
     protected List retrieve(Class persistentObject, List inputMaps, String profile) {
 468  0
         if (persistentObject == null) {
 469  0
             throw new IllegalArgumentException("'persistentObject' parameter is required.");
 470  
         }
 471  0
         if (inputMaps == null || inputMaps.size() == 0) {
 472  
             //nothing to fetch, so return an empty list
 473  0
             return new ArrayList(0);
 474  
         }
 475  
 
 476  
         //create threadpool and call retrieve parellel...
 477  0
         PooledExecutor pool = new PooledExecutor(maxParallelCalls);
 478  0
         pool.setMaximumPoolSize(maxParallelCalls);
 479  0
         pool.setMinimumPoolSize(1);
 480  0
         pool.waitWhenBlocked();
 481  
 
 482  0
         logger.warn("Using internal thread pool to execute " + inputMaps.size() + " requests in parallel");
 483  
         try {
 484  0
             List results = new ArrayList(inputMaps.size());
 485  0
             List calls = new ArrayList(inputMaps.size());
 486  0
             for (int i = 0; i < inputMaps.size(); i++) {
 487  0
                 Map input = (Map) inputMaps.get(i);
 488  0
                 AsyncRetrieveCall call = new AsyncRetrieveCall(persistentObject, input, profile);
 489  0
                 calls.add(call);
 490  0
                 pool.execute(call);
 491  
             }
 492  0
             for (int i = 0; i < calls.size(); i++) {
 493  0
                 AsyncRetrieveCall asyncRetrieveCall = (AsyncRetrieveCall) calls.get(i);
 494  0
                 Object result = asyncRetrieveCall.getMethodReturnValue();
 495  0
                 if (result != null) {
 496  0
                     results.add(result);
 497  
                 }
 498  
             }
 499  0
             return results;
 500  0
         } catch (InterruptedException e) {
 501  0
             throw new DataRetrievalFailureException("The request has been interrupted by the system", e);
 502  
         }
 503  
     }
 504  
 
 505  
     protected Object retrieve(Class persistentObject, Map input) {
 506  0
         return retrieve(persistentObject, input, null);
 507  
     }
 508  
 
 509  
     /**
 510  
      * Retrieves value objects of type <code>persistentObject</code>.
 511  
      * <p/>
 512  
      * The <code>input</code> parameter specifies the input paramters that should be passed to SAP.
 513  
      * If the result of this call will be one value object, the entries in the Map will be mapped to SAP call import parameters.
 514  
      * If the result of this call is a List of value Objects, and a SAP input table is used rather than import parameters,
 515  
      * the Map contains a Collection as well. The key of this Collection is the same as the SAP input table.
 516  
      * <p/>
 517  
      * This Collection can contain two types of values.
 518  
      * <ol>
 519  
      * <li>
 520  
      * The elements of the collection contain a value that should be assigned to the primary key field of the table,
 521  
      * which is defined in the input list mapping. Each value of in the collection will be assigned to the primary
 522  
      * key of a new <code>JCO.Table</code> row
 523  
      * </li>
 524  
      * <li>
 525  
      * The elements of the collection contain a Map. The keys of this Map should correspond exactly to the SAP
 526  
      * fields and the values will be assigned to these fields. Every Map in the collection correspond to
 527  
      * one <code>JCO.Table</code> row.
 528  
      * </li>
 529  
      * </ol>
 530  
      * <p/>
 531  
      * Here is an example of the second situation: a <code>Collection of Maps</code>.
 532  
      * Each map can contain a different number of field/value pairs to process.
 533  
      * <pre>
 534  
      * Collection
 535  
      *    0 - Map
 536  
      *       * DOCUMENT_NUMBER -> value
 537  
      *    1 - Map
 538  
      *       * DOCUMENT_NUMBER -> value
 539  
      *       * DOCUMENT_TYPE -> value
 540  
      *    2 - Map
 541  
      *       * MATERIAL -> value
 542  
      *       * DOCUMENT_NUMBER -> value
 543  
      *       * DOCUMENT_TYPE -> value
 544  
      *    etc.
 545  
      * </pre>
 546  
      *
 547  
      * @param persistentObject type of the value object
 548  
      * @param input            input map
 549  
      * @param profile          name to retrieve fields for
 550  
      * @return - one single value object of type <code>persistentObject</code>
 551  
      *         <br/>   - or a List of value objects of type <code>persistentObject</code> if the SAP call supports input/output tables
 552  
      */
 553  
     protected Object retrieve(Class persistentObject, Map input, String profile) {
 554  0
         if (persistentObject == null) {
 555  0
             throw new IllegalArgumentException("'persistentObject' parameter is required.");
 556  
         }
 557  0
         if (input == null) {
 558  0
             input = new HashMap(0);
 559  
         }
 560  
         try {
 561  
             //first validate the mapping...
 562  0
             SapMapping sapMapping = SapMappingParser.getInfo(persistentObject);
 563  0
             return performSapCall(sapMapping, input, persistentObject, profile);
 564  0
         } catch (Exception e) {
 565  0
             throw new DataRetrievalFailureException("Error fetching data", e);
 566  
         }
 567  
     }
 568  
 
 569  
     protected void checkReturnStructure(JCO.Function function) throws DataRetrievalFailureException {
 570  0
         if (function.getExportParameterList() != null && function.getExportParameterList().hasField("RETURN")) {
 571  0
             JCO.Structure returnStructure = function.getExportParameterList().getStructure("RETURN");
 572  0
             if (! ("".equals(returnStructure.getString("TYPE")) ||
 573  
                     "S".equals(returnStructure.getString("TYPE")) ||
 574  
                     "W".equals(returnStructure.getString("TYPE")))) {
 575  0
                 throw new DataRetrievalFailureException(new StringBuffer(returnStructure.getString("TYPE"))
 576  
                         .append(": ").append(returnStructure.getString("MESSAGE")).toString());
 577  
             }
 578  
         }
 579  0
     }
 580  
 
 581  
     protected void mapRecordToEntity(Class entityDef, Object entity, JCO.Record record,
 582  
                                      SapStructureMapping structureMapping) {
 583  0
         for (Iterator fieldsIt = structureMapping.getFields().iterator(); fieldsIt.hasNext();) {
 584  0
             SapFieldMapping sapFieldMapping = (SapFieldMapping) fieldsIt.next();
 585  0
             mapOneField(entityDef, entity, record, sapFieldMapping);
 586  
         }
 587  0
     }
 588  
 
 589  
     protected void mapOneField(Class entityDef, Object entity, JCO.Record record, SapFieldMapping sapFieldMapping) {
 590  
         try {
 591  0
             if (logger.isDebugEnabled()) {
 592  0
                 logger.debug(
 593  
                         "Mapping property '" + sapFieldMapping.getPropertyName() + "' from field '" + sapFieldMapping
 594  
                                 .getFieldName() + "'");
 595  
             }
 596  0
             MethodInvoker invoker = reflectionCache.getCachedInvoker(sapFieldMapping);
 597  
 //            MethodInvoker invoker = null;
 598  0
             JCO.Field sapField = record.getField(sapFieldMapping.getFieldName());
 599  0
             if (invoker == null) {
 600  0
                 SapType type = SapType.getEnum(sapFieldMapping.getType());
 601  0
                 if (type == null) {
 602  0
                     throw new SapJcoMappingException(new StringBuffer("Invalid type '").append(
 603  
                             sapFieldMapping.getType()).append("'").toString());
 604  
                 }
 605  0
                 invoker = new MethodInvoker();
 606  0
                 invoker.setTargetClass(sapField.getClass());
 607  0
                 invoker.setTargetMethod("get" + type.getName());
 608  0
                 reflectionCache.cacheInvoker(sapFieldMapping, invoker);
 609  
             }
 610  0
             Object value = null;
 611  0
             synchronized (invoker) {
 612  0
                 invoker.setTargetObject(sapField);
 613  0
                 invoker.prepare();
 614  
                 try {
 615  0
                     value = invoker.invoke();
 616  0
                 } catch (InvocationTargetException e) {
 617  0
                     String stringValue = String.valueOf(value);
 618  0
                     if (JCO.ConversionException.class.equals(e.getTargetException().getClass())) {
 619  
                         //conversion failed...
 620  0
                         if (value == null || stringValue.length() == 0 || "?".equalsIgnoreCase(stringValue)) {
 621  0
                             value = null;
 622  
                         } else {
 623  0
                             throw (JCO.ConversionException) e.getTargetException();
 624  
                         }
 625  
                     }
 626  0
                 }
 627  0
             }
 628  0
             String stringValue = String.valueOf(value);
 629  0
             if (value != null && stringValue.length() > 0 && !"?".equals(stringValue)) {
 630  0
                 Field classField = reflectionCache.getCachedField(sapFieldMapping);
 631  
 //                Field classField = null;
 632  0
                 if (classField == null) {
 633  0
                     classField = entityDef.getDeclaredField(sapFieldMapping.getPropertyName());
 634  0
                     classField.setAccessible(true);
 635  0
                     reflectionCache.cacheField(sapFieldMapping, classField);
 636  
                 }
 637  0
                 classField.set(entity, value);
 638  
             }
 639  0
         } catch (Exception e) {
 640  0
             String msg = new StringBuffer("Error mapping property '")
 641  
                     .append(sapFieldMapping.getPropertyName())
 642  
                     .append("'").toString();
 643  0
             logger.error(msg, e);
 644  0
             throw new SapJcoMappingException(msg, e);
 645  0
         }
 646  0
     }
 647  
 
 648  
     protected void convertMap(JCO.Table table, Object entity, SapStructureMapping sapStructureMapping,
 649  
                               Class persistentObject) {
 650  
         // ok this is fishy, we have to do some sort of inverse mapping
 651  
         // a structure with type map means that in SAP we have a key,value pair set of rows that are returned
 652  
         // in java we want to map them on individual properties(the XML defines which key belongs to which property...)
 653  0
         table.firstRow();
 654  
         do {
 655  0
             String currentKey = table.getString(sapStructureMapping.getKeyField());
 656  0
             SapFieldMapping fieldMapping = (SapFieldMapping) sapStructureMapping.getFieldsMap().get(currentKey);
 657  0
             if (fieldMapping != null) {
 658  0
                 mapOneField(persistentObject, entity, table, fieldMapping);
 659  
             } else {
 660  0
                 if (logger.isDebugEnabled()) {
 661  0
                     logger.debug(
 662  
                             "No field mapping found for map structure or field is primary key of list_of_maps; key=" + currentKey);
 663  
                 }
 664  
             }
 665  0
         } while (table.nextRow());
 666  0
     }
 667  
 
 668  
     private Object instantiate(Class clazz) throws InstantiationException {
 669  
         try {
 670  0
             if (clazz.getName().indexOf("$") > 0) {
 671  0
                 String parentClassname = clazz.getName().substring(0, clazz.getName().indexOf("$"));
 672  0
                 Class parentClass = Class.forName(parentClassname);
 673  
                 //inner class
 674  0
                 Constructor constructor = clazz.getConstructor(new Class[]{parentClass});
 675  0
                 if (constructor == null) {
 676  0
                     throw new InstantiationException("Error instantiating inner class");
 677  
                 }
 678  0
                 return constructor.newInstance(new Object[]{parentClass.newInstance()});
 679  
             } else {
 680  0
                 return clazz.newInstance();
 681  
             }
 682  0
         } catch (Exception e) {
 683  0
             logger.error("Error creating new instance", e);
 684  0
             throw new InstantiationException("Error creating new instance");
 685  
         }
 686  
     }
 687  
 }