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