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