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