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