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