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