Update the license for 2017-2018 license
[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 package org.onap.aai.restcore;
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.URI;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.HashMap;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.Future;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.Callable;
33 import java.util.concurrent.TimeoutException;
34
35 import javax.ws.rs.core.HttpHeaders;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.Response;
38 import javax.ws.rs.core.UriInfo;
39
40 import org.onap.aai.db.props.AAIProperties;
41 import org.onap.aai.dbmap.DBConnectionType;
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.logging.LoggingContext;
53 import org.onap.aai.util.AAIConfig;
54 import org.onap.aai.util.FormatDate;
55
56 import com.att.eelf.configuration.EELFLogger;
57 import com.att.eelf.configuration.EELFManager;
58 import com.google.common.base.Joiner;
59
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         /**
138          * Gen date.
139          *
140          * @return the string
141          */
142         protected String genDate() {
143                 FormatDate fd = new FormatDate( "YYMMdd-HH:mm:ss:SSS");
144                 
145                 return fd.getDateTime();
146         }
147
148         /**
149          * Gets the media type.
150          *
151          * @param mediaTypeList the media type list
152          * @return the media type
153          */
154         protected String getMediaType(List <MediaType> mediaTypeList) {
155                 String mediaType = MediaType.APPLICATION_JSON;  // json is the default    
156                 for (MediaType mt : mediaTypeList) {
157                         if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
158                                 mediaType = MediaType.APPLICATION_XML;
159                         } 
160                 }
161                 return mediaType;
162         }
163         
164
165         /* ----------helpers for common consumer actions ----------- */
166         
167         /**
168          * Sets the depth.
169          *
170          * @param depthParam the depth param
171          * @return the int
172          * @throws AAIException the AAI exception
173          */
174         protected int setDepth(String depthParam) throws AAIException {
175                 int depth = AAIProperties.MAXIMUM_DEPTH; //default 
176                 if (depthParam != null && depthParam.length() > 0 && !depthParam.equals("all")){
177                         try {
178                                 depth = Integer.valueOf(depthParam);
179                         } catch (Exception e) {
180                                 throw new AAIException("AAI_4016");
181                         }
182                 }
183                 return depth;
184         }
185
186         /**
187          * Consumer exception response generator.
188          *
189          * @param headers the headers
190          * @param info the info
191          * @param templateAction the template action
192          * @param e the e
193          * @return the response
194          */
195         protected Response consumerExceptionResponseGenerator(HttpHeaders headers, UriInfo info, HttpMethod templateAction, AAIException e) {
196                 ArrayList<String> templateVars = new ArrayList<String>();
197                 templateVars.add(templateAction.toString()); //GET, PUT, etc
198                 templateVars.add(info.getPath().toString());
199                 templateVars.addAll(e.getTemplateVars());
200
201                 ErrorLogHelper.logException(e);
202                 return Response
203                                 .status(e.getErrorObject().getHTTPResponseCode())
204                                 .entity(ErrorLogHelper.getRESTAPIErrorResponseWithLogging(headers.getAcceptableMediaTypes(), e, templateVars))
205                                 .build();
206         }
207         
208         /**
209          * Validate introspector.
210          *
211          * @param obj the obj
212          * @param loader the loader
213          * @param uri the uri
214          * @throws AAIException the AAI exception
215          * @throws UnsupportedEncodingException the unsupported encoding exception
216          */
217         protected void validateIntrospector(Introspector obj, Loader loader, URI uri, HttpMethod method) 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()
226                                 .validateRequired(validateRequired)
227                                 .restrictDepth(maximumDepth)
228                                 .addResolver(new RemoveNonVisibleProperty())
229                                 .addResolver(new CreateUUID())
230                                 .addResolver(new DefaultFields())
231                                 .addResolver(new InjectKeysFromURI(loader, uri))
232                                 .build();
233                 boolean result = validator.validate(obj);
234                 if (!result) {
235                         result = validator.resolveIssues();
236                 }
237                 if (!result) {
238                         List<String> messages = new ArrayList<>();
239                         for (Issue issue : validator.getIssues()) {
240                                 if (!issue.isResolved()) {
241                                         messages.add(issue.getDetail());
242                                 }
243                         }
244                         String errors = Joiner.on(",").join(messages);
245                         throw new AAIException("AAI_3000", errors);
246                 }
247                 //check that key in payload and key in request uri are the same
248         String objURI = obj.getURI();
249         //if requested object is a parent objURI will have a leading slash the input uri will lack
250         //this adds that leading slash for the comparison
251         String testURI = "/" + uri.getRawPath();
252         if (!testURI.endsWith(objURI)) {
253                 throw new AAIException("AAI_3000", "uri and payload keys don't match");
254         }
255         }
256
257         protected DBConnectionType determineConnectionType(String fromAppId, String realTime) throws AAIException {
258                 if (fromAppId == null) {
259                         throw new AAIException("AAI_4009", "X-FromAppId is not set");
260                 }
261
262                 DBConnectionType type = DBConnectionType.REALTIME;
263                 boolean isRealTimeClient = AAIConfig.get("aai.realtime.clients", "").contains(fromAppId);
264                 if (isRealTimeClient || realTime != null) {
265                         type = DBConnectionType.REALTIME;
266                 } else {
267                         type = DBConnectionType.CACHED;
268                 }
269
270                 return type;
271         }
272         
273         /**
274          * Gets the input media type.
275          *
276          * @param mediaType the media type
277          * @return the input media type
278          */
279         protected String getInputMediaType(MediaType mediaType) {
280                 String result = mediaType.getType() + "/" + mediaType.getSubtype();
281                 
282                 return result;
283                 
284         }
285
286         /**
287          * Returns the app specific timeout in milliseconds, -1 overrides the timeout for an app
288          *
289          * @param sot
290          * @param appTimeouts
291          * @param defaultTimeout
292          * @return integer timeout in  or -1 to bypass
293          * @throws AAIException
294          */
295
296         public int getTimeoutLimit(String sot, String appTimeouts, String defaultTimeout) throws AAIException{
297                 String[] ignoreAppIds = (appTimeouts).split("\\|");
298                 int appLimit = Integer.parseInt(defaultTimeout);
299                 final Map<String, Integer> m = new HashMap<String, Integer>();
300                 if(ignoreAppIds != null) {
301                         for (int i = 0; i < ignoreAppIds.length; i++) {
302                                 String[] vals = ignoreAppIds[i].split(",");
303                                 m.put(vals[0], Integer.parseInt(vals[1]));
304                         }
305                         if (m.get(sot) != null) {
306                                 appLimit = m.get(sot);
307                         }
308                 }
309                 return appLimit;
310         }
311
312         /**
313          * Returns whether time out is enabled
314          * @param sot
315          * @param isEnabled
316          * @param appTimeouts
317          * @param defaultTimeout
318          * @return boolean of whether the timeout is enabled
319          * @throws AAIException
320          */
321         public boolean isTimeoutEnabled(String sot, String isEnabled, String appTimeouts, String defaultTimeout) throws AAIException{
322                 Boolean isTimeoutEnabled = Boolean.parseBoolean(isEnabled);
323                 int ata = -1;
324                 if(isTimeoutEnabled) {
325                         ata = getTimeoutLimit(sot, appTimeouts, defaultTimeout);
326                 }
327                 return isTimeoutEnabled && (ata > -1);
328         }
329
330         /**
331          * Executes the process thread and watches the future for the timeout
332          * @param handler
333          * @param sourceOfTruth
334          * @param appTimeoutLimit
335          * @param defaultTimeoutLimit
336          * @param method
337          * @param headers
338          * @param info
339          * @return the response
340          */
341
342         public Response executeProcess(Future<Response> handler, String sourceOfTruth, String appTimeoutLimit, String defaultTimeoutLimit, HttpMethod method, HttpHeaders headers, UriInfo info){
343                 Response response = null;
344                 int timeoutLimit = 0;
345                 try {
346                         timeoutLimit = getTimeoutLimit(sourceOfTruth, appTimeoutLimit, defaultTimeoutLimit);
347                         response = handler.get(timeoutLimit, TimeUnit.MILLISECONDS);
348                 } catch (TimeoutException e) {
349                         AAIException ex = new AAIException("AAI_7406", 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          * @param toe
362          * @param tba
363          * @param tdl
364          * @param headers
365          * @param info
366          * @param httpMethod
367          * @param c
368          * @return the response
369          */
370         public Response runner(String toe, String tba, String tdl, HttpHeaders headers, UriInfo info, HttpMethod httpMethod, Callable c){
371                 Response response = null;
372                 Future<Response> handler = null;
373                 ExecutorService executor = null;
374                 try {
375                         String timeoutEnabled = AAIConfig.get(toe);
376                         String timeoutByApp = AAIConfig.get(tba);
377                         String timeoutDefaultLimit = AAIConfig.get(tdl);
378                         String sourceOfTruth = headers.getRequestHeaders().getFirst("X-FromAppId");
379                         if (isTimeoutEnabled(sourceOfTruth, timeoutEnabled, timeoutByApp, timeoutDefaultLimit)) {
380                                 executor = Executors.newSingleThreadExecutor();
381                                 handler = executor.submit(c);
382                                 response = executeProcess(handler, sourceOfTruth, timeoutByApp, timeoutDefaultLimit, httpMethod, headers, info);
383                         } else {
384                                 response = (Response) c.call();
385                         }
386                 }catch(Exception e){
387                         AAIException ex = new AAIException("AAI_4000", e);
388                         response = consumerExceptionResponseGenerator(headers, info, httpMethod, ex);
389                 }finally{
390                         if(executor != null && handler != null){
391                                 executor.shutdownNow();
392                         }
393                 }
394                 return response;
395         }
396
397 }
398