fixed sonar issues in AAIClientRESTExecutor
[ccsdk/sli/adaptors.git] / aai-service / provider / src / main / java / org / onap / ccsdk / sli / adaptors / aai / AAIClientRESTExecutor.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openECOMP : SDN-C
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights
6  *                         reserved.
7  * ================================================================================
8  * Modifications Copyright (C) 2018 IBM.
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.ccsdk.sli.adaptors.aai;
25
26 import java.io.BufferedReader;
27 import java.io.ByteArrayInputStream;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.OutputStreamWriter;
33 import java.lang.reflect.Field;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Method;
36 import java.lang.reflect.Modifier;
37 import java.net.HttpURLConnection;
38 import java.net.MalformedURLException;
39 import java.net.URL;
40 import java.nio.charset.StandardCharsets;
41 import java.security.KeyManagementException;
42 import java.security.KeyStore;
43 import java.security.NoSuchAlgorithmException;
44 import java.text.SimpleDateFormat;
45 import java.util.Properties;
46
47 import javax.net.ssl.HostnameVerifier;
48 import javax.net.ssl.HttpsURLConnection;
49 import javax.net.ssl.KeyManagerFactory;
50 import javax.net.ssl.SSLContext;
51 import javax.net.ssl.SSLSession;
52 import javax.net.ssl.SSLSocketFactory;
53
54 import org.apache.commons.codec.binary.Base64;
55 import org.onap.ccsdk.sli.adaptors.aai.AAIService.TransactionIdTracker;
56 import org.onap.ccsdk.sli.adaptors.aai.data.AAIDatum;
57 import org.onap.ccsdk.sli.adaptors.aai.data.ErrorResponse;
58 import org.onap.ccsdk.sli.adaptors.aai.data.RequestError;
59 import org.onap.ccsdk.sli.adaptors.aai.data.ResourceVersion;
60 import org.onap.ccsdk.sli.adaptors.aai.data.ServiceException;
61 import org.onap.ccsdk.sli.core.sli.MetricLogger;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import com.fasterxml.jackson.databind.DeserializationFeature;
66 import com.fasterxml.jackson.databind.ObjectMapper;
67 import org.apache.http.impl.EnglishReasonPhraseCatalog;
68
69 /**
70  * The AAIClientRESTExecutor class provides CRUD API for AAI Client service.
71  * @author  richtabedzki
72  */
73 public class AAIClientRESTExecutor implements AAIExecutorInterface {
74
75     private final String truststorePath;
76     private final String truststorePassword;
77     private final String keystorePath;
78     private final String keystorePassword;
79     private final Boolean ignoreCertificateHostError;
80     // authentication credentials
81     private String userName;
82     private String userPassword;
83     private final String applicationId;
84     private static final String HTTP_URL_CONNECTION_RESULT="HttpURLConnection result: {} : {}";
85
86     /**
87      * class Constructor
88      * @param props - properties to initialize an instance.
89      */
90     public AAIClientRESTExecutor(Properties props) {
91         super();
92
93         userName            = props.getProperty(AAIService.CLIENT_NAME);
94         userPassword        = props.getProperty(AAIService.CLIENT_PWWD);
95
96         if(userName == null || userName.isEmpty()){
97             LOG.debug("Basic user name is not set");
98         }
99         if(userPassword == null || userPassword.isEmpty()) {
100             LOG.debug("Basic password is not set");
101         }
102
103         truststorePath     = props.getProperty(AAIService.TRUSTSTORE_PATH);
104         truststorePassword = props.getProperty(AAIService.TRUSTSTORE_PSSWD);
105         keystorePath         = props.getProperty(AAIService.KEYSTORE_PATH);
106         keystorePassword     = props.getProperty(AAIService.KEYSTORE_PSSWD);
107
108         String tmpApplicationId =props.getProperty(AAIService.APPLICATION_ID);
109         if(tmpApplicationId == null || tmpApplicationId.isEmpty()) {
110             tmpApplicationId = "SDNC";
111         }
112         applicationId = tmpApplicationId;
113
114         String iche = props.getProperty(AAIService.CERTIFICATE_HOST_ERROR);
115         boolean host_error = false;
116         if(iche != null && !iche.isEmpty()) {
117             host_error = Boolean.valueOf(iche);
118         }
119
120         ignoreCertificateHostError = host_error;
121
122         HttpsURLConnection.setDefaultHostnameVerifier( (String string,SSLSession ssls)  -> {
123              return ignoreCertificateHostError;
124             
125         });
126
127         if(truststorePath != null && truststorePassword != null && (new File(truststorePath)).exists()) {
128             System.setProperty("javax.net.ssl.trustStore", truststorePath);
129             System.setProperty("javax.net.ssl.trustStorePassword", truststorePassword);
130         }
131
132         if(keystorePath != null && keystorePassword != null && (new File(keystorePath)).exists())
133         {
134             //both jersey and HttpURLConnection can use this
135             SSLContext ctx = null;
136             try {
137                 ctx = SSLContext.getInstance("TLS");
138
139                 KeyManagerFactory kmf = null;
140                 try (FileInputStream fin = new FileInputStream(keystorePath)){
141                     String storeType = "PKCS12";
142                     String def = KeyStore.getDefaultType();
143                     kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
144
145                     String extension = keystorePath.substring(keystorePath.lastIndexOf(".") + 1);
146
147                     if(extension != null && !extension.isEmpty() && extension.equalsIgnoreCase("JKS")) {
148                         storeType = "JKS";
149                     }
150                     KeyStore ks = KeyStore.getInstance(storeType);
151
152                     char[] pwd = keystorePassword.toCharArray();
153                     ks.load(fin, pwd);
154                     kmf.init(ks, pwd);
155                 } catch (Exception ex) {
156                     LOG.error("AAIResource", ex);
157                 }
158
159                 ctx.init(kmf.getKeyManagers(), null, null);
160
161                 CTX = ctx;
162                 LOG.debug("SSLContext created");
163
164             } catch (KeyManagementException | NoSuchAlgorithmException exc) {
165                 LOG.error("AAIResource", exc);
166             }
167         }
168
169         try {
170             Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
171             methodsField.setAccessible(true);
172             // get the methods field modifiers
173             Field modifiersField = Field.class.getDeclaredField("modifiers");
174             // bypass the "private" modifier
175             modifiersField.setAccessible(true);
176
177             // remove the "final" modifier
178             modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
179
180             /* valid HTTP methods */
181             String[] methods = {
182                        "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE", "PATCH"
183             };
184             // set the new methods - including patch
185             methodsField.set(null, methods);
186
187         } catch (SecurityException | IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
188             LOG.warn("Adding PATCH method", e);
189         }
190         LOG.info("AAIResource.ctor initialized.");
191
192     }
193
194     private static final Logger LOG = LoggerFactory.getLogger(AAIService.class);
195     private static final String NOT_PROVIDED = "NOT PROVIDED";
196     private final MetricLogger ml = new MetricLogger();
197
198     private SSLContext CTX;
199
200
201     private int connection_timeout = 300000;
202
203     private int read_timeout = 300000;
204
205     /**
206      * Returns an String that contains JSON data returned from the AAI Server.
207      * <p>
208      * This method always returns immediately, whether or not the
209      * data exists.
210      *
211      * @param  request  an instance of AAIRequiest representing
212      *                 the request made by DirectedGraph node.
213      * @return      the JSON based representation of data instance requested.
214      * @see         String
215      */
216     @Override
217     public String get(AAIRequest request) throws AAIServiceException {
218         String response = null;
219         InputStream inputStream = null;
220         HttpURLConnection con = null;
221         URL requestUrl = null;
222
223         StringBuilder errorStringBuilder = new StringBuilder();
224
225         try {
226
227             if(request.getRequestObject() != null) {
228                 requestUrl = request.getRequestUrl(HttpMethod.POST, null);
229                 requestUrl = appendDepth(requestUrl, request);
230                 con = getConfiguredConnection(requestUrl, HttpMethod.POST);
231                 String json_text = request.toJSONString();
232                 LOGwriteDateTrace("data", json_text);
233                 logMetricRequest("POST "+requestUrl.getPath(), json_text, requestUrl.getPath());
234                 OutputStreamWriter osw = new OutputStreamWriter(con.getOutputStream());
235                 osw.write(json_text);
236                 osw.flush();
237             } else {
238                 requestUrl = request.getRequestUrl(HttpMethod.GET, null);
239                 requestUrl = appendDepth(requestUrl, request);
240                 con = getConfiguredConnection(requestUrl, HttpMethod.GET);
241                 logMetricRequest("GET "+requestUrl.getPath(), "", requestUrl.getPath());
242             }
243
244             // Check for errors
245             int responseCode = con.getResponseCode();
246             if (responseCode == HttpURLConnection.HTTP_OK) {
247                 inputStream = con.getInputStream();
248             } else {
249                 inputStream = con.getErrorStream();
250             }
251             String responseMessage = null;
252             try {
253                 responseMessage = con.getResponseMessage();
254             } catch(Exception exc) {
255                 responseMessage = EnglishReasonPhraseCatalog.INSTANCE.getReason(responseCode,null);
256             } finally {
257                 if(responseMessage == null)
258                         responseMessage = NOT_PROVIDED;
259             }
260
261             // Process the response
262             LOG.info(HTTP_URL_CONNECTION_RESULT, responseCode, responseMessage);
263             logMetricResponse(responseCode, responseMessage);
264
265             if(inputStream == null) inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
266             BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
267
268             ObjectMapper mapper = AAIService.getObjectMapper();
269
270             if (responseCode == HttpURLConnection.HTTP_OK) {
271                 StringBuilder stringBuilder = new StringBuilder();
272                 String line = null;
273                 while( ( line = reader.readLine() ) != null ) {
274                     stringBuilder.append( line );
275                 }
276                 response = stringBuilder.toString();
277                 try {
278                     Object object = mapper.readValue(response, Object.class);
279                     LOGwriteEndingTrace(HttpURLConnection.HTTP_OK, responseMessage, mapper.writeValueAsString(object));
280                 } catch(Exception exc) {
281                     LOGwriteEndingTrace(HttpURLConnection.HTTP_OK, responseMessage, mapper.writeValueAsString(response));
282                 }
283             } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
284                 LOGwriteEndingTrace(responseCode, responseMessage, "Entry does not exist.");
285                 ErrorResponse errorresponse = null;
286                 try {
287                     errorresponse = mapper.readValue(reader, ErrorResponse.class);
288                 } catch(Exception exc) {
289                     errorresponse = new ErrorResponse();
290                     RequestError requestError = new RequestError();
291                     ServiceException serviceException = new ServiceException();
292                     serviceException.setText("Entry does not exist.");
293                     requestError.setServiceException(serviceException);
294                     errorresponse.setRequestError(requestError );
295                 }
296                 throw new AAIServiceException(responseCode, errorresponse);
297             } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
298                 StringBuilder stringBuilder = new StringBuilder();
299                 String line = null;
300                 while( ( line = reader.readLine() ) != null ) {
301                     stringBuilder.append( line );
302                 }
303                 LOGwriteEndingTrace(responseCode, responseMessage, stringBuilder.toString());
304                 ServiceException serviceException = new ServiceException();
305                 serviceException.setMessageId("HTTP_UNAUTHORIZED");
306                 serviceException.setText(stringBuilder.toString());
307                 RequestError requestError = new RequestError();
308                 requestError.setServiceException(serviceException);
309                 ErrorResponse errorresponse = new ErrorResponse();
310                 errorresponse.setRequestError(requestError);
311                 throw new AAIServiceException(responseCode, errorresponse);
312             } else {
313                 String line = null;
314                 while( ( line = reader.readLine() ) != null ) {
315                     errorStringBuilder.append("\n").append( line );
316                 }
317
318                 ErrorResponse errorresponse = mapper.readValue(errorStringBuilder.toString(), ErrorResponse.class);
319                 LOGwriteEndingTrace(responseCode, responseMessage, mapper.writeValueAsString(errorresponse));
320                 throw new AAIServiceException(responseCode, errorresponse);
321             }
322
323         } catch(AAIServiceException aaiexc) {
324             throw aaiexc;
325         } catch (Exception exc) {
326             LOG.warn(errorStringBuilder.toString(), exc);
327             throw new AAIServiceException(exc);
328         } finally {
329             if(inputStream != null){
330                 try {
331                     inputStream.close();
332                 } catch(Exception exc) {
333                     LOG.warn("", exc);
334                 }
335             }
336         }
337         return response;
338     }
339
340     /**
341      * Returns an String that contains JSON data returned from the AAI Server.
342      * <p>
343      * This method always returns immediately, whether or not the
344      * data exists.
345      *
346      * @param  request  an instance of AAIRequiest representing
347      *                 the request made by DirectedGraph node.
348      * @return      the JSON based representation of data instance requested.
349      * @see         String
350      */
351     @Override
352     public String post(AAIRequest request) throws AAIServiceException {
353         InputStream inputStream = null;
354
355         try {
356             String resourceVersion = null;
357             AAIDatum instance = request.getRequestObject();
358
359             try {
360                 Method getResourceVersionMethod = instance.getClass().getMethod("getResourceVersion");
361                 if(getResourceVersionMethod != null){
362                     try {
363                         Object object = getResourceVersionMethod.invoke(instance);
364                         if(object != null)
365                             resourceVersion = object.toString();
366                     } catch (InvocationTargetException exc) {
367                         LOG.warn("", exc);
368                     }
369                 }
370             } catch(Exception exc) {
371                 LOG.error("", exc);
372             }
373
374             URL requestUrl = request.getRequestUrl(HttpMethod.PUT, resourceVersion);
375             HttpURLConnection con = getConfiguredConnection(requestUrl, HttpMethod.PUT);
376             ObjectMapper mapper = AAIService.getObjectMapper();
377             String jsonText = request.toJSONString();
378
379             LOGwriteDateTrace("data", jsonText);
380             logMetricRequest("PUT "+requestUrl.getPath(), jsonText, requestUrl.getPath());
381
382             OutputStreamWriter osw = new OutputStreamWriter(con.getOutputStream());
383             osw.write(jsonText);
384             osw.flush();
385
386             // Check for errors
387             int responseCode = con.getResponseCode();
388             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_ACCEPTED || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
389                 inputStream = con.getInputStream();
390             } else {
391                 inputStream = con.getErrorStream();
392             }
393             String responseMessage = null;
394             try {
395                 responseMessage = con.getResponseMessage();
396             } catch(Exception exc) {
397                 responseMessage = EnglishReasonPhraseCatalog.INSTANCE.getReason(responseCode,null);
398             } finally {
399                 if(responseMessage == null)
400                         responseMessage = NOT_PROVIDED;
401             }
402
403             LOG.info(HTTP_URL_CONNECTION_RESULT, responseCode, responseMessage);
404             logMetricResponse(responseCode, responseMessage);
405
406             // Process the response
407             BufferedReader reader;
408             String line = null;
409             reader = new BufferedReader( new InputStreamReader( inputStream ) );
410             mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
411
412             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_ACCEPTED || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
413                 StringBuilder stringBuilder = new StringBuilder();
414
415                 while( ( line = reader.readLine() ) != null ) {
416                     stringBuilder.append( line );
417                 }
418                 LOGwriteEndingTrace(responseCode, responseMessage, (stringBuilder.length() > 0) ? stringBuilder.toString() : "{no-data}");
419                 return stringBuilder.toString();
420             } else {
421                 ErrorResponse errorresponse = mapper.readValue(reader, ErrorResponse.class);
422                 LOGwriteEndingTrace(responseCode, responseMessage, mapper.writeValueAsString(errorresponse));
423
424                 throw new AAIServiceException(responseCode, errorresponse);
425             }
426         } catch(AAIServiceException aaiexc) {
427             throw aaiexc;
428         } catch (Exception exc) {
429             LOG.warn("AAIRequestExecutor.post", exc);
430             throw new AAIServiceException(exc);
431         } finally {
432             try {
433                 if(inputStream != null)
434                 inputStream.close();
435             } catch (Exception exc) {
436                 LOG.warn("AAIRequestExecutor.post", exc);
437             }
438         }
439     }
440
441     /**
442      * Returns Boolean that contains completion state of the command executed.
443      * <p>
444      * This method always returns immediately, whether or not the
445      * data exists.
446      *
447      * @param  request  an instance of AAIRequiest representing
448      * @param  resourceVersion  a resource version of the data instacne to be deleted.
449      *                 the request made by DirectedGraph node.
450      * @return      completion state of the command.
451      * @see         String
452      */
453     @Override
454     public Boolean delete(AAIRequest request, String resourceVersion) throws AAIServiceException {
455         Boolean response = null;
456         InputStream inputStream = null;
457
458         if(resourceVersion == null) {
459             throw new AAIServiceException("resource-version is required for DELETE request");
460         }
461
462         try {
463             URL requestUrl = request.getRequestUrl(HttpMethod.DELETE, resourceVersion);
464             HttpURLConnection conn = getConfiguredConnection(requestUrl, HttpMethod.DELETE);
465             logMetricRequest("DELETE "+requestUrl.getPath(), "", requestUrl.getPath());
466             conn.setDoOutput(true);
467
468             // Check for errors
469             int responseCode = conn.getResponseCode();
470             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
471                 inputStream = conn.getInputStream();
472             } else {
473                 inputStream = conn.getErrorStream();
474             }
475             String responseMessage = null;
476             try {
477                 responseMessage = conn.getResponseMessage();
478             } catch(Exception exc) {
479                 responseMessage = EnglishReasonPhraseCatalog.INSTANCE.getReason(responseCode,null);
480             } finally {
481                 if(responseMessage == null)
482                         responseMessage = NOT_PROVIDED;
483             }
484
485             // Process the response
486             LOG.info(HTTP_URL_CONNECTION_RESULT, responseCode, responseMessage);
487             logMetricResponse(responseCode, responseMessage);
488
489             if(inputStream == null) inputStream = new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
490             BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
491             String line = null;
492
493             ObjectMapper mapper = AAIService.getObjectMapper();
494
495             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
496                 StringBuilder stringBuilder = new StringBuilder();
497
498                 while( ( line = reader.readLine() ) != null ) {
499                     stringBuilder.append( line );
500                 }
501                 LOGwriteEndingTrace(responseCode, responseMessage, stringBuilder.toString());
502                 response = true;
503             } else if(responseCode == HttpURLConnection.HTTP_NOT_FOUND ) {
504                 LOGwriteEndingTrace(responseCode, responseMessage, "Entry does not exist.");
505                 response = false;
506             } else {
507                 ErrorResponse errorresponse = mapper.readValue(reader, ErrorResponse.class);
508                 LOGwriteEndingTrace(responseCode, responseMessage, mapper.writeValueAsString(errorresponse));
509                 throw new AAIServiceException(responseCode, errorresponse);
510             }
511         } catch(AAIServiceException aaiexc) {
512             throw aaiexc;
513         } catch (Exception exc) {
514             LOG.warn("delete", exc);
515             throw new AAIServiceException(exc);
516         } finally {
517             if(inputStream != null){
518                 try {
519                     inputStream.close();
520                 } catch(Exception exc) {
521                     LOG.warn("delete", exc);
522                 }
523             }
524         }
525         return response;
526     }
527
528     /**
529      * Returns an String that contains JSON data returned from the AAI Server.
530      * <p>
531      * This method always returns immediately, whether or not the
532      * data exists.
533      *
534      * @param  request  an instance of AAIRequiest representing
535      *                 the request made by DirectedGraph node.
536      * @param clas   an definition of the class for which data will be returned
537      * @return      the instance of the class with data.
538      * @see         String
539      */
540     @Override
541     public Object query(AAIRequest request, Class clas) throws AAIServiceException {
542         Object response = null;
543         InputStream inputStream = null;
544
545         try {
546             URL requestUrl = request.getRequestQueryUrl(HttpMethod.GET);
547             HttpURLConnection con = getConfiguredConnection(requestUrl, HttpMethod.GET);
548             logMetricRequest("GET "+requestUrl.getPath(), "", requestUrl.getPath());
549
550             // Check for errors
551             int responseCode = con.getResponseCode();
552             if (responseCode == HttpURLConnection.HTTP_OK) {
553                 inputStream = con.getInputStream();
554             } else {
555                 inputStream = con.getErrorStream();
556             }
557             String responseMessage = null;
558             try {
559                 responseMessage = con.getResponseMessage();
560             } catch(Exception exc) {
561                 responseMessage = EnglishReasonPhraseCatalog.INSTANCE.getReason(responseCode,null);
562             } finally {
563                 if(responseMessage == null)
564                         responseMessage = NOT_PROVIDED;
565             }
566
567             LOG.info(HTTP_URL_CONNECTION_RESULT, responseCode, responseMessage);
568             logMetricResponse(responseCode, responseMessage);
569             ObjectMapper mapper = AAIService.getObjectMapper();
570
571             if (responseCode == HttpURLConnection.HTTP_OK) {
572                 // Process the response
573                 BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
574                 response = mapper.readValue(reader, clas);
575                 LOGwriteEndingTrace(HttpURLConnection.HTTP_OK, "SUCCESS", mapper.writeValueAsString(response));
576             } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
577                 LOGwriteEndingTrace(responseCode, "HTTP_NOT_FOUND", "Entry does not exist.");
578                 return response;
579             } else {
580                 BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) );
581                 ErrorResponse errorresponse = mapper.readValue(reader, ErrorResponse.class);
582                 LOGwriteEndingTrace(responseCode, "FAILURE", mapper.writeValueAsString(errorresponse));
583                 throw new AAIServiceException(responseCode, errorresponse);
584             }
585
586         } catch(AAIServiceException aaiexc) {
587             throw aaiexc;
588         } catch (Exception exc) {
589             LOG.warn("GET", exc);
590             throw new AAIServiceException(exc);
591         } finally {
592             if(inputStream != null){
593                 try {
594                     inputStream.close();
595                 } catch(Exception exc) {
596                     LOG.warn("GET", exc);
597                 }
598             }
599         }
600         return response;
601     }
602
603     @Override
604     public Boolean patch(AAIRequest request, String resourceVersion) throws AAIServiceException {
605         InputStream inputStream = null;
606
607         try {
608             AAIDatum instance = request.getRequestObject();
609             if(instance instanceof ResourceVersion) {
610                 resourceVersion = ((ResourceVersion)instance).getResourceVersion();
611             }
612
613             URL requestUrl = null;
614             HttpURLConnection con = getConfiguredConnection(requestUrl = request.getRequestUrl("PATCH", resourceVersion), "PATCH");
615             ObjectMapper mapper = AAIService.getObjectMapper();
616             String jsonText = request.toJSONString();
617
618             LOGwriteDateTrace("data", jsonText);
619             logMetricRequest("PATCH "+requestUrl.getPath(), jsonText, requestUrl.getPath());
620
621             OutputStreamWriter osw = new OutputStreamWriter(con.getOutputStream());
622             osw.write(jsonText);
623             osw.flush();
624
625             // Check for errors
626             int responseCode = con.getResponseCode();
627             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_ACCEPTED || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
628                 inputStream = con.getInputStream();
629             } else {
630                 inputStream = con.getErrorStream();
631             }
632             String responseMessage = null;
633             try {
634                 responseMessage = con.getResponseMessage();
635             } catch(Exception exc) {
636                 responseMessage = EnglishReasonPhraseCatalog.INSTANCE.getReason(responseCode,null);
637             } finally {
638                 if(responseMessage == null)
639                         responseMessage = NOT_PROVIDED;
640             }
641
642             LOG.info(HTTP_URL_CONNECTION_RESULT, responseCode, responseMessage);
643             logMetricResponse(responseCode, responseMessage);
644
645             // Process the response
646             BufferedReader reader;
647             String line = null;
648             reader = new BufferedReader( new InputStreamReader( inputStream ) );
649             mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
650
651             if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_ACCEPTED || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
652                 StringBuilder stringBuilder = new StringBuilder();
653
654                 while( ( line = reader.readLine() ) != null ) {
655                     stringBuilder.append( line );
656                 }
657                 LOGwriteEndingTrace(responseCode, responseMessage, (stringBuilder.length() > 0) ? stringBuilder.toString() : "{no-data}");
658                 return true;
659             } else {
660                 StringBuilder stringBuilder = new StringBuilder();
661
662                 while( ( line = reader.readLine() ) != null ) {
663                     stringBuilder.append("\n").append( line );
664                 }
665                 LOG.info(stringBuilder.toString());
666
667
668                 ErrorResponse errorresponse = mapper.readValue(reader, ErrorResponse.class);
669                 LOGwriteEndingTrace(responseCode, responseMessage, mapper.writeValueAsString(errorresponse));
670
671                 throw new AAIServiceException(responseCode, errorresponse);
672             }
673         } catch(AAIServiceException aaiexc) {
674             throw aaiexc;
675         } catch (Exception exc) {
676             LOG.warn("AAIRequestExecutor.patch", exc);
677             throw new AAIServiceException(exc);
678         } finally {
679             try {
680                 if(inputStream != null)
681                 inputStream.close();
682             } catch (Exception exc) {
683                 LOG.warn("AAIRequestExecutor.patch", exc);
684             }
685         }
686     }
687
688     /**
689      *
690      * @param httpReqUrl
691      * @param method
692      * @return
693      * @throws Exception
694      */
695     protected HttpURLConnection getConfiguredConnection(URL httpReqUrl, String method) throws Exception {
696         HttpURLConnection con = (HttpURLConnection) httpReqUrl.openConnection();
697
698         // Set up the connection properties
699         con.setRequestProperty("Connection", "close");
700         con.setDoInput(true);
701         con.setDoOutput(true);
702         con.setUseCaches(false);
703         con.setConnectTimeout(connection_timeout);
704         con.setReadTimeout(read_timeout);
705         con.setRequestMethod(method);
706         con.setRequestProperty("Accept", "application/json");
707         con.setRequestProperty("Transfer-Encoding","chunked");
708         con.setRequestProperty("Content-Type",
709                 "PATCH".equalsIgnoreCase(method) ? "application/merge-patch+json" : "application/json");
710         con.setRequestProperty("X-FromAppId", applicationId);
711         con.setRequestProperty("X-TransactionId", TransactionIdTracker.getNextTransactionId());
712         String mlId = ml.getRequestID();
713         if (mlId != null && !mlId.isEmpty()) {
714             LOG.debug(String.format("MetricLogger requestId = %s", mlId));
715             con.setRequestProperty(MetricLogger.REQUEST_ID, mlId);
716         } else {
717             LOG.debug("MetricLogger requestId is null");
718         }
719
720         if (userName != null && !userName.isEmpty() && userPassword != null && !userPassword.isEmpty()) {
721             String basicAuth = "Basic " + new String(Base64.encodeBase64((userName + ":" + userPassword).getBytes()));
722             con.setRequestProperty("Authorization", basicAuth);
723         }
724
725         if (con instanceof HttpsURLConnection && CTX != null) {
726             SSLSocketFactory sockFact = CTX.getSocketFactory();
727             HttpsURLConnection.class.cast(con).setSSLSocketFactory(sockFact);
728         }
729         return con;
730     }
731
732     private URL appendDepth(URL requestUrl, AAIRequest request) throws MalformedURLException {
733
734         String depth = request.requestProperties.getProperty("depth", "1");
735         String path = requestUrl.toString();
736         if(path.contains("?depth=") || path.contains("&depth=")) {
737             return requestUrl;
738         } else {
739             if(path.contains("?")) {
740                 path = String.format("%s&depth=%s", path, depth);
741             } else {
742                 path = String.format("%s?depth=%s", path, depth);
743             }
744             return new URL(path);
745         }
746     }
747
748     public void logMetricRequest(String targetServiceName, String msg, String path){
749         String svcInstanceId = "";
750         String svcName = null;
751         String partnerName = null;
752         String targetEntity = "A&AI";
753         String targetVirtualEntity = null;
754
755         ml.logRequest(svcInstanceId, svcName, partnerName, targetEntity, targetServiceName, targetVirtualEntity, msg);
756     }
757
758     public void logMetricResponse(int responseCode, String responseDescription){
759         ml.logResponse(responseCode < 400 ? "COMPLETE" : "ERROR", Integer.toString(responseCode), responseDescription);
760     }
761
762     protected void LOGwriteFirstTrace(String method, String url) {
763         String time = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(System.currentTimeMillis());
764         LOG.info("A&AI transaction :");
765         LOG.info("Request Time : " + time + ", Method : " + method);
766         LOG.info("Request URL : "+ url);
767     }
768
769     protected void LOGwriteDateTrace(String name, String data) {
770         LOG.info("Input - " + name  + " : " + data);
771     }
772
773     protected void LOGwriteEndingTrace(int response_code, String comment, String data) {
774         LOG.info("Response code : " + response_code +", " + comment);
775         LOG.info(String.format("Response data : %s", data));
776     }
777
778 }