fb794e251d2b2e33dab87c3b3c65db27f3c874cd
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / onap / so / bpmn / core / ResponseBuilder.java
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.core;
24
25 import org.camunda.bpm.engine.delegate.DelegateExecution;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * Used in the output variable mapping configuration of subflow call activity
31  * tasks to normalize subflow responses.  The output mapping is normally set up
32  * as follows.  Note that the order of these mappings is important!
33  * <p>
34  * OUTPUT MAPPING
35  * <pre>
36  *   SOURCE EXPRESSION                                      TARGET
37  *   ${ResponseBuilder.buildWorkflowException(execution)}   WorkflowException
38  *   ${ResponseBuilder.buildWorkflowResponse(execution)}    SomeResponseVariable
39  * </pre>
40  */
41 public class ResponseBuilder implements java.io.Serializable {
42         private static final long serialVersionUID = 1L;
43         private static final Logger logger = LoggerFactory.getLogger(ResponseBuilder.class);
44
45         /**
46          * Creates a WorkflowException using data from the execution variables.
47          * If the variables do not indicate that there was an error, null
48          * is returned.
49          * @param execution the execution
50          */
51         public WorkflowException buildWorkflowException(DelegateExecution execution) {
52
53                 String method = getClass().getSimpleName() + ".buildWorkflowException(" +
54                         "execution=" + execution.getId() +
55                         ")";
56
57                 logger.debug("Entered " + method);
58
59                 String prefix = (String) execution.getVariable("prefix");
60                 String processKey = getProcessKey(execution);
61
62                 logger.debug("processKey=" + processKey);
63
64                 // See if there"s already a WorkflowException object in the execution.
65                 WorkflowException theException = (WorkflowException) execution.getVariable("WorkflowException");
66
67                 if (theException != null) {
68                         logger.debug("Exited " + method + " - propagated " + theException);
69                         return theException;
70                 }
71
72                 // Look in the legacy variables: ErrorResponse and ResponseCode
73
74                 String errorResponse = trimString(execution.getVariable(prefix + "ErrorResponse"), null);
75                 String responseCode = trimString(execution.getVariable(prefix + "ResponseCode"), null);
76                 logger.debug("errorResponse=" + errorResponse);
77                 logger.debug("responseCode=" + responseCode);
78                 if (errorResponse != null || !isOneOf(responseCode, null, "0", "200", "201", "202", "204")) {
79                         // This is an error condition.  We need to return a WorkflowExcpetion
80
81                         if (errorResponse == null) {
82                                 // No errorResponse string.  See if there"s something in the Response variable
83                                 String response = trimString(execution.getVariable(processKey + "Response"), null);
84                                 if (response == null) {
85                                         errorResponse = "Received response code " + responseCode + " from " + processKey;
86                                 } else {
87                                         errorResponse = response;
88                                 }
89                         }
90
91                         // Some subflows may try to return a WorkflowException as XML in the
92                         // errorResponse. If provided, use the errorCode and errorMessage
93                         // from the XML
94
95                         String maybeXML = removeXMLNamespaces(errorResponse);
96
97                         String xmlErrorMessage = trimString(getXMLTextElement(maybeXML, "ErrorMessage"), null);
98                         String xmlErrorCode = trimString(getXMLTextElement(maybeXML, "ErrorCode"), null);
99
100                         if (xmlErrorMessage != null || xmlErrorCode != null) {
101                                 logger.debug("xmlErrorMessage=" + xmlErrorMessage);
102                                 logger.debug("xmlErrorCode=" + xmlErrorCode);
103
104                                 if (xmlErrorMessage == null) {
105                                         errorResponse = "Received error code " + xmlErrorCode + " from " + processKey;
106                                 } else {
107                                         errorResponse = xmlErrorMessage;
108                                 }
109
110                                 if (xmlErrorCode != null) {
111                                         responseCode = xmlErrorCode;
112                                 }
113                         }
114
115                         // Convert the responseCode to an integer
116
117                         int intResponseCode;
118
119                         try {
120                                 intResponseCode = Integer.valueOf(responseCode);
121                         } catch (NumberFormatException e) {
122                                 // Internal Error
123                                 intResponseCode = 2000;
124                         }
125
126                         // Convert 3-digit HTTP response codes (we should not be using them here)
127                         // to appropriate 4-digit response codes
128
129                         if (intResponseCode < 1000) {
130                                 if (intResponseCode >= 400 && intResponseCode <= 499) {
131                                         // Invalid Message
132                                         intResponseCode = 1002;
133                                 } else {
134                                         // Internal Error
135                                         intResponseCode = 2000;
136                                 }
137                         }
138
139                         // Create a new WorkflowException object
140
141                         theException = new WorkflowException(processKey, intResponseCode, errorResponse);
142                         execution.setVariable("WorkflowException", theException);
143                         logger.debug("Exited " + method + " - created " + theException);
144                         return theException;
145                 }
146
147                 logger.debug("Exited " + method + " - no WorkflowException");
148                 return null;
149         }
150
151         /**
152          * Returns the "Response" variable, unless the execution variables
153          * indicate there was an error. In that case, null is returned.
154          * @param execution the execution
155          */
156         public Object buildWorkflowResponse(DelegateExecution execution) {
157
158                 String method = getClass().getSimpleName() + ".buildWorkflowResponse(" +
159                         "execution=" + execution.getId() +
160                         ")";
161                 logger.debug("Entered " + method);
162
163                 String prefix = (String) execution.getVariable("prefix");
164                 String processKey = getProcessKey(execution);
165
166                 Object theResponse = null;
167
168                 WorkflowException theException = (WorkflowException) execution.getVariable("WorkflowException");
169                 String errorResponse = trimString(execution.getVariable(prefix + "ErrorResponse"), null);
170                 String responseCode = trimString(execution.getVariable(prefix + "ResponseCode"), null);
171
172                 if (theException == null && errorResponse == null &&
173                                 isOneOf(responseCode, null, "0", "200", "201", "202", "204")) {
174
175                         theResponse = execution.getVariable("WorkflowResponse");
176
177                         if (theResponse == null) {
178                                 theResponse = execution.getVariable(processKey + "Response");
179                         }
180                 }
181
182                 logger.debug("Exited " + method);
183                 return theResponse;
184         }
185
186         /**
187          * Checks if the specified item is one of the specified values.
188          * @param item the item
189          * @param values the list of values
190          * @return true if the item is in the list of values
191          */
192         private boolean isOneOf(Object item, Object ... values) {
193                 if (values == null) {
194                         return item == null;
195                 }
196
197                 for (Object value : values) {
198                         if (value == null) {
199                                 if (item == null) {
200                                         return true;
201                                 }
202                         } else {
203                                 if (value.equals(item)) {
204                                         return true;
205                                 }
206                         }
207                 }
208
209                 return false;
210         }
211
212         /**
213          * Creates a string value of the specified object, trimming whitespace in
214          * the process.  If the result is null or empty, the specified empty string
215          * value is returned.  Otherwise the trimmed value is returned.  This method
216          * helps ensure consistent treatment of empty and null strings.
217          * @param object the object to convert (possibly null)
218          * @param emptyStringValue the desired value for empty results
219          */
220         private String trimString(Object object, String emptyStringValue) {
221                 if (object == null) {
222                         return emptyStringValue;
223                 }
224
225                 String s = String.valueOf(object).trim();
226                 return s.equals("") ? emptyStringValue : s;
227         }
228
229         /**
230          * Returns the process definition key (i.e. the process name) from the
231          * execution.
232          * @param execution the execution
233          */
234         private String getProcessKey(DelegateExecution execution) {
235                 Object testKey = execution.getVariable("testProcessKey");
236
237                 if (testKey instanceof String) {
238                         return (String) testKey;
239                 }
240
241                 return execution.getProcessEngineServices().getRepositoryService()
242                         .getProcessDefinition(execution.getProcessDefinitionId()).getKey();
243         }
244
245         /**
246          * Removes namespace definitions and prefixes from XML, if any.
247          */
248         private String removeXMLNamespaces(String xml) {
249                 // remove xmlns declaration
250                 xml = xml.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
251
252                 // remove opening tag prefix
253                 xml = xml.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
254
255                 // remove closing tags prefix
256                 xml = xml.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
257
258                 // remove extra spaces left when xmlns declarations are removed
259                 xml = xml.replaceAll("\\s+>", ">");
260
261                 return xml;
262         }
263
264         /**
265          * Extracts text from an XML element. This method is not namespace aware
266          * (namespaces are ignored).  The first matching element is selected.
267          * @param xml the XML document or fragment
268          * @param tag the desired element, e.g. "<name>"
269          * @return the element text, or null if the element was not found
270          */
271         private String getXMLTextElement(String xml, String tag) {
272                 xml = removeXMLNamespaces(xml);
273
274                 if (!tag.startsWith("<")) {
275                         tag = "<" + tag + ">";
276                 }
277
278                 int start = xml.indexOf(tag);
279
280                 if (start == -1) {
281                         return null;
282                 }
283
284                 int end = xml.indexOf('<', start + tag.length());
285
286                 if (end == -1) {
287                         return null;
288                 }
289
290                 return xml.substring(start + tag.length(), end);
291         }
292 }