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