ffaf8a839954da9c8365b8c56f9b56d7bd59a4bd
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / servlets / BeGenericServlet.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 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
21 package org.openecomp.sdc.be.servlets;
22
23 import com.fasterxml.jackson.databind.DeserializationFeature;
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.fasterxml.jackson.databind.module.SimpleModule;
26 import com.google.common.annotations.VisibleForTesting;
27 import com.google.gson.Gson;
28 import com.google.gson.GsonBuilder;
29 import com.google.gson.reflect.TypeToken;
30 import fj.data.Either;
31 import io.swagger.v3.oas.annotations.OpenAPIDefinition;
32 import io.swagger.v3.oas.annotations.info.Info;
33 import io.swagger.v3.oas.annotations.servers.Server;
34 import org.json.simple.JSONArray;
35 import org.json.simple.JSONObject;
36 import org.json.simple.parser.JSONParser;
37 import org.json.simple.parser.ParseException;
38 import org.openecomp.sdc.be.components.impl.ArtifactsBusinessLogic;
39 import org.openecomp.sdc.be.components.impl.BaseBusinessLogic;
40 import org.openecomp.sdc.be.components.impl.ComponentInstanceBusinessLogic;
41 import org.openecomp.sdc.be.components.impl.ElementBusinessLogic;
42 import org.openecomp.sdc.be.components.impl.GenericArtifactBrowserBusinessLogic;
43 import org.openecomp.sdc.be.components.impl.InputsBusinessLogic;
44 import org.openecomp.sdc.be.components.impl.OutputsBusinessLogic;
45 import org.openecomp.sdc.be.components.impl.PolicyBusinessLogic;
46 import org.openecomp.sdc.be.components.impl.ResourceBusinessLogic;
47 import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic;
48 import org.openecomp.sdc.be.components.impl.exceptions.ByActionStatusComponentException;
49 import org.openecomp.sdc.be.components.impl.exceptions.ComponentException;
50 import org.openecomp.sdc.be.config.BeEcompErrorManager;
51 import org.openecomp.sdc.be.dao.api.ActionStatus;
52 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
53 import org.openecomp.sdc.be.datatypes.enums.DeclarationTypeEnum;
54 import org.openecomp.sdc.be.datatypes.tosca.ToscaDataDefinition;
55 import org.openecomp.sdc.be.impl.ComponentsUtils;
56 import org.openecomp.sdc.be.impl.WebAppContextWrapper;
57 import org.openecomp.sdc.be.model.ComponentInstInputsMap;
58 import org.openecomp.sdc.be.model.PropertyConstraint;
59 import org.openecomp.sdc.be.model.PropertyDefinition;
60 import org.openecomp.sdc.be.model.InputDefinition;
61 import org.openecomp.sdc.be.model.User;
62 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
63 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation.PropertyConstraintJacksonDeserializer;
64 import org.openecomp.sdc.be.model.operations.impl.UniqueIdBuilder;
65 import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum;
66 import org.openecomp.sdc.be.user.UserBusinessLogic;
67 import org.openecomp.sdc.common.api.Constants;
68 import org.openecomp.sdc.common.log.wrappers.Logger;
69 import org.openecomp.sdc.common.servlets.BasicServlet;
70 import org.openecomp.sdc.exception.ResponseFormat;
71 import org.springframework.web.context.WebApplicationContext;
72
73 import javax.servlet.ServletContext;
74 import javax.servlet.http.HttpServletRequest;
75 import javax.ws.rs.core.Context;
76 import javax.ws.rs.core.Response;
77 import javax.ws.rs.core.Response.ResponseBuilder;
78 import java.io.IOException;
79 import java.lang.reflect.Type;
80 import java.util.HashMap;
81 import java.util.Iterator;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Map.Entry;
85 import java.util.Objects;
86 import java.util.Set;
87 import java.util.function.Supplier;
88
89 @OpenAPIDefinition(info = @Info(title = "SDC API", description = "SDC External, Distribution and Internal APIs"),
90         servers = {@Server(url = "/sdc", description = "SDC External and Distribution APIs"),
91                 @Server(url = "/sdc2/rest", description = "SDC Internal APIs")})
92 public class BeGenericServlet extends BasicServlet {
93
94     public BeGenericServlet(UserBusinessLogic userAdminManager,
95         ComponentsUtils componentsUtils) {
96         this.userAdminManager = userAdminManager;
97         this.componentsUtils = componentsUtils;
98     }
99
100     @Context
101     protected HttpServletRequest servletRequest;
102
103     private static final Logger log = Logger.getLogger(BeGenericServlet.class);
104
105     private static final String PROPERTY_NAME_REGEX = "[\\w,\\d,_]+";
106
107     private UserBusinessLogic userAdminManager;
108     protected ComponentsUtils componentsUtils;
109
110     /******************** New error response mechanism
111      * @param requestErrorWrapper **************/
112
113     protected Response buildErrorResponse(ResponseFormat requestErrorWrapper) {
114         return Response.status(requestErrorWrapper.getStatus()).entity(gson.toJson(requestErrorWrapper.getRequestError())).build();
115     }
116
117     protected Response buildGeneralErrorResponse() {
118         return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR));
119     }
120
121     protected Response buildOkResponse(Object entity) {
122         return buildOkResponseStatic(entity);
123     }
124
125     private static Response buildOkResponseStatic(Object entity) {
126         return Response.status(Response.Status.OK)
127             .entity(entity)
128             .build();
129     }
130
131     public HttpServletRequest getServletRequest() {
132         return servletRequest;
133     }
134
135     @VisibleForTesting
136     public void setRequestServlet(HttpServletRequest request) {this.servletRequest = request;}
137
138     protected Response buildOkResponse(ResponseFormat errorResponseWrapper, Object entity) {
139         return buildOkResponse(errorResponseWrapper, entity, null);
140     }
141
142     protected Response buildOkResponse(ResponseFormat errorResponseWrapper, Object entity, Map<String, String> additionalHeaders) {
143         int status = errorResponseWrapper.getStatus();
144         ResponseBuilder responseBuilder = Response.status(status);
145         if (entity != null) {
146             if (log.isTraceEnabled())
147                 log.trace("returned entity is {}", entity.toString());
148             responseBuilder = responseBuilder.entity(entity);
149         }
150         if (additionalHeaders != null) {
151             for (Entry<String, String> additionalHeader : additionalHeaders.entrySet()) {
152                 String headerName = additionalHeader.getKey();
153                 String headerValue = additionalHeader.getValue();
154                 if (log.isTraceEnabled())
155                     log.trace("Adding header {} with value {} to the response", headerName, headerValue);
156                 responseBuilder.header(headerName, headerValue);
157             }
158         }
159         return responseBuilder.build();
160     }
161
162     /*******************************************************************************************************/
163     protected Either<User, ResponseFormat> getUser(final HttpServletRequest request, String userId) {
164         User user;
165         try {
166             user = getUserAdminManager(request.getSession().getServletContext()).getUser(userId, false);
167             return Either.left(user);
168         } catch (ComponentException ce) {
169             log.info("createResource method - user is not listed. userId= {}", userId);
170             ResponseFormat errorResponse = getComponentsUtils().getResponseFormat(ce);
171             user = new User("", "", userId, "", null, null);
172             getComponentsUtils().auditResource(errorResponse, user, "", AuditingActionEnum.CHECKOUT_RESOURCE);
173             return Either.right(errorResponse);
174         }
175     }
176
177     UserBusinessLogic getUserAdminManager(ServletContext context) {
178         return getClassFromWebAppContext(context, () -> UserBusinessLogic.class);
179     }
180
181     protected GenericArtifactBrowserBusinessLogic getGenericArtifactBrowserBL(ServletContext context) {
182         return getClassFromWebAppContext(context, () -> GenericArtifactBrowserBusinessLogic.class);
183     }
184
185     protected ResourceBusinessLogic getResourceBL(ServletContext context) {
186         return getClassFromWebAppContext(context, () -> ResourceBusinessLogic.class);
187     }
188
189     protected ServiceBusinessLogic getServiceBL(ServletContext context) {
190         return getClassFromWebAppContext(context, () -> ServiceBusinessLogic.class);
191     }
192
193     protected ArtifactsBusinessLogic getArtifactBL(ServletContext context) {
194         return getClassFromWebAppContext(context, () -> ArtifactsBusinessLogic.class);
195     }
196
197     protected ElementBusinessLogic getElementBL(ServletContext context) {
198         return getClassFromWebAppContext(context, () -> ElementBusinessLogic.class);
199     }
200
201     <T> T getClassFromWebAppContext(final ServletContext context, final Supplier<Class<T>> businessLogicClassGen) {
202         return getWebAppContext(context).getBean(businessLogicClassGen.get());
203     }
204
205     protected ComponentInstanceBusinessLogic getComponentInstanceBL(final ServletContext context) {
206         return getClassFromWebAppContext(context, () -> ComponentInstanceBusinessLogic.class);
207     }
208
209     protected ComponentsUtils getComponentsUtils() {
210         final ServletContext context = this.servletRequest.getSession().getServletContext();
211         return getClassFromWebAppContext(context, () -> ComponentsUtils.class);
212     }
213
214     /**
215      * Used to support Unit Test.<br>
216      * Header Params are not supported in Unit Tests
217      *
218      * @return
219      */
220     String initHeaderParam(String headerValue, HttpServletRequest request, String headerName) {
221         String retValue;
222         if (headerValue != null) {
223             retValue = headerValue;
224         } else {
225             retValue = request.getHeader(headerName);
226         }
227         return retValue;
228     }
229
230     protected String getContentDispositionValue(String artifactFileName) {
231         return new StringBuilder().append("attachment; filename=\"").append(artifactFileName).append("\"").toString();
232     }
233
234     <T> T convertJsonToObjectOfClass(String json, Class<T> clazz) {
235         T object = null;
236         ObjectMapper mapper = new ObjectMapper()
237             .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
238             .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
239         try {
240             log.trace("Starting to convert json to object. Json=\n{}", json);
241
242             SimpleModule module = new SimpleModule("customDeserializationModule");
243             module.addDeserializer(PropertyConstraint.class, new PropertyConstraintJacksonDeserializer());
244             mapper.registerModule(module);
245
246             object = mapper.readValue(json, clazz);
247             if (object != null) {
248                 return object;
249             } else {
250                 BeEcompErrorManager.getInstance().logBeInvalidJsonInput("convertJsonToObject");
251                 log.debug("The object of class {} is null after converting from json. ", clazz);
252                 throw new ByActionStatusComponentException(ActionStatus.INVALID_CONTENT);
253             }
254         } catch (IOException e) {
255             BeEcompErrorManager.getInstance().logBeInvalidJsonInput("convertJsonToObject");
256             log.debug("The exception {} occurred upon json to object convertation. Json=\n{}", e, json);
257             throw new ByActionStatusComponentException(ActionStatus.INVALID_CONTENT);
258         }
259     }
260
261     protected Either<Map<String, PropertyDefinition>, ActionStatus> getPropertyModel(String componentId, String data) {
262         JSONParser parser = new JSONParser();
263         JSONObject root;
264         try {
265             Map<String, PropertyDefinition> properties = new HashMap<>();
266             root = (JSONObject) parser.parse(data);
267
268             Set entrySet = root.entrySet();
269             Iterator iterator = entrySet.iterator();
270             while (iterator.hasNext()) {
271                 Entry next = (Entry) iterator.next();
272                 String propertyName = (String) next.getKey();
273
274                 if(!isPropertyNameValid(propertyName)) {
275                     return Either.right(ActionStatus.INVALID_PROPERTY_NAME);
276                 }
277
278                 JSONObject value = (JSONObject) next.getValue();
279                 Either<PropertyDefinition, ActionStatus> propertyDefinitionEither =
280                         getPropertyDefinitionFromJson(componentId, propertyName, value);
281
282                 if(propertyDefinitionEither.isRight()) {
283                     return Either.right(propertyDefinitionEither.right().value());
284                 }
285
286                 properties.put(propertyName, propertyDefinitionEither.left().value());
287             }
288
289             return Either.left(properties);
290         } catch (ParseException e) {
291             log.info("Property conetnt is invalid - {}", data);
292             return Either.right(ActionStatus.INVALID_CONTENT);
293         }
294     }
295
296     protected boolean isPropertyNameValid(String propertyName) {
297         return Objects.nonNull(propertyName)
298                        && propertyName.matches(PROPERTY_NAME_REGEX);
299
300     }
301
302     private Either<PropertyDefinition, ActionStatus> getPropertyDefinitionFromJson(String componentId, String propertyName, JSONObject value) {
303         String jsonString = value.toJSONString();
304         Either<PropertyDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(jsonString, PropertyDefinition.class);
305         if (convertJsonToObject.isRight()) {
306             return Either.right(convertJsonToObject.right().value());
307         }
308         PropertyDefinition propertyDefinition = convertJsonToObject.left().value();
309         String uniqueId = UniqueIdBuilder.buildPropertyUniqueId(componentId, propertyName);
310         propertyDefinition.setUniqueId(uniqueId);
311
312         return Either.left(propertyDefinition);
313     }
314
315     private Either<InputDefinition, ActionStatus> getInputDefinitionFromJson(String componentId, String inputName, JSONObject value) {
316         String jsonString = value.toJSONString();
317         Either<InputDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(jsonString, InputDefinition.class);
318         if (convertJsonToObject.isRight()) {
319             return Either.right(convertJsonToObject.right().value());
320         }
321         InputDefinition inputDefinition = convertJsonToObject.left().value();
322         String uniqueId = UniqueIdBuilder.buildPropertyUniqueId(componentId, inputName);
323         inputDefinition.setUniqueId(uniqueId);
324
325         return Either.left(inputDefinition);
326     }
327
328     protected Either<Map<String, PropertyDefinition>, ActionStatus> getPropertiesListForUpdate(String data) {
329
330         Map<String, PropertyDefinition> properties = new HashMap<>();
331         JSONParser parser = new JSONParser();
332         JSONArray jsonArray;
333
334         try {
335             jsonArray = (JSONArray) parser.parse(data);
336             for (Object jsonElement : jsonArray) {
337                 String propertyAsString = jsonElement.toString();
338                 Either<PropertyDefinition, ActionStatus> convertJsonToObject = convertJsonToObject(propertyAsString, PropertyDefinition.class);
339
340                 if (convertJsonToObject.isRight()) {
341                     return Either.right(convertJsonToObject.right().value());
342                 }
343
344                 PropertyDefinition propertyDefinition = convertJsonToObject.left().value();
345                 properties.put(propertyDefinition.getName(), propertyDefinition);
346             }
347
348             return Either.left(properties);
349         } catch (Exception e) {
350             log.info("Property content is invalid - {}", data);
351             return Either.right(ActionStatus.INVALID_CONTENT);
352         }
353
354     }
355
356     protected String propertyToJson(Map.Entry<String, PropertyDefinition> property) {
357         JSONObject root = new JSONObject();
358         String propertyName = property.getKey();
359         PropertyDefinition propertyDefinition = property.getValue();
360         JSONObject propertyDefinitionO = getPropertyDefinitionJSONObject(propertyDefinition);
361         root.put(propertyName, propertyDefinitionO);
362         propertyDefinition.getType();
363         return root.toString();
364     }
365
366     private JSONObject getPropertyDefinitionJSONObject(PropertyDefinition propertyDefinition) {
367
368         Either<String, ActionStatus> either = convertObjectToJson(propertyDefinition);
369         if (either.isRight()) {
370             return new JSONObject();
371         }
372         try {
373             return  (JSONObject) new JSONParser().parse(either.left().value());
374         } catch (ParseException e) {
375             log.info("failed to convert input to json");
376             log.error("failed to convert to json", e);
377             return new JSONObject();
378         }
379
380     }
381
382     protected  <T> Either<T, ActionStatus> convertJsonToObject(String data, Class<T> clazz) {
383         T t = null;
384         Type constraintType = new TypeToken<PropertyConstraint>() {
385         }.getType();
386         Gson
387             gson = new GsonBuilder().registerTypeAdapter(constraintType, new PropertyOperation.PropertyConstraintDeserialiser()).create();
388         try {
389             log.trace("convert json to object. json=\n {}", data);
390             t = gson.fromJson(data, clazz);
391             if (t == null) {
392                 log.info("object is null after converting from json");
393                 return Either.right(ActionStatus.INVALID_CONTENT);
394             }
395         } catch (Exception e) {
396             // INVALID JSON
397             log.info("failed to convert from json");
398             log.error("failed to convert from json", e);
399             return Either.right(ActionStatus.INVALID_CONTENT);
400         }
401         return Either.left(t);
402     }
403
404     private Either<String, ActionStatus> convertObjectToJson(PropertyDefinition propertyDefinition) {
405         Type constraintType = new TypeToken<PropertyConstraint>() {
406         }.getType();
407         Gson gson = new GsonBuilder().registerTypeAdapter(constraintType, new PropertyOperation.PropertyConstraintSerialiser()).create();
408         try {
409             log.trace("convert object to json. propertyDefinition= {}", propertyDefinition);
410             String json = gson.toJson(propertyDefinition);
411             if (json == null) {
412                 log.info("object is null after converting to json");
413                 return Either.right(ActionStatus.INVALID_CONTENT);
414             }
415             return Either.left(json);
416         } catch (Exception e) {
417             // INVALID JSON
418             log.info("failed to convert to json");
419             log.debug("failed to convert fto json", e);
420             return Either.right(ActionStatus.INVALID_CONTENT);
421         }
422
423     }
424
425     private OutputsBusinessLogic getOutputBL(final ServletContext context) {
426         return getClassFromWebAppContext(context, () -> OutputsBusinessLogic.class);
427     }
428
429     private InputsBusinessLogic getInputBL(final ServletContext context) {
430         return getClassFromWebAppContext(context, () -> InputsBusinessLogic.class);
431     }
432
433     private PolicyBusinessLogic getPolicyBL(final ServletContext context) {
434         return getClassFromWebAppContext(context, () -> PolicyBusinessLogic.class);
435     }
436
437     private WebApplicationContext getWebAppContext(final ServletContext context) {
438         return ((WebAppContextWrapper) context.getAttribute(Constants.WEB_APPLICATION_CONTEXT_WRAPPER_ATTR)).getWebAppContext(context);
439     }
440
441     protected <T> Either<T, ResponseFormat> parseToComponentInstanceMap(final String componentJson,
442                                                                         final User user,
443                                                                         final ComponentTypeEnum componentType,
444                                                                         final Class<T> clazz) {
445         return getComponentsUtils()
446             .convertJsonToObjectUsingObjectMapper(componentJson, user, clazz, AuditingActionEnum.CREATE_RESOURCE, componentType);
447     }
448
449     protected Response declareProperties(String userId, String componentId, String componentType,
450                                          String componentInstInputsMapObj, DeclarationTypeEnum typeEnum, HttpServletRequest request) {
451         ServletContext context = request.getSession().getServletContext();
452         String url = request.getMethod() + " " + request.getRequestURI();
453         log.debug("(get) Start handle request of {}", url);
454
455         try {
456             BaseBusinessLogic businessLogic = getBlForDeclaration(typeEnum, context);
457
458             // get modifier id
459             User modifier = new User();
460             modifier.setUserId(userId);
461             log.debug("modifier id is {}", userId);
462             ComponentTypeEnum componentTypeEnum = ComponentTypeEnum.findByParamName(componentType);
463             Either<ComponentInstInputsMap, ResponseFormat> componentInstInputsMapRes = parseToComponentInstanceMap(
464                 componentInstInputsMapObj, modifier, componentTypeEnum, ComponentInstInputsMap.class);
465             if (componentInstInputsMapRes.isRight()) {
466                 log.debug("failed to parse componentInstInputsMap");
467                 return buildErrorResponse(componentInstInputsMapRes.right().value());
468             }
469
470             Either<List<ToscaDataDefinition>, ResponseFormat> propertiesAfterDeclaration = businessLogic
471                 .declareProperties(userId, componentId,
472                     componentTypeEnum,
473                     componentInstInputsMapRes.left().value());
474             if (propertiesAfterDeclaration.isRight()) {
475                 log.debug("failed to create inputs  for service: {}", componentId);
476                 return buildErrorResponse(propertiesAfterDeclaration.right().value());
477             }
478             Object properties = RepresentationUtils.toRepresentation(propertiesAfterDeclaration.left().value());
479             return buildOkResponse(getComponentsUtils().getResponseFormat(ActionStatus.OK), properties);
480
481         } catch (Exception e) {
482             BeEcompErrorManager.getInstance().logBeRestApiGeneralError("Create inputs for service with id: " + componentId);
483             log.debug("Properties declaration failed with exception", e);
484             return buildErrorResponse(getComponentsUtils().getResponseFormat(ActionStatus.GENERAL_ERROR));
485         }
486     }
487
488     public BaseBusinessLogic getBlForDeclaration(final DeclarationTypeEnum typeEnum,
489                                                  final ServletContext context) {
490         switch (typeEnum) {
491             case OUTPUT:
492                 return getOutputBL(context);
493             case POLICY:
494                 return getPolicyBL(context);
495             case INPUT:
496                 return getInputBL(context);
497             default:
498                 throw new IllegalArgumentException("Invalid DeclarationTypeEnum");
499         }
500     }
501
502     protected Either<Map<String, InputDefinition>, ActionStatus> getInputModel(String componentId, String data) {
503         JSONParser parser = new JSONParser();
504         JSONObject root;
505         try {
506             Map<String, InputDefinition> inputs = new HashMap<>();
507             root = (JSONObject) parser.parse(data);
508
509             Set entrySet = root.entrySet();
510             Iterator iterator = entrySet.iterator();
511             while (iterator.hasNext()) {
512                 Entry next = (Entry) iterator.next();
513                 String inputName = (String) next.getKey();
514
515                 if(!isInputNameValid(inputName)) {
516                     return Either.right(ActionStatus.INVALID_INPUT_NAME);
517                 }
518
519                 JSONObject value = (JSONObject) next.getValue();
520                 Either<InputDefinition, ActionStatus> inputDefinitionEither =
521                         getInputDefinitionFromJson(componentId, inputName, value);
522
523                 if(inputDefinitionEither.isRight()) {
524                     return Either.right(inputDefinitionEither.right().value());
525                 }
526
527                 inputs.put(inputName, inputDefinitionEither.left().value());
528             }
529
530             return Either.left(inputs);
531         } catch (ParseException e) {
532             log.warn("Input content is invalid - {}", data, e);
533             return Either.right(ActionStatus.INVALID_CONTENT);
534         }
535     }
536
537     protected boolean isInputNameValid(String inputName) {
538         return Objects.nonNull(inputName)
539                 && inputName.matches(PROPERTY_NAME_REGEX);
540
541     }
542 }