Java tutorial
/* * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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. */ package org.wso2.carbon.apimgt.impl.workflow; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.axis2.util.URL; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.wso2.carbon.apimgt.api.APIManagementException; import org.wso2.carbon.apimgt.api.WorkflowResponse; import org.wso2.carbon.apimgt.api.model.APIIdentifier; import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.dao.ApiMgtDAO; import org.wso2.carbon.apimgt.impl.dto.WorkflowDTO; import org.wso2.carbon.apimgt.impl.dto.WorkflowProperties; import org.wso2.carbon.apimgt.impl.internal.ServiceReferenceHolder; import org.wso2.carbon.apimgt.impl.utils.APIUtil; import org.wso2.carbon.apimgt.impl.workflow.WorkflowConstants.PayloadConstants; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.governance.api.generic.dataobjects.GenericArtifact; import org.wso2.carbon.registry.core.Registry; import org.wso2.carbon.registry.core.exceptions.RegistryException; /** * APIStateChangeWSWorkflowExecutor is used to provide approval process to API state change using external BPMN process. * This class is associated with the BPMN process provided with the APIStateChangeApprovalProcess.bar * */ public class APIStateChangeWSWorkflowExecutor extends WorkflowExecutor { private static final String RUNTIME_INSTANCE_RESOURCE_PATH = "/runtime/process-instances"; private static final Log log = LogFactory.getLog(APIStateChangeWSWorkflowExecutor.class); private String clientId; private String clientSecret; private String tokenAPI; private String serviceEndpoint; private String username; private String password; private String processDefinitionKey; private String stateList; public String getStateList() { return stateList; } public void setStateList(String stateList) { this.stateList = stateList; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getClientSecret() { return clientSecret; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getProcessDefinitionKey() { return processDefinitionKey; } public void setProcessDefinitionKey(String processDefinitionKey) { this.processDefinitionKey = processDefinitionKey; } public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } public String getTokenAPI() { return tokenAPI; } public void setTokenAPI(String tokenAPI) { this.tokenAPI = tokenAPI; } public String getServiceEndpoint() { return serviceEndpoint; } public void setServiceEndpoint(String serviceEndpoint) { this.serviceEndpoint = serviceEndpoint; } @Override public String getWorkflowType() { return WorkflowConstants.WF_TYPE_AM_API_STATE; } @Override public List<WorkflowDTO> getWorkflowDetails(String workflowStatus) throws WorkflowException { return Collections.emptyList(); } @Override public WorkflowResponse execute(WorkflowDTO workflowDTO) throws WorkflowException { if (log.isDebugEnabled()) { log.debug("Executing API State change Workflow."); log.debug("Execute workflowDTO " + workflowDTO.toString()); } if (stateList != null) { Map<String, List<String>> stateActionMap = getSelectedStatesToApprove(); APIStateWorkflowDTO apiStateWorkFlowDTO = (APIStateWorkflowDTO) workflowDTO; if (stateActionMap.containsKey(apiStateWorkFlowDTO.getApiCurrentState().toUpperCase()) && stateActionMap.get(apiStateWorkFlowDTO.getApiCurrentState().toUpperCase()) .contains(apiStateWorkFlowDTO.getApiLCAction())) { //set the auth application related info. This will be used to call the callback service setOAuthApplicationInfo(apiStateWorkFlowDTO); // build request payload String jsonPayload = buildPayloadForBPMNProcess(apiStateWorkFlowDTO); if (log.isDebugEnabled()) { log.debug("APIStateChange payload: " + jsonPayload); } if (serviceEndpoint == null) { // set the bps endpoint from the global configurations WorkflowProperties workflowProperties = ServiceReferenceHolder.getInstance() .getAPIManagerConfigurationService().getAPIManagerConfiguration() .getWorkflowProperties(); serviceEndpoint = workflowProperties.getServerUrl(); } URL serviceEndpointURL = new URL(serviceEndpoint); HttpClient httpClient = APIUtil.getHttpClient(serviceEndpointURL.getPort(), serviceEndpointURL.getProtocol()); HttpPost httpPost = new HttpPost(serviceEndpoint + RUNTIME_INSTANCE_RESOURCE_PATH); //Generate the basic auth header using provided user credentials String authHeader = getBasicAuthHeader(); httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHeader); StringEntity requestEntity = new StringEntity(jsonPayload, ContentType.APPLICATION_JSON); httpPost.setEntity(requestEntity); try { HttpResponse response = httpClient.execute(httpPost); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { String error = "Error while starting the process: " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); log.error(error); throw new WorkflowException(error); } } catch (ClientProtocolException e) { String errorMsg = "Error while creating the http client"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } catch (IOException e) { String errorMsg = "Error while connecting to the BPMN process server from the WorkflowExecutor."; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } finally { httpPost.reset(); } super.execute(workflowDTO); } else { // For any other states, act as simpleworkflow executor. workflowDTO.setStatus(WorkflowStatus.APPROVED); // calling super.complete() instead of complete() to act as the simpleworkflow executor super.complete(workflowDTO); } } else { String msg = "State change list is not provided. Please check <stateList> element in "; log.error(msg); throw new WorkflowException(msg); } return new GeneralWorkflowResponse(); } /** * Complete the API state change workflow process. */ @Override public WorkflowResponse complete(WorkflowDTO workflowDTO) throws WorkflowException { if (log.isDebugEnabled()) { log.debug("Completing API State change Workflow.."); log.debug("response: " + workflowDTO.toString()); } workflowDTO.setUpdatedTime(System.currentTimeMillis()); super.complete(workflowDTO); String action = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_API_LC_ACTION); String apiName = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_APINAME); String providerName = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_APIPROVIDER); String version = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_APIVERSION); String invoker = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_INVOKER); String currentStatus = workflowDTO.getAttributes().get(PayloadConstants.VARIABLE_APISTATE); int tenantId = workflowDTO.getTenantId(); ApiMgtDAO apiMgtDAO = ApiMgtDAO.getInstance(); try { // tenant flow is already started from the rest api service impl. no need to start from here PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(invoker); Registry registry = ServiceReferenceHolder.getInstance().getRegistryService() .getGovernanceUserRegistry(invoker, tenantId); APIIdentifier apiIdentifier = new APIIdentifier(providerName, apiName, version); GenericArtifact apiArtifact = APIUtil.getAPIArtifact(apiIdentifier, registry); if (WorkflowStatus.APPROVED.equals(workflowDTO.getStatus())) { String targetStatus; apiArtifact.invokeAction(action, APIConstants.API_LIFE_CYCLE); targetStatus = apiArtifact.getLifecycleState(); if (!currentStatus.equals(targetStatus)) { apiMgtDAO.recordAPILifeCycleEvent(apiIdentifier, currentStatus.toUpperCase(), targetStatus.toUpperCase(), invoker, tenantId); } if (log.isDebugEnabled()) { String logMessage = "API Status changed successfully. API Name: " + apiIdentifier.getApiName() + ", API Version " + apiIdentifier.getVersion() + ", New Status : " + targetStatus; log.debug(logMessage); } } } catch (RegistryException e) { String errorMsg = "Could not complete api state change workflow"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } catch (APIManagementException e) { String errorMsg = "Could not complete api state change workflow"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } return new GeneralWorkflowResponse(); } /** * Handle cleanup task for api state change workflow ws executor. This queries the BPMN process related to the given * workflow reference id and delete that process */ @Override public void cleanUpPendingTask(String workflowExtRef) throws WorkflowException { if (log.isDebugEnabled()) { log.debug("Starting cleanup task for APIStateChangeWSWorkflowExecutor for :" + workflowExtRef); } String errorMsg; if (serviceEndpoint == null) { // set the bps endpoint from the global configurations WorkflowProperties workflowProperties = ServiceReferenceHolder.getInstance() .getAPIManagerConfigurationService().getAPIManagerConfiguration().getWorkflowProperties(); serviceEndpoint = workflowProperties.getServerUrl(); } URL serviceEndpointURL = new URL(serviceEndpoint); HttpClient httpClient = APIUtil.getHttpClient(serviceEndpointURL.getPort(), serviceEndpointURL.getProtocol()); // get the basic auth header value to connect to the bpmn process String authHeader = getBasicAuthHeader(); JSONParser parser = new JSONParser(); HttpGet httpGet = null; HttpDelete httpDelete = null; try { // Get the process instance details related to the given workflow reference id. If there is a process that // is already started with the given wf reference as the businesskey, that process needes to be deleted httpGet = new HttpGet( serviceEndpoint + RUNTIME_INSTANCE_RESOURCE_PATH + "?businessKey=" + workflowExtRef); httpGet.setHeader(HttpHeaders.AUTHORIZATION, authHeader); HttpResponse response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); String processId = null; if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { // already exists a process related to the given workflow reference String responseStr = EntityUtils.toString(entity); if (log.isDebugEnabled()) { log.debug("Process instance details for ref : " + workflowExtRef + ": " + responseStr); } JSONObject obj = (JSONObject) parser.parse(responseStr); JSONArray data = (JSONArray) obj.get(PayloadConstants.DATA); if (data != null) { JSONObject instanceDetails = (JSONObject) data.get(0); // extract the id related to that process. this id is used to delete the process processId = (String) instanceDetails.get(PayloadConstants.ID); } if (processId != null) { // delete the process using the id httpDelete = new HttpDelete(serviceEndpoint + RUNTIME_INSTANCE_RESOURCE_PATH + "/" + processId); httpDelete.setHeader(HttpHeaders.AUTHORIZATION, authHeader); response = httpClient.execute(httpDelete); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) { errorMsg = "Error while deleting process instance details for " + workflowExtRef + " code: " + response.getStatusLine().getStatusCode(); log.error(errorMsg); throw new WorkflowException(errorMsg); } if (log.isDebugEnabled()) { log.debug("Successfully deleted process instance for : " + workflowExtRef); } //remove entry from the db ApiMgtDAO apiMgtDAO = ApiMgtDAO.getInstance(); apiMgtDAO.removeWorkflowEntry(workflowExtRef, WorkflowConstants.WF_TYPE_AM_API_STATE.toString()); } } else { errorMsg = "Error while getting process instance details for " + workflowExtRef + " code: " + response.getStatusLine().getStatusCode(); log.error(errorMsg); throw new WorkflowException(errorMsg); } } catch (ClientProtocolException e) { log.error("Error while creating the http client", e); throw new WorkflowException("Error while creating the http client", e); } catch (IOException e) { log.error("Error while connecting to the BPMN process server from the WorkflowExecutor.", e); throw new WorkflowException("Error while connecting to the external service", e); } catch (ParseException e) { log.error("Error while parsing response from BPS server", e); throw new WorkflowException("Error while parsing response from BPS server", e); } catch (APIManagementException e) { log.error("Error removing the workflow entry", e); throw new WorkflowException("Error removing the workflow entry", e); } finally { if (httpGet != null) { httpGet.reset(); } if (httpDelete != null) { httpDelete.reset(); } } } /** * get credentials that are needed to call the rest api in BPMN engine */ private String getBasicAuthHeader() { // if credentials are not defined in the workflow-extension.xml file, then get the global credentials from the // api-manager.xml configuration if (username == null || password == null) { WorkflowProperties workflowProperties = ServiceReferenceHolder.getInstance() .getAPIManagerConfigurationService().getAPIManagerConfiguration().getWorkflowProperties(); username = workflowProperties.getServerUser(); password = workflowProperties.getServerPassword(); } byte[] encodedAuth = Base64 .encodeBase64((username + ":" + password).getBytes(Charset.forName("ISO-8859-1"))); return "Basic " + new String(encodedAuth); } /** * build the payload to call the BPMN process */ private String buildPayloadForBPMNProcess(APIStateWorkflowDTO apiStateWorkFlowDTO) { JSONArray variables = new JSONArray(); JSONObject clientIdObj = new JSONObject(); clientIdObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_CLIENTID); clientIdObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getClientId()); variables.add(clientIdObj); JSONObject clientSecretObj = new JSONObject(); clientSecretObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_CLIENTSECRET); clientSecretObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getClientSecret()); variables.add(clientSecretObj); JSONObject scopeObj = new JSONObject(); scopeObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_SCOPE); scopeObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getScope()); variables.add(scopeObj); JSONObject tokenAPIObj = new JSONObject(); tokenAPIObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_TOKENAPI); tokenAPIObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getTokenAPI()); variables.add(tokenAPIObj); JSONObject apiCurrentStateObj = new JSONObject(); apiCurrentStateObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_APISTATE); apiCurrentStateObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getApiCurrentState()); variables.add(apiCurrentStateObj); JSONObject apiLCActionObj = new JSONObject(); apiLCActionObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_API_LC_ACTION); apiLCActionObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getApiLCAction()); variables.add(apiLCActionObj); JSONObject apiNameObj = new JSONObject(); apiNameObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_APINAME); apiNameObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getApiName()); variables.add(apiNameObj); JSONObject apiVersionObj = new JSONObject(); apiVersionObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_APIVERSION); apiVersionObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getApiVersion()); variables.add(apiVersionObj); JSONObject apiProviderObj = new JSONObject(); apiProviderObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_APIPROVIDER); apiProviderObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getApiProvider()); variables.add(apiProviderObj); JSONObject callbackUrlObj = new JSONObject(); callbackUrlObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_CALLBACKURL); callbackUrlObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getCallbackUrl()); variables.add(callbackUrlObj); JSONObject wfReferenceObj = new JSONObject(); wfReferenceObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_WFREF); wfReferenceObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getExternalWorkflowReference()); variables.add(wfReferenceObj); JSONObject invokerObj = new JSONObject(); invokerObj.put(PayloadConstants.VARIABLE_NAME, PayloadConstants.VARIABLE_INVOKER); invokerObj.put(PayloadConstants.VARIABLE_VALUE, apiStateWorkFlowDTO.getInvoker()); variables.add(invokerObj); JSONObject payload = new JSONObject(); payload.put(PayloadConstants.PROCESS_DEF_KEY, processDefinitionKey); payload.put(PayloadConstants.TENANT_ID, apiStateWorkFlowDTO.getTenantId()); // set workflowreferencid to business key so we can later query the process instance using this value // if we want to delete the instance payload.put(PayloadConstants.BUSINESS_KEY, apiStateWorkFlowDTO.getExternalWorkflowReference()); payload.put(PayloadConstants.VARIABLES, variables); return payload.toJSONString(); } /** * set information that are needed to invoke callback service */ private void setOAuthApplicationInfo(APIStateWorkflowDTO apiStateWorkFlowDTO) throws WorkflowException { // if credentials are not defined in the workflow-extension.xml file call dcr endpoint and generate a // oauth application and pass the client id and secret WorkflowProperties workflowProperties = ServiceReferenceHolder.getInstance() .getAPIManagerConfigurationService().getAPIManagerConfiguration().getWorkflowProperties(); if (clientId == null || clientSecret == null) { String dcrUsername = workflowProperties.getdCREndpointUser(); String dcrPassword = workflowProperties.getdCREndpointPassword(); byte[] encodedAuth = Base64 .encodeBase64((dcrUsername + ":" + dcrPassword).getBytes(Charset.forName("ISO-8859-1"))); JSONObject payload = new JSONObject(); payload.put(PayloadConstants.KEY_OAUTH_APPNAME, WorkflowConstants.WORKFLOW_OAUTH_APP_NAME); payload.put(PayloadConstants.KEY_OAUTH_OWNER, dcrUsername); payload.put(PayloadConstants.KEY_OAUTH_SAASAPP, "true"); payload.put(PayloadConstants.KEY_OAUTH_GRANT_TYPES, WorkflowConstants.WORKFLOW_OAUTH_APP_GRANT_TYPES); URL serviceEndpointURL = new URL(workflowProperties.getdCREndPoint()); HttpClient httpClient = APIUtil.getHttpClient(serviceEndpointURL.getPort(), serviceEndpointURL.getProtocol()); HttpPost httpPost = new HttpPost(workflowProperties.getdCREndPoint()); String authHeader = "Basic " + new String(encodedAuth); httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHeader); StringEntity requestEntity = new StringEntity(payload.toJSONString(), ContentType.APPLICATION_JSON); httpPost.setEntity(requestEntity); try { HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK || response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) { String responseStr = EntityUtils.toString(entity); if (log.isDebugEnabled()) { log.debug("Workflow oauth app created: " + responseStr); } JSONParser parser = new JSONParser(); JSONObject obj = (JSONObject) parser.parse(responseStr); clientId = (String) obj.get(PayloadConstants.VARIABLE_CLIENTID); clientSecret = (String) obj.get(PayloadConstants.VARIABLE_CLIENTSECRET); } else { String error = "Error while starting the process: " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); log.error(error); throw new WorkflowException(error); } } catch (ClientProtocolException e) { String errorMsg = "Error while creating the http client"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } catch (IOException e) { String errorMsg = "Error while connecting to dcr endpoint"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } catch (ParseException e) { String errorMsg = "Error while parsing response from DCR endpoint"; log.error(errorMsg, e); throw new WorkflowException(errorMsg, e); } finally { httpPost.reset(); } } apiStateWorkFlowDTO.setClientId(clientId); apiStateWorkFlowDTO.setClientSecret(clientSecret); apiStateWorkFlowDTO.setScope(WorkflowConstants.API_WF_SCOPE); apiStateWorkFlowDTO.setTokenAPI(workflowProperties.getTokenEndPoint()); } /** * Read the user provided lifecycle states for the approval task. These are provided in the workflow-extension.xml */ private Map<String, List<String>> getSelectedStatesToApprove() { Map<String, List<String>> stateAction = new HashMap<String, List<String>>(); // exract selected states from stateList and populate the map if (stateList != null) { // list will be something like ' Created:Publish,Created:Deploy as a Prototype,Published:Block ' String // It will have State:action pairs String[] statelistArray = stateList.split(","); for (int i = 0; i < statelistArray.length; i++) { String[] stateActionArray = statelistArray[i].split(":"); if (stateAction.containsKey(stateActionArray[0].toUpperCase())) { ArrayList<String> actionList = (ArrayList<String>) stateAction .get(stateActionArray[0].toUpperCase()); actionList.add(stateActionArray[1]); } else { ArrayList<String> actionList = new ArrayList<String>(); actionList.add(stateActionArray[1]); stateAction.put(stateActionArray[0].toUpperCase(), actionList); } } } if (log.isDebugEnabled()) { log.debug("selected states: " + stateAction.toString()); } return stateAction; } }