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