b5a9df706fa06fde4ed6e8e91046a2c21ed1c81d
[ccsdk/sli.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - CCSDK
4  * ================================================================================
5  * Copyright (C) 2018 Huawei Technologies Co., Ltd. All rights reserved.
6  * Modifications Copyright (c) 2021 AT&T
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.ccsdk.sli.plugins.restconfapicall;
23
24 import static com.google.common.base.Strings.repeat;
25 import static java.lang.String.format;
26 import static java.lang.String.valueOf;
27 import static org.apache.commons.lang3.StringUtils.join;
28 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.DELETE;
29 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.GET;
30 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.PATCH;
31 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.PUT;
32 import static org.onap.ccsdk.sli.plugins.restapicall.RestapiCallNode.parseParam;
33 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.COLON;
34 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.COMMA;
35 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HEADER;
36 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HTTP_REQ;
37 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HTTP_RES;
38 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.REQ_ERR;
39 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_CODE;
40 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_MSG;
41 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_PRE;
42 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.getSchemaCtxFromDir;
43 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.getUpdatedXmlReq;
44 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.getYangParameters;
45 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.parseUrl;
46 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfListenerFactory.instance;
47 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerUtil.FORMAT_ERR;
48 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerUtil.UTF_HEADER;
49 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerUtil.XML_TREE_ERR;
50 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerUtil.getXmlWriter;
51 import static org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.MdsalPropertiesNodeUtils.getModuleNamespace;
52 import com.google.gson.Gson;
53 import com.google.gson.JsonElement;
54 import com.google.gson.JsonObject;
55 import com.google.gson.JsonParser;
56 import com.google.gson.stream.JsonWriter;
57 import java.io.StringWriter;
58 import java.io.Writer;
59 import java.net.SocketException;
60 import java.net.URI;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Map;
65 import org.dom4j.Document;
66 import org.dom4j.DocumentException;
67 import org.dom4j.DocumentHelper;
68 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
69 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
70 import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin;
71 import org.onap.ccsdk.sli.plugins.restapicall.Format;
72 import org.onap.ccsdk.sli.plugins.restapicall.HttpResponse;
73 import org.onap.ccsdk.sli.plugins.restapicall.RestapiCallNode;
74 import org.onap.ccsdk.sli.plugins.restapicall.XmlParser;
75 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DataFormatSerializer;
76 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DataFormatSerializerContext;
77 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerFactory;
78 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.Listener;
79 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.MdsalSerializerHelper;
80 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.SerializerHelper;
81 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.YangParameters;
82 import org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.MdsalPropertiesNodeSerializer;
83 import org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.Namespace;
84 import org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.PropertiesNodeSerializer;
85 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
86 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
87 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
88 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
89 import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
90 import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
93
94 /**
95  * Representation of a plugin to enable RESTCONF based CRUD operations from DG.
96  */
97 public class RestconfApiCallNode implements SvcLogicJavaPlugin {
98
99     /**
100      * Logger for the restconf api call node class.
101      */
102     private static final Logger log = LoggerFactory.getLogger(
103             RestconfApiCallNode.class);
104
105     /**
106      * Rest api call node service instance
107      */
108     private RestapiCallNode restapiCallNode;
109
110     /**
111      * Yang parser factory
112      */
113     private YangParserFactory parserFactory;
114
115
116     /**
117      * Creates an instance of restconf api call node with restapi call node, within OSGi
118      *
119      * @param r restapi call node
120      * @param parserFactory Yang parser factory
121      */
122     public RestconfApiCallNode(RestapiCallNode r, YangParserFactory parserFactory) {
123         this.restapiCallNode = r;
124         this.parserFactory = parserFactory;
125     }
126
127     /**
128      * Returns the restapi call node instance.
129      * @return
130      */
131     public RestapiCallNode getRestapiCallNode() {
132         return restapiCallNode;
133     }
134
135
136     /**
137      * Returns the yang parser factory instance
138      * @return
139      */
140     public YangParserFactory getParserFactory() {
141         return parserFactory;
142     }
143
144     /**
145      * set the yang parser factory instance
146      * @return
147      */
148     public void setParserFactory(YangParserFactory parserFactory) {
149         this.parserFactory = parserFactory;
150     }
151
152
153
154     /**
155      * Sends the restconf request using the parameters map and the memory
156      * context. And this method allows the directed graphs to interact with
157      * the restconf api call node
158      *
159      * @param paramMap parameters map
160      * @param ctx      service logic context
161      * @throws SvcLogicException when svc logic exception occurs
162      */
163     public void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx)
164             throws SvcLogicException {
165         sendRequest(paramMap, ctx, 0);
166     }
167
168     /**
169      * Sends the restconf request using the parameters map and the memory
170      * context along with the retry count.
171      *
172      * @param paramMap   parameters map
173      * @param ctx        service logic context
174      * @param retryCount number of retry counts
175      * @throws SvcLogicException when svc logic exception occurs
176      */
177     public void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx,
178                             Integer retryCount) throws SvcLogicException {
179         RestapiCallNode rest = getRestapiCallNode();
180         HttpResponse r = new HttpResponse();
181         try {
182             YangParameters p = getYangParameters(paramMap);
183
184             String pp = p.responsePrefix != null ? p.responsePrefix + '.' : "";
185             Map<String, String> props = new HashMap<>((Map)ctx.toProperties());
186             String uri = parseUrl(p.restapiUrl, p.httpMethod);
187             InstanceIdentifierContext<?> insIdCtx = getInsIdCtx(p, uri);
188
189             String req = null;
190             if (p.httpMethod != GET && p.httpMethod != DELETE) {
191                 req = serializeRequest(props, p, uri, insIdCtx);
192                 if (p.httpMethod == PUT || p.httpMethod == PATCH) {
193                     updateReq(req, p, insIdCtx);
194                 }
195             }
196             if (req == null && p.requestBody != null) {
197                 req = p.requestBody;
198             }
199
200             r = rest.sendHttpRequest(req, p);
201             if (p.returnRequestPayload && req != null) {
202                 ctx.setAttribute(pp + HTTP_REQ, req);
203             }
204
205             String response = getResponse(ctx, p, pp, r);
206             if (response != null) {
207                 try {
208                     Map<String, String> resProp = serializeResponse(
209                             p, uri, response, insIdCtx);
210                     for (Map.Entry<String, String> pro : resProp.entrySet()) {
211                         ctx.setAttribute(pro.getKey(), pro.getValue());
212                     }
213                 } catch (SvcLogicException e) {
214                     convertToNormalRes(ctx, p, pp, response);
215                 }
216             }
217         } catch (SvcLogicException e) {
218             boolean shouldRetry = false;
219             if (e.getCause().getCause() instanceof SocketException) {
220                 shouldRetry = true;
221             }
222
223             log.error(REQ_ERR + e.getMessage(), e);
224             String prefix = parseParam(paramMap, RES_PRE, false, null);
225             setFailureResponseStatus(ctx, prefix, e.getMessage());
226         }
227
228         if (r != null && r.code >= 300) {
229             throw new SvcLogicException(valueOf(r.code) +
230                                                 COLON + " " + r.message);
231         }
232     }
233
234     private void convertToNormalRes(SvcLogicContext ctx ,
235                                     YangParameters p, String pp, String body)
236             throws SvcLogicException {
237         if (p.convertResponse) {
238             Map<String, String> mm = null;
239             if (p.format == Format.XML) {
240                 mm = XmlParser.convertToProperties(body, p.listNameList);
241             } else if (p.format == Format.JSON) {
242                 mm = org.onap.ccsdk.sli.plugins.restapicall.JsonParser
243                         .convertToProperties(body);
244             }
245
246             if (mm != null) {
247                 for (Map.Entry<String, String> entry : mm.entrySet()) {
248                     ctx.setAttribute(pp + entry.getKey(),
249                                      entry.getValue());
250                 }
251             }
252         }
253     }
254
255     /**
256      * Serializes the request message to JSON or XML from the properties.
257      *
258      * @param properties properties
259      * @param params     YANG parameters
260      * @param uri        URI
261      * @param insIdCtx   instance identifier context
262      * @return JSON or XML message to be sent
263      * @throws SvcLogicException when serializing the request fails
264      */
265      public String serializeRequest(Map<String, String> properties,
266                                     YangParameters params, String uri,
267                                     InstanceIdentifierContext insIdCtx)
268              throws SvcLogicException {
269         PropertiesNodeSerializer propSer = new MdsalPropertiesNodeSerializer(
270                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
271         DataFormatSerializerContext serCtx = new DataFormatSerializerContext(
272                 null, uri, null, propSer);
273         DataFormatSerializer ser = DfSerializerFactory.instance()
274                 .getSerializer(serCtx, params);
275          //TODO: Handling of XML annotations
276         return ser.encode(properties, null);
277     }
278
279     /**
280      * Serializes the response message from JSON or XML to the properties.
281      *
282      * @param params   YANG parameters
283      * @param uri      URI
284      * @param response response message
285      * @param insIdCtx instance identifier context
286      * @return response message as properties
287      * @throws SvcLogicException when serializing the response fails
288      */
289     public Map<String, String> serializeResponse(YangParameters params,
290                                                  String uri, String response,
291                                                  InstanceIdentifierContext insIdCtx)
292             throws SvcLogicException {
293         PropertiesNodeSerializer propSer = new MdsalPropertiesNodeSerializer(
294                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
295         SerializerHelper helper = new MdsalSerializerHelper(
296                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
297         Listener listener = instance().getListener(helper, params);
298         DataFormatSerializerContext serCtx = new DataFormatSerializerContext(
299                 listener, uri, null, propSer);
300         DataFormatSerializer ser = DfSerializerFactory.instance()
301                 .getSerializer(serCtx, params);
302         return ser.decode(response);
303     }
304
305     /**
306      * Returns instance identifier context for a uri using the schema context.
307      *
308      * @param params YANG parameters
309      * @param uri    URI
310      * @return instance identifier context
311      * @throws SvcLogicException when getting schema context fails
312      */
313     private InstanceIdentifierContext<?> getInsIdCtx(YangParameters params,
314                                                      String uri)
315             throws SvcLogicException {
316         EffectiveModelContext context = getSchemaContext(params);
317         return ParserIdentifier.toInstanceIdentifier(uri, context, null);
318     }
319
320     /**
321      * Returns the global schema context or schema context of particular YANG
322      * files present in a directory path.
323      *
324      * @param params YANG parameters
325      * @return schema context
326      * @throws SvcLogicException when schema context fetching fails
327      */
328     private EffectiveModelContext getSchemaContext(YangParameters params) throws SvcLogicException {
329         try {
330
331             if (params.dirPath != null) {
332                 return getSchemaCtxFromDir(getParserFactory(), params.dirPath);
333             } else {
334                 return (getParserFactory().createParser().buildEffectiveModel());
335             }
336         } catch (YangParserException e) {
337             throw new SvcLogicException("Caught exception creating yang model context", e);
338         }
339     }
340
341     /**
342      * Returns the response message body of a http response message.
343      *
344      * @param ctx    svc logic context
345      * @param params parameters
346      * @param pre    prefix to be appended
347      * @param res    http response
348      * @return response message body
349      */
350     public String getResponse(SvcLogicContext ctx, YangParameters params,
351                                String pre, HttpResponse res) {
352         ctx.setAttribute(pre + RES_CODE, valueOf(res.code));
353         ctx.setAttribute(pre + RES_MSG, res.message);
354
355         if (params.dumpHeaders && res.headers != null) {
356             for (Map.Entry<String, List<String>> a : res.headers.entrySet()) {
357                 ctx.setAttribute(pre + HEADER + a.getKey(),
358                                  join(a.getValue(), COMMA));
359             }
360         }
361
362         if (res.body != null && res.body.trim().length() > 0) {
363             ctx.setAttribute(pre + HTTP_RES, res.body);
364             return res.body;
365         }
366         return null;
367     }
368
369     /**
370      * Sets the failure response status in the context memory.
371      *
372      * @param ctx    service logic context
373      * @param prefix prefix to be added
374      * @param errMsg error message
375      */
376     private void setFailureResponseStatus(SvcLogicContext ctx, String prefix,
377                                           String errMsg) {
378         HttpResponse res = new HttpResponse();
379         res.code = 500;
380         res.message = errMsg;
381         ctx.setAttribute(prefix + RES_CODE, valueOf(res.code));
382         ctx.setAttribute(prefix + RES_MSG, res.message);
383     }
384
385     /**
386      * Updates request message for JSON and XML data format, when the HTTP
387      * method points it as PUT or PATCH.
388      *
389      * @param req      current request message
390      * @param p        YANG parameters
391      * @param insIdCtx instance identifier context
392      * @return update request message
393      * @throws SvcLogicException when the data format type is wrong
394      */
395     public String updateReq(String req, YangParameters p,
396                              InstanceIdentifierContext<?> insIdCtx)
397             throws SvcLogicException {
398
399         SchemaNode schemaNode = insIdCtx.getSchemaNode();
400         Namespace modNs = getModuleNamespace(schemaNode.getQName(),
401                                              insIdCtx.getSchemaContext());
402         String nodeName = schemaNode.getQName().getLocalName();
403
404         switch (p.format) {
405             case JSON:
406                 return getUpdatedJsonReq(req, nodeName, modNs.moduleName());
407
408             case XML:
409                 return getXmlReqForPutOp(req, nodeName, modNs.moduleNs());
410
411             default:
412                 throw new SvcLogicException(format(FORMAT_ERR, p.format));
413         }
414     }
415
416     /**
417      * Returns the updated JSON request message, when the HTTP method
418      * points to PUT or PATCH.
419      *
420      * @param req      current JSON request message
421      * @param nodeName root node name
422      * @param modName  module name of the root node
423      * @return update JSON request message
424      */
425     private String getUpdatedJsonReq(String req, String nodeName,
426                                      String modName) {
427         Writer writer = new StringWriter();
428         JsonWriter jsonWriter = new JsonWriter(writer);
429         jsonWriter.setIndent(repeat(" ", 4));
430
431         JsonParser jsonParser = new JsonParser();
432         JsonObject oldJson = (JsonObject)jsonParser.parse(req);
433         oldJson = remChildModName(oldJson, modName);
434         JsonObject newJson = new JsonObject();
435         newJson.add(modName + COLON + nodeName, oldJson.deepCopy());
436
437         Gson gson= new Gson();
438         gson.toJson(newJson, jsonWriter);
439         return writer.toString();
440     }
441
442     /**
443      * Removes module name from all the updated first level child node, if it
444      * is same as the root node added.
445      *
446      * @param oldJson JSON object for old request
447      * @param modName module name of root node
448      * @return JSON object for old request with updated child module name
449      */
450     private JsonObject remChildModName(JsonObject oldJson, String modName) {
451         Iterator<Map.Entry<String, JsonElement>> it = oldJson.entrySet().iterator();
452         Map<String, JsonElement> m = new HashMap<>();
453         while (it.hasNext()) {
454             Map.Entry<String, JsonElement> jNode = it.next();
455             if (jNode.getKey().contains(COLON)) {
456                 String[] modArr = jNode.getKey().split(COLON);
457                 if (modArr[0].equals(modName)) {
458                     it.remove();
459                     m.put(modArr[1], jNode.getValue());
460                 }
461             }
462         }
463         if (!m.isEmpty()) {
464             for (Map.Entry<String, JsonElement> element : m.entrySet()) {
465                 oldJson.add(element.getKey(), element.getValue());
466             }
467         }
468         return oldJson;
469     }
470
471     /**
472      * Returns the updated XML request message, when the HTTP method points
473      * to PUT or PATCH.
474      *
475      * @param req      current JSON request message
476      * @param nodeName root node name
477      * @param modNs    module namespace of the root node
478      * @return update JSON request message
479      * @throws SvcLogicException when XML parsing fails
480      */
481     private String getXmlReqForPutOp(String req, String nodeName,
482                                      URI modNs) throws SvcLogicException {
483         req = getUpdatedXmlReq(req, nodeName, modNs.toString());
484         Document oldDoc;
485         try {
486             oldDoc = DocumentHelper.parseText(req);
487         } catch (DocumentException e) {
488             throw new SvcLogicException(XML_TREE_ERR, e);
489         }
490         Writer writer = getXmlWriter(
491                 UTF_HEADER + oldDoc.getRootElement().asXML(), "4");
492         return writer.toString();
493     }
494 }