ace6e1937db47f80ff6d6cb14682310a78d51471
[so.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Modifications Copyright (c) 2019 Samsung
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.so.bpmn.common.workflow.service;
24
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.UUID;
29 import javax.ws.rs.Consumes;
30 import javax.ws.rs.POST;
31 import javax.ws.rs.Path;
32 import javax.ws.rs.PathParam;
33 import javax.ws.rs.Produces;
34 import javax.ws.rs.core.Response;
35 import javax.ws.rs.ext.Provider;
36 import org.camunda.bpm.engine.ProcessEngineServices;
37 import org.camunda.bpm.engine.variable.impl.VariableMapImpl;
38 import org.onap.logging.ref.slf4j.ONAPLogConstants;
39 import org.onap.so.bpmn.common.workflow.context.WorkflowContext;
40 import org.onap.so.bpmn.common.workflow.context.WorkflowContextHolder;
41 import org.onap.so.bpmn.common.workflow.context.WorkflowResponse;
42 import org.openecomp.mso.bpmn.common.workflow.service.WorkflowProcessorException;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.slf4j.MDC;
46 import org.springframework.beans.factory.annotation.Autowired;
47 import org.springframework.stereotype.Component;
48 import io.swagger.annotations.Api;
49 import io.swagger.annotations.ApiOperation;
50
51
52 /**
53  * 
54  * @version 1.0 Asynchronous Workflow processing using JAX RS RESTeasy implementation Both Synchronous and Asynchronous
55  *          BPMN process can benefit from this implementation since the workflow gets executed in the background and the
56  *          server thread is freed up, server scales better to process more incoming requests
57  * 
58  *          Usage: For synchronous process, when you are ready to send the response invoke the callback to write the
59  *          response For asynchronous process - the activity may send a acknowledgement response and then proceed
60  *          further on executing the process
61  */
62 @Path("/async")
63 @Api(value = "/async", description = "Provides asynchronous starting of a bpmn process")
64 @Provider
65 @Component
66 public class WorkflowAsyncResource extends ProcessEngineAwareService {
67
68     private static final WorkflowContextHolder contextHolder = WorkflowContextHolder.getInstance();
69
70     long workflowPollInterval = 1000;
71
72     @Autowired
73     private WorkflowProcessor processor;
74
75     @Autowired
76     private WorkflowContextHolder workflowContext;
77
78     public void setProcessor(WorkflowProcessor processor) {
79         this.processor = processor;
80     }
81
82     protected static final Logger logger = LoggerFactory.getLogger(WorkflowAsyncResource.class);
83     protected static final long DEFAULT_WAIT_TIME = 60000; // default wait time
84
85     /**
86      * Asynchronous JAX-RS method that starts a process instance.
87      * 
88      * @param processKey the process key
89      * @param variableMap input variables to the process
90      * @return
91      */
92
93     @POST
94     @Path("/services/{processKey}")
95     @ApiOperation(value = "Starts a new process with the appropriate process Key",
96             notes = "Aysnc fall outs are only logged")
97     @Produces("application/json")
98     @Consumes("application/json")
99     public Response startProcessInstanceByKey(@PathParam("processKey") String processKey, VariableMapImpl variableMap) {
100         Map<String, Object> inputVariables = getInputVariables(variableMap);
101         try {
102             MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, getRequestId(inputVariables));
103             processor.startProcess(processKey, variableMap);
104             WorkflowResponse response = waitForResponse(inputVariables);
105             return Response.status(202).entity(response).build();
106         } catch (WorkflowProcessorException e) {
107             WorkflowResponse response = e.getWorkflowResponse();
108             return Response.status(500).entity(response).build();
109         } catch (Exception e) {
110             WorkflowResponse response = buildUnkownError(getRequestId(inputVariables), e.getMessage());
111             return Response.status(500).entity(response).build();
112         }
113     }
114
115     private WorkflowResponse waitForResponse(Map<String, Object> inputVariables) throws Exception {
116         String requestId = getRequestId(inputVariables);
117         long currentWaitTime = 0;
118         long waitTime = getWaitTime(inputVariables);
119         logger.debug("WorkflowAsyncResource.waitForResponse using timeout: " + waitTime);
120         while (waitTime > currentWaitTime) {
121             Thread.sleep(workflowPollInterval);
122             currentWaitTime = currentWaitTime + workflowPollInterval;
123             WorkflowContext foundContext = contextHolder.getWorkflowContext(requestId);
124             if (foundContext != null) {
125                 contextHolder.remove(foundContext);
126                 return buildResponse(foundContext);
127             }
128         }
129         throw new Exception("TimeOutOccured in WorkflowAsyncResource.waitForResponse for time " + waitTime + "ms");
130     }
131
132     private WorkflowResponse buildUnkownError(String requestId, String error) {
133         WorkflowResponse response = new WorkflowResponse();
134         response.setMessage(error);
135         response.setResponse("UnknownError, request id:" + requestId);
136         response.setMessageCode(500);
137         return response;
138     }
139
140     private WorkflowResponse buildResponse(WorkflowContext foundContext) {
141         return foundContext.getWorkflowResponse();
142     }
143
144     protected static String getOrCreate(Map<String, Object> inputVariables, String key) {
145         String value = Objects.toString(inputVariables.get(key), null);
146         if (value == null) {
147             value = UUID.randomUUID().toString();
148             inputVariables.put(key, value);
149         }
150         return value;
151     }
152
153     protected static String getRequestId(Map<String, Object> inputVariables) {
154         return getOrCreate(inputVariables, "mso-request-id");
155     }
156
157     protected boolean isProcessEnded(String processInstanceId) {
158         ProcessEngineServices pes = getProcessEngineServices();
159         return pes.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId)
160                 .singleResult() == null;
161     }
162
163     protected static Map<String, Object> getInputVariables(VariableMapImpl variableMap) {
164         Map<String, Object> inputVariables = new HashMap<>();
165         @SuppressWarnings("unchecked")
166         Map<String, Object> vMap = (Map<String, Object>) variableMap.get("variables");
167         for (Map.Entry<String, Object> entry : vMap.entrySet()) {
168             String vName = entry.getKey();
169             Object value = entry.getValue();
170             @SuppressWarnings("unchecked")
171             Map<String, Object> valueMap = (Map<String, Object>) value; // value, type
172             inputVariables.put(vName, valueMap.get("value"));
173         }
174         return inputVariables;
175     }
176
177     /**
178      * Returns the wait time, this is used by the resource on how long it should wait to send a response If none
179      * specified DEFAULT_WAIT_TIME is used
180      *
181      * @param inputVariables
182      * @return
183      */
184     private long getWaitTime(Map<String, Object> inputVariables) {
185         String timeout = inputVariables.get("mso-service-request-timeout") == null ? null
186                 : inputVariables.get("mso-service-request-timeout").toString();
187
188         if (timeout != null) {
189             try {
190                 return Long.parseLong(timeout) * 1000;
191             } catch (NumberFormatException nex) {
192                 logger.debug("Invalid input for mso-service-request-timeout");
193             }
194         }
195         return DEFAULT_WAIT_TIME;
196     }
197
198 }