ae102a834e0549d4ed259f0a607cf78dd4e1c996
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / restcore / RESTAPI.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 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.onap.aai.restcore;
22
23 import com.att.eelf.configuration.EELFLogger;
24 import com.att.eelf.configuration.EELFManager;
25 import com.google.common.base.Joiner;
26
27 import java.io.UnsupportedEncodingException;
28 import java.net.URI;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.ExecutorService;
35 import java.util.concurrent.Executors;
36 import java.util.concurrent.Future;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
39
40 import javax.ws.rs.core.HttpHeaders;
41 import javax.ws.rs.core.MediaType;
42 import javax.ws.rs.core.Response;
43 import javax.ws.rs.core.UriInfo;
44
45 import org.onap.aai.db.props.AAIProperties;
46 import org.onap.aai.dbmap.DBConnectionType;
47 import org.onap.aai.exceptions.AAIException;
48 import org.onap.aai.introspection.Introspector;
49 import org.onap.aai.introspection.Loader;
50 import org.onap.aai.introspection.tools.CreateUUID;
51 import org.onap.aai.introspection.tools.DefaultFields;
52 import org.onap.aai.introspection.tools.InjectKeysFromURI;
53 import org.onap.aai.introspection.tools.IntrospectorValidator;
54 import org.onap.aai.introspection.tools.Issue;
55 import org.onap.aai.introspection.tools.RemoveNonVisibleProperty;
56 import org.onap.aai.logging.ErrorLogHelper;
57 import org.onap.aai.logging.LoggingContext;
58 import org.onap.aai.util.AAIConfig;
59 import org.onap.aai.util.FormatDate;
60
61 /**
62  * Base class for AAI REST API classes.
63  * Provides method to validate header information
64  * TODO should authenticate caller and authorize them for the API they are calling
65  * TODO should store the transaction
66  *
67  * 
68  */
69 public class RESTAPI {
70
71     private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(RESTAPI.class);
72
73     protected final String COMPONENT = "aairest";
74
75     /**
76      * The Enum Action.
77      */
78     public enum Action {
79         GET, PUT, POST, DELETE
80     };
81
82     /**
83      * Gets the from app id.
84      *
85      * @param headers the headers
86      * @return the from app id
87      * @throws AAIException the AAI exception
88      */
89     protected String getFromAppId(HttpHeaders headers) throws AAIException {
90         String fromAppId = null;
91         if (headers != null) {
92             List<String> fromAppIdHeader = headers.getRequestHeader("X-FromAppId");
93             if (fromAppIdHeader != null) {
94                 for (String fromAppIdValue : fromAppIdHeader) {
95                     fromAppId = fromAppIdValue;
96                 }
97             }
98         }
99
100         if (fromAppId == null) {
101             throw new AAIException("AAI_4009");
102         }
103
104         LoggingContext.partnerName(fromAppId);
105
106         return fromAppId;
107     }
108
109     /**
110      * Gets the trans id.
111      *
112      * @param headers the headers
113      * @return the trans id
114      * @throws AAIException the AAI exception
115      */
116     protected String getTransId(HttpHeaders headers) throws AAIException {
117         String transId = null;
118         if (headers != null) {
119             List<String> transIdHeader = headers.getRequestHeader("X-TransactionId");
120             if (transIdHeader != null) {
121                 for (String transIdValue : transIdHeader) {
122                     transId = transIdValue;
123                 }
124             }
125         }
126
127         if (transId == null) {
128             throw new AAIException("AAI_4010");
129         }
130
131         LoggingContext.requestId(transId);
132
133         return transId;
134     }
135
136     /**
137      * Gen date.
138      *
139      * @return the string
140      */
141     protected String genDate() {
142         FormatDate fd = new FormatDate("YYMMdd-HH:mm:ss:SSS");
143
144         return fd.getDateTime();
145     }
146
147     /**
148      * Gets the media type.
149      *
150      * @param mediaTypeList the media type list
151      * @return the media type
152      */
153     protected String getMediaType(List<MediaType> mediaTypeList) {
154         String mediaType = MediaType.APPLICATION_JSON; // json is the default
155         for (MediaType mt : mediaTypeList) {
156             if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
157                 mediaType = MediaType.APPLICATION_XML;
158             }
159         }
160         return mediaType;
161     }
162
163     /* ----------helpers for common consumer actions ----------- */
164
165     /**
166      * Sets the depth.
167      *
168      * @param depthParam the depth param
169      * @return the int
170      * @throws AAIException the AAI exception
171      */
172     protected int setDepth(String depthParam) throws AAIException {
173         int depth = AAIProperties.MAXIMUM_DEPTH; // default
174         if (depthParam != null && depthParam.length() > 0 && !depthParam.equals("all")) {
175             try {
176                 depth = Integer.valueOf(depthParam);
177             } catch (Exception e) {
178                 throw new AAIException("AAI_4016");
179             }
180         }
181         return depth;
182     }
183
184     /**
185      * Consumer exception response generator.
186      *
187      * @param headers the headers
188      * @param info the info
189      * @param templateAction the template action
190      * @param e the e
191      * @return the response
192      */
193     protected Response consumerExceptionResponseGenerator(HttpHeaders headers, UriInfo info, HttpMethod templateAction,
194             AAIException e) {
195         ArrayList<String> templateVars = new ArrayList<String>();
196         templateVars.add(templateAction.toString()); // GET, PUT, etc
197         templateVars.add(info.getPath().toString());
198         templateVars.addAll(e.getTemplateVars());
199
200         ErrorLogHelper.logException(e);
201         return Response
202                 .status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
203                         .getRESTAPIErrorResponseWithLogging(headers.getAcceptableMediaTypes(), e, templateVars))
204                 .build();
205     }
206
207     /**
208      * Validate introspector.
209      *
210      * @param obj the obj
211      * @param loader the loader
212      * @param uri the uri
213      * @throws AAIException the AAI exception
214      * @throws UnsupportedEncodingException the unsupported encoding exception
215      */
216     protected void validateIntrospector(Introspector obj, Loader loader, URI uri, HttpMethod method)
217             throws AAIException, UnsupportedEncodingException {
218
219         int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
220         boolean validateRequired = true;
221         if (method.equals(HttpMethod.MERGE_PATCH)) {
222             validateRequired = false;
223             maximumDepth = 0;
224         }
225         IntrospectorValidator validator = new IntrospectorValidator.Builder().validateRequired(validateRequired)
226                 .restrictDepth(maximumDepth).addResolver(new RemoveNonVisibleProperty()).addResolver(new CreateUUID())
227                 .addResolver(new DefaultFields()).addResolver(new InjectKeysFromURI(loader, uri)).build();
228         boolean result = validator.validate(obj);
229         if (!result) {
230             result = validator.resolveIssues();
231         }
232         if (!result) {
233             List<String> messages = new ArrayList<>();
234             for (Issue issue : validator.getIssues()) {
235                 if (!issue.isResolved()) {
236                     messages.add(issue.getDetail());
237                 }
238             }
239             String errors = Joiner.on(",").join(messages);
240             throw new AAIException("AAI_3000", errors);
241         }
242         // check that key in payload and key in request uri are the same
243         String objURI = obj.getURI();
244         // if requested object is a parent objURI will have a leading slash the input uri will lack
245         // this adds that leading slash for the comparison
246         String testURI = "/" + uri.getRawPath();
247         if (!testURI.endsWith(objURI)) {
248             throw new AAIException("AAI_3000", "uri and payload keys don't match");
249         }
250     }
251
252     protected DBConnectionType determineConnectionType(String fromAppId, String realTime) throws AAIException {
253         if (fromAppId == null) {
254             throw new AAIException("AAI_4009", "X-FromAppId is not set");
255         }
256
257         DBConnectionType type = DBConnectionType.REALTIME;
258         boolean isRealTimeClient = AAIConfig.get("aai.realtime.clients", "").contains(fromAppId);
259         if (isRealTimeClient || realTime != null) {
260             type = DBConnectionType.REALTIME;
261         } else {
262             type = DBConnectionType.CACHED;
263         }
264
265         return type;
266     }
267
268     /**
269      * Gets the input media type.
270      *
271      * @param mediaType the media type
272      * @return the input media type
273      */
274     protected String getInputMediaType(MediaType mediaType) {
275         String result = mediaType.getType() + "/" + mediaType.getSubtype();
276
277         return result;
278
279     }
280
281     /**
282      * Returns the app specific timeout in milliseconds, -1 overrides the timeout for an app
283      *
284      * @param sot
285      * @param appTimeouts
286      * @param defaultTimeout
287      * @return integer timeout in or -1 to bypass
288      * @throws AAIException
289      */
290
291     public int getTimeoutLimit(String sot, String appTimeouts, String defaultTimeout) throws AAIException {
292         String[] ignoreAppIds = (appTimeouts).split("\\|");
293         int appLimit = Integer.parseInt(defaultTimeout);
294         final Map<String, Integer> m = new HashMap<String, Integer>();
295         if (ignoreAppIds != null) {
296             for (int i = 0; i < ignoreAppIds.length; i++) {
297                 String[] vals = ignoreAppIds[i].split(",");
298                 m.put(vals[0], Integer.parseInt(vals[1]));
299             }
300             if (m.get(sot) != null) {
301                 appLimit = m.get(sot);
302             }
303         }
304         return appLimit;
305     }
306
307     /**
308      * Returns whether time out is enabled
309      * 
310      * @param sot
311      * @param isEnabled
312      * @param appTimeouts
313      * @param defaultTimeout
314      * @return boolean of whether the timeout is enabled
315      * @throws AAIException
316      */
317     public boolean isTimeoutEnabled(String sot, String isEnabled, String appTimeouts, String defaultTimeout)
318             throws AAIException {
319         Boolean isTimeoutEnabled = Boolean.parseBoolean(isEnabled);
320         int ata = -1;
321         if (isTimeoutEnabled) {
322             ata = getTimeoutLimit(sot, appTimeouts, defaultTimeout);
323         }
324         return isTimeoutEnabled && (ata > -1);
325     }
326
327     /**
328      * Executes the process thread and watches the future for the timeout
329      * 
330      * @param handler
331      * @param sourceOfTruth
332      * @param appTimeoutLimit
333      * @param defaultTimeoutLimit
334      * @param method
335      * @param headers
336      * @param info
337      * @return the response
338      */
339
340     public Response executeProcess(Future<Response> handler, String sourceOfTruth, String appTimeoutLimit,
341             String defaultTimeoutLimit, HttpMethod method, HttpHeaders headers, UriInfo info) {
342         Response response = null;
343         int timeoutLimit = 0;
344         try {
345             timeoutLimit = getTimeoutLimit(sourceOfTruth, appTimeoutLimit, defaultTimeoutLimit);
346             response = handler.get(timeoutLimit, TimeUnit.MILLISECONDS);
347         } catch (TimeoutException e) {
348             AAIException ex = new AAIException("AAI_7406",
349                     String.format("Timeout limit of %s seconds reached.", timeoutLimit / 1000));
350             response = consumerExceptionResponseGenerator(headers, info, method, ex);
351             handler.cancel(true);
352         } catch (Exception e) {
353             AAIException ex = new AAIException("AAI_4000", e);
354             response = consumerExceptionResponseGenerator(headers, info, method, ex);
355         }
356         return response;
357     }
358
359     /**
360      * runner sets up the timer logic and invokes it
361      * 
362      * @param toe
363      * @param tba
364      * @param tdl
365      * @param headers
366      * @param info
367      * @param httpMethod
368      * @param c
369      * @return the response
370      */
371     public Response runner(String toe, String tba, String tdl, HttpHeaders headers, UriInfo info, HttpMethod httpMethod,
372             Callable c) {
373         Response response = null;
374         Future<Response> handler = null;
375         ExecutorService executor = null;
376         try {
377             String timeoutEnabled = AAIConfig.get(toe);
378             String timeoutByApp = AAIConfig.get(tba);
379             String timeoutDefaultLimit = AAIConfig.get(tdl);
380             String sourceOfTruth = headers.getRequestHeaders().getFirst("X-FromAppId");
381             if (isTimeoutEnabled(sourceOfTruth, timeoutEnabled, timeoutByApp, timeoutDefaultLimit)) {
382                 executor = Executors.newSingleThreadExecutor();
383                 handler = executor.submit(c);
384                 response = executeProcess(handler, sourceOfTruth, timeoutByApp, timeoutDefaultLimit, httpMethod,
385                         headers, info);
386             } else {
387                 response = (Response) c.call();
388             }
389         } catch (Exception e) {
390             AAIException ex = new AAIException("AAI_4000", e);
391             response = consumerExceptionResponseGenerator(headers, info, httpMethod, ex);
392         } finally {
393             if (executor != null && handler != null) {
394                 executor.shutdownNow();
395             }
396         }
397         return response;
398     }
399
400 }