50c8bf038d3280d2dd96071a281f3a20c17097a4
[aai/model-loader.git] / src / main / java / org / onap / aai / modelloader / restclient / AaiRestClient.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *     http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  *
22  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23  */
24 package org.onap.aai.modelloader.restclient;
25
26 import com.sun.jersey.api.client.Client;
27 import com.sun.jersey.api.client.ClientResponse;
28 import com.sun.jersey.api.client.config.ClientConfig;
29 import com.sun.jersey.api.client.config.DefaultClientConfig;
30 import com.sun.jersey.api.client.filter.LoggingFilter;
31 import com.sun.jersey.client.urlconnection.HTTPSProperties;
32
33 import org.onap.aai.modelloader.config.ModelLoaderConfig;
34 import org.onap.aai.modelloader.restclient.AaiRestClient;
35 import org.onap.aai.modelloader.service.ModelLoaderMsgs;
36 import org.onap.aai.cl.api.LogFields;
37 import org.onap.aai.cl.api.LogLine;
38 import org.onap.aai.cl.api.Logger;
39 import org.onap.aai.cl.eelf.LoggerFactory;
40 import org.onap.aai.cl.mdc.MdcContext;
41 import org.onap.aai.cl.mdc.MdcOverride;
42 import org.w3c.dom.Document;
43 import org.w3c.dom.Node;
44 import org.w3c.dom.NodeList;
45 import org.xml.sax.InputSource;
46 import org.xml.sax.SAXException;
47
48 import java.io.ByteArrayOutputStream;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.PrintStream;
52 import java.io.StringReader;
53 import java.security.GeneralSecurityException;
54 import java.security.KeyStore;
55 import java.security.cert.X509Certificate;
56 import java.text.SimpleDateFormat;
57
58 import javax.net.ssl.HostnameVerifier;
59 import javax.net.ssl.HttpsURLConnection;
60 import javax.net.ssl.KeyManagerFactory;
61 import javax.net.ssl.SSLContext;
62 import javax.net.ssl.SSLSession;
63 import javax.net.ssl.TrustManager;
64 import javax.net.ssl.X509TrustManager;
65 import javax.ws.rs.core.Response;
66 import javax.xml.parsers.DocumentBuilder;
67 import javax.xml.parsers.DocumentBuilderFactory;
68 import javax.xml.parsers.ParserConfigurationException;
69
70 public class AaiRestClient {
71   public enum MimeType {
72     XML("application/xml"), JSON("application/json");
73
74     private String httpType;
75
76     MimeType(String httpType) {
77       this.httpType = httpType;
78     }
79
80     String getHttpHeaderType() {
81       return httpType;
82     }
83   }
84
85   private static String HEADER_TRANS_ID = "X-TransactionId";
86   private static String HEADER_FROM_APP_ID = "X-FromAppId";
87   private static String HEADER_AUTHORIZATION = "Authorization";
88   private static String ML_APP_NAME = "ModelLoader";
89   private static String RESOURCE_VERSION_PARAM = "resource-version";
90
91   private static SimpleDateFormat dateFormatter = new SimpleDateFormat(
92       "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
93
94   private static Logger logger = LoggerFactory.getInstance()
95       .getLogger(AaiRestClient.class.getName());
96   private static Logger metricsLogger = LoggerFactory.getInstance()
97       .getMetricsLogger(AaiRestClient.class.getName());
98
99   private ModelLoaderConfig config = null;
100
101   public AaiRestClient(ModelLoaderConfig config) {
102     this.config = config;
103   }
104
105   /**
106    * Send a PUT request to the A&AI.
107    *
108    * @param url
109    *          - the url
110    * @param transId
111    *          - transaction ID
112    * @param payload
113    *          - the XML or JSON payload for the request
114    * @param mimeType
115    *          - the content type (XML or JSON)
116    * @return ClientResponse
117    */
118   public ClientResponse putResource(String url, String payload, String transId, MimeType mimeType) {
119     ClientResponse result = null;
120     ByteArrayOutputStream baos = new ByteArrayOutputStream();
121     long startTimeInMs = 0;
122     MdcOverride override = new MdcOverride();
123
124     try {
125       Client client = setupClient();
126
127       baos = new ByteArrayOutputStream();
128       PrintStream ps = new PrintStream(baos);
129       if (logger.isDebugEnabled()) {
130         client.addFilter(new LoggingFilter(ps));
131       }
132
133       // Grab the current time so that we can use it for metrics purposes later.
134       startTimeInMs = System.currentTimeMillis();
135       override.addAttribute(MdcContext.MDC_START_TIME, dateFormatter.format(startTimeInMs));
136
137       if (useBasicAuth()) {
138         result = client.resource(url).header(HEADER_TRANS_ID, transId)
139             .header(HEADER_FROM_APP_ID, ML_APP_NAME)
140             .header(HEADER_AUTHORIZATION, getAuthenticationCredentials())
141             .type(mimeType.getHttpHeaderType()).put(ClientResponse.class, payload);
142       } else {
143         result = client.resource(url).header(HEADER_TRANS_ID, transId)
144             .header(HEADER_FROM_APP_ID, ML_APP_NAME).type(mimeType.getHttpHeaderType())
145             .put(ClientResponse.class, payload);
146       }
147     } catch (Exception ex) {
148       logger.error(ModelLoaderMsgs.AAI_REST_REQUEST_ERROR, "PUT", url, ex.getLocalizedMessage());
149       return null;
150     } finally {
151       if (logger.isDebugEnabled()) {
152         logger.debug(baos.toString());
153       }
154     }
155
156     if ((result != null) && ((result.getStatus() == Response.Status.CREATED.getStatusCode())
157         || (result.getStatus() == Response.Status.OK.getStatusCode()))) {
158       logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS, "PUT", url,
159           Integer.toString(result.getStatus()));
160       metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS,
161           new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus())
162               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
163                   result.getResponseStatus().toString()),
164           override, "PUT", url, Integer.toString(result.getStatus()));
165     } else {
166       // If response is not 200 OK, then additionally log the reason
167       String respMsg = result.getEntity(String.class);
168       if (respMsg == null) {
169         respMsg = result.getStatusInfo().getReasonPhrase();
170       }
171       logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_UNSUCCESSFUL, "PUT", url,
172           Integer.toString(result.getStatus()), respMsg);
173       metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_UNSUCCESSFUL,
174           new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus())
175               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
176                   result.getResponseStatus().toString()),
177           override, "PUT", url, Integer.toString(result.getStatus()), respMsg);
178     }
179
180     return result;
181   }
182
183   /**
184    * Send a DELETE request to the A&AI.
185    *
186    * @param url
187    *          - the url
188    * @param resourceVersion
189    *          - the resource-version of the model to delete
190    * @param transId
191    *          - transaction ID
192    * @return ClientResponse
193    */
194   public ClientResponse deleteResource(String url, String resourceVersion, String transId) {
195     ClientResponse result = null;
196     ByteArrayOutputStream baos = new ByteArrayOutputStream();
197     long startTimeInMs = 0;
198     MdcOverride override = new MdcOverride();
199
200     try {
201       Client client = setupClient();
202
203       baos = new ByteArrayOutputStream();
204       PrintStream ps = new PrintStream(baos);
205       if (logger.isDebugEnabled()) {
206         client.addFilter(new LoggingFilter(ps));
207       }
208
209       // Grab the current time so that we can use it for metrics purposes later.
210       startTimeInMs = System.currentTimeMillis();
211       override.addAttribute(MdcContext.MDC_START_TIME, dateFormatter.format(startTimeInMs));
212
213       if (useBasicAuth()) {
214         result = client.resource(url).queryParam(RESOURCE_VERSION_PARAM, resourceVersion)
215             .header(HEADER_TRANS_ID, transId).header(HEADER_FROM_APP_ID, ML_APP_NAME)
216             .header(HEADER_AUTHORIZATION, getAuthenticationCredentials())
217             .delete(ClientResponse.class);
218       } else {
219         result = client.resource(url).queryParam(RESOURCE_VERSION_PARAM, resourceVersion)
220             .header(HEADER_TRANS_ID, transId).header(HEADER_FROM_APP_ID, ML_APP_NAME)
221             .delete(ClientResponse.class);
222       }
223     } catch (Exception ex) {
224       logger.error(ModelLoaderMsgs.AAI_REST_REQUEST_ERROR, "DELETE", url, ex.getLocalizedMessage());
225       return null;
226     } finally {
227       if (logger.isDebugEnabled()) {
228         logger.debug(baos.toString());
229       }
230     }
231
232     logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS, "DELETE", url,
233         Integer.toString(result.getStatus()));
234     metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS,
235         new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus()).setField(
236             LogLine.DefinedFields.RESPONSE_DESCRIPTION, result.getResponseStatus().toString()),
237         override, "DELETE", url, Integer.toString(result.getStatus()));
238
239     return result;
240   }
241
242   /**
243    * Send a GET request to the A&AI for a resource.
244    *
245    * @param url
246    *          - the url to use
247    * @param transId
248    *          - transaction ID
249    * @return ClientResponse
250    */
251   public ClientResponse getResource(String url, String transId, MimeType mimeType) {
252     ClientResponse result = null;
253     ByteArrayOutputStream baos = new ByteArrayOutputStream();
254     long startTimeInMs = 0;
255     MdcOverride override = new MdcOverride();
256
257     try {
258       Client client = setupClient();
259
260       baos = new ByteArrayOutputStream();
261       PrintStream ps = new PrintStream(baos);
262       if (logger.isDebugEnabled()) {
263         client.addFilter(new LoggingFilter(ps));
264       }
265
266       // Grab the current time so that we can use it for metrics purposes later.
267       startTimeInMs = System.currentTimeMillis();
268       override.addAttribute(MdcContext.MDC_START_TIME, dateFormatter.format(startTimeInMs));
269
270       if (useBasicAuth()) {
271         result = client.resource(url).header(HEADER_TRANS_ID, transId)
272             .header(HEADER_FROM_APP_ID, ML_APP_NAME).accept(mimeType.getHttpHeaderType())
273             .header(HEADER_AUTHORIZATION, getAuthenticationCredentials()).get(ClientResponse.class);
274       } else {
275         result = client.resource(url).header(HEADER_TRANS_ID, transId)
276             .header(HEADER_FROM_APP_ID, ML_APP_NAME).accept(mimeType.getHttpHeaderType())
277             .get(ClientResponse.class);
278
279       }
280     } catch (Exception ex) {
281       logger.error(ModelLoaderMsgs.AAI_REST_REQUEST_ERROR, "GET", url, ex.getLocalizedMessage());
282       return null;
283     } finally {
284       if (logger.isDebugEnabled()) {
285         logger.debug(baos.toString());
286       }
287     }
288
289     logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS, "GET", url,
290         Integer.toString(result.getStatus()));
291     metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS,
292         new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus()).setField(
293             LogLine.DefinedFields.RESPONSE_DESCRIPTION, result.getResponseStatus().toString()),
294         override, "GET", url, Integer.toString(result.getStatus()));
295
296     return result;
297   }
298
299   /**
300    * Send a POST request to the A&AI.
301    *
302    * @param url
303    *          - the url
304    * @param transId
305    *          - transaction ID
306    * @param payload
307    *          - the XML or JSON payload for the request
308    * @param mimeType
309    *          - the content type (XML or JSON)
310    * @return ClientResponse
311    */
312   public ClientResponse postResource(String url, String payload, String transId, MimeType mimeType) {
313     ClientResponse result = null;
314     ByteArrayOutputStream baos = new ByteArrayOutputStream();
315     long startTimeInMs = 0;
316     MdcOverride override = new MdcOverride();
317
318     try {
319       Client client = setupClient();
320
321       baos = new ByteArrayOutputStream();
322       PrintStream ps = new PrintStream(baos);
323       if (logger.isDebugEnabled()) {
324         client.addFilter(new LoggingFilter(ps));
325       }
326
327       // Grab the current time so that we can use it for metrics purposes later.
328       startTimeInMs = System.currentTimeMillis();
329       override.addAttribute(MdcContext.MDC_START_TIME, dateFormatter.format(startTimeInMs));
330
331       if (useBasicAuth()) {
332         result = client.resource(url).header(HEADER_TRANS_ID, transId)
333             .header(HEADER_FROM_APP_ID, ML_APP_NAME)
334             .header(HEADER_AUTHORIZATION, getAuthenticationCredentials())
335             .type(mimeType.getHttpHeaderType()).post(ClientResponse.class, payload);
336       } else {
337         result = client.resource(url).header(HEADER_TRANS_ID, transId)
338             .header(HEADER_FROM_APP_ID, ML_APP_NAME).type(mimeType.getHttpHeaderType())
339             .post(ClientResponse.class, payload);
340       }
341     } catch (Exception ex) {
342       logger.error(ModelLoaderMsgs.AAI_REST_REQUEST_ERROR, "POST", url, ex.getLocalizedMessage());
343       return null;
344     } finally {
345       if (logger.isDebugEnabled()) {
346         logger.debug(baos.toString());
347       }
348     }
349
350     if ((result != null) && ((result.getStatus() == Response.Status.CREATED.getStatusCode())
351         || (result.getStatus() == Response.Status.OK.getStatusCode()))) {
352       logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS, "POST", url,
353           Integer.toString(result.getStatus()));
354       metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_SUCCESS,
355           new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus())
356               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
357                   result.getResponseStatus().toString()),
358           override, "POST", url, Integer.toString(result.getStatus()));
359     } else {
360       // If response is not 200 OK, then additionally log the reason
361       String respMsg = result.getEntity(String.class);
362       if (respMsg == null) {
363         respMsg = result.getStatusInfo().getReasonPhrase();
364       }
365       logger.info(ModelLoaderMsgs.AAI_REST_REQUEST_UNSUCCESSFUL, "POST", url,
366           Integer.toString(result.getStatus()), respMsg);
367       metricsLogger.info(ModelLoaderMsgs.AAI_REST_REQUEST_UNSUCCESSFUL,
368           new LogFields().setField(LogLine.DefinedFields.RESPONSE_CODE, result.getStatus())
369               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION,
370                   result.getResponseStatus().toString()),
371           override, "POST", url, Integer.toString(result.getStatus()), respMsg);
372     }
373
374     return result;
375   }
376   
377   /**
378    * Does a GET on a resource to retrieve the resource version, and then DELETE
379    * that version.
380    *
381    * @param url
382    *          - the url
383    * @param transId
384    *          - transaction ID
385    * @return ClientResponse
386    */
387   public ClientResponse getAndDeleteResource(String url, String transId) {
388     // First, GET the model
389     ClientResponse getResponse = getResource(url, transId, MimeType.XML);
390     if ((getResponse == null) || (getResponse.getStatus() != Response.Status.OK.getStatusCode())) {
391       return getResponse;
392     }
393
394     // Delete the model using the resource version in the response
395     String resVersion = null;
396     try {
397       resVersion = getResourceVersion(getResponse);
398     } catch (Exception e) {
399       logger.error(ModelLoaderMsgs.AAI_REST_REQUEST_ERROR, "GET", url, e.getLocalizedMessage());
400       return null;
401     }
402
403     return deleteResource(url, resVersion, transId);
404   }
405
406   private Client setupClient() throws IOException, GeneralSecurityException {
407     ClientConfig clientConfig = new DefaultClientConfig();
408
409     HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
410       @Override
411       public boolean verify(String string, SSLSession ssls) {
412         return true;
413       }
414     });
415
416     // Create a trust manager that does not validate certificate chains
417     TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
418       @Override
419       public X509Certificate[] getAcceptedIssuers() {
420         return null;
421       }
422
423       @Override
424       public void checkClientTrusted(X509Certificate[] certs, String authType) {}
425
426       @Override
427       public void checkServerTrusted(X509Certificate[] certs, String authType) {}
428     } };
429
430     SSLContext ctx = SSLContext.getInstance("TLS");
431     KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
432     FileInputStream fin = new FileInputStream(config.getAaiKeyStorePath());
433     KeyStore ks = KeyStore.getInstance("PKCS12");
434     char[] pwd = config.getAaiKeyStorePassword().toCharArray();
435     ks.load(fin, pwd);
436     kmf.init(ks, pwd);
437
438     ctx.init(kmf.getKeyManagers(), trustAllCerts, null);
439     clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
440         new HTTPSProperties(new HostnameVerifier() {
441           @Override
442           public boolean verify(String theString, SSLSession sslSession) {
443             return true;
444           }
445         }, ctx));
446
447     Client client = Client.create(clientConfig);
448
449     return client;
450   }
451
452   private String getResourceVersion(ClientResponse response)
453       throws ParserConfigurationException, SAXException, IOException {
454     String respData = response.getEntity(String.class);
455
456     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
457     DocumentBuilder builder = factory.newDocumentBuilder();
458     InputSource is = new InputSource(new StringReader(respData));
459     Document doc = builder.parse(is);
460
461     NodeList nodeList = doc.getDocumentElement().getChildNodes();
462     for (int i = 0; i < nodeList.getLength(); i++) {
463       Node currentNode = nodeList.item(i);
464       if (currentNode.getNodeName().equals(RESOURCE_VERSION_PARAM)) {
465         return currentNode.getTextContent();
466       }
467     }
468
469     return null;
470   }
471
472   private String getAuthenticationCredentials() {
473
474     String usernameAndPassword = config.getAaiAuthenticationUser() + ":"
475         + config.getAaiAuthenticationPassword();
476     return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes());
477   }
478
479   public boolean useBasicAuth() {
480     return (config.getAaiAuthenticationUser() != null)
481         && (config.getAaiAuthenticationPassword() != null);
482   }
483 }