diff options
Diffstat (limited to 'bpmn/MSOCoreBPMN/src/main/java')
22 files changed, 4410 insertions, 0 deletions
diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BPMNLogger.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BPMNLogger.java new file mode 100644 index 0000000000..821380f562 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BPMNLogger.java @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.openecomp.mso.logger.MsoLogger; + +public class BPMNLogger { + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + + public static void debug (String isDebugLogEnabled, String LogText) { + if (("true").equalsIgnoreCase(isDebugLogEnabled)) + msoLogger.debug(LogText); + } +} + diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BadInjectedFieldException.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BadInjectedFieldException.java new file mode 100644 index 0000000000..84c0954386 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BadInjectedFieldException.java @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +public class BadInjectedFieldException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * @param fieldName the field name + * @param taskName the task name + * @param info additional information, e.g. the field value + */ + public BadInjectedFieldException(String fieldName, String taskName, + Object info) { + super(taskName + " injected field '" + fieldName + "' is bad: " + info); + } + + /** + * Constructor. + * + * @param fieldName the field name + * @param taskName the task name + * @param info additional information, e.g. the field value + * @param cause the cause + */ + public BadInjectedFieldException(String fieldName, + String taskName, Object info, Throwable cause) { + super(taskName + " injected field '" + fieldName + "' is bad: " + + info, cause); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BaseTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BaseTask.java new file mode 100644 index 0000000000..849c8ba4f8 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/BaseTask.java @@ -0,0 +1,529 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; +import org.camunda.bpm.engine.delegate.JavaDelegate; + +/** + * Base class for service tasks. + */ +public abstract class BaseTask implements JavaDelegate { + + /** + * Get the value of a required field. This method throws + * MissingInjectedFieldException if the expression is null, and + * BadInjectedFieldException if the expression evaluates to a null + * value. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value + */ + protected Object getField(Expression expression, + DelegateExecution execution, String fieldName) { + return getFieldImpl(expression, execution, fieldName, false); + } + + /** + * Gets the value of an optional field. There are three conditions + * in which this method returns null: + * <p> + * <ol> + * <li> The expression itself is null (i.e. the field is missing + * altogether.</li> + * <li>The expression evaluates to a null value.</li> + * <li>The expression references a single variable which has not + * been set.</li> + * </ol> + * <p> + * Examples:<br> + * Expression ${x} when x is null: return null<br> + * Expression ${x} when x is unset: return null<br> + * Expression ${x+y} when x and/or y are unset: exception<br> + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value, possibly null + */ + protected Object getOptionalField(Expression expression, + DelegateExecution execution, String fieldName) { + return getFieldImpl(expression, execution, fieldName, true); + } + + /** + * Get the value of a required output variable field. This method + * throws MissingInjectedFieldException if the expression is null, and + * BadInjectedFieldException if the expression produces a null or + * illegal variable name. Legal variable names contain only letters, + * numbers, and the underscore character ('_'). + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the output variable name + */ + protected String getOutputField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, false); + if (o instanceof String) { + String variable = (String) o; + if (!isLegalVariable(variable)) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "'" + variable + + "' is not a legal variable name"); + } + return variable; + } else { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "expected a variable name string" + + ", got object of type " + o.getClass().getName()); + } + } + + /** + * Get the value of an optional output variable field. This method + * throws BadInjectedFieldException if the expression produces an illegal + * variable name. Legal variable names contain only letters, numbers, + * and the underscore character ('_'). + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the output variable name, possibly null + */ + protected String getOptionalOutputField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, true); + if (o instanceof String) { + String variable = (String) o; + if (!isLegalVariable(variable)) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "'" + variable + + "' is not a legal variable name"); + } + return variable; + } else if (o == null) { + return null; + } else { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "expected a variable name string" + + ", got object of type " + o.getClass().getName()); + } + } + + /** + * Get the value of a required string field. This method throws + * MissingInjectedFieldException if the expression is null, and + * BadInjectedFieldException if the expression evaluates to a null + * value. + * <p> + * Note: the result is coerced to a string value, if necessary. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value + */ + protected String getStringField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, false); + if (o instanceof String) { + return (String) o; + } else { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "cannot convert '" + o.toString() + + "' to Integer"); + } + } + + /** + * Gets the value of an optional string field. There are three conditions + * in which this method returns null: + * <p> + * <ol> + * <li> The expression itself is null (i.e. the field is missing + * altogether.</li> + * <li>The expression evaluates to a null value.</li> + * <li>The expression references a single variable which has not + * been set.</li> + * </ol> + * <p> + * Examples:<br> + * Expression ${x} when x is null: return null<br> + * Expression ${x} when x is unset: return null<br> + * Expression ${x+y} when x and/or y are unset: exception<br> + * <p> + * Note: the result is coerced to a string value, if necessary. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value, possibly null + */ + protected String getOptionalStringField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, true); + if (o instanceof String) { + return (String) o; + } else if (o == null) { + return null; + } else { + return o.toString(); + } + } + + /** + * Get the value of a required integer field. This method throws + * MissingInjectedFieldException if the expression is null, and + * BadInjectedFieldException if the expression evaluates to a null + * value or a value that cannot be coerced to an integer. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value + */ + protected Integer getIntegerField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, false); + if (o instanceof Integer) { + return (Integer) o; + } else { + try { + return Integer.parseInt(o.toString()); + } catch (NumberFormatException e) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "cannot convert '" + o.toString() + + "' to Integer"); + } + } + } + + /** + * Gets the value of an optional integer field. There are three conditions + * in which this method returns null: + * <p> + * <ol> + * <li> The expression itself is null (i.e. the field is missing + * altogether.</li> + * <li>The expression evaluates to a null value.</li> + * <li>The expression references a single variable which has not + * been set.</li> + * </ol> + * <p> + * Examples:<br> + * Expression ${x} when x is null: return null<br> + * Expression ${x} when x is unset: return null<br> + * Expression ${x+y} when x and/or y are unset: exception<br> + * <p> + * Note: the result is coerced to an integer value, if necessary. This + * method throws BadInjectedFieldException if the result cannot be coerced + * to an integer. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value, possibly null + */ + protected Integer getOptionalIntegerField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, true); + if (o instanceof Integer) { + return (Integer) o; + } else if (o == null) { + return null; + } else { + try { + return Integer.parseInt(o.toString()); + } catch (NumberFormatException e) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "cannot convert '" + o.toString() + + "' to Integer"); + } + } + } + + /** + * Gets the value of an optional long field. There are three conditions + * in which this method returns null: + * <p> + * <ol> + * <li> The expression itself is null (i.e. the field is missing + * altogether.</li> + * <li>The expression evaluates to a null value.</li> + * <li>The expression references a single variable which has not + * been set.</li> + * </ol> + * <p> + * Examples:<br> + * Expression ${x} when x is null: return null<br> + * Expression ${x} when x is unset: return null<br> + * Expression ${x+y} when x and/or y are unset: exception<br> + * <p> + * Note: the result is coerced to a long value, if necessary. This + * method throws BadInjectedFieldException if the result cannot be coerced + * to a long. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value, possibly null + */ + protected Long getOptionalLongField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, true); + if (o instanceof Long) { + return (Long) o; + } else if (o == null) { + return null; + } else { + try { + return Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "cannot convert '" + o.toString() + + "' to Long"); + } + } + } + + /** + * Get the value of a required long field. This method throws + * MissingInjectedFieldException if the expression is null, and + * BadInjectedFieldException if the expression evaluates to a null + * value or a value that cannot be coerced to a long. + * + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @return the field value + */ + protected Long getLongField(Expression expression, + DelegateExecution execution, String fieldName) { + Object o = getFieldImpl(expression, execution, fieldName, false); + if (o instanceof Long) { + return (Long) o; + } else { + try { + return Long.parseLong(o.toString()); + } catch (NumberFormatException e) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "cannot convert '" + o.toString() + + "' to Long"); + } + } + } + + /** + * Common implementation for field "getter" methods. + * @param expression the expression + * @param execution the execution + * @param fieldName the field name (for logging and exceptions) + * @param optional true if the field is optional + * @return the field value, possibly null + */ + private Object getFieldImpl(Expression expression, + DelegateExecution execution, String fieldName, boolean optional) { + if (expression == null) { + if (!optional) { + throw new MissingInjectedFieldException( + fieldName, getTaskName()); + } + return null; + } + + Object value; + + try { + value = expression.getValue(execution); + } catch (Exception e) { + if (!optional) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // At this point, we have an exception that occurred while + // evaluating an expression for an optional field. A common + // problem is that the expression is a simple reference to a + // variable which has never been set, e.g. the expression is + // ${x}. The normal activiti behavior is to throw an exception, + // but we don't like that, so we have the following workaround, + // which parses the expression text to see if it is a "simple" + // variable reference, and if so, returns null. If the + // expression is anything other than a single variable + // reference, then an exception is thrown, as it would have + // been without this workaround. + + // Get the expression text so we can parse it + String s = expression.getExpressionText(); + +// if (isDebugEnabled(execution)) { +// logDebug(execution, getTaskName() + " field '" + fieldName +// + "' expression evaluation failed: " + s); +// } + + int len = s.length(); + int i = 0; + + // Skip whitespace + while (i < len && Character.isWhitespace(s.charAt(i))) { + i++; + } + + // Next character must be '$' + if (i == len || s.charAt(i++) != '$') { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // Skip whitespace + while (i < len && Character.isWhitespace(s.charAt(i))) { + i++; + } + + // Next character must be '{' + if (i == len || s.charAt(i++) != '{') { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // Skip whitespace + while (i < len && Character.isWhitespace(s.charAt(i))) { + i++; + } + + // Collect the variable name + StringBuilder variable = new StringBuilder(); + while (i < len && isWordCharacter(s.charAt(i))) { + variable.append(s.charAt(i)); + i++; + } + + if (variable.length() == 0) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // Skip whitespace + while (i < len && Character.isWhitespace(s.charAt(i))) { + i++; + } + + // Next character must be '}' + if (i == len || s.charAt(i++) != '}') { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // Skip whitespace + while (i < len && Character.isWhitespace(s.charAt(i))) { + i++; + } + + // Must be at end of string + if (i != len) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + +// if (isDebugEnabled(execution)) { +// logDebug(execution, "Checking if variable '" +// + variable.toString() + "' exists"); +// } + + // If the variable exists then the problem was + // something else... + if (execution.hasVariable(variable.toString())) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), e.getClass().getSimpleName(), e); + } + + // The variable doesn't exist. + +// if (isDebugEnabled(execution)) { +// logDebug(execution, "Variable '" + variable.toString() +// + "' does not exist [ok]"); +// } + + value = null; + } + + if (value == null && !optional) { + throw new BadInjectedFieldException( + fieldName, getTaskName(), "required field has null value"); + } + + return value; + } + + /** + * Tests if a character is a "word" character. + * @param c the character + * @return true if the character is a "word" character. + */ + private boolean isWordCharacter(char c) { + return (Character.isLetterOrDigit(c) || c == '_'); + } + + /** + * Tests if the specified string is a legal flow variable name. + * @param name the string + * @return true if the string is a legal flow variable name + */ + private boolean isLegalVariable(String name) { + if (name == null) { + return false; + } + + int len = name.length(); + + if (len == 0) { + return false; + } + + char c = name.charAt(0); + + if (!Character.isLetter(c) && c != '_') { + return false; + } + + for (int i = 1; i < len; i++) { + c = name.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '_') { + return false; + } + } + + return true; + } + + /** + * Returns the name of the task (normally the java class name). + * @return the name of the task + */ + public String getTaskName() { + return getClass().getSimpleName(); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/HealthCheckHandler.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/HealthCheckHandler.java new file mode 100644 index 0000000000..df6213284c --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/HealthCheckHandler.java @@ -0,0 +1,203 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.UUID; + +import org.openecomp.mso.logger.MsoLogger; +import org.openecomp.mso.logger.MessageEnum; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.camunda.bpm.engine.ProcessEngines; + +@Path("/") +public class HealthCheckHandler { + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static final String SITENAME = "mso.sitename"; + private static final String ADPTER_ENDPOINT = "mso.adapters.db.endpoint"; + private static final String CONFIG = "mso.bpmn.urn.properties"; + + private static final String CHECK_HTML = "<!DOCTYPE html><html><head><meta charset=\"ISO-8859-1\"><title>Health Check</title></head><body>Application ready</body></html>"; + private static final String NOT_FOUND = "<!DOCTYPE html><html><head><meta charset=\"ISO-8859-1\"><title>Application Not Started</title></head><body>Application not started. Properties file missing or invalid or database Connection failed</body></html>"; + private static final String NOT_HEALTHY = "<!DOCTYPE html><html><head><meta charset=\"ISO-8859-1\"><title>Application Not Started</title></head><body>Application not available or at least one of the sub-modules is not available.</body></html>"; + public static final Response HEALTH_CHECK_RESPONSE = Response.status (HttpStatus.SC_OK) + .entity (CHECK_HTML) + .build (); + public static final Response HEALTH_CHECK_NOK_RESPONSE = Response.status (HttpStatus.SC_SERVICE_UNAVAILABLE) + .entity (NOT_HEALTHY) + . build (); + public static final Response NOT_STARTED_RESPONSE = Response.status (HttpStatus.SC_SERVICE_UNAVAILABLE) + .entity (NOT_FOUND) + .build (); + + @HEAD + @GET + @Path("/healthcheck") + @Produces("text/html") + public Response healthcheck (@QueryParam("requestId") String requestId) { + MsoLogger.setServiceName ("Healthcheck"); + verifyOldUUID(requestId); + + PropertyConfiguration propertyConfiguration = PropertyConfiguration.getInstance(); + Map<String,String> props = propertyConfiguration.getProperties(CONFIG); + + if (props == null) { + + msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.AvailabilityError, "Unable to load " + CONFIG); + + return NOT_STARTED_RESPONSE; + } + + String siteName = props.get(SITENAME); + String endpoint = props.get(ADPTER_ENDPOINT); + + if (null == siteName || siteName.length () == 0 || null == endpoint || endpoint.length () == 0) { + + msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.DataError, "Unable to load key attributes (" + SITENAME + " or " + ADPTER_ENDPOINT + ") from the config file:" + CONFIG); + + return NOT_STARTED_RESPONSE; + } + + try { + if (!this.getSiteStatus (endpoint, siteName)) { + msoLogger.debug("This site is currently disabled for maintenance."); + return HEALTH_CHECK_NOK_RESPONSE; + } + } catch (Exception e) { + + msoLogger.error(MessageEnum.GENERAL_EXCEPTION_ARG, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "Exception while getting SiteStatus", e); + + msoLogger.debug("Exception while getting SiteStatus"); + return NOT_STARTED_RESPONSE; + } + + try { + ProcessEngines.getDefaultProcessEngine().getIdentityService().createGroupQuery().list(); + } catch (final Exception e) { + + msoLogger.error(MessageEnum.GENERAL_EXCEPTION_ARG, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "Exception while verifying Camunda engine", e); + + msoLogger.debug("Exception while verifying Camunda engine"); + return NOT_STARTED_RESPONSE; + } + + return HEALTH_CHECK_RESPONSE; + } + + + private String verifyOldUUID (String oldId) { + if (!isValidUUID(oldId)) { + String newId = UUID.randomUUID().toString(); + MsoLogger.setLogContext(newId, null); + return newId; + } + MsoLogger.setLogContext(oldId, null); + return oldId; + } + + + private boolean isValidUUID (String id) { + try { + if (null == id) { + return false; + } + UUID uuid = UUID.fromString(id); + return uuid.toString().equalsIgnoreCase(id); + } catch (IllegalArgumentException iae) { + return false; + } + } + + private boolean getSiteStatus (String url, String site) throws Exception { + HttpResponse response; + // set the connection timeout value to 30 seconds (30000 milliseconds) + RequestConfig.Builder requestBuilder = RequestConfig.custom(); + requestBuilder = requestBuilder.setConnectTimeout(30000); + requestBuilder = requestBuilder.setConnectionRequestTimeout(30000); + HttpClientBuilder builder = HttpClientBuilder.create (); + builder.setDefaultRequestConfig (requestBuilder.build ()); + + HttpPost post = new HttpPost(url); + msoLogger.debug("Post url is: " + url); + + //now create a soap request message as follows: + final StringBuffer payload = new StringBuffer(); + payload.append("\n"); + payload.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:req=\"http://com.att.mso/requestsdb\">\n"); + payload.append("<soapenv:Header/>\n"); + payload.append("<soapenv:Body>\n"); + payload.append("<req:getSiteStatus>\n"); + payload.append("<siteName>" + site + "</siteName>\n"); + payload.append("</req:getSiteStatus>\n"); + payload.append("</soapenv:Body>\n"); + payload.append("</soapenv:Envelope>\n"); + + msoLogger.debug ("Initialize SOAP request to url:" + url); + msoLogger.debug ("The payload of the request is:" + payload); + HttpEntity entity = new StringEntity(payload.toString(),"UTF-8"); + post.setEntity(entity); + + try (CloseableHttpClient client = builder.build()) { + response = client.execute(post); + msoLogger.debug("Response received is:" + response); + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != 200) { + + msoLogger.error(MessageEnum.GENERAL_EXCEPTION_ARG, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.DataError, + "Communication with DB Adapter failed, The response received from DB Adapter is with failed status code:" + statusCode); + + Exception e = new Exception("Communication with DB Adapter failed"); + throw e; + } + BufferedReader rd = new BufferedReader( + new InputStreamReader(response.getEntity().getContent())); + + StringBuffer result = new StringBuffer(); + String line = ""; + while ((line = rd.readLine()) != null) { + result.append(line); + } + msoLogger.debug("Content of the response is:" + result); + String status = result.substring(result.indexOf("<return>") + 8, result.indexOf("</return>")); + + return Boolean.valueOf(status); + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/LogTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/LogTask.java new file mode 100644 index 0000000000..41033d9e12 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/LogTask.java @@ -0,0 +1,98 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; + +import org.openecomp.mso.logger.MsoAlarmLogger; +import org.openecomp.mso.logger.MsoLogger; + +/** + * Logs a text message. The text may contain variable references. + * For example:<br/><br/> + * name=$name, address=$address + * <p> + * Required fields:<br/><br/> + * text: The text to log<br/> + * Optional fields:<br/><br/> + * level: The log level (TRACE, DEBUG, INFO, WARN, ERROR)<br/> + */ +public class LogTask extends BaseTask { + + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static MsoAlarmLogger alarmLogger = new MsoAlarmLogger(); + + private Expression text; + private Expression level; + + public void execute(DelegateExecution execution) throws Exception { + String theText = getStringField(text, execution, "text"); + + + + StringBuilder out = new StringBuilder(); + StringBuilder var = new StringBuilder(); + boolean inVar = false; + + int pos = 0; + int len = theText.length(); + + while (pos < len) { + char c = theText.charAt(pos++); + + if (inVar && !Character.isLetterOrDigit(c) && c != '_') { + if (var.length() > 0) { + Object value = execution.getVariable(var.toString()); + + if (value != null) { + out.append(value.toString()); + } + + var.setLength(0); + } + + inVar = false; + } + + if (c == '$') { + inVar = true; + } else { + if (inVar) { + var.append(c); + } else { + out.append(c); + } + } + } + + if (inVar && var.length() > 0) { + Object value = execution.getVariable(var.toString()); + if (value != null) { + out.append(value.toString()); + } + } + + + + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/MissingInjectedFieldException.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/MissingInjectedFieldException.java new file mode 100644 index 0000000000..589a111b3b --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/MissingInjectedFieldException.java @@ -0,0 +1,39 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +/** + * A BadInjectedFieldException that indicates a required field is missing. + */ +public class MissingInjectedFieldException extends BadInjectedFieldException { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * @param fieldName the field name + * @param taskName the task name + */ + public MissingInjectedFieldException(String fieldName, String taskName) { + super(fieldName, taskName, "missing required field"); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/PropertyConfiguration.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/PropertyConfiguration.java new file mode 100644 index 0000000000..90df1da7e5 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/PropertyConfiguration.java @@ -0,0 +1,440 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.MDC; + +import org.openecomp.mso.logger.MessageEnum; +import org.openecomp.mso.logger.MsoLogger; + +/** + * Loads the property configuration from file system and refreshes the + * properties when the property gets changed. + * + * WARNING: automatic refreshes might not work on network filesystems. + */ +public class PropertyConfiguration { + + /** + * The base name of the MSO BPMN properties file (mso.bpmn.properties). + */ + public static final String MSO_BPMN_PROPERTIES = "mso.bpmn.properties"; + + /** + * The base name of the MSO BPMN URN-Mappings properties file (mso.bpmn.urn.properties). + */ + public static final String MSO_BPMN_URN_PROPERTIES = "mso.bpmn.urn.properties"; + + /** + * The name of the meta-property holding the time the properties were loaded + * from the file. + */ + public static final String TIMESTAMP_PROPERTY = "mso.properties.timestamp"; + + private static final MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + + private static final List<String> SUPPORTED_FILES = + Arrays.asList(MSO_BPMN_PROPERTIES, MSO_BPMN_URN_PROPERTIES); + + private volatile String msoConfigPath = null; + + private final ConcurrentHashMap<String, Map<String, String>> propFileCache = + new ConcurrentHashMap<String, Map<String, String>>(); + + private final Object CACHELOCK = new Object(); + private FileWatcherThread fileWatcherThread = null; + + // The key is the file name + private Map<String, TimerTask> timerTaskMap = new HashMap<String, TimerTask>(); + + /** + * Singleton holder pattern eliminates locking when accessing the instance + * and still provides for lazy initialization. + */ + private static class PropertyConfigurationInstanceHolder { + private static PropertyConfiguration instance = new PropertyConfiguration(); + } + + /** + * Gets the one and only instance of this class. + */ + public static PropertyConfiguration getInstance() { + return PropertyConfigurationInstanceHolder.instance; + } + + /** + * Returns the list of supported files. + */ + public static List<String> supportedFiles() { + return new ArrayList<String>(SUPPORTED_FILES); + } + + /** + * Private Constructor. + */ + private PropertyConfiguration() { + startUp(); + } + + /** + * May be called to restart the PropertyConfiguration if it was previously shut down. + */ + public synchronized void startUp() { + msoConfigPath = System.getProperty("mso.config.path"); + + if (msoConfigPath == null) { + LOGGER.debug("mso.config.path JVM system property is not set"); + return; + } + + try { + Path directory = FileSystems.getDefault().getPath(msoConfigPath); + WatchService watchService = FileSystems.getDefault().newWatchService(); + directory.register(watchService, ENTRY_MODIFY); + + LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", "Starting FileWatcherThread"); + LOGGER.debug("Starting FileWatcherThread"); + fileWatcherThread = new FileWatcherThread(watchService); + fileWatcherThread.start(); + } catch (Exception e) { + LOGGER.debug("Error occurred while starting FileWatcherThread:", e); + LOGGER.error( + MessageEnum.BPMN_GENERAL_EXCEPTION, + "BPMN", + "Property Configuration", + MsoLogger.ErrorCode.UnknownError, + "Error occurred while starting FileWatcherThread:" + e); + } + } + + /** + * May be called to shut down the PropertyConfiguration. A shutDown followed + * by a startUp will reset the PropertyConfiguration to its initial state. + */ + public synchronized void shutDown() { + if (fileWatcherThread != null) { + LOGGER.debug("Shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread)); + fileWatcherThread.shutdown(); + + long waitInSeconds = 10; + + try { + fileWatcherThread.join(waitInSeconds * 1000); + } catch (InterruptedException e) { + LOGGER.debug("FileWatcherThread " + System.identityHashCode(fileWatcherThread) + + " shutdown did not occur within " + waitInSeconds + " seconds"); + } + + LOGGER.debug("Finished shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread)); + fileWatcherThread = null; + } + + clearCache(); + msoConfigPath = null; + } + + public synchronized boolean isFileWatcherRunning() { + return fileWatcherThread != null; + } + + public void clearCache() { + synchronized(CACHELOCK) { + propFileCache.clear(); + } + } + + public int cacheSize() { + return propFileCache.size(); + } + + // TODO: throw IOException? + public Map<String, String> getProperties(String fileName) { + Map<String, String> properties = propFileCache.get(fileName); + + if (properties == null) { + if (!SUPPORTED_FILES.contains(fileName)) { + throw new IllegalArgumentException("Not a supported property file: " + fileName); + } + + if (msoConfigPath == null) { + LOGGER.debug("mso.config.path JVM system property must be set to load " + fileName); + + LOGGER.error( + MessageEnum.BPMN_GENERAL_EXCEPTION, + "BPMN", + MDC.get(fileName), + MsoLogger.ErrorCode.UnknownError, + "mso.config.path JVM system property must be set to load " + fileName); + + return null; + } + + try { + properties = readProperties(new File(msoConfigPath, fileName)); + } catch (Exception e) { + LOGGER.debug("Error loading " + fileName); + + LOGGER.error( + MessageEnum.BPMN_GENERAL_EXCEPTION, + "BPMN", + MDC.get(fileName), + MsoLogger.ErrorCode.UnknownError, + "Error loading " + fileName, e); + + return null; + } + } + + return Collections.unmodifiableMap(properties); + } + + /** + * Reads properties from the specified file, updates the property file cache, and + * returns the properties in a map. + * @param file the file to read + * @param reload true if this is a reload event + * @return a map of properties + */ + private Map<String, String> readProperties(File file) throws IOException { + String fileName = file.getName(); + LOGGER.debug("Reading " + fileName); + + Map<String, String> properties = new HashMap<String, String>(); + Properties newProperties = new Properties(); + + FileReader reader = null; + try { + reader = new FileReader(file); + newProperties.load(reader); + } finally { + if (reader != null) { + try { + reader.close(); + LOGGER.debug("Closed " + fileName); + } catch (Exception e) { + // Ignore + } + } + } + + for (Entry<Object, Object> entry : newProperties.entrySet()) { + properties.put(entry.getKey().toString(), entry.getValue().toString()); + } + + properties.put(TIMESTAMP_PROPERTY, String.valueOf(System.currentTimeMillis())); + + synchronized(CACHELOCK) { + propFileCache.put(fileName, properties); + } + + return properties; + } + + /** + * File watcher thread which monitors a directory for file modification. + */ + private class FileWatcherThread extends Thread { + private final WatchService watchService; + private final Timer timer = new Timer("FileWatcherTimer"); + + public FileWatcherThread(WatchService service) { + this.watchService = service; + } + + public void shutdown() { + interrupt(); + } + + public void run() { + LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", + "FileWatcherThread started"); + + LOGGER.debug("Started FileWatcherThread " + System.identityHashCode(fileWatcherThread)); + + try { + WatchKey watchKey = null; + + while (!isInterrupted()) { + try { + if (watchKey != null) { + watchKey.reset(); + } + + watchKey = watchService.take(); + + for (WatchEvent<?> event : watchKey.pollEvents()) { + @SuppressWarnings("unchecked") + WatchEvent<Path> pathEvent = (WatchEvent<Path>) event; + + if ("EVENT_OVERFLOW".equals(pathEvent.kind())) { + LOGGER.debug("Ignored overflow event for " + msoConfigPath); + continue; + } + + String fileName = pathEvent.context().getFileName().toString(); + + if (!SUPPORTED_FILES.contains(fileName)) { + LOGGER.debug("Ignored modify event for " + fileName); + continue; + } + + LOGGER.debug("Configuration file has changed: " + fileName); + + LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", + "Configuation file has changed: " + fileName); + + // There's a potential problem here. The MODIFY event is + // triggered as soon as somebody starts writing the file but + // there's no obvious way to know when the write is done. If we + // read the file while the write is still in progress, then the + // cache can really be messed up. As a workaround, we use a timer + // to sleep for at least one second, and then we sleep for as long + // as it takes for the file's lastModified time to stop changing. + // The timer has another benefit: it consolidates multiple events + // that we seem to receive when a file is modified. + + synchronized(timerTaskMap) { + TimerTask task = timerTaskMap.get(fileName); + + if (task != null) { + task.cancel(); + } + + File file = new File(msoConfigPath, fileName); + task = new DelayTimerTask(timer, file, 1000); + timerTaskMap.put(fileName, task); + } + } + } catch (InterruptedException e) { + break; + } catch (ClosedWatchServiceException e) { + LOGGER.info( + MessageEnum.BPMN_GENERAL_INFO, + "BPMN", + "FileWatcherThread shut down because the watch service was closed"); + break; + } catch (Exception e) { + LOGGER.error( + MessageEnum.BPMN_GENERAL_EXCEPTION, + "BPMN", + "Property Configuration", + MsoLogger.ErrorCode.UnknownError, + "FileWatcherThread caught unexpected " + e.getClass().getSimpleName(), e); + } + + } + } finally { + timer.cancel(); + + synchronized(timerTaskMap) { + timerTaskMap.clear(); + } + + try { + watchService.close(); + } catch (IOException e) { + LOGGER.debug("FileWatcherThread caught " + e.getClass().getSimpleName() + + " while closing the watch service"); + } + + LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", + "FileWatcherThread stopped"); + } + } + } + + private class DelayTimerTask extends TimerTask { + private final File file; + private final long lastModifiedTime; + private final Timer timer; + + public DelayTimerTask(Timer timer, File file, long delay) { + this.timer = timer; + this.file = file; + this.lastModifiedTime = file.lastModified(); + timer.schedule(this, delay); + } + + @Override + public void run() { + try { + long newLastModifiedTime = file.lastModified(); + + if (newLastModifiedTime == lastModifiedTime) { + try { + readProperties(file); + } catch (Exception e) { + LOGGER.error( + MessageEnum.BPMN_GENERAL_EXCEPTION, + "BPMN", + "Property Configuration", + MsoLogger.ErrorCode.UnknownError, + "Unable to reload " + file, e); + } + } else { + LOGGER.debug("Delaying reload of " + file + " by 1 second"); + + synchronized(timerTaskMap) { + TimerTask task = timerTaskMap.get(file.getName()); + + if (task != null && task != this) { + task.cancel(); + } + + task = new DelayTimerTask(timer, file, 1000); + timerTaskMap.put(file.getName(), task); + } + } + } finally { + synchronized(timerTaskMap) { + TimerTask task = timerTaskMap.get(file.getName()); + + if (task == this) { + timerTaskMap.remove(file.getName()); + } + } + } + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadConfigTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadConfigTask.java new file mode 100644 index 0000000000..b46ffcd7f7 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadConfigTask.java @@ -0,0 +1,111 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import org.camunda.bpm.engine.ProcessEngineException; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; + +import org.openecomp.mso.logger.MsoLogger; + +/** + * Reads the contents of a resource file as a string and stores it in an + * execution variable. + * <p> + * Required fields:<br/><br/> + * file: the resource file path<br/> + * outputVariable: the output variable name<br/> + */ +public class ReadConfigTask extends BaseTask { + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static Properties properties = null; + + private Expression propertiesFile; + + public void execute(DelegateExecution execution) throws Exception { + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Started Executing " + getTaskName()); + } + + String thePropertiesFile = + getStringField(propertiesFile, execution, "propertiesFile"); + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("propertiesFile = " + thePropertiesFile); + } + + Boolean shouldFail = (Boolean) execution.getVariable("shouldFail"); + + if (shouldFail != null && shouldFail) { + throw new ProcessEngineException(getClass().getSimpleName() + " Failed"); + } + + synchronized (ReadConfigTask.class) { + if (properties == null) { + properties = new Properties(); + + InputStream stream = null; + + try { + stream = getClass().getResourceAsStream(thePropertiesFile); + + if (stream == null) { + throw new IOException("Resource not found: " + thePropertiesFile); + } + + properties.load(stream); + + stream.close(); + stream = null; + + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + for (Object objectKey : properties.keySet()) { + String key = (String) objectKey; + String value = properties.getProperty(key); + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Setting variable '" + key + "' to '" + value + "'"); + } + + execution.setVariable(key, value); + } + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Done Executing " + getTaskName()); + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadFileTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadFileTask.java new file mode 100644 index 0000000000..389fdc0518 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ReadFileTask.java @@ -0,0 +1,117 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.camunda.bpm.engine.ProcessEngineException; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; + +import org.openecomp.mso.logger.MsoLogger; + +/** + * Conditionally reads the contents of a resource file as a string and stores it + * in an execution variable. The file is read only if the value of the input + * variable is null. + * <p> + * Required fields:<br/><br/> + * file: the resource file path<br/> + * inputVariable: the input variable name<br/> + * outputVariable: the output variable name<br/> + */ +public class ReadFileTask extends BaseTask { + + private Expression file; + private Expression inputVariable; + private Expression outputVariable; + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + + public void execute(DelegateExecution execution) throws Exception { + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Started Executing " + getTaskName()); + } + + String theInputVariable = + getStringField(inputVariable, execution, "inputVariable"); + String theOutputVariable = + getOutputField(outputVariable, execution, "outputVariable"); + String theFile =getStringField(file, execution, "file"); + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("inputVariable = " + theInputVariable + + " outputVariable = " + theOutputVariable + + "file = " + theFile); + } + + Boolean shouldFail = (Boolean) execution.getVariable("shouldFail"); + + if (shouldFail != null && shouldFail) { + throw new ProcessEngineException(getClass().getSimpleName() + " Failed"); + } + + Object value = execution.getVariable(theInputVariable); + + if (value == null) { + InputStream xmlStream = null; + + try { + xmlStream = getClass().getResourceAsStream(theFile); + + if (xmlStream == null) { + throw new IOException("Resource not found: " + theFile); + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(xmlStream)); + StringBuilder output = new StringBuilder(); + String line; + + while ((line = reader.readLine()) != null) { + output.append(line); + } + + xmlStream.close(); + xmlStream = null; + + value = output.toString(); + + } finally { + if (xmlStream != null) { + try { + xmlStream.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + execution.setVariable(theInputVariable, value); + execution.setVariable(theOutputVariable, value); + System.out.println("ServiceInput - " + execution.getVariable("gServiceInput")); + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Done Executing " + getTaskName()); + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ResponseBuilder.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ResponseBuilder.java new file mode 100644 index 0000000000..632933d3a3 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/ResponseBuilder.java @@ -0,0 +1,297 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.camunda.bpm.engine.delegate.DelegateExecution; + +/** + * Used in the output variable mapping configuration of subflow call activity + * tasks to normalize subflow responses. The output mapping is normally set up + * as follows. Note that the order of these mappings is important! + * <p> + * OUTPUT MAPPING + * <pre> + * SOURCE EXPRESSION TARGET + * ${ResponseBuilder.buildWorkflowException(execution)} WorkflowException + * ${ResponseBuilder.buildWorkflowResponse(execution)} SomeResponseVariable + * </pre> + */ +public class ResponseBuilder implements java.io.Serializable { + private static final long serialVersionUID = 1L; + + /** + * Creates a WorkflowException using data from the execution variables. + * If the variables do not indicate that there was an error, null + * is returned. + * @param execution the execution + */ + public WorkflowException buildWorkflowException(DelegateExecution execution) { + + String method = getClass().getSimpleName() + ".buildWorkflowException(" + + "execution=" + execution.getId() + + ")"; + String isDebugLogEnabled = (String) execution.getVariable("isDebugLogEnabled"); + logDebug("Entered " + method, isDebugLogEnabled); + + String prefix = (String) execution.getVariable("prefix"); + String processKey = getProcessKey(execution); + + logDebug("processKey=" + processKey, isDebugLogEnabled); + + // See if there"s already a WorkflowException object in the execution. + WorkflowException theException = (WorkflowException) execution.getVariable("WorkflowException"); + + if (theException != null) { + logDebug("Exited " + method + " - propagated " + theException, isDebugLogEnabled); + return theException; + } + + // Look in the legacy variables: ErrorResponse and ResponseCode + + String errorResponse = trimString(execution.getVariable(prefix + "ErrorResponse"), null); + String responseCode = trimString(execution.getVariable(prefix + "ResponseCode"), null); + logDebug("errorResponse=" + errorResponse, isDebugLogEnabled); + logDebug("responseCode=" + responseCode, isDebugLogEnabled); + if (errorResponse != null || !isOneOf(responseCode, null, "0", "200", "201", "202", "204")) { + // This is an error condition. We need to return a WorkflowExcpetion + + if (errorResponse == null) { + // No errorResponse string. See if there"s something in the Response variable + String response = trimString(execution.getVariable(processKey + "Response"), null); + if (response == null) { + errorResponse = "Received response code " + responseCode + " from " + processKey; + } else { + errorResponse = response; + } + } + + // Some subflows may try to return a WorkflowException as XML in the + // errorResponse. If provided, use the errorCode and errorMessage + // from the XML + + String maybeXML = removeXMLNamespaces(errorResponse); + + String xmlErrorMessage = trimString(getXMLTextElement(maybeXML, "ErrorMessage"), null); + String xmlErrorCode = trimString(getXMLTextElement(maybeXML, "ErrorCode"), null); + + if (xmlErrorMessage != null || xmlErrorCode != null) { + logDebug("xmlErrorMessage=" + xmlErrorMessage, isDebugLogEnabled); + logDebug("xmlErrorCode=" + xmlErrorCode, isDebugLogEnabled); + + if (xmlErrorMessage == null) { + errorResponse = "Received error code " + xmlErrorCode + " from " + processKey; + } else { + errorResponse = xmlErrorMessage; + } + + if (xmlErrorCode != null) { + responseCode = xmlErrorCode; + } + } + + // Convert the responseCode to an integer + + int intResponseCode; + + try { + intResponseCode = Integer.valueOf(responseCode); + } catch (NumberFormatException e) { + // Internal Error + intResponseCode = 2000; + } + + // Convert 3-digit HTTP response codes (we should not be using them here) + // to appropriate 4-digit response codes + + if (intResponseCode < 1000) { + if (intResponseCode >= 400 && intResponseCode <= 499) { + // Invalid Message + intResponseCode = 1002; + } else { + // Internal Error + intResponseCode = 2000; + } + } + + // Create a new WorkflowException object + + theException = new WorkflowException(processKey, intResponseCode, errorResponse); + execution.setVariable("WorkflowException", theException); + logDebug("Exited " + method + " - created " + theException, isDebugLogEnabled); + return theException; + } + + logDebug("Exited " + method + " - no WorkflowException", isDebugLogEnabled); + return null; + } + + /** + * Returns the "Response" variable, unless the execution variables + * indicate there was an error. In that case, null is returned. + * @param execution the execution + */ + public Object buildWorkflowResponse(DelegateExecution execution) { + + String method = getClass().getSimpleName() + ".buildWorkflowResponse(" + + "execution=" + execution.getId() + + ")"; + String isDebugLogEnabled = (String) execution.getVariable("isDebugLogEnabled"); + logDebug("Entered " + method, isDebugLogEnabled); + + String prefix = (String) execution.getVariable("prefix"); + String processKey = getProcessKey(execution); + + Object theResponse = null; + + WorkflowException theException = (WorkflowException) execution.getVariable("WorkflowException"); + String errorResponse = trimString(execution.getVariable(prefix + "ErrorResponse"), null); + String responseCode = trimString(execution.getVariable(prefix + "ResponseCode"), null); + + if (theException == null && errorResponse == null && + isOneOf(responseCode, null, "0", "200", "201", "202", "204")) { + + theResponse = execution.getVariable("WorkflowResponse"); + + if (theResponse == null) { + theResponse = execution.getVariable(processKey + "Response"); + } + } + + logDebug("Exited " + method, isDebugLogEnabled); + return theResponse; + } + + /** + * Checks if the specified item is one of the specified values. + * @param item the item + * @param values the list of values + * @return true if the item is in the list of values + */ + private boolean isOneOf(Object item, Object ... values) { + if (values == null) { + return item == null; + } + + for (Object value : values) { + if (value == null) { + if (item == null) { + return true; + } + } else { + if (value.equals(item)) { + return true; + } + } + } + + return false; + } + + /** + * Creates a string value of the specified object, trimming whitespace in + * the process. If the result is null or empty, the specified empty string + * value is returned. Otherwise the trimmed value is returned. This method + * helps ensure consistent treatment of empty and null strings. + * @param object the object to convert (possibly null) + * @param emptyStringValue the desired value for empty results + */ + private String trimString(Object object, String emptyStringValue) { + if (object == null) { + return emptyStringValue; + } + + String s = String.valueOf(object).trim(); + return s.equals("") ? emptyStringValue : s; + } + + /** + * Returns the process definition key (i.e. the process name) from the + * execution. + * @param execution the execution + */ + private String getProcessKey(DelegateExecution execution) { + Object testKey = execution.getVariable("testProcessKey"); + + if (testKey instanceof String) { + return (String) testKey; + } + + return execution.getProcessEngineServices().getRepositoryService() + .getProcessDefinition(execution.getProcessDefinitionId()).getKey(); + } + + /** + * Logs a message at the DEBUG level. + * @param message the message + * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled + */ + private void logDebug(String message, String isDebugLogEnabled) { + BPMNLogger.debug(isDebugLogEnabled, message); + } + + /** + * Removes namespace definitions and prefixes from XML, if any. + */ + private String removeXMLNamespaces(String xml) { + // remove xmlns declaration + xml = xml.replaceAll("xmlns.*?(\"|\').*?(\"|\')", ""); + + // remove opening tag prefix + xml = xml.replaceAll("(<)(\\w+:)(.*?>)", "$1$3"); + + // remove closing tags prefix + xml = xml.replaceAll("(</)(\\w+:)(.*?>)", "$1$3"); + + // remove extra spaces left when xmlns declarations are removed + xml = xml.replaceAll("\\s+>", ">"); + + return xml; + } + + /** + * Extracts text from an XML element. This method is not namespace aware + * (namespaces are ignored). The first matching element is selected. + * @param xml the XML document or fragment + * @param tag the desired element, e.g. "<name>" + * @return the element text, or null if the element was not found + */ + private String getXMLTextElement(String xml, String tag) { + xml = removeXMLNamespaces(xml); + + if (!tag.startsWith("<")) { + tag = "<" + tag + ">"; + } + + int start = xml.indexOf(tag); + + if (start == -1) { + return null; + } + + int end = xml.indexOf('<', start + tag.length()); + + if (end == -1) { + return null; + } + + return xml.substring(start + tag.length(), end); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/RollbackData.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/RollbackData.java new file mode 100644 index 0000000000..1b2bb8752c --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/RollbackData.java @@ -0,0 +1,108 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * An object that stores data for rollbacks. Data is organized by type. A + * type is simply a string identifier. Multiple types of data may be stored + * in the same object for separate rollback operations. + */ +public class RollbackData implements Serializable { + private static final long serialVersionUID = 1L; + + private Map<String, Map<String, Serializable>> dictionary = + new HashMap<String, Map<String, Serializable>>(); + + /** + * Returns true if the specified type is stored in this object. + * @param type the data type + */ + public boolean hasType(String type) { + return dictionary.containsKey(type); + } + + /** + * Stores a single item. + * @param type the data type + * @param key the key + * @param value the value + */ + public void put(String type, String key, String value) { + Map<String, Serializable> mapForType = dictionary.get(type); + + if (mapForType == null) { + mapForType = new HashMap<String, Serializable>(); + dictionary.put(type, mapForType); + } + + mapForType.put(key, value); + } + + /** + * Gets a single item. + * @param type the data type + * @param key the key + * @return the item or null if there is no item for the specified type and key + */ + public Serializable get(String type, String key) { + Map<String, Serializable> mapForType = dictionary.get(type); + + if (mapForType == null) { + return null; + } + + return mapForType.get(key); + } + + /** + * Gets a map containing all items associated with the specified data type. + * @param type the data type + * @return a map, or null if there are no items associated with the specified + * data type + */ + public Map<String, Serializable> get(String type) { + return dictionary.get(type); + } + + /** + * Returns a string representation of this object. + */ + public String toString() { + StringBuilder out = new StringBuilder(); + out.append(getClass().getSimpleName()); + out.append('['); + boolean hasOne = false; + for (String type : dictionary.keySet()) { + if (hasOne) { + out.append(','); + } + out.append(type); + out.append(dictionary.get(type)); + hasOne = true; + } + out.append(']'); + return out.toString(); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/URNMappingsTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/URNMappingsTask.java new file mode 100644 index 0000000000..6c6d96ad2c --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/URNMappingsTask.java @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import org.camunda.bpm.engine.delegate.DelegateExecution; + +/** + * DEPRECATION WARNING: setting of URN mappings is now done by a plugin. + */ +@Deprecated +public class URNMappingsTask extends BaseTask { + public void execute(DelegateExecution execution) throws Exception { + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/WorkflowException.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/WorkflowException.java new file mode 100644 index 0000000000..559ec6df00 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/WorkflowException.java @@ -0,0 +1,84 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import java.io.Serializable; + +/** + * An object that represents a workflow exception. + */ +public class WorkflowException implements Serializable { + private static final long serialVersionUID = 1L; + + private final String processKey; + private final int errorCode; + private final String errorMessage; + + /** + * Constructor + * @param processKey the process key for the process that generated the exception + * @param errorCode the numeric error code (normally 1xxx or greater) + * @param errorMessage a short error message + */ + public WorkflowException(String processKey, int errorCode, + String errorMessage) { + this.processKey = processKey; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + /** + * Returns the process key. + */ + public String getProcessKey() { + return processKey; + } + + /** + * Returns the error code. + */ + public int getErrorCode() { + return errorCode; + } + + /** + * Returns the error message. + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Returns a string representation of this object. + */ + public String toString() { + StringBuilder out = new StringBuilder(); + out.append(getClass().getSimpleName()); + out.append("[processKey="); + out.append(getProcessKey()); + out.append(",errorCode="); + out.append(getErrorCode()); + out.append(",errorMessage="); + out.append(getErrorMessage()); + out.append("]"); + return out.toString(); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/XQueryScriptTask.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/XQueryScriptTask.java new file mode 100644 index 0000000000..8a7b20016b --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/XQueryScriptTask.java @@ -0,0 +1,243 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.URI; +import java.util.Iterator; + +import javax.xml.transform.stream.StreamSource; + +import org.camunda.bpm.engine.ProcessEngineException; +import org.camunda.bpm.engine.delegate.DelegateExecution; +//import java.util.logging.Logger; +import org.camunda.bpm.engine.delegate.Expression; + +import org.openecomp.mso.logger.MessageEnum; +import org.openecomp.mso.logger.MsoLogger; + +import net.sf.saxon.Configuration; +import net.sf.saxon.s9api.DocumentBuilder; +import net.sf.saxon.s9api.Processor; +import net.sf.saxon.s9api.QName; +import net.sf.saxon.s9api.XQueryCompiler; +import net.sf.saxon.s9api.XQueryEvaluator; +import net.sf.saxon.s9api.XQueryExecutable; +import net.sf.saxon.s9api.XdmAtomicValue; +import net.sf.saxon.s9api.XdmItem; +import net.sf.saxon.s9api.XdmNode; + +/** + * Executes an XQuery script. + * <p> + * Required fields:<br/><br/> + * scriptFile: the XQuery script file path<br/> + * outputVariable: the output variable name<br/> + * <p> + * Optional fields:<br/><br/> + * xmlInputVariables: CSV list of variables containing + * XML data to be injected into the script<br/> + * atomicInputVariables: CSV list of variables containing + * atomic data to be injected into the script<br/> + */ +public class XQueryScriptTask extends BaseTask { + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + + private Expression scriptFile; + private Expression xmlInputVariables; + private Expression atomicInputVariables; + private Expression outputVariable; + + public void execute(DelegateExecution execution) throws Exception { + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Started Executing " + getTaskName()); + } + + String theScriptFile = + getStringField(scriptFile, execution, "scriptFile"); + String theXmlInputVariables = + getOptionalStringField(xmlInputVariables, execution, "xmlInputVariables"); + String theAtomicInputVariables = + getOptionalStringField(atomicInputVariables, execution, "atomicInputVariables"); + String theOutputVariable = + getStringField(outputVariable, execution, "outputVariable"); + + if (msoLogger.isDebugEnabled()) { + System.out.println("scriptFile = " + theScriptFile + + " xmlInputVariables = " + theXmlInputVariables + + " atomicInputVariables = " + theAtomicInputVariables + + "outputVariable = " + theOutputVariable); + } + + String[] xmlInputVariableArray = (theXmlInputVariables == null) + ? new String[0] : theXmlInputVariables.split(",[ ]*"); + + String[] atomicInputVariableArray = (theAtomicInputVariables == null) + ? new String[0] : theAtomicInputVariables.split(",[ ]*"); + + Boolean shouldFail = (Boolean) execution.getVariable("shouldFail"); + + if (shouldFail != null && shouldFail) { + throw new ProcessEngineException(getClass().getSimpleName() + " Failed"); + } + + // The script could be compiled once and reused, but we are reading it + // and compiling it every time. + Configuration configuration = new Configuration(); + Processor processor = new Processor(configuration); + XQueryCompiler compiler = processor.newXQueryCompiler(); + XQueryExecutable executable = compile(compiler, theScriptFile); + + // The evaluator must not be shared by multiple threads. Here is where + // the initial context may be set, as well as values of external variables. + XQueryEvaluator evaluator = executable.load(); + + // Convert XML string variable content to document-node objects and inject + // these into the evaluator. Note: the script must accept the document-node + // type. Most MSO scripts today expect element() input, not document-node + // input. TODO: figure out how to pass the variable data as element() types. + + for (String xmlInputVariable : xmlInputVariableArray) { + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Injecting XML variable '" + xmlInputVariable + "'"); + msoLogger.debug("printing the variable content>>'" + execution.getVariable(xmlInputVariable) +"'"); + } + + String xml = (String) execution.getVariable(xmlInputVariable); + DocumentBuilder documentBuilder = processor.newDocumentBuilder(); + StreamSource source = new StreamSource(new ByteArrayInputStream(xml.getBytes("UTF-8"))); + XdmNode xdmNode = documentBuilder.build(source); + + // Inject the document-node object into the XQueryEvaluator. + // TODO: transform it to an element() + QName variable = new QName(xmlInputVariable); + evaluator.setExternalVariable(variable, xdmNode); + } + + // Inject atomic variables into the evaluator. + + for (String atomicInputVariable : atomicInputVariableArray) { + + if (msoLogger.isDebugEnabled()) { + System.out.println("Injecting object variable '" + + atomicInputVariable + "'"); + } + + QName variable = new QName(atomicInputVariable); + Object value = execution.getVariable(atomicInputVariable); + + if (value == null) { + // The variable value is null, so we have no way to know what + // type it is. I don't know how to deal with this, so for + // now, just skip it. + + msoLogger.warn (MessageEnum.BPMN_VARIABLE_NULL, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.DataError, atomicInputVariable); + + continue; + } + + // There might be a better way to do this... + if (value instanceof BigDecimal) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((BigDecimal) value)); + } else if (value instanceof Boolean) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((Boolean) value)); + } else if (value instanceof Double) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((Double) value)); + } else if (value instanceof Float) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((Float) value)); + } else if (value instanceof Long) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((Long) value)); + } else if (value instanceof String) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((String) value)); + } else if (value instanceof URI) { + evaluator.setExternalVariable(variable, + new XdmAtomicValue((URI) value)); + } else { + throw new BadInjectedFieldException( + "atomicInputVariables", getTaskName(), + "'" + atomicInputVariable + "' type is not supported: " + + value.getClass()); + } + } + + // Evaluate the query and collect the output. + StringBuilder output = new StringBuilder(); + Iterator<XdmItem> xdmItems = evaluator.iterator(); + while (xdmItems.hasNext()) { + XdmItem item = xdmItems.next(); + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("XQuery result item = " + item); + } + + output.append(item.toString()); + } + + // Set the output variable. + execution.setVariable(theOutputVariable, output.toString()); + + if (msoLogger.isDebugEnabled()) { + msoLogger.debug("Done Executing " + getTaskName()); + } + } + + /** + * Compiles an XQuery script contained in a resource (file). + * @param compiler the XQueryCompiler + * @param resource the resource path + * @return an XQueryExecutable + * @throws Exception on error + */ + private XQueryExecutable compile(XQueryCompiler compiler, String resource) + throws Exception { + InputStream xqStream = null; + try { + xqStream = getClass().getResourceAsStream(resource); + + if (xqStream == null) { + throw new IOException("Resource not found: " + resource); + } + + XQueryExecutable executable = compiler.compile(xqStream); + xqStream.close(); + xqStream = null; + return executable; + } finally { + if (xqStream != null) { + try { + xqStream.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/json/JsonUtils.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/json/JsonUtils.java new file mode 100644 index 0000000000..8329746347 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/json/JsonUtils.java @@ -0,0 +1,443 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.json; + +import java.util.Iterator; +import java.util.StringTokenizer; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.XML; + +//import org.openecomp.mso.bpmn.core.BPMNLogger; +import org.openecomp.mso.bpmn.core.xml.XmlTool; +import org.openecomp.mso.logger.MsoLogger; + +/** + * Utility class for JSON processing + * + * @version 1.0 + */ + +public class JsonUtils { + + private static MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static int MSOJsonIndentFactor = 3; + + /** + * Uses the JSONObject static method to convert a XML doc to JSON. + * + * @param xml String containing the XML doc + * @param pretty flag to determine if the output should be formatted + * @return String containing the JSON translation + */ + public static String xml2json(String xml, Boolean pretty) { +// String isDebugLogEnabled = "true"; + try { + // name spaces cause problems, so just remove them + JSONObject jsonObj = XML.toJSONObject(XmlTool.removeNamespaces(xml)); + if (!pretty) { + return jsonObj.toString(); + } else { + // add an indent to make it 'pretty' + return jsonObj.toString(MSOJsonIndentFactor); + } + } catch (Exception e){ + msoLogger.debug("xml2json(): unable to parse xml and convert to json. Exception was: " + e.toString()); + return null; + } + } + + /** + * Invokes xml2json(String, Boolean) defaulting to 'pretty' output. + * + * @param xml String containing the XML doc + * @return String containing the JSON translation + */ + public static String xml2json(String xml) { + return xml2json(xml, true); + } + + /** + * Uses the JSONObject static method to convert a JSON doc to XML. + * Note: this method will not generate valid XML if the JSONObject + * contains JSONArrays which are used to represent XML attributes + * in the JSON doc. + * + * @param jsonStr String containing the JSON doc + * @param pretty flag to determine if the output should be formatted + * @return String containing the XML translation + */ + public static String json2xml(String jsonStr, Boolean pretty) { +// String isDebugLogEnabled = "true"; + try { + JSONObject jsonObj = new JSONObject(jsonStr); + if (pretty) { + return XmlTool.normalize(XML.toString(jsonObj)); + } else { + return XML.toString(jsonObj); + } + } catch (Exception e){ + msoLogger.debug("json2xml(): unable to parse json and convert to xml. Exception was: " + e.toString()); + return null; + } + } + + /** + * Invokes json2xml(String, Boolean) defaulting to 'pretty' output. + * + * @param jsonStr String containing the XML doc + * @return String containing the JSON translation + */ + public static String json2xml(String jsonStr) { + return json2xml(jsonStr, true); + } + + /** + * Uses the JSONObject static method to convert a JSON doc to XML. + * + * @param jsonStr String containing the JSON doc + * @return Iterator over the JSON keys + */ + public static Iterator <String> getJsonIterator(String jsonStr) { +// String isDebugLogEnabled = "true"; + try { + JSONObject json = new JSONObject(jsonStr); + return json.keys(); + + } catch (Exception e){ + msoLogger.debug("getJsonIterator(): unable to parse json to retrieve the keys iterator. Exception was: " + e.toString()); + return null; + } + } + + /** + * Invokes the getJsonRawValue() method and returns the String equivalent of + * the object returned. + * + * TBD: May need separate methods for boolean, float, and integer fields if the + * String representation is not sufficient to meet client needs. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the target value in the format of "key1.key2.key3..." + * @return String field value associated with keys + */ + public static String getJsonValue(String jsonStr, String keys) { +// String isDebugLogEnabled = "true"; + try { + Object rawValue = getJsonRawValue(jsonStr, keys); + if (rawValue == null) { + return null; + } else { + if (rawValue instanceof String) { + msoLogger.debug("getJsonValue(): the raw value is a String Object=" + ((String) rawValue).toString()); + return (String) rawValue; + } else { + msoLogger.debug("getJsonValue(): the raw value is NOT a String Object=" + rawValue.toString()); + return rawValue.toString(); + } + } + } catch (Exception e) { + msoLogger.debug("getJsonValue(): unable to parse json to retrieve value for field=" + keys + ". Exception was: " + e.toString()); + } + return null; + } + + /** + * Invokes the getJsonRawValue() method to obtain the JSONArray associated with + * the specified keys. The JSONArray is then walked to retrieve the content value of + * the specified field name. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the target value in the format of "key1.key2.key3..." + * @param name field name for the param to be retrieved + * @return String param value associated with field name + */ + public static String getJsonParamValue(String jsonStr, String keys, String name) { +// String isDebugLogEnabled = "true"; + try { + Object rawValue = getJsonRawValue(jsonStr, keys); + if (rawValue == null) { + return null; + } else { + if (rawValue instanceof JSONArray) { + msoLogger.debug("getJsonParamValue(): keys=" + keys + " points to JSONArray: " + ((JSONArray) rawValue).toString()); + for (int i = 0; i < ((JSONArray) rawValue).length(); i++) { + msoLogger.debug("getJsonParamValue(): index: " + i + ", value: " + ((JSONArray) rawValue).get(i).toString()); + if (((JSONArray) rawValue).get(i) instanceof JSONObject) { + msoLogger.debug("getJsonParamValue(): index: " + i + " is a JSONObject"); + JSONObject jsonObj = (JSONObject)((JSONArray) rawValue).get(i); + if (jsonObj.get("name").equals(name)) { + msoLogger.debug("getJsonParamValue(): found value: " + (String) jsonObj.get("content") + " for name: " + name); + return (String) jsonObj.get("content"); + } + } else { + msoLogger.debug("getJsonParamValue(): the JSONArray element is NOT a JSONObject=" + rawValue.toString()); + return null; + } + } + msoLogger.debug("getJsonParamValue(): content value NOT found for name: " + name); + return null; + } else { + msoLogger.debug("getJsonParamValue(): the raw value is NOT a JSONArray Object=" + rawValue.toString()); + return null; + } + } + } catch (JSONException je) { + // JSONObject::get() throws this exception if one of the specified keys is not found + msoLogger.debug("getJsonParamValue(): caught JSONException attempting to retrieve param value for keys:" + keys + ", name=" + name); + } catch (Exception e) { + msoLogger.debug("getJsonParamValue(): unable to parse json to retrieve value for field=" + keys + ". Exception was: " + e.toString()); + } + return null; + } + + /** + * Wrapper to generate the JSONObject to pass to the getJsonValueForKey(JSONObject, String) + * method so that recursion over the subobjects can be supported there + * + * @param jsonStr String containing the JSON doc + * @param key key to the target value + * @return String field value associated with key + */ + public static String getJsonValueForKey(String jsonStr, String key) { +// String isDebugLogEnabled = "true"; + try { + JSONObject jsonObj = new JSONObject(jsonStr); + if (jsonObj != null) { + return getJsonValueForKey(jsonObj, key); + } + } catch (Exception e) { + msoLogger.debug("getJsonValueForKey(): unable to parse json to retrieve value for field=" + key + ". Exception was: " + e.toString()); + } + return null; + } + + /** + * Walks the JSONObject (and sub-objects recursively), searching for the first value associated with the + * single key/field name specified. Returns the associated value if found or null if the key is not found + * + * @param jsonObj JSONObject representation of the the JSON doc + * @param key key to the target value + * @return String field value associated with key + */ + public static String getJsonValueForKey(JSONObject jsonObj, String key) { +// String isDebugLogEnabled = "true"; + String keyValue = null; + try { + if (jsonObj.has(key)) { + msoLogger.debug("getJsonValueForKey(): found value for key=" + key); + return ((String) jsonObj.get(key)); + } else { + msoLogger.debug("getJsonValueForKey(): iterating over the keys"); + Iterator <String> itr = jsonObj.keys(); + while (itr.hasNext()) { + String nextKey = (String) itr.next(); + Object obj = jsonObj.get(nextKey); + if (obj instanceof JSONObject) { + msoLogger.debug("getJsonValueForKey(): key=" + nextKey + ", points to JSONObject, recursive call"); + keyValue = getJsonValueForKey((JSONObject) obj, key); + if (keyValue != null) { + msoLogger.debug("getJsonValueForKey(): found value=" + keyValue + ", for key=" + key); + break; + } + } else { + msoLogger.debug("getJsonValueForKey(): key=" + nextKey + ", does not point to a JSONObject, next key"); + } + } + } + } catch (JSONException je) { + // JSONObject::get() throws this exception if one of the specified keys is not found + msoLogger.debug("getJsonValueForKey(): caught JSONException attempting to retrieve value for key=" + key); + keyValue = null; + } catch (Exception e) { + msoLogger.debug("getJsonValueForKey(): unable to parse json to retrieve value for field=" + key + ". Exception was: " + e.toString()); + } + return keyValue; + } + + /** + * Boolean method to determine if a key path is valid for the JSON doc. Invokes + * getJsonValue(). + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the target value in the format of "key1.key2.key3..." + * @return Boolean true if keys points to value in the JSON doc + */ + public static Boolean jsonValueExists(String jsonStr, String keys) { + if (getJsonRawValue(jsonStr, keys) == null) { + return false; + } else { + return true; + } + } + + /** + * Inserts the new key/value pair at the appropriate location in the JSON + * document after first determining if keyed field already exists. If + * it does exist, return the JSON unmodified, otherwise return the new JSON + * Note: this method currently only supports String value inserts. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the value to be added in the format of "key1.key2.key3..." + * @return String containing the updated JSON doc + */ + public static String addJsonValue(String jsonStr, String keys, String value) { +// String isDebugLogEnabled = "true"; + // only attempt to insert the key/value pair if it does not exist + if (!jsonValueExists(jsonStr, keys)) { + return putJsonValue(jsonStr, keys, value); + } else { + msoLogger.debug("addJsonValue(): JSON add failed, key=" + keys + "/value=" + (String) value + " already exists"); + return jsonStr; + } + } + + /** + * Updates the value for the specified key in the JSON document + * after first determining if keyed field exists. If it does + * not exist, return the JSON unmodified, otherwise return the updated JSON. + * Note: this method currently only supports String value updates. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the value to be updated in the format of "key1.key2.key3..." + * @return String containing the updated JSON doc + */ + public static String updJsonValue(String jsonStr, String keys, String newValue) { +// String isDebugLogEnabled = "true"; + // only attempt to modify the key/value pair if it exists + if (jsonValueExists(jsonStr, keys)) { + return putJsonValue(jsonStr, keys, newValue); + } else { + msoLogger.debug("updJsonValue(): JSON update failed, no value exists for key=" + keys); + return jsonStr; + } + } + + /** + * Deletes the value for the specified key in the JSON document + * after first determining if keyed field exists. If it does + * not exist, return the JSON unmodified, otherwise return the updated JSON + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the value to be deleted in the format of "key1.key2.key3..." + * @return String containing the updated JSON doc + */ + public static String delJsonValue(String jsonStr, String keys) { +// String isDebugLogEnabled = "true"; + // only attempt to remove the key/value pair if it exists + if (jsonValueExists(jsonStr, keys)) { + // passing a null value results in a delete + return putJsonValue(jsonStr, keys, null); + } else { + msoLogger.debug("delJsonValue(): JSON delete failed, no value exists for key=" + keys); + return jsonStr; + } + } + + /** + * Walks the JSON doc using the full key path to retrieve the associated + * value. All but the last key points to the 'parent' object name(s) in order + * in the JSON hierarchy with the last key pointing to the target value. + * The value returned is a Java object. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the target value in the format of "key1.key2.key3..." + * @return Object field value associated with keys + */ + private static Object getJsonRawValue(String jsonStr, String keys) { +// String isDebugLogEnabled = "true"; + String keyStr = ""; + try { + JSONObject jsonObj = new JSONObject(jsonStr); + StringTokenizer keyTokens = new StringTokenizer(keys, "."); + while (keyTokens.hasMoreElements()) { + keyStr = keyTokens.nextToken(); + Object keyValue = jsonObj.get(keyStr); + if (keyValue instanceof JSONObject) { + msoLogger.debug("getJsonRawValue(): key=" + keyStr + " points to json object"); + jsonObj = (JSONObject) keyValue; + } else { + if (keyTokens.hasMoreElements()) { + msoLogger.debug("getJsonRawValue(): value found prior to last key for key=" + keyStr); + } + return keyValue; + } + } + // we should not hit this point: either the key points to a valid value and + // we return it above or the key is invalid and we handle the JSONException + // below and return null + return null; + + } catch (JSONException je) { + // JSONObject::get() throws this exception if one of the specified keys is not found + msoLogger.debug("getJsonRawValue(): caught JSONException attempting to retrieve raw value for key=" + keyStr); + } catch (Exception e) { + msoLogger.debug("getJsonRawValue(): unable to parse json to retrieve value for field=" + keys + ". Exception was: " + e.toString()); + } + return null; + } + + /** + * Private method invoked by the public add, update, and delete methods. + * + * @param jsonStr String containing the JSON doc + * @param keys full key path to the value to be deleted in the format of "key1.key2.key3..." + * @return String containing the updated JSON doc + */ + private static String putJsonValue(String jsonStr, String keys, String value) { +// String isDebugLogEnabled = "true"; + String keyStr = ""; + try { + JSONObject jsonObj = new JSONObject(jsonStr); + JSONObject jsonObjOut = jsonObj; + StringTokenizer keyTokens = new StringTokenizer(keys, "."); + while (keyTokens.hasMoreElements()) { + keyStr = keyTokens.nextToken(); + if (keyTokens.hasMoreElements()) { + Object keyValue = jsonObj.get(keyStr); + if (keyValue instanceof JSONObject) { + msoLogger.debug("putJsonValue(): key=" + keyStr + " points to json object"); + jsonObj = (JSONObject) keyValue; + } else { + msoLogger.debug("putJsonValue(): key=" + keyStr + " not the last key but points to non-json object: " + (String) keyValue); + return null; + } + } else { // at the last/new key value + jsonObj.put(keyStr, value); + return jsonObjOut.toString(3); + } + } + // should not hit this point if the key points to a valid key value + return null; + + } catch (JSONException je) { + // JSONObject::get() throws this exception if one of the specified keys is not found + msoLogger.debug("putJsonValue(): caught JSONException attempting to retrieve value for key=" + keyStr); + return null; + } catch (Exception e) { + msoLogger.debug("putJsonValue(): unable to parse json to put value for key=" + keys + ". Exception was: " + e.toString()); + } + return null; + } +} + diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/CustomMyBatisSessionFactory.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/CustomMyBatisSessionFactory.java new file mode 100644 index 0000000000..3a731558a4 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/CustomMyBatisSessionFactory.java @@ -0,0 +1,102 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.mybatis; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.camunda.bpm.engine.impl.cfg.StandaloneProcessEngineConfiguration; +import org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor; +import org.camunda.bpm.engine.impl.interceptor.CommandInterceptor; +import org.camunda.bpm.engine.impl.interceptor.LogInterceptor; +import org.camunda.bpm.engine.impl.util.ReflectUtil; + + +/** + * A special process engine that provides access to MyBatis mappings. + * @version 1.0 + */ +public class CustomMyBatisSessionFactory extends + StandaloneProcessEngineConfiguration { + + private String resourceName; + + /** + * Overridden to ensure nobody ever tries to initialize this process engine + * in the normal way. We are using this process engine only for MyBatis + * access. + */ + @Override + protected void init() { + throw new UnsupportedOperationException("init"); + } + + /** + * Initialize the ProcessEngineConfiguration from an existing one, just + * using the database settings to initialize the database / MyBatis stuff. + */ + public void initFromProcessEngineConfiguration( + ProcessEngineConfigurationImpl processEngineConfiguration, + String resourceName) { + this.resourceName = resourceName; + + setDatabaseType(processEngineConfiguration.getDatabaseType()); + setDataSource(processEngineConfiguration.getDataSource()); + setDatabaseTablePrefix(processEngineConfiguration + .getDatabaseTablePrefix()); + + initDataSource(); + // initVariableTypes(); + initCommandContextFactory(); + initTransactionFactory(); + initTransactionContextFactory(); + initCommandExecutors(); + initSqlSessionFactory(); + initIncidentHandlers(); + initIdentityProviderSessionFactory(); + initSessionFactories(); + } + + /** + * In order to always open a new command context set the property + * "alwaysOpenNew" to true inside the CommandContextInterceptor. + * + * If you execute the custom queries inside the process engine (for example + * in a service task), you have to do this. + */ + @Override + protected Collection<? extends CommandInterceptor> getDefaultCommandInterceptorsTxRequired() { + List<CommandInterceptor> defaultCommandInterceptorsTxRequired = + new ArrayList<CommandInterceptor>(); + defaultCommandInterceptorsTxRequired.add(new LogInterceptor()); + defaultCommandInterceptorsTxRequired.add(new CommandContextInterceptor( + commandContextFactory, this, true)); + return defaultCommandInterceptorsTxRequired; + } + + @Override + protected InputStream getMyBatisXmlConfigurationSteam() { + return ReflectUtil.getResourceAsStream(resourceName); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/URNMapping.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/URNMapping.java new file mode 100644 index 0000000000..07959cb016 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/mybatis/URNMapping.java @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.mybatis; + +/** + * A bean that represents a single URN mapping. + */ +public class URNMapping { + private String name; + private String value; + private String rev; + + /** + * Get the name. + * @return the name + */ + public String getName() { + return name; + } + + /** + * Set the name. + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the value mapped to the name. + * @return the value mapped to the name + */ + public String getValue() { + return value; + } + + /** + * Set the value mapped to the name. + * @param value the value mapped to the name + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Get the revision attribute (currently unused). + * @return the revision attribute + */ + public String getRev() { + return rev; + } + + /** + * Set the revision attribute (currently unused). + * @param rev the revision attribute + */ + public void setRev(String rev) { + this.rev = rev; + } + + /** + * Converts a URN to "normal" form so it can used as a java or groovy + * variable identifier. This is done in a way that makes the identifier + * as readable as possible, but note that it might result in a loss of + * uniqueness. + * <ol> + * <li> URN_ is prepended </li> + * <li> All characters that are not letters or digits are converted to + * underscore characters </li> + * <li> Sequences of multiple underscores are collapsed to a single + * underscore character </li> + * </ol> + * Examples: + * <p> + * aai:endpoint becomes URN_aai_endpoint <br/> + * ae:internal-reporting becomes URN_ae_internal_reporting <br/> + * + * @param name the URN + * @return a normalized identifier + */ + public static String createIdentifierFromURN(String urn) { + StringBuilder builder = new StringBuilder(); + builder.append("URN_"); + char last = builder.charAt(builder.length() - 1); + + int len = urn.length(); + + for (int i = 0; i < len; i++) { + char c = urn.charAt(i); + + if (!Character.isLetterOrDigit(c) && c != '_') { + c = '_'; + } + + if (!(c == '_' && last == '_')) { + builder.append(c); + } + + last = c; + } + + return builder.toString(); + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/LoggingAndURNMappingPlugin.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/LoggingAndURNMappingPlugin.java new file mode 100644 index 0000000000..8e3f254def --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/LoggingAndURNMappingPlugin.java @@ -0,0 +1,424 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.plugins; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.ExecutionListener; +import org.camunda.bpm.engine.impl.bpmn.parser.AbstractBpmnParseListener; +import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener; +import org.camunda.bpm.engine.impl.cfg.AbstractProcessEnginePlugin; +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.camunda.bpm.engine.impl.context.Context; +import org.camunda.bpm.engine.impl.interceptor.Command; +import org.camunda.bpm.engine.impl.interceptor.CommandContext; +import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity; +import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl; +import org.camunda.bpm.engine.impl.pvm.process.ScopeImpl; +import org.camunda.bpm.engine.impl.pvm.process.TransitionImpl; +import org.camunda.bpm.engine.impl.util.xml.Element; +import org.camunda.bpm.engine.impl.variable.VariableDeclaration; + +import org.openecomp.mso.bpmn.core.BPMNLogger; +import org.openecomp.mso.bpmn.core.PropertyConfiguration; +import org.openecomp.mso.bpmn.core.mybatis.CustomMyBatisSessionFactory; +import org.openecomp.mso.bpmn.core.mybatis.URNMapping; +import org.openecomp.mso.logger.MessageEnum; +import org.openecomp.mso.logger.MsoLogger; + +/** + * Plugin for MSO logging and URN mapping. + */ +public class LoggingAndURNMappingPlugin extends AbstractProcessEnginePlugin { + private static MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static final String FSPROPKEY = "URNMapping.FileSystemLoading.Enabled"; + + @Override + public void preInit( + ProcessEngineConfigurationImpl processEngineConfiguration) { + List<BpmnParseListener> preParseListeners = processEngineConfiguration + .getCustomPreBPMNParseListeners(); + if (preParseListeners == null) { + preParseListeners = new ArrayList<BpmnParseListener>(); + processEngineConfiguration.setCustomPreBPMNParseListeners(preParseListeners); + } + preParseListeners.add(new LoggingParseListener()); + } + + /** + * Called when a process flow is parsed so we can inject listeners. + */ + public static class LoggingParseListener extends AbstractBpmnParseListener { + private void injectLogExecutionListener(ActivityImpl activity) { + activity.addListener( + ExecutionListener.EVENTNAME_END, + new LoggingExecutionListener("END")); + + activity.addListener( + ExecutionListener.EVENTNAME_START, + new LoggingExecutionListener("START")); + + activity.addListener( + ExecutionListener.EVENTNAME_TAKE, + new LoggingExecutionListener("TAKE")); + } + + public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition) { + } + + public void parseStartEvent(Element startEventElement, ScopeImpl scope, ActivityImpl startEventActivity) { + // Inject these listeners only on the main start event for the flow, not on any embedded subflow start events + if (scope instanceof ProcessDefinitionEntity) { + startEventActivity.addListener(ExecutionListener.EVENTNAME_START, new URNMappingInitializerListener("START")); + startEventActivity.addListener(ExecutionListener.EVENTNAME_START, new LoggingInitializerListener("START")); + } + + injectLogExecutionListener(startEventActivity); + } + + public void parseServiceTask(Element serviceTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseExclusiveGateway(Element exclusiveGwElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseInclusiveGateway(Element inclusiveGwElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseParallelGateway(Element parallelGwElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseScriptTask(Element scriptTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseBusinessRuleTask(Element businessRuleTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseTask(Element taskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseManualTask(Element manualTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseEndEvent(Element endEventElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseBoundaryTimerEventDefinition(Element timerEventDefinition, boolean interrupting, ActivityImpl timerActivity) { + injectLogExecutionListener(timerActivity); + } + + public void parseBoundaryErrorEventDefinition(Element errorEventDefinition, boolean interrupting, ActivityImpl activity, ActivityImpl nestedErrorEventActivity) { + injectLogExecutionListener(activity); + } + + public void parseSubProcess(Element subProcessElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseCallActivity(Element callActivityElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseProperty(Element propertyElement, VariableDeclaration variableDeclaration, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseSequenceFlow(Element sequenceFlowElement, ScopeImpl scopeElement, TransitionImpl transition) { + //injectLogExecutionListener(activity); + } + + public void parseSendTask(Element sendTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseMultiInstanceLoopCharacteristics(Element activityElement, Element multiInstanceLoopCharacteristicsElement, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseIntermediateTimerEventDefinition(Element timerEventDefinition, ActivityImpl timerActivity) { + injectLogExecutionListener(timerActivity); + } + + public void parseRootElement(Element rootElement, List<ProcessDefinitionEntity> processDefinitions) { + //injectLogExecutionListener(activity); + } + + public void parseReceiveTask(Element receiveTaskElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseIntermediateSignalCatchEventDefinition(Element signalEventDefinition, ActivityImpl signalActivity) { + injectLogExecutionListener(signalActivity); + } + + public void parseBoundarySignalEventDefinition(Element signalEventDefinition, boolean interrupting, ActivityImpl signalActivity) { + injectLogExecutionListener(signalActivity); + } + + public void parseEventBasedGateway(Element eventBasedGwElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseTransaction(Element transactionElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseCompensateEventDefinition(Element compensateEventDefinition, ActivityImpl compensationActivity) { + injectLogExecutionListener(compensationActivity); + } + + public void parseIntermediateThrowEvent(Element intermediateEventElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseIntermediateCatchEvent(Element intermediateEventElement, ScopeImpl scope, ActivityImpl activity) { + injectLogExecutionListener(activity); + } + + public void parseBoundaryEvent(Element boundaryEventElement, ScopeImpl scopeElement, ActivityImpl nestedActivity) { + injectLogExecutionListener(nestedActivity); + } + + public void parseIntermediateMessageCatchEventDefinition(Element messageEventDefinition, ActivityImpl nestedActivity) { + injectLogExecutionListener(nestedActivity); + } + + public void parseBoundaryMessageEventDefinition(Element element, boolean interrupting, ActivityImpl messageActivity) { + injectLogExecutionListener(messageActivity); + } + } + + /** + * Initializes URN mapping variables on process entry. + */ + public static class URNMappingInitializerListener implements ExecutionListener { + private String event; + + public URNMappingInitializerListener(String eventData) { + this.event = eventData; + } + + public String getEvent() { + return event; + } + + public void notify(DelegateExecution execution) throws Exception { + ProcessEngineConfigurationImpl processEngineConfiguration = + Context.getProcessEngineConfiguration(); + loadURNProperties(execution, processEngineConfiguration); + } + + private void loadURNProperties(DelegateExecution execution, + ProcessEngineConfigurationImpl processEngineConfiguration) { + Map<String,String> bpmnProps = PropertyConfiguration.getInstance().getProperties("mso.bpmn.properties"); + if (bpmnProps == null) { + LOGGER.debug("Unable to load mso.bpmn.properties; loading URN Mapping from DB"); + + LOGGER.error (MessageEnum.BPMN_GENERAL_EXCEPTION, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, + "Unable to load mso.bpmn.properties; loading URN Mapping from DB"); + + loadFromDB(execution, processEngineConfiguration); + } else { + String fsEnabled = bpmnProps.get(FSPROPKEY); + if (fsEnabled != null) { + if (Boolean.parseBoolean(fsEnabled)) { + LOGGER.debug("File system loading is enabled; loading URN properties from File system"); + LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", "File system loading is enabled; loading URN properties from File System"); + loadFromFileSystem(execution); + } else { + LOGGER.debug("File system loading is disabled; loading URN properties from DB"); + LOGGER.info (MessageEnum.BPMN_GENERAL_INFO, "BPMN", "File system loading is disabled; loading URN properties from DB"); + + loadFromDB(execution, processEngineConfiguration); + } + } else { + + LOGGER.error (MessageEnum.BPMN_GENERAL_EXCEPTION, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, + "Unable to retrieve URNMapping.FileSystemLoading.Enabled from mso.bpmn.properties; loading URN Mapping from DB"); + + loadFromDB(execution, processEngineConfiguration); + } + } + } + + private void loadFromFileSystem(DelegateExecution execution) { + PropertyConfiguration propertyConfiguration = PropertyConfiguration.getInstance(); + Map<String,String> props = propertyConfiguration.getProperties("mso.bpmn.urn.properties"); + for (String key : props.keySet()) { + String varName = URNMapping.createIdentifierFromURN(key); + String varValue = props.get(key); + execution.setVariable(varName, varValue); + } + } + + private void loadFromDB(DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { + Command<List<URNMapping>> command = new Command<List<URNMapping>>() { + @SuppressWarnings("unchecked") + public List<URNMapping> execute(CommandContext commandContext) { + return (List<URNMapping>) commandContext.getDbSqlSession().selectList( + "mso.urnMapping.selectAll", null); + } + }; + + CustomMyBatisSessionFactory sessionFactory = new CustomMyBatisSessionFactory(); + sessionFactory.initFromProcessEngineConfiguration(processEngineConfiguration, + "customMyBatisConfiguration.xml"); + + List<URNMapping> mappings = sessionFactory.getCommandExecutorTxRequired().execute(command); + + if (mappings != null && mappings.size() > 0) { + for (URNMapping mapping : mappings) { + String varName = URNMapping.createIdentifierFromURN(mapping.getName()); + String varValue = mapping.getValue(); + + LOGGER.debug("URN Mapping = '" + mapping.getName() + + "', setting variable '" + varName + "' to '" + varValue + "'"); + + execution.setVariable(varName, varValue); + } + } + } + } + + /** + * Sets the isDebugLogEnabled variable on process entry. + */ + public static class LoggingInitializerListener implements ExecutionListener { + private String event; + + public LoggingInitializerListener(String eventData) { + this.event = eventData; + } + + public String getEvent() { + return event; + } + + public void notify(DelegateExecution execution) throws Exception { + String processKey = execution.getProcessEngineServices().getRepositoryService() + .getProcessDefinition(execution.getProcessDefinitionId()).getKey(); + + // If a "true" value is already injected, e.g. from a top-level flow, it SHOULD NOT be + // overridden by the value in the URN mapping. This allows a top-level flow and all + // invoked subflows to be debugged by turning on the debug flag for just the top-level + // flow, assuming the isDebugEnabled flag variable is passed from the top-level flow to + // its subflows. + + // If a "false" value is already injected, e.g. from a top-level flow, it SHOULD be + // overridden by the value in the URN mapping. This allows a subflow to be debugged + // without turning on the the debug flag for the top-level flow. + + String injectedValue = (String) execution.getVariable("isDebugLogEnabled"); + String urnValue = "true".equals(execution.getVariable("URN_log_debug_" + processKey)) ? "true" : "false"; + + if ("true".equals(injectedValue)) { + LOGGER.debug("Setting isDebugLogEnabled to \"" + injectedValue + "\" for process: " + processKey + " (injected value)"); + execution.setVariable("isDebugLogEnabled", injectedValue); + } else { + LOGGER.debug("Setting isDebugLogEnabled to \"" + urnValue + "\" for process: " + processKey + " (from URN mapping)"); + execution.setVariable("isDebugLogEnabled", urnValue); + } + } + } + + /** + * Logs details about the current activity. + */ + public static class LoggingExecutionListener implements ExecutionListener { + private static MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL); + private static ConcurrentHashMap<String, Long> startTimes = new ConcurrentHashMap<String, Long>(); + + private String event; + + public LoggingExecutionListener(String event) { + this.event = event; + } + + public String getEvent() { + return event; + } + + public void notify(DelegateExecution execution) throws Exception { + BPMNLogger.debug( + (String) execution.getVariable("isDebugLogEnabled"), + "Logging for activity---------------:" + event + ":" + + execution.getCurrentActivityName() + + ", processDefinitionId=" + + execution.getProcessDefinitionId() + ", activtyId=" + + execution.getCurrentActivityId() + ", activtyName='" + + execution.getCurrentActivityName() + "'" + + ", processInstanceId=" + + execution.getProcessInstanceId() + ", businessKey=" + + execution.getProcessBusinessKey() + ", executionId=" + + execution.getId()); + + if (!isBlank(execution.getCurrentActivityName())) { + try { + String id = execution.getId(); + if ("START".equals(event) && id != null ) { + startTimes.put(id, (Long)System.currentTimeMillis()); + } else if ("END".equals(event) && id != null) { + String prefix = (String) execution.getVariable("prefix"); + + if (prefix != null ) { + MsoLogger.setServiceName("MSO." + prefix.substring(0,prefix.length()-1)); + } + + String requestId = (String) execution.getVariable("att-mso-request-id"); + String svcid = (String) execution.getVariable("att-mso-service-instance-id"); + MsoLogger.setLogContext(requestId, svcid); + long startTime = startTimes.remove(id); + + if (startTime != 0) { + + LOGGER.recordMetricEvent (startTime, MsoLogger.StatusCode.COMPLETE, MsoLogger.ResponseCode.Suc, + event + ": " + execution.getCurrentActivityName(), "BPMN", execution.getCurrentActivityName(), null); + + } + } + } catch(Exception e) { + // Do nothing + } + } + } + + private boolean isBlank(Object object) { + return object == null || object.toString().trim().equals(""); + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/WorkflowExceptionPlugin.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/WorkflowExceptionPlugin.java new file mode 100644 index 0000000000..10386d721e --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/plugins/WorkflowExceptionPlugin.java @@ -0,0 +1,170 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.plugins; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.ExecutionListener; +import org.camunda.bpm.engine.delegate.JavaDelegate; +import org.camunda.bpm.engine.impl.bpmn.behavior.ClassDelegateActivityBehavior; +import org.camunda.bpm.engine.impl.bpmn.parser.AbstractBpmnParseListener; +import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener; +import org.camunda.bpm.engine.impl.bpmn.parser.FieldDeclaration; +import org.camunda.bpm.engine.impl.cfg.AbstractProcessEnginePlugin; +import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity; +import org.camunda.bpm.engine.impl.pvm.PvmTransition; +import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl; +import org.camunda.bpm.engine.impl.pvm.process.TransitionImpl; +import org.camunda.bpm.engine.impl.util.xml.Element; + +import org.openecomp.mso.bpmn.core.BPMNLogger; +import org.openecomp.mso.bpmn.core.WorkflowException; + +/** + * This plugin does the following: + * <ol> + * <li> + * Adds logic at the start of every Call Activity to remove any existing + * WorkflowException object from the execution (saving a copy of it in a + * different variable). + * </li> + * <li> + * Adds logic at the end of every Call Activity to generate a MSOWorkflowException + * event if there is a WorkflowException object in the execution. + * </li> + * </ol> + */ +public class WorkflowExceptionPlugin extends AbstractProcessEnginePlugin { + + @Override + public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) { + List<BpmnParseListener> preParseListeners = + processEngineConfiguration.getCustomPreBPMNParseListeners(); + + if (preParseListeners == null) { + preParseListeners = new ArrayList<BpmnParseListener>(); + processEngineConfiguration.setCustomPreBPMNParseListeners(preParseListeners); + } + + preParseListeners.add(new WorkflowExceptionParseListener()); + } + + public static class WorkflowExceptionParseListener extends AbstractBpmnParseListener { + @Override + public void parseProcess(Element processElement, ProcessDefinitionEntity processDefinition) { + AtomicInteger triggerTaskIndex = new AtomicInteger(1); + List<ActivityImpl> activities = new ArrayList<ActivityImpl>(processDefinition.getActivities()); + recurse(activities, triggerTaskIndex); + } + + /** + * Helper method that recurses (into subprocesses) over all the listed activities. + * @param activities a list of workflow activities + * @param triggerTaskIndex the index of the next trigger task (mutable) + */ + private void recurse(List<ActivityImpl> activities, AtomicInteger triggerTaskIndex) { + for (ActivityImpl activity : activities) { + String type = (String) activity.getProperty("type"); + + if ("callActivity".equals(type)) { + // Add a WorkflowExceptionResetListener to clear the WorkflowException + // variable when each Call Activity starts. + + activity.addListener( + ExecutionListener.EVENTNAME_START, + new WorkflowExceptionResetListener()); + + // Add a WorkflowExceptionTriggerTask after the call activity. + // It must be a task because a listener cannot be used to generate + // an event. Throwing BpmnError from an execution listener will + // cause the process to die. + + List<PvmTransition> outTransitions = + new ArrayList<PvmTransition>(activity.getOutgoingTransitions()); + + for (PvmTransition transition : outTransitions) { + String triggerTaskId = "WorkflowExceptionTriggerTask_" + triggerTaskIndex; + + ActivityImpl triggerTask = activity.getFlowScope().createActivity(triggerTaskId); + + ClassDelegateActivityBehavior behavior = new ClassDelegateActivityBehavior( + WorkflowExceptionTriggerTask.class.getName(), + new ArrayList<FieldDeclaration>(0)); + + triggerTask.setActivityBehavior(behavior); + triggerTask.setName("Workflow Exception Trigger Task " + triggerTaskIndex); + triggerTaskIndex.getAndIncrement(); + + TransitionImpl transitionImpl = (TransitionImpl) transition; + TransitionImpl triggerTaskOutTransition = triggerTask.createOutgoingTransition(); + triggerTaskOutTransition.setDestination((ActivityImpl)transitionImpl.getDestination()); + transitionImpl.setDestination(triggerTask); + } + } else if ("subProcess".equals(type)) { + recurse(new ArrayList<ActivityImpl>(activity.getActivities()), triggerTaskIndex); + } + } + } + } + + /** + * If there is a WorkflowException object in the execution, this method + * removes it (saving a copy of it in a different variable). + */ + public static class WorkflowExceptionResetListener implements ExecutionListener { + public void notify(DelegateExecution execution) throws Exception { + Object workflowException = execution.getVariable("WorkflowException"); + + if (workflowException instanceof WorkflowException) { + int index = 1; + String saveName = "SavedWorkflowException" + index; + while (execution.getVariable(saveName) != null) { + saveName = "SavedWorkflowException" + (++index); + } + + BPMNLogger.debug((String)execution.getVariable("isDebugLogEnabled"), + "WorkflowExceptionResetTask is moving WorkflowException to " + saveName); + + execution.setVariable(saveName, workflowException); + execution.setVariable("WorkflowException", null); + } + } + } + + /** + * Generates an MSOWorkflowException event if there is a WorkflowException + * object in the execution. + */ + public static class WorkflowExceptionTriggerTask implements JavaDelegate { + public void execute(DelegateExecution execution) throws Exception { + if (execution.getVariable("WorkflowException") instanceof WorkflowException) { + BPMNLogger.debug((String)execution.getVariable("isDebugLogEnabled"), + "WorkflowExceptionTriggerTask is generating a MSOWorkflowException event"); + throw new BpmnError("MSOWorkflowException"); + } + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/xml/XmlTool.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/xml/XmlTool.java new file mode 100644 index 0000000000..42114758b0 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/core/xml/XmlTool.java @@ -0,0 +1,340 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.core.xml; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * XML transformation methods and other useful functions. + */ +public final class XmlTool { + + private static final Map<String, Integer> ENTITIES = new HashMap<String, Integer>(); + + static { + ENTITIES.put("amp", new Integer(38)); + ENTITIES.put("quot", new Integer(34)); + ENTITIES.put("lt", new Integer(60)); + ENTITIES.put("gt", new Integer(62)); + } + + /** + * Normalizes and formats XML. This method consolidates and moves all namespace + * declarations to the root element. The result will not have an XML prolog or + * a trailing newline. + * @param xml the XML to normalize + * @throws IOException + * @throws TransformerException + * @throws ParserConfigurationException + * @throws SAXException + * @throws XPathExpressionException + */ + public static String normalize(Object xml) throws IOException, TransformerException, + ParserConfigurationException, SAXException, XPathExpressionException { + + if (xml == null) { + return null; + } + + Source xsltSource = new StreamSource(new StringReader( + readResourceFile("normalize-namespaces.xsl"))); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + dbFactory.setNamespaceAware(true); + DocumentBuilder db = dbFactory.newDocumentBuilder(); + InputSource source = new InputSource(new StringReader(String.valueOf(xml))); + Document doc = db.parse(source); + + // Start of code to remove whitespace outside of tags + XPath xPath = XPathFactory.newInstance().newXPath(); + NodeList nodeList = (NodeList) xPath.evaluate( + "//text()[normalize-space()='']", doc, XPathConstants.NODESET); + + for (int i = 0; i < nodeList.getLength(); ++i) { + Node node = nodeList.item(i); + node.getParentNode().removeChild(node); + } + // End of code to remove whitespace outside of tags + + // the factory pattern supports different XSLT processors + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(xsltSource); + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + return writer.toString().trim(); + } + + /** + * Encodes a value so it can be used inside an XML text element. + * @param s the string to encode + * @return the encoded string + */ + public static String encode(Object value) { + if (value == null) { + return null; + } + + String s = String.valueOf(value); + StringBuilder out = new StringBuilder(); + boolean modified = false; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == '<') { + out.append("<"); + modified = true; + } else if (c == '>') { + out.append(">"); + modified = true; + } else if (c == '&') { + out.append("&"); + modified = true; + } else if (c < 32 || c > 126) { + out.append("&#" + (int)c + ";"); + modified = true; + } else { + out.append(c); + } + } + + if (modified) { + return out.toString(); + } else { + return s; + } + } + + /** + * Encodes a value so it can be used inside an XML attribute. + * @param s the string to encode + * @return the encoded string + */ + public static String encodeAttr(Object value) { + if (value == null) { + return null; + } + + String s = String.valueOf(value); + StringBuilder out = new StringBuilder(); + boolean modified = false; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == '<') { + out.append("<"); + modified = true; + } else if (c == '>') { + out.append(">"); + modified = true; + } else if (c == '"') { + out.append("""); + modified = true; + } else if (c == '&') { + out.append("&"); + modified = true; + } else if (c < 32 || c > 126) { + out.append("&#" + (int)c + ";"); + modified = true; + } else { + out.append(c); + } + } + + if (modified) { + return out.toString(); + } else { + return s; + } + } + + /** + * Decodes XML entities in a string value + * @param value a value with embedded XML entities + * @return the decoded string + */ + public static String decode(Object value) { + if (value == null) { + return null; + } + + String s = String.valueOf(value); + + StringBuilder out = new StringBuilder(s.length()); + int ampIndex = s.indexOf("&"); + int lastEnd = 0; + + while (ampIndex >= 0) { + int nextAmpIndex = s.indexOf("&", ampIndex + 1); + int nextSemiIndex = s.indexOf(";", ampIndex + 1); + if (nextSemiIndex != -1 && (nextAmpIndex == -1 || nextSemiIndex < nextAmpIndex)) { + int code = -1; + String entity = s.substring(ampIndex + 1, nextSemiIndex); + + try { + if (entity.startsWith("#")) { + code = Integer.parseInt(entity.substring(1), 10); + } else { + if (ENTITIES.containsKey(entity)) { + code = ENTITIES.get(entity); + } + } + } catch (NumberFormatException x) { + // Do nothing + } + + out.append(s.substring(lastEnd, ampIndex)); + lastEnd = nextSemiIndex + 1; + if (code >= 0 && code <= 0xffff) { + out.append((char) code); + } else { + out.append("&"); + out.append(entity); + out.append(";"); + } + } + + ampIndex = nextAmpIndex; + } + + out.append(s.substring(lastEnd)); + return out.toString(); + } + + /** + * Removes the preamble, if present, from an XML document. + * @param xml the XML document + * @return a possibly modified document + */ + public static String removePreamble(Object xml) { + if (xml == null) { + return null; + } + + return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", ""); + } + + /** + * Removes namespaces and namespace declarations from an XML document. + * @param xml the XML document + * @return a possibly modified document + */ + public static String removeNamespaces(Object xml) { + if (xml == null) { + return null; + } + + String text = String.valueOf(xml); + + // remove xmlns declaration + text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", ""); + // remove opening tag prefix + text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3"); + // remove closing tags prefix + text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3"); + // remove extra spaces left when xmlns declarations are removed + text = text.replaceAll("\\s+>", ">"); + + return text; + } + + + /** + * Reads the specified resource file and return the contents as a string. + * @param file Name of the resource file + * @return the contents of the resource file as a String + * @throws IOException if there is a problem reading the file + */ + private static String readResourceFile(String file) throws IOException { + InputStream stream = null; + try { + stream = XmlTool.class.getClassLoader().getResourceAsStream(file); + + if (stream == null) { + throw new FileNotFoundException("No such resource file: " + file); + } + + Reader reader = new InputStreamReader(stream, "UTF-8"); + StringBuilder out = new StringBuilder(); + char[] buf = new char[1024]; + int n; + + while ((n = reader.read(buf)) >= 0) { + out.append(buf, 0, n); + } + + stream.close(); + stream = null; + return out.toString(); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception e) { + // Ignore + } + } + } + } + + /** + * Instantiation is not allowed. + */ + private XmlTool() { + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/CamundaDBSetup.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/CamundaDBSetup.java new file mode 100644 index 0000000000..13eed2d530 --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/CamundaDBSetup.java @@ -0,0 +1,108 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Sets up the unit test (H2) database for Camunda. + */ +public class CamundaDBSetup { + private static boolean isDBConfigured = false; + + public static synchronized void configure() throws SQLException { + if (isDBConfigured) { + return; + } + + System.out.println("Configuring the Camunda H2 database for MSO"); + + Connection connection = null; + PreparedStatement stmt = null; + + try { + connection = DriverManager.getConnection( + "jdbc:h2:mem:camunda;DB_CLOSE_DELAY=-1", "sa", ""); + + stmt = connection.prepareStatement("delete from ACT_HI_VARINST"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("ALTER TABLE ACT_HI_VARINST alter column TEXT_ clob"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("ALTER TABLE ACT_HI_VARINST alter column NAME_ clob"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("delete from ACT_HI_DETAIL"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("ALTER TABLE ACT_HI_DETAIL alter column TEXT_ clob"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("ALTER TABLE ACT_HI_DETAIL alter column NAME_ clob"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + stmt = connection.prepareStatement("ALTER TABLE ACT_RU_VARIABLE alter column TEXT_ clob"); + stmt.executeUpdate(); + stmt.close(); + stmt = null; + + connection.close(); + connection = null; + + isDBConfigured = true; + } catch (SQLException e) { + System.out.println("CamundaDBSetup caught " + e.getClass().getSimpleName()); + e.printStackTrace(); + } finally { + if (stmt != null) { + try { + stmt.close(); + } catch (Exception e) { + // Ignore + } + } + + if (connection != null) { + try { + connection.close(); + } catch (Exception e) { + // Ignore + } + } + } + } +} diff --git a/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/PropertyConfigurationSetup.java b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/PropertyConfigurationSetup.java new file mode 100644 index 0000000000..6f1cd7d8cc --- /dev/null +++ b/bpmn/MSOCoreBPMN/src/main/java/org/openecomp/mso/bpmn/test/PropertyConfigurationSetup.java @@ -0,0 +1,315 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.bpmn.test; + +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.openecomp.mso.bpmn.core.PropertyConfiguration; + +/** + * Sets up mso.bpmn.properties and mso.bpmn.urn.properties for unit tests. + */ +public class PropertyConfigurationSetup { + + private static Path msoConfigPath = null; + private static Path bpmnPropertiesPath = null; + private static Path bpmnUrnPropertiesPath = null; + private static boolean modifiedConfiguration = false; + + /** + * Ensures that the the PropertyConfiguration is initialized and that the + * property data is reset to initial values. Any extra properties that are + * specified will be merged with the initial values. The following example + * shows how a test can specify a replacement URN mapping property. + * <pre> + * Map<String, String> urnProperties = + * PropertyConfigurationSetup.createBpmnUrnProperties(); + * urnProperties.add("mso.po.timeout", "PT1M"); + * PropertyConfiguration.init(urnProperties); + * </pre> + * @param args one or more maps created with createBpmnProperties() + * and/or createBpmnUrnProperties() + */ + public static synchronized void init(Object ... args) throws IOException { + + Map<String, String> extraBpmnProperties = null; + Map<String, String> extraBpmnUrnProperties = null; + + boolean propertiesSpecified = false; + + for (Object arg : args) { + @SuppressWarnings("unchecked") + Map<String, String> properties = (Map<String, String>) arg; + + String type = properties.get("PROPERTIES-TYPE"); + + if (PropertyConfiguration.MSO_BPMN_PROPERTIES.equals(type)) { + if (properties.size() > 1) { + extraBpmnProperties = properties; + propertiesSpecified = false; + } + } else if (PropertyConfiguration.MSO_BPMN_URN_PROPERTIES.equals(type)) { + if (properties.size() > 1) { + extraBpmnUrnProperties = properties; + propertiesSpecified = false; + } + } else { + throw new IllegalArgumentException("Not a supported PROPERTIES-TYPE map"); + } + } + + // There are three cases in which we need to change the existing configuration: + // 1) There is no existing configuration, i.e. first time setup + // 2) The existing configuration was modified, i.e. it has non-default values + // 3) Non-default values are specified for this initialization + + if (msoConfigPath == null || modifiedConfiguration || propertiesSpecified) { + modifiedConfiguration = propertiesSpecified; + + Path bpmnPropertiesSourcePath = Paths.get("src", "test", "resources", "mso.bpmn.properties"); + Path bpmnUrnPropertiesSourcePath = Paths.get("src", "test", "resources", "mso.bpmn.urn.properties"); + + if (msoConfigPath == null) { + // Initialize from scratch. + msoConfigPath = Files.createTempDirectory("mso-config-path-"); + System.setProperty("mso.config.path", msoConfigPath.toString()); + msoConfigPath.toFile().deleteOnExit(); + + bpmnPropertiesPath = msoConfigPath.resolve("mso.bpmn.properties"); + mergeCopy(bpmnPropertiesSourcePath, extraBpmnProperties, bpmnPropertiesPath); + bpmnPropertiesPath.toFile().deleteOnExit(); + + bpmnUrnPropertiesPath = msoConfigPath.resolve("mso.bpmn.urn.properties"); + mergeCopy(bpmnUrnPropertiesSourcePath, extraBpmnUrnProperties, bpmnUrnPropertiesPath); + bpmnUrnPropertiesPath.toFile().deleteOnExit(); + + PropertyConfiguration.getInstance(); + } else { + // Just reset the data. + PropertyConfiguration.getInstance().clearCache(); + mergeCopy(bpmnPropertiesSourcePath, extraBpmnProperties, bpmnPropertiesPath); + mergeCopy(bpmnUrnPropertiesSourcePath, extraBpmnUrnProperties, bpmnUrnPropertiesPath); + } + } + } + + /** + * Resets the PropertyConfiguration to its initial state, as if it had never + * been started. Note that this is a very expensive option and should not + * be needed by most unit tests. + * @throws IOException + */ + public static synchronized void nuke() throws IOException { + if (msoConfigPath == null) { + return; + } + + PropertyConfiguration.getInstance().shutDown(); + + bpmnUrnPropertiesPath.toFile().delete(); + bpmnUrnPropertiesPath = null; + + bpmnPropertiesPath.toFile().delete(); + bpmnPropertiesPath = null; + + msoConfigPath.toFile().delete(); + msoConfigPath = null; + + System.setProperty("mso.config.path", null); + + modifiedConfiguration = false; + } + + /** + * Create a map to hold properties to be added to mso.bpmn.properties. + */ + public static Map<String, String> createBpmnProperties() { + Map<String, String> properties = new HashMap<String, String>(); + properties.put("PROPERTIES-TYPE", PropertyConfiguration.MSO_BPMN_PROPERTIES); + return properties; + } + + /** + * Create a map to hold properties to be added to mso.bpmn.urn.properties. + */ + public static Map<String, String> createBpmnUrnProperties() { + Map<String, String> properties = new HashMap<String, String>(); + properties.put("PROPERTIES-TYPE", PropertyConfiguration.MSO_BPMN_URN_PROPERTIES); + return properties; + } + + /** + * Adds (or replaces) the specified values in the mso.bpmn.urn.properties file. + * Note that properties added this way may take some time to be loaded by the + * PropertyConfiguration, just like they do when a property file is updated on + * a real MSO system. This method will optionally wait for the new properties + * to be loaded. Timeout results in an IOException. + * @param values new properties + * @param wait maximum amount of time to wait for new properties to be loaded, + * in milliseconds. A value of zero means, "Do not wait." + * @throws IOException + */ + public static synchronized void addProperties(Map<String, String> properties, long wait) + throws IOException, InterruptedException { + + if (msoConfigPath == null) { + throw new IllegalStateException(); + } + + String type = properties.get("PROPERTIES-TYPE"); + Path path; + + if (PropertyConfiguration.MSO_BPMN_PROPERTIES.equals(type)) { + path = bpmnPropertiesPath; + } else if (PropertyConfiguration.MSO_BPMN_URN_PROPERTIES.equals(type)) { + path = bpmnUrnPropertiesPath; + } else { + throw new IllegalArgumentException("Not a supported PROPERTIES-TYPE map"); + } + + String oldTimestamp = PropertyConfiguration.getInstance().getProperties(type) + .get(PropertyConfiguration.TIMESTAMP_PROPERTY); + + modifiedConfiguration = true; + addProperties(properties, path); + + if (wait <= 0) { + return; + } + + long endTime = System.currentTimeMillis() + wait; + + while (true) { + Thread.sleep(250); + + String newTimestamp = PropertyConfiguration.getInstance().getProperties(type) + .get(PropertyConfiguration.TIMESTAMP_PROPERTY); + + if (newTimestamp != oldTimestamp) { + return; + } + + long now = System.currentTimeMillis(); + + if (now >= endTime) { + throw new IOException("Timed out after " + wait + + "ms waiting for PropertyConfiguration change"); + } + } + } + + /** + * Helper method that adds properties to the specified file. + */ + private static void addProperties(Map<String, String> values, Path path) + throws IOException { + + FileReader fileReader = null; + FileOutputStream outputStream = null; + + try { + fileReader = new FileReader(path.toFile()); + Properties properties = new Properties(); + properties.load(fileReader); + + for (String key : values.keySet()) { + if (!key.equals("PROPERTIES-TYPE")) { + properties.setProperty(key, values.get(key)); + } + } + + outputStream = new FileOutputStream(path.toFile()); + properties.store(outputStream, "Custom Test Properties"); + } finally { + if (fileReader != null) { + try { + fileReader.close(); + } catch (IOException e) { + // Ignore + } + } + + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + + /** + * Helper method that copies properties from the specified source file, and + * optionally merges them with the specified extra values, then writes the + * whole mess to the destination file. + */ + private static void mergeCopy(Path sourcePath, Map<String, String> extraValues, Path destPath) + throws IOException { + if (extraValues == null || extraValues.isEmpty()) { + Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); + return; + } + + FileReader fileReader = null; + FileOutputStream outputStream = null; + + try { + fileReader = new FileReader(sourcePath.toFile()); + Properties properties = new Properties(); + properties.load(fileReader); + + for (String key : extraValues.keySet()) { + if (!key.equals("PROPERTIES-TYPE")) { + properties.setProperty(key, extraValues.get(key)); + } + } + + outputStream = new FileOutputStream(destPath.toFile()); + properties.store(outputStream, "Custom Test Properties"); + } finally { + if (fileReader != null) { + try { + fileReader.close(); + } catch (IOException e) { + // Ignore + } + } + + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + // Ignore + } + } + } + } +} |