Merge "Dependency management for Restconf Client"
[ccsdk/sli/plugins.git] / restconf-client / provider / src / main / java / org / onap / ccsdk / sli / plugins / restconfapicall / RestconfApiCallNode.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - CCSDK
4  * ================================================================================
5  * Copyright (C) 2018 Huawei Technologies Co., Ltd. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.ccsdk.sli.plugins.restconfapicall;
22
23 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
24 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
25 import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin;
26 import org.onap.ccsdk.sli.plugins.restapicall.HttpResponse;
27 import org.onap.ccsdk.sli.plugins.restapicall.RestapiCallNode;
28 import org.onap.ccsdk.sli.plugins.restapicall.RetryException;
29 import org.onap.ccsdk.sli.plugins.restapicall.RetryPolicy;
30 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DataFormatSerializer;
31 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DataFormatSerializerContext;
32 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfSerializerFactory;
33 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.Listener;
34 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.MdsalSerializerHelper;
35 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.SerializerHelper;
36 import org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.YangParameters;
37 import org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.MdsalPropertiesNodeSerializer;
38 import org.onap.ccsdk.sli.plugins.yangserializers.pnserializer.PropertiesNodeSerializer;
39 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
40 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.osgi.framework.BundleContext;
43 import org.osgi.framework.ServiceReference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import javax.ws.rs.core.UriBuilder;
48 import java.net.SocketException;
49 import java.net.URI;
50 import java.net.URISyntaxException;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54
55 import static java.lang.String.format;
56 import static org.apache.commons.lang3.StringUtils.join;
57 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.PATCH;
58 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.POST;
59 import static org.onap.ccsdk.sli.plugins.restapicall.HttpMethod.PUT;
60 import static org.onap.ccsdk.sli.plugins.restapicall.RestapiCallNode.parseParam;
61 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.ATTEMPTS_MSG;
62 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.COMMA;
63 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.COMM_FAIL;
64 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HEADER;
65 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HTTP_REQ;
66 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.HTTP_RES;
67 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.MAX_RETRY_ERR;
68 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.NO_MORE_RETRY;
69 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.REQ_ERR;
70 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.REST_API_URL;
71 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_CODE;
72 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_MSG;
73 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RES_PRE;
74 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RETRY_COUNT;
75 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.RETRY_FAIL;
76 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.UPDATED_URL;
77 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.getSchemaCtxFromDir;
78 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.getYangParameters;
79 import static org.onap.ccsdk.sli.plugins.restconfapicall.RestconfApiUtils.parseUrl;
80 import static org.onap.ccsdk.sli.plugins.yangserializers.dfserializer.DfListenerFactory.instance;
81 import static org.osgi.framework.FrameworkUtil.getBundle;
82
83 /**
84  * Representation of a plugin to enable RESTCONF based CRUD operations from DG.
85  */
86 public class RestconfApiCallNode implements SvcLogicJavaPlugin {
87
88     /**
89      * Logger for the restconf api call node class.
90      */
91     private static final Logger log = LoggerFactory.getLogger(
92             RestconfApiCallNode.class);
93
94     /**
95      * Rest api call node service instance
96      */
97     private RestapiCallNode restapiCallNode;
98
99     /**
100      * Creates an instance of restconf api call node with restapi call node.
101      *
102      * @param r restapi call node
103      */
104     public RestconfApiCallNode(RestapiCallNode r) {
105         this.restapiCallNode = r;
106     }
107
108     /**
109      * Returns the restapi call node instance.
110      * @return
111      */
112     public RestapiCallNode getRestapiCallNode() {
113         return restapiCallNode;
114     }
115
116     /**
117      * Sends the restconf request using the parameters map and the memory
118      * context. And this method allows the directed graphs to interact with
119      * the restconf api call node
120      *
121      * @param paramMap parameters map
122      * @param ctx      service logic context
123      * @throws SvcLogicException when svc logic exception occurs
124      */
125     public void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx)
126             throws SvcLogicException {
127         sendRequest(paramMap, ctx, 0);
128     }
129
130     /**
131      * Sends the restconf request using the parameters map and the memory
132      * context along with the retry count.
133      *
134      * @param paramMap   parameters map
135      * @param ctx        service logic context
136      * @param retryCount number of retry counts
137      * @throws SvcLogicException when svc logic exception occurs
138      */
139     public void sendRequest(Map<String, String> paramMap, SvcLogicContext ctx,
140                             Integer retryCount) throws SvcLogicException {
141         RestapiCallNode rest = getRestapiCallNode();
142         RetryPolicy retryPolicy = null;
143         HttpResponse r = new HttpResponse();
144         try {
145             YangParameters p = getYangParameters(paramMap);
146             if (p.partner != null) {
147                 retryPolicy = rest.getRetryPolicyStore()
148                         .getRetryPolicy(p.partner);
149             }
150
151             String pp = p.responsePrefix != null ? p.responsePrefix + '.' : "";
152             Map<String, String> props = new HashMap<>((Map)ctx.toProperties());
153             String uri = parseUrl(p.restapiUrl, p.httpMethod);
154             InstanceIdentifierContext<?> insIdCtx = getInsIdCtx(p, uri);
155
156             String req = null;
157             if (p.httpMethod == POST || p.httpMethod == PUT
158                     || p.httpMethod == PATCH) {
159                 req = serializeRequest(props, p, uri, insIdCtx);
160             }
161             if (req == null && p.requestBody != null) {
162                 req = p.requestBody;
163             }
164
165             r = rest.sendHttpRequest(req, p);
166             if (p.returnRequestPayload && req != null) {
167                 ctx.setAttribute(pp + HTTP_REQ, req);
168             }
169
170             String response = getResponse(ctx, p, pp, r);
171             if (response != null) {
172                 Map<String, String> resProp = serializeResponse(
173                         p, uri, response, insIdCtx);
174                 for (Map.Entry<String, String> pro : resProp.entrySet()) {
175                     ctx.setAttribute(pro.getKey(), pro.getValue());
176                 }
177             }
178         } catch (SvcLogicException e) {
179             boolean shouldRetry = false;
180             if (e.getCause().getCause() instanceof SocketException) {
181                 shouldRetry = true;
182             }
183
184             log.error(REQ_ERR + e.getMessage(), e);
185             String prefix = parseParam(paramMap, RES_PRE, false, null);
186             if (retryPolicy == null || !shouldRetry) {
187                 setFailureResponseStatus(ctx, prefix, e.getMessage());
188             } else {
189                 if (retryCount == null) {
190                     retryCount = 0;
191                 }
192                 log.debug(format(ATTEMPTS_MSG, retryCount,
193                                  retryPolicy.getMaximumRetries()));
194                 try {
195                     retryCount = retryCount + 1;
196                     if (retryCount < retryPolicy.getMaximumRetries() + 1) {
197                         setRetryUri(paramMap, retryPolicy);
198                         log.debug(format(RETRY_COUNT, retryCount, retryPolicy
199                                 .getMaximumRetries()));
200                         sendRequest(paramMap, ctx, retryCount);
201                     } else {
202                         log.debug(MAX_RETRY_ERR);
203                         setFailureResponseStatus(ctx, prefix, e.getMessage());
204                     }
205                 } catch (Exception ex) {
206                     log.error(NO_MORE_RETRY, ex);
207                     setFailureResponseStatus(ctx, prefix, RETRY_FAIL);
208                 }
209             }
210         }
211
212         if (r != null && r.code >= 300) {
213             throw new SvcLogicException(
214                     String.valueOf(r.code) + ": " + r.message);
215         }
216     }
217
218     /**
219      * Serializes the request message to JSON or XML from the properties.
220      *
221      * @param properties properties
222      * @param params     YANG parameters
223      * @param uri        URI
224      * @param insIdCtx   instance identifier context
225      * @return JSON or XML message to be sent
226      * @throws SvcLogicException when serializing the request fails
227      */
228      public String serializeRequest(Map<String, String> properties,
229                                     YangParameters params, String uri,
230                                     InstanceIdentifierContext insIdCtx)
231              throws SvcLogicException {
232         PropertiesNodeSerializer propSer = new MdsalPropertiesNodeSerializer(
233                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
234         DataFormatSerializerContext serCtx = new DataFormatSerializerContext(
235                 null, uri, null, propSer);
236         DataFormatSerializer ser = DfSerializerFactory.instance()
237                 .getSerializer(serCtx, params);
238          //TODO: Handling of XML annotations
239         return ser.encode(properties, null);
240     }
241
242     /**
243      * Serializes the response message from JSON or XML to the properties.
244      *
245      * @param params   YANG parameters
246      * @param uri      URI
247      * @param response response message
248      * @param insIdCtx instance identifier context
249      * @return response message as properties
250      * @throws SvcLogicException when serializing the response fails
251      */
252     public Map<String, String> serializeResponse(YangParameters params,
253                                                  String uri, String response,
254                                                  InstanceIdentifierContext insIdCtx)
255             throws SvcLogicException {
256         PropertiesNodeSerializer propSer = new MdsalPropertiesNodeSerializer(
257                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
258         SerializerHelper helper = new MdsalSerializerHelper(
259                 insIdCtx.getSchemaNode(), insIdCtx.getSchemaContext(), uri);
260         Listener listener = instance().getListener(helper, params);
261         DataFormatSerializerContext serCtx = new DataFormatSerializerContext(
262                 listener, uri, null, propSer);
263         DataFormatSerializer ser = DfSerializerFactory.instance()
264                 .getSerializer(serCtx, params);
265         return ser.decode(response);
266     }
267
268     /**
269      * Returns instance identifier context for a uri using the schema context.
270      *
271      * @param params YANG parameters
272      * @param uri    URI
273      * @return instance identifier context
274      * @throws SvcLogicException when getting schema context fails
275      */
276     private InstanceIdentifierContext<?> getInsIdCtx(YangParameters params,
277                                                      String uri)
278             throws SvcLogicException {
279         SchemaContext context = getSchemaContext(params);
280         return ParserIdentifier.toInstanceIdentifier(uri, context, null);
281     }
282
283     /**
284      * Returns the global schema context or schema context of particular YANG
285      * files present in a directory path.
286      *
287      * @param params YANG parameters
288      * @return schema context
289      * @throws SvcLogicException when schema context fetching fails
290      */
291     private SchemaContext getSchemaContext(YangParameters params)
292             throws SvcLogicException {
293         if (params.dirPath != null) {
294             return getSchemaCtxFromDir(params.dirPath);
295         }
296         BundleContext bc = getBundle(SchemaContext.class).getBundleContext();
297         SchemaContext schemaContext = null;
298         if (bc != null) {
299             ServiceReference reference = bc.getServiceReference(
300                     SchemaContext.class);
301             if (reference != null) {
302                 schemaContext = (SchemaContext) bc.getService(reference);
303             }
304         }
305         return schemaContext;
306     }
307
308     /**
309      * Returns the response message body of a http response message.
310      *
311      * @param ctx    svc logic context
312      * @param params parameters
313      * @param pre    prefix to be appended
314      * @param res    http response
315      * @return response message body
316      */
317     public String getResponse(SvcLogicContext ctx, YangParameters params,
318                                String pre, HttpResponse res) {
319         ctx.setAttribute(pre + RES_CODE, String.valueOf(res.code));
320         ctx.setAttribute(pre + RES_MSG, res.message);
321
322         if (params.dumpHeaders && res.headers != null) {
323             for (Map.Entry<String, List<String>> a : res.headers.entrySet()) {
324                 ctx.setAttribute(pre + HEADER + a.getKey(),
325                                  join(a.getValue(), COMMA));
326             }
327         }
328
329         if (res.body != null && res.body.trim().length() > 0) {
330             ctx.setAttribute(pre + HTTP_RES, res.body);
331             return res.body;
332         }
333         return null;
334     }
335
336     /**
337      * Sets the failure response status in the context memory.
338      *
339      * @param ctx    service logic context
340      * @param prefix prefix to be added
341      * @param errMsg error message
342      */
343     private void setFailureResponseStatus(SvcLogicContext ctx, String prefix,
344                                           String errMsg) {
345         HttpResponse res = new HttpResponse();
346         res.code = 500;
347         res.message = errMsg;
348         ctx.setAttribute(prefix + RES_CODE, String.valueOf(res.code));
349         ctx.setAttribute(prefix + RES_MSG, res.message);
350     }
351
352     /**
353      * Sets the retry URI to the param map from the retry policies different
354      * host.
355      *
356      * @param paramMap            parameter map
357      * @param retryPolicy         retry policy
358      * @throws URISyntaxException when new URI creation fails
359      * @throws RetryException     when retry policy cannot give another host
360      */
361     private void setRetryUri(Map<String, String> paramMap,
362                              RetryPolicy retryPolicy)
363             throws URISyntaxException, RetryException {
364         URI uri = new URI(paramMap.get(REST_API_URL));
365         String hostName = uri.getHost();
366         String retryString = retryPolicy.getNextHostName(uri.toString());
367
368         URI uriTwo = new URI(retryString);
369         URI retryUri = UriBuilder.fromUri(uri).host(uriTwo.getHost()).port(
370                 uriTwo.getPort()).scheme(uriTwo.getScheme()).build();
371
372         paramMap.put(REST_API_URL, retryUri.toString());
373         log.debug(UPDATED_URL + retryUri.toString());
374         log.debug(format(COMM_FAIL, hostName, retryString));
375     }
376 }