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