View Javadoc

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  public abstract class SapJcoInterface {
24      private static final Logger logger = Logger.getLogger(SapJcoInterface.class);
25      private static final Logger sapJcoXmlLogger = Logger.getLogger("SapJcoXML");
26      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          cache = Collections.synchronizedMap(new HashMap());
33      }
34  
35      protected SapDataSource dataSource;
36      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          this.maxParallelCalls = calls;
45      }
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          this.dataSource = source;
54      }
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          String cacheKey = name + FUNCTIONTEMPLATE_CACHE_KEY;
69          IFunctionTemplate template = (IFunctionTemplate) cache.get(cacheKey);
70          if (template == null) {
71              template = dataSource.getRepository().getFunctionTemplate(name);
72              if (template == null) {
73                  throw new SapJcoMappingException(new StringBuffer("Could not find function '").append(name).append(
74                          "'").toString());
75              }
76              cache.put(cacheKey, template);
77          }
78          return template.getFunction();
79      }
80  
81      private class AsyncRetrieveCall extends AsyncMethodCall {
82          private Class persistentObject;
83          private Map input;
84          private Object result;
85          private String profile;
86  
87          public AsyncRetrieveCall(Class persistentObject, Map input, String profile) {
88              this.persistentObject = persistentObject;
89              this.input = input;
90              this.profile = profile;
91          }
92  
93          protected void executeMethod() {
94              result = retrieve(persistentObject, input, profile);
95          }
96  
97          protected Object getMethodReturnValue() {
98              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         List inputListMappings = sapMapping.getInputLists();
107         SapListMapping outputListMapping = sapMapping.getOutputList();
108         SapListMapping fieldSelection = sapMapping.getFieldSelection();
109 
110         SapBapiMapping sapBapiMapping = sapMapping.getBapi();
111         JCO.Function function = getFunction(sapBapiMapping.getName());
112         JCO.Client connection = dataSource.getConnection();
113 
114         if (logger.isInfoEnabled()) {
115             logger.info("Starting SAP backend call: " + sapBapiMapping.getName());
116         }
117 
118         //map normal inported parameters
119         prepareFunctionInput(sapBapiMapping, input, function);
120         //handle input lists
121         prepareFunctionInputList(inputListMappings, function, input);
122         //handle field selections (profiles)
123         handleFieldSelection(fieldSelection, function, sapBapiMapping, profile);
124 
125         try {
126 
127             long currTime = System.currentTimeMillis();
128             if (sapJcoXmlLogger.isDebugEnabled()) {
129                 String tempFile = System.getProperty("java.io.tmpdir") + function
130                         .getName() + "_Request-" + currTime + ".xml";
131                 function.writeXML(tempFile);
132                 sapJcoXmlLogger.debug("BAPI function input written to location: " + tempFile);
133             }
134             connection.execute(function);
135             if (sapJcoXmlLogger.isDebugEnabled()) {
136                 String tempFile = System.getProperty("java.io.tmpdir") + function
137                         .getName() + "_Response-" + currTime + ".xml";
138                 function.writeXML(tempFile);
139                 sapJcoXmlLogger.debug("BAPI function output written to location: " + tempFile);
140             }
141             try {
142                 checkReturnStructure(function);
143             } catch (DataRetrievalFailureException e) {
144                 logger.error("The backend call returned no data.", e);
145                 return null; //no data found, so nothing do do...
146             }
147             JCO.ParameterList output = function.getExportParameterList();
148             if (output == null) {
149                 output = function.getTableParameterList();
150                 if (output == null) {
151                     throw new DataRetrievalFailureException("No results returned");
152                 }
153             }
154 
155             if (outputListMapping != null) {    // multiple results
156                 return parseOutputList(function, outputListMapping, sapBapiMapping, fieldSelection, persistentObject);
157             } else {    // single result
158                 //now map to persistent object
159                 return parseOutput(sapBapiMapping, fieldSelection, function, persistentObject);
160             }
161         } catch (JCO.Exception e) {
162             throw new DataRetrievalFailureException("Underlying JCO bridge threw an exception", e);
163         } catch (IllegalAccessException e) {
164             throw new DataRetrievalFailureException("Instantiation of new value object instance failed", e);
165         } catch (InstantiationException e) {
166             throw new DataRetrievalFailureException("Instantiation of new value object instance failed", e);
167         } finally {
168             dataSource.release(connection);
169         }
170     }
171 
172     private Object parseOutput(SapBapiMapping sapBapiMapping, SapListMapping fieldSelection, JCO.Function function,
173                                Class persistentObject) throws IllegalAccessException, InstantiationException {
174         Object entity = persistentObject.newInstance();
175         for (int j = 0; j < sapBapiMapping.getStructures().size(); j++) {
176             SapStructureMapping sapStructureMapping = (SapStructureMapping) sapBapiMapping.getStructures().get(j);
177             if (sapStructureMapping.isExcluded() || (fieldSelection != null && sapStructureMapping.getName().equals(
178                     fieldSelection.getName()))) {
179                 // discard excluded lists and the field selection structure when processing output
180                 continue;
181             }
182             JCO.Field field = getField(function, sapStructureMapping.getName());
183             if (field == null) {
184                 throw new DataRetrievalFailureException(new StringBuffer("Field ").append(sapStructureMapping.getName())
185                         .append(" not found in resultset").toString());
186             }
187             if (sapStructureMapping.isMap()) {
188                 if (!field.isTable()) {
189                     throw new SapJcoMappingException(new StringBuffer("Field ").append(sapStructureMapping.getName())
190                             .append(" is mapped as table but ist one").toString());
191                 }
192                 JCO.Table table = field.getTable();
193                 if (table.getNumRows() > 0) {
194                     convertMap(table, entity, sapStructureMapping, persistentObject);
195                 } else {
196                     if (logger.isDebugEnabled()) {
197                         logger.debug("Single result table contains no rows: " + table.getName());
198                     }
199                 }
200             } else if (sapStructureMapping.isEntity()) {
201                 if (!field.isTable()) {
202                     throw new SapJcoMappingException(new StringBuffer("Field ").append(sapStructureMapping.getName())
203                             .append(" is mapped as table but ist one").toString());
204                 }
205                 JCO.Table table = field.getTable();
206                 if (table.getNumRows() > 0) {
207                     if (sapStructureMapping.getCollectionName() == null) {
208                         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                     List collection = new ArrayList(table.getNumRows());
213                     try {
214                         Class entityClass = Class.forName(sapStructureMapping.getEntityClass());
215                         table.firstRow();
216                         do {
217                             Object childEntity = instantiate(entityClass);
218                             mapRecordToEntity(entityClass, childEntity, table, sapStructureMapping);
219                             collection.add(childEntity);
220                         } while (table.nextRow());
221                         BeanUtils.setProperty(entity, sapStructureMapping.getCollectionName(), collection);
222                     } catch (InvocationTargetException e) {
223                         throw new SapJcoMappingException(
224                                 "Error mapping entity collection " + sapStructureMapping.getCollectionName(), e);
225                     } catch (ClassNotFoundException e) {
226                         throw new SapJcoMappingException(
227                                 "Can not find entity class " + sapStructureMapping.getEntityClass(), e);
228                     }
229                 } else {
230                     logger.info("No rows for entity collection " + sapStructureMapping.getName());
231                 }
232             } else {
233                 JCO.Record record;
234                 if (!field.isStructure()) {
235                     field.getTable().firstRow();
236                     record = field.getTable();
237                 } else {
238                     record = field.getStructure();
239                 }
240                 mapRecordToEntity(persistentObject, entity, record, sapStructureMapping);
241             }
242         }
243         if (entity != null && entity instanceof InitializingBean) {
244             try {
245                 ((InitializingBean) entity).afterPropertiesSet();
246             } catch (Exception e) {
247                 throw new DataRetrievalFailureException(
248                         "Encountered problems while executing 'afterPropertiesSet' on value bean", e);
249             }
250         }
251         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         JCO.Field outputField = getField(function, outputListMapping.getName());
258         JCO.Table outputList = checkAndGetTable(outputField);
259         outputList.firstRow();
260         List results = new ArrayList();
261         do {
262             Object entity = persistentObject.newInstance();
263             String pkField = outputListMapping.getPrimaryKey();
264             if (outputList.getNumRows() == 0) {
265                 return results;
266             }
267             String pkValue = outputList.getString(pkField);
268             if (pkValue == null) {
269                 continue;
270             }
271             for (int j = 0; j < sapBapiMapping.getStructures().size(); j++) {
272                 SapStructureMapping sapStructureMapping = (SapStructureMapping) sapBapiMapping.getStructures().get(j);
273                 if (sapStructureMapping.isExcluded() || (fieldSelection != null && sapStructureMapping.getName().equals(
274                         fieldSelection.getName()))) {
275                     // discard excluded lists and the field selection structure when processing output
276                     continue;
277                 }
278                 JCO.Field field = getField(function, sapStructureMapping.getName());
279                 if (field == null) {
280                     throw new DataRetrievalFailureException(new StringBuffer("Field ").append(
281                             sapStructureMapping.getName()).append(" not found in resultset").toString());
282                 }
283 
284                 if (sapStructureMapping.getName().equalsIgnoreCase(outputListMapping.getName())) {
285                     //map the current row
286                     mapRecordToEntity(persistentObject, entity, outputList, sapStructureMapping);
287                 } else {
288                     //handle map
289                     if (sapStructureMapping.isListOfMaps()) {
290                         JCO.Table table = checkAndGetTable(field);
291                         if (table.getNumRows() == 0) {
292                             if (logger.isDebugEnabled()) {
293                                 logger.debug("Multiple result table contains no rows: " + table.getName());
294                             }
295                             continue; // process next row
296                         }
297 
298                         //filter
299                         JCO.Table filteredTable = filterTable(table, pkField, pkValue);
300                         if (filteredTable.getNumRows() == 0) {
301                             if (logger.isDebugEnabled()) {
302                                 logger.debug("Filtered result table contains no rows: " + table
303                                         .getName() + "; filtered on key: " + pkField + "=" + pkValue);
304                             }
305                         } else {
306                             //map
307                             convertMap(filteredTable, entity, sapStructureMapping, persistentObject);
308                         }
309                     } else if (sapStructureMapping.isList()) {
310                         //advance pointer
311                         JCO.Table record = checkAndGetTable(field);
312                         record.setRow(outputList.getRow());
313                         mapRecordToEntity(persistentObject, entity, record, sapStructureMapping);
314                     } else {
315                         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             if (entity != null && entity instanceof InitializingBean) {
321                 try {
322                     ((InitializingBean) entity).afterPropertiesSet();
323                 } catch (Exception e) {
324                     throw new DataRetrievalFailureException(
325                             "Encountered problems while executing 'afterPropertiesSet' on value bean", e);
326                 }
327             }
328             results.add(entity);
329         } while (outputList.nextRow());
330         return results;
331     }
332 
333     private void handleFieldSelection(SapListMapping fieldSelection, JCO.Function function,
334                                       SapBapiMapping sapBapiMapping, String profile) {
335         if (fieldSelection != null) {
336             JCO.Structure fieldSelectionStructure = function.getImportParameterList().getStructure(
337                     fieldSelection.getName());
338             Collection selectedFields = sapBapiMapping.getFieldSelection(profile);
339             if (selectedFields == null) {
340                 //select ALL
341                 JCO.FieldIterator iterator = fieldSelectionStructure.fields();
342                 while (iterator.hasNextFields()) {
343                     JCO.Field field = iterator.nextField();
344                     field.setValue(SELECTED);
345                 }
346             } else {
347                 for (Iterator i = selectedFields.iterator(); i.hasNext();) {
348                     Object value = i.next();
349                     fieldSelectionStructure.setValue(SELECTED, String.valueOf(value));
350                 }
351             }
352         }
353     }
354 
355     private void prepareFunctionInputList(List inputListMappings, JCO.Function function, Map input) {
356         if (inputListMappings != null) {
357             JCO.ParameterList tableList = function.getTableParameterList();
358             for (Iterator inputListIterator = inputListMappings.iterator(); inputListIterator.hasNext();) {
359                 SapListMapping inputListMapping = (SapListMapping) inputListIterator.next();
360                 JCO.Table inputTable = tableList.getTable(inputListMapping.getName());
361                 if (inputTable == null) {
362                     throw new SapJcoMappingException("Input table '" + inputListMapping.getName() + "' doesn't exist");
363                 }
364                 Collection inputValues = (Collection) input.get(inputListMapping.getName());
365                 if (inputValues != null) {
366                     for (Iterator valueIterator = inputValues.iterator(); valueIterator.hasNext();) {
367                         Object value = valueIterator.next();
368                         inputTable.appendRow();
369                         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                             Map fields = (Map) value;
374                             for (Iterator entrySetIterator = fields.entrySet().iterator(); entrySetIterator.hasNext();)
375                             {
376                                 Map.Entry entry = (Map.Entry) entrySetIterator.next();
377                                 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                             inputTable.setValue(String.valueOf(value), inputListMapping.getPrimaryKey());
383                         }
384                     }
385                 }
386             }
387         }
388     }
389 
390     private void prepareFunctionInput(SapBapiMapping sapBapiMapping, Map input, JCO.Function function) {
391         // Prepare input parameters for SAP function call
392         for (int j = 0; j < sapBapiMapping.getInput().size(); j++) {
393             SapInput sapInput = (SapInput) sapBapiMapping.getInput().get(j);
394             String value = (String) input.get(sapInput.getName());
395             if ((value == null || value.trim().length() == 0) && sapInput.isRequired()) {
396                 if (sapInput.getDefaultValue() != null && sapInput.getDefaultValue().length() > 0) {
397                     value = sapInput.getDefaultValue();
398                 } else {
399                     throw new SapJcoMappingException("Missing input parameter '" + sapInput
400                             .getName() + "' (required and no default value supplied)");
401                 }
402             }
403             if (logger.isDebugEnabled()) {
404                 logger.debug(new StringBuffer("Setting import parameter '").append(sapInput.getName()).append("' to '")
405                         .append(value).append("'"));
406             }
407             function.getImportParameterList().setValue(value, sapInput.getName());
408         }
409     }
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         JCO.Table subTable = new JCO.Table(table.getMetaData());
423         table.firstRow();
424         do {
425             if (pkValue.equals(table.getString(pkField))) {
426                 // copy current row
427                 subTable.appendRow();
428                 for (int counter = 0; counter < table.getNumColumns(); counter++) {
429                     subTable.setValue(table.getValue(counter), counter);
430                 }
431             }
432         } while (table.nextRow());
433         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         if (!field.isTable()) {
445             throw new SapJcoMappingException(new StringBuffer("Field ").append(field.getName()).append(
446                     " is mapped as table but isn't one").toString());
447         }
448         return field.getTable();
449     }
450 
451     private JCO.Field getField(JCO.Function function, String name) {
452         JCO.ParameterList output = function.getExportParameterList();
453         if (output != null && output.hasField(name)) {
454             return output.getField(name);
455         }
456         output = function.getTableParameterList();
457         if (output != null && output.hasField(name)) {
458             return output.getField(name);
459         }
460         return null;
461     }
462 
463     protected List retrieve(Class persistentObject, List inputMaps) {
464         return retrieve(persistentObject, inputMaps, null);
465     }
466 
467     protected List retrieve(Class persistentObject, List inputMaps, String profile) {
468         if (persistentObject == null) {
469             throw new IllegalArgumentException("'persistentObject' parameter is required.");
470         }
471         if (inputMaps == null || inputMaps.size() == 0) {
472             //nothing to fetch, so return an empty list
473             return new ArrayList(0);
474         }
475 
476         //create threadpool and call retrieve parellel...
477         PooledExecutor pool = new PooledExecutor(maxParallelCalls);
478         pool.setMaximumPoolSize(maxParallelCalls);
479         pool.setMinimumPoolSize(1);
480         pool.waitWhenBlocked();
481 
482         logger.warn("Using internal thread pool to execute " + inputMaps.size() + " requests in parallel");
483         try {
484             List results = new ArrayList(inputMaps.size());
485             List calls = new ArrayList(inputMaps.size());
486             for (int i = 0; i < inputMaps.size(); i++) {
487                 Map input = (Map) inputMaps.get(i);
488                 AsyncRetrieveCall call = new AsyncRetrieveCall(persistentObject, input, profile);
489                 calls.add(call);
490                 pool.execute(call);
491             }
492             for (int i = 0; i < calls.size(); i++) {
493                 AsyncRetrieveCall asyncRetrieveCall = (AsyncRetrieveCall) calls.get(i);
494                 Object result = asyncRetrieveCall.getMethodReturnValue();
495                 if (result != null) {
496                     results.add(result);
497                 }
498             }
499             return results;
500         } catch (InterruptedException e) {
501             throw new DataRetrievalFailureException("The request has been interrupted by the system", e);
502         }
503     }
504 
505     protected Object retrieve(Class persistentObject, Map input) {
506         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         if (persistentObject == null) {
555             throw new IllegalArgumentException("'persistentObject' parameter is required.");
556         }
557         if (input == null) {
558             input = new HashMap(0);
559         }
560         try {
561             //first validate the mapping...
562             SapMapping sapMapping = SapMappingParser.getInfo(persistentObject);
563             return performSapCall(sapMapping, input, persistentObject, profile);
564         } catch (Exception e) {
565             throw new DataRetrievalFailureException("Error fetching data", e);
566         }
567     }
568 
569     protected void checkReturnStructure(JCO.Function function) throws DataRetrievalFailureException {
570         if (function.getExportParameterList() != null && function.getExportParameterList().hasField("RETURN")) {
571             JCO.Structure returnStructure = function.getExportParameterList().getStructure("RETURN");
572             if (! ("".equals(returnStructure.getString("TYPE")) ||
573                     "S".equals(returnStructure.getString("TYPE")) ||
574                     "W".equals(returnStructure.getString("TYPE")))) {
575                 throw new DataRetrievalFailureException(new StringBuffer(returnStructure.getString("TYPE"))
576                         .append(": ").append(returnStructure.getString("MESSAGE")).toString());
577             }
578         }
579     }
580 
581     protected void mapRecordToEntity(Class entityDef, Object entity, JCO.Record record,
582                                      SapStructureMapping structureMapping) {
583         for (Iterator fieldsIt = structureMapping.getFields().iterator(); fieldsIt.hasNext();) {
584             SapFieldMapping sapFieldMapping = (SapFieldMapping) fieldsIt.next();
585             mapOneField(entityDef, entity, record, sapFieldMapping);
586         }
587     }
588 
589     protected void mapOneField(Class entityDef, Object entity, JCO.Record record, SapFieldMapping sapFieldMapping) {
590         try {
591             if (logger.isDebugEnabled()) {
592                 logger.debug(
593                         "Mapping property '" + sapFieldMapping.getPropertyName() + "' from field '" + sapFieldMapping
594                                 .getFieldName() + "'");
595             }
596             MethodInvoker invoker = reflectionCache.getCachedInvoker(sapFieldMapping);
597 //            MethodInvoker invoker = null;
598             JCO.Field sapField = record.getField(sapFieldMapping.getFieldName());
599             if (invoker == null) {
600                 SapType type = SapType.getEnum(sapFieldMapping.getType());
601                 if (type == null) {
602                     throw new SapJcoMappingException(new StringBuffer("Invalid type '").append(
603                             sapFieldMapping.getType()).append("'").toString());
604                 }
605                 invoker = new MethodInvoker();
606                 invoker.setTargetClass(sapField.getClass());
607                 invoker.setTargetMethod("get" + type.getName());
608                 reflectionCache.cacheInvoker(sapFieldMapping, invoker);
609             }
610             Object value = null;
611             synchronized (invoker) {
612                 invoker.setTargetObject(sapField);
613                 invoker.prepare();
614                 try {
615                     value = invoker.invoke();
616                 } catch (InvocationTargetException e) {
617                     String stringValue = String.valueOf(value);
618                     if (JCO.ConversionException.class.equals(e.getTargetException().getClass())) {
619                         //conversion failed...
620                         if (value == null || stringValue.length() == 0 || "?".equalsIgnoreCase(stringValue)) {
621                             value = null;
622                         } else {
623                             throw (JCO.ConversionException) e.getTargetException();
624                         }
625                     }
626                 }
627             }
628             String stringValue = String.valueOf(value);
629             if (value != null && stringValue.length() > 0 && !"?".equals(stringValue)) {
630                 Field classField = reflectionCache.getCachedField(sapFieldMapping);
631 //                Field classField = null;
632                 if (classField == null) {
633                     classField = entityDef.getDeclaredField(sapFieldMapping.getPropertyName());
634                     classField.setAccessible(true);
635                     reflectionCache.cacheField(sapFieldMapping, classField);
636                 }
637                 classField.set(entity, value);
638             }
639         } catch (Exception e) {
640             String msg = new StringBuffer("Error mapping property '")
641                     .append(sapFieldMapping.getPropertyName())
642                     .append("'").toString();
643             logger.error(msg, e);
644             throw new SapJcoMappingException(msg, e);
645         }
646     }
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         table.firstRow();
654         do {
655             String currentKey = table.getString(sapStructureMapping.getKeyField());
656             SapFieldMapping fieldMapping = (SapFieldMapping) sapStructureMapping.getFieldsMap().get(currentKey);
657             if (fieldMapping != null) {
658                 mapOneField(persistentObject, entity, table, fieldMapping);
659             } else {
660                 if (logger.isDebugEnabled()) {
661                     logger.debug(
662                             "No field mapping found for map structure or field is primary key of list_of_maps; key=" + currentKey);
663                 }
664             }
665         } while (table.nextRow());
666     }
667 
668     private Object instantiate(Class clazz) throws InstantiationException {
669         try {
670             if (clazz.getName().indexOf("$") > 0) {
671                 String parentClassname = clazz.getName().substring(0, clazz.getName().indexOf("$"));
672                 Class parentClass = Class.forName(parentClassname);
673                 //inner class
674                 Constructor constructor = clazz.getConstructor(new Class[]{parentClass});
675                 if (constructor == null) {
676                     throw new InstantiationException("Error instantiating inner class");
677                 }
678                 return constructor.newInstance(new Object[]{parentClass.newInstance()});
679             } else {
680                 return clazz.newInstance();
681             }
682         } catch (Exception e) {
683             logger.error("Error creating new instance", e);
684             throw new InstantiationException("Error creating new instance");
685         }
686     }
687 }