001/*
002 * Copyright 2017 the original author or authors.
003 *
004 * Licensed to the Apache Software Foundation (ASF) under one or more
005 * contributor license agreements.  See the NOTICE file distributed with
006 * this work for additional information regarding copyright ownership.
007 * The ASF licenses this file to You under the Apache License, Version 2.0
008 * (the "License"); you may not use this file except in compliance with
009 * the License.  You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package net.sf.jkniv.camel.sap.jco3;
020
021import java.time.Duration;
022import java.time.Instant;
023import java.time.LocalDate;
024import java.time.LocalDateTime;
025import java.time.LocalTime;
026import java.util.ArrayList;
027import java.util.Calendar;
028import java.util.Date;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Map.Entry;
035import java.util.Set;
036
037import org.apache.camel.Exchange;
038import org.apache.camel.RuntimeCamelException;
039import org.apache.camel.impl.DefaultProducer;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043import com.sap.conn.jco.JCoDestination;
044import com.sap.conn.jco.JCoDestinationManager;
045import com.sap.conn.jco.JCoField;
046import com.sap.conn.jco.JCoFieldIterator;
047import com.sap.conn.jco.JCoFunction;
048import com.sap.conn.jco.JCoStructure;
049import com.sap.conn.jco.JCoTable;
050
051/**
052 * Supports communication with the SAP Server in inbound calls (Java calls ABAP) using JCo.
053 *  
054 */
055public class SapJcoProducer extends DefaultProducer
056{
057    private static final Logger        LOG             = LoggerFactory.getLogger(SapJcoProducer.class);
058    private SapJcoEndpoint             endpoint;
059    private static final Set<Class<?>> SUPPORTED_TYPES = new HashSet<Class<?>>();
060    static
061    {
062        SUPPORTED_TYPES.add(String.class);
063        SUPPORTED_TYPES.add(boolean.class);
064        SUPPORTED_TYPES.add(char.class);
065        SUPPORTED_TYPES.add(byte.class);
066        SUPPORTED_TYPES.add(short.class);
067        SUPPORTED_TYPES.add(int.class);
068        SUPPORTED_TYPES.add(long.class);
069        SUPPORTED_TYPES.add(float.class);
070        SUPPORTED_TYPES.add(double.class);
071        SUPPORTED_TYPES.add(Boolean.class);
072        SUPPORTED_TYPES.add(Character.class);
073        SUPPORTED_TYPES.add(Byte.class);
074        SUPPORTED_TYPES.add(Short.class);
075        SUPPORTED_TYPES.add(Integer.class);
076        SUPPORTED_TYPES.add(Long.class);
077        SUPPORTED_TYPES.add(Float.class);
078        SUPPORTED_TYPES.add(Double.class);
079        SUPPORTED_TYPES.add(Void.class);
080        SUPPORTED_TYPES.add(Date.class);
081        SUPPORTED_TYPES.add(Calendar.class);
082        SUPPORTED_TYPES.add(LocalDate.class);
083        SUPPORTED_TYPES.add(LocalDateTime.class);
084        SUPPORTED_TYPES.add(LocalTime.class);
085        SUPPORTED_TYPES.add(Duration.class);
086        SUPPORTED_TYPES.add(Instant.class);
087    }
088    
089    public SapJcoProducer(SapJcoEndpoint endpoint)
090    {
091        super(endpoint);
092        this.endpoint = endpoint;
093    }
094    
095    public void process(Exchange exchange) throws Exception
096    {
097        JCoDestination destination = JCoDestinationManager.getDestination(endpoint.getSapDestName());
098        JCoFunction function = destination.getRepository().getFunction(endpoint.getSapFunction());
099        setParams(exchange, function);
100        function.execute(destination);
101        
102        JCoTable codes = function.getTableParameterList().getTable(endpoint.getSapJcoTable());
103        List<Object> result = new ArrayList<Object>();
104        ParserResult<?> parserResult = getParserResult();
105        for (int i = 0; i < codes.getNumRows(); i++)
106        {
107            codes.setRow(i);
108            Object o = parserResult.getValues(codes, i);
109            if (o != null || endpoint.isSupportsNull())
110                result.add(o);
111        }
112        // preserve headers
113        exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
114        exchange.getOut().setBody(result);
115    }
116    
117    @SuppressWarnings("unchecked")
118    private <T> ParserResult<T> getParserResult()
119    {
120        ParserResult<T> result = null;
121        try
122        {
123            result = (ParserResult<T>) endpoint.getClassToParserResultStrategy().newInstance();
124        }
125        catch (InstantiationException | IllegalAccessException e)
126        {
127            throw new RuntimeCamelException(
128                    "Cannot create new instance of [" + endpoint.getParserResultStrategy() + "]", e);
129        }
130        return result;
131    }
132    
133    @SuppressWarnings("unchecked")
134    private void setParams(Exchange exchange, JCoFunction function)
135    {
136        Map<String, Object> params = getParams(exchange);
137        Set<String> keys = params.keySet();
138        //Find structure parameter with this name and set the appropriate values
139        JCoFieldIterator iter = function.getImportParameterList().getFieldIterator();
140        while (iter.hasNextField())
141        {
142            JCoField f = iter.nextField();
143            //LOG.debug("field={}, isTable={}, isStructure={}", f.getName(), f.isTable(), f.isStructure());
144            if (f.isStructure())
145            {
146                setStructureParameter(f, params);
147            }
148            else
149            {
150                Object value = params.get(f.getName());
151                if (isSupportedType(value))
152                {
153                    function.getImportParameterList().setValue(f.getName(), value);
154                    if (LOG.isDebugEnabled())
155                        LOG.debug("setting param=[{}] value=[{}], typeof=[{}], prefix=[{}]", f.getName(), value,
156                                (value != null ? value.getClass().getName() : "null"), endpoint.getPrefixParams());
157                }
158                else
159                    LOG.info("Type [{}] not is supported as parameter try a type of {}", value.getClass(),
160                            SUPPORTED_TYPES);
161            }
162        }
163        if (endpoint.getSapJcoTableIn() != null && !endpoint.getSapJcoTableIn().equals(""))
164        {
165            JCoTable codes = function.getTableParameterList().getTable(endpoint.getSapJcoTableIn());
166            if (codes != null)
167            {
168                //LOG.debug("{}", codes);
169                Object tableIn = getParams(exchange).get(endpoint.getSapJcoTableIn());
170                if (tableIn instanceof List)
171                {
172                    List<Map<String, Object>> list = (List<Map<String, Object>>) tableIn;
173                    setTableParameter(codes, list);
174                }
175                else if (tableIn != null)
176                {
177                    LOG.warn("The parameter sapJcoTableIn must be instance of List of Map, type [{}] isn't supported",
178                            tableIn.getClass());
179                }
180            }
181        }
182    }
183    
184    /**
185     * Sets a single Importing or Changing parameter that is a structure
186     * @param f field name of parameter
187     * @param map the value of the parameter
188     */
189    private void setStructureParameter(JCoField f, Map<String, Object> map)
190    {
191        Iterator fieldIter = map.entrySet().iterator();
192        JCoStructure structure = f.getStructure();
193        while (fieldIter.hasNext())
194        {
195            Entry field = (Map.Entry) fieldIter.next();
196            String k = field.getKey().toString();
197            Object v = field.getValue();
198            if (isSupportedType(v))
199            {
200                structure.setValue(k, v);
201                if (LOG.isDebugEnabled())
202                    LOG.debug("setting param=[{}] value=[{}], typeof=[{}], prefix=[{}]", k, v,
203                            (v != null ? v.getClass().getName() : "null"), endpoint.getPrefixParams());
204            }
205        }
206    }
207    
208    /**
209     *  Sets a single Table parameter that is a structure
210     * @param table JCo table for input parameters
211     * @param list The value of the parameter (A List of HashMap)
212     */
213    @SuppressWarnings("unchecked")
214    private void setTableParameter(JCoTable table, List<Map<String, Object>> list)
215    {
216        Iterator it = list.listIterator();
217        while (it.hasNext())
218        {
219            table.appendRow();
220            Map<String, Object> params = (Map<String, Object>) it.next();
221            Iterator itParams = params.entrySet().iterator();
222            while (itParams.hasNext())
223            {
224                Entry<String, Object> entry = (Map.Entry<String, Object>) itParams.next();
225                table.setValue(entry.getKey(), entry.getValue());
226                if (LOG.isDebugEnabled())
227                    LOG.debug("setting param=[{}] value=[{}], typeof=[{}]", entry.getKey(), entry.getValue(),
228                            (entry.getValue() != null ? entry.getValue().getClass().getName() : "null"));
229                
230            }
231        }
232    }
233    
234    /*
235    public void setStructureParameter(JCoFunction function, String name, Map<String, Object> map)
236    {
237        //Find structure parameter with this name and set the appropriate values
238        JCoFieldIterator iter = function.getImportParameterList().getFieldIterator();
239        while(iter.hasNextField())
240        {
241            JCoField f = iter.nextField();
242            if(f.getName().equals(name) & f.isStructure())
243            {
244                Iterator fieldIter = map.entrySet().iterator();
245                JCoStructure structure = f.getStructure();
246                while(fieldIter.hasNext())
247                {
248                     Entry field = (Map.Entry)fieldIter.next();
249                     structure.setValue(field.getKey().toString(), field.getValue().toString());
250                     LOG.debug("setting param=[{}] value=[{}], typeof=[{}], prefix=[{}]", field.getKey().toString(), field.getValue().toString(),
251                             field.getValue().getClass().getName(), endpoint.getPrefixParams() );
252                }
253            }
254        }
255    }
256    */
257    
258    /*
259     *  Sets a single Table parameter that is a structure
260     * @param name the name of the parameter
261     * @param list The value of the parameter (A LinkedList of LinkedHashmaps)
262     *
263    private void setTableParameter(JCoFunction function,  String name, LinkedList list)
264    {
265        //Find table parameter with this name and set the appropriate valies
266        JCoFieldIterator iter = function.getTableParameterList().getFieldIterator();
267        while(iter.hasNextField())
268        {
269            JCoField f = iter.nextField();
270            if(f.getName().equals(name) & f.isTable() )
271            {
272                Iterator recordIter = list.listIterator();
273                JCoTable table = f.getTable();
274                while(recordIter.hasNext())
275                {
276                   table.appendRow();
277                   LinkedHashMap fields = (LinkedHashMap)recordIter.next();
278                   Iterator fieldIter = fields.entrySet().iterator();
279                   while(fieldIter.hasNext())
280                   {
281                         Entry field = (Map.Entry)fieldIter.next();
282                         table.setValue(field.getKey().toString(), field.getValue().toString());
283                   }
284                }
285            }
286        }
287    }
288    */
289    
290    @SuppressWarnings("unchecked")
291    private Map<String, Object> getParams(Exchange exchange)
292    {
293        Map<String, Object> params = exchange.getIn().getBody(Map.class);
294        boolean usingPrefix = false;
295        if (endpoint.isUseHeaderAsParam() || params == null || params.isEmpty())
296        {
297            Map<String, Object> paramsSap = exchange.getIn().getHeader(endpoint.getPrefixParams(), Map.class);
298            if (paramsSap == null)
299                paramsSap = exchange.getIn().getHeaders();
300            
301            Set<String> keys = paramsSap.keySet();
302            params = new HashMap<String, Object>();
303            for (String key : keys)
304            {
305                if (key.startsWith(endpoint.getPrefixParams()))
306                {
307                    usingPrefix = true;
308                    String paramName = key.substring(endpoint.getPrefixParams().length());
309                    Object value = paramsSap.get(key);
310                    params.put(paramName, value);
311                }
312            }
313        }
314        if (endpoint.isUseHeaderAsParam() && !usingPrefix)
315        {
316            LOG.warn("When Endpoint option useHeaderAsParam=true the parameters name must be prefixed with [{}]",
317                    endpoint.getPrefixParams());
318        }
319        return params;
320    }
321    
322    private boolean isSupportedType(Object value)
323    {
324        boolean supported = true;
325        if (value != null)
326        {
327            supported = SUPPORTED_TYPES.contains(value.getClass());
328        }
329        return supported;
330    }
331}