diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/Constants.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/Constants.java index bb1a14ad7c..b17f1751c8 100644 --- a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/Constants.java +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/Constants.java @@ -59,6 +59,8 @@ public class Constants { public static final String PATH_PARAM_TRANSACTION = "/" + "{param}"; public static final String ROOT_CONTEXT = "/"; public static final String PREFIX_CONFIGS = "/configs"; + public static final String PREFIX_ICP = "/icp"; + public static final String PREFIX_ARTIFACTS = "/artifacts"; public static final String COUNT = "count"; public static final String TOTAL_COUNT = "totalCount"; diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPArtifactResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPArtifactResource.java new file mode 100644 index 0000000000..43be1ad349 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPArtifactResource.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.util.AXIOMUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.synapse.MessageContext; +import org.apache.synapse.api.API; +import org.apache.synapse.config.SynapseConfiguration; +import org.apache.synapse.config.xml.SequenceMediatorSerializer; +import org.apache.synapse.config.xml.endpoints.EndpointSerializer; +import org.apache.synapse.config.xml.endpoints.TemplateSerializer; +import org.apache.synapse.config.xml.rest.APISerializer; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.apache.synapse.core.axis2.ProxyService; +import org.apache.synapse.endpoints.Endpoint; +import org.apache.synapse.mediators.base.SequenceMediator; +import org.apache.synapse.task.TaskDescription; +import org.apache.synapse.task.TaskDescriptionSerializer; +import org.apache.synapse.Startup; +import org.apache.synapse.SynapseConstants; +import org.apache.synapse.core.SynapseEnvironment; +import org.apache.synapse.config.xml.ProxyServiceSerializer; +import org.apache.synapse.inbound.InboundEndpoint; +import org.apache.axis2.engine.AxisConfiguration; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.synapse.config.xml.inbound.InboundEndpointSerializer; +import org.apache.synapse.message.processor.MessageProcessor; +import org.apache.synapse.message.store.MessageStore; +import org.apache.synapse.config.xml.MessageProcessorSerializer; +import org.apache.synapse.config.xml.MessageStoreSerializer; +import org.json.JSONObject; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; +import org.wso2.micro.integrator.ndatasource.core.CarbonDataSource; +import org.wso2.micro.integrator.ndatasource.core.DataSourceManager; +import org.wso2.micro.integrator.ndatasource.core.DataSourceMetaInfo; +import org.wso2.micro.integrator.ndatasource.core.DataSourceRepository; +import org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils; +import org.wso2.micro.application.deployer.CarbonApplication; +import org.wso2.micro.application.deployer.config.Artifact; +import org.wso2.micro.integrator.initializer.deployment.application.deployer.CappDeployer; +import org.wso2.micro.integrator.dataservices.core.engine.DataService; +import org.wso2.micro.integrator.dataservices.core.engine.DataServiceSerializer; +import org.apache.synapse.mediators.template.TemplateMediator; +import org.apache.synapse.config.xml.TemplateMediatorSerializer; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Resource handler for retrieving synapse configuration of artifacts. + * Supports endpoint: /icp/artifacts?type={artifactType}&name={artifactName} + */ +public class ICPArtifactResource extends APIResource { + + private static final Log LOG = LogFactory.getLog(ICPArtifactResource.class); + + private static final String PARAM_TYPE = "type"; + private static final String PARAM_NAME = "name"; + + // Artifact types + private static final String TYPE_API = "api"; + private static final String TYPE_PROXY = "proxy-service"; + private static final String TYPE_ENDPOINT = "endpoint"; + private static final String TYPE_SEQUENCE = "sequence"; + private static final String TYPE_TASK = "task"; + private static final String TYPE_INBOUND_ENDPOINT = "inbound-endpoint"; + private static final String TYPE_MESSAGE_PROCESSOR = "message-processor"; + private static final String TYPE_MESSAGE_STORE = "message-store"; + private static final String TYPE_CARBON_APPLICATION = "carbonapp"; + private static final String TYPE_DATA_SOURCE = "data-source"; + private static final String TYPE_DATA_SERVICE = "data-service"; + private static final String TYPE_TEMPLATE = "template"; + + public ICPArtifactResource(String urlTemplate) { + super(urlTemplate); + } + + @Override + public Set getMethods() { + Set methods = new HashSet<>(); + methods.add(Constants.HTTP_GET); + return methods; + } + + @Override + public boolean invoke(MessageContext messageContext) { + buildMessage(messageContext); + + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + + if (!messageContext.isDoingGET()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Only GET method is supported", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + String artifactType = Utils.getQueryParameter(messageContext, PARAM_TYPE); + String artifactName = Utils.getQueryParameter(messageContext, PARAM_NAME); + + if (Objects.isNull(artifactType) || artifactType.trim().isEmpty()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: type", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + if (Objects.isNull(artifactName) || artifactName.trim().isEmpty()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: name", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + try { + JSONObject response = getArtifactConfiguration(messageContext, artifactType.toLowerCase(), artifactName, axisMsgCtx); + + if (Objects.nonNull(response)) { + Utils.setJsonPayLoad(axisMsgCtx, response); + } else { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Artifact not found: " + artifactType + " - " + artifactName, + axisMsgCtx, Constants.NOT_FOUND)); + } + } catch (Exception e) { + LOG.error("Error retrieving artifact configuration", e); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Error retrieving artifact configuration: " + e.getMessage(), + axisMsgCtx, Constants.INTERNAL_SERVER_ERROR)); + } + + return true; + } + + /** + * Retrieves the synapse configuration for the specified artifact. + * + * @param messageContext the message context + * @param artifactType the type of artifact (api, proxy-service, endpoint, sequence, etc.) + * @param artifactName the name of the artifact + * @param axisMsgCtx the axis2 message context + * @return JSONObject containing the artifact configuration, or null if not found + */ + private JSONObject getArtifactConfiguration(MessageContext messageContext, String artifactType, + String artifactName, org.apache.axis2.context.MessageContext axisMsgCtx) { + SynapseConfiguration synapseConfig = messageContext.getConfiguration(); + JSONObject response = new JSONObject(); + + response.put(Constants.NAME, artifactName); + response.put(Constants.TYPE, artifactType); + + OMElement configuration = null; + + switch (artifactType) { + case TYPE_API: + configuration = getAPIConfiguration(synapseConfig, artifactName); + break; + case TYPE_PROXY: + configuration = getProxyServiceConfiguration(synapseConfig, artifactName); + break; + case TYPE_ENDPOINT: + configuration = getEndpointConfiguration(synapseConfig, artifactName); + break; + case TYPE_SEQUENCE: + configuration = getSequenceConfiguration(synapseConfig, artifactName); + break; + case TYPE_TASK: + configuration = getTaskConfiguration(synapseConfig, artifactName, axisMsgCtx); + break; + case TYPE_INBOUND_ENDPOINT: + configuration = getInboundEndpointConfiguration(synapseConfig, artifactName); + break; + case TYPE_MESSAGE_PROCESSOR: + configuration = getMessageProcessorConfiguration(synapseConfig, artifactName); + break; + case TYPE_MESSAGE_STORE: + configuration = getMessageStoreConfiguration(synapseConfig, artifactName); + break; + case TYPE_CARBON_APPLICATION: + configuration = getCarbonAppConfigurationElement(artifactName); + break; + case TYPE_DATA_SOURCE: + configuration = getDataSourceConfigurationElement(artifactName); + break; + case TYPE_DATA_SERVICE: + configuration = getDataServiceConfigurationElement(synapseConfig, artifactName); + break; + case TYPE_TEMPLATE: + configuration = getTemplateConfigurationElement(synapseConfig, artifactName); + break; + default: + LOG.warn("Unsupported artifact type: " + artifactType); + return null; + } + + if (Objects.nonNull(configuration)) { + response.put(Constants.SYNAPSE_CONFIGURATION, configuration.toString()); + return response; + } + + return null; + } + + private OMElement getAPIConfiguration(SynapseConfiguration synapseConfig, String apiName) { + API api = synapseConfig.getAPI(apiName); + if (Objects.nonNull(api)) { + return APISerializer.serializeAPI(api); + } + return null; + } + + private OMElement getProxyServiceConfiguration(SynapseConfiguration synapseConfig, String proxyName) { + ProxyService proxyService = synapseConfig.getProxyService(proxyName); + if (Objects.nonNull(proxyService)) { + return ProxyServiceSerializer.serializeProxy(null, proxyService); + } + return null; + } + + private OMElement getEndpointConfiguration(SynapseConfiguration synapseConfig, String endpointName) { + Endpoint endpoint = synapseConfig.getEndpoint(endpointName); + if (Objects.nonNull(endpoint)) { + return EndpointSerializer.getElementFromEndpoint(endpoint); + } + return null; + } + + private OMElement getSequenceConfiguration(SynapseConfiguration synapseConfig, String sequenceName) { + SequenceMediator sequence = synapseConfig.getDefinedSequences().get(sequenceName); + if (Objects.nonNull(sequence)) { + return new SequenceMediatorSerializer().serializeSpecificMediator(sequence); + } + return null; + } + + private OMElement getTaskConfiguration(SynapseConfiguration synapseConfig, String taskName, + org.apache.axis2.context.MessageContext axisMsgCtx) { + Startup startup = synapseConfig.getStartup(taskName); + if (Objects.nonNull(startup)) { + // Get the task description from the task manager + AxisConfiguration axisConfig = axisMsgCtx.getConfigurationContext().getAxisConfiguration(); + SynapseEnvironment synapseEnvironment = + (SynapseEnvironment) axisConfig.getParameter(SynapseConstants.SYNAPSE_ENV).getValue(); + + if (Objects.nonNull(synapseEnvironment) && + Objects.nonNull(synapseEnvironment.getTaskManager())) { + TaskDescription task = synapseEnvironment.getTaskManager() + .getTaskDescriptionRepository().getTaskDescription(taskName); + if (Objects.nonNull(task)) { + return TaskDescriptionSerializer.serializeTaskDescription(null, task); + } + } + } + return null; + } + + private OMElement getInboundEndpointConfiguration(SynapseConfiguration synapseConfig, String inboundName) { + InboundEndpoint inboundEndpoint = synapseConfig.getInboundEndpoint(inboundName); + if (Objects.nonNull(inboundEndpoint)) { + return InboundEndpointSerializer.serializeInboundEndpoint(inboundEndpoint); + } + return null; + } + + private OMElement getMessageProcessorConfiguration(SynapseConfiguration synapseConfig, + String messageProcessorName) { + MessageProcessor messageProcessor = synapseConfig.getMessageProcessors().get(messageProcessorName); + if (Objects.nonNull(messageProcessor)) { + return MessageProcessorSerializer.serializeMessageProcessor(null, messageProcessor); + } + return null; + } + + private OMElement getMessageStoreConfiguration(SynapseConfiguration synapseConfig, String messageStoreName) { + MessageStore messageStore = synapseConfig.getMessageStore(messageStoreName); + if (Objects.nonNull(messageStore)) { + return MessageStoreSerializer.serializeMessageStore(null, messageStore, true); + } + return null; + } + + private OMElement getCarbonAppConfigurationElement(String carbonAppName) { + for (CarbonApplication app : CappDeployer.getCarbonApps()) { + if (app.getAppName().equals(carbonAppName)) { + OMFactory fac = OMAbstractFactory.getOMFactory(); + OMElement root = fac.createOMElement("carbonApplication", null); + root.addAttribute("name", app.getAppName(), null); + root.addAttribute("version", app.getAppVersion(), null); + + OMElement artifactsEl = fac.createOMElement("artifacts", null); + root.addChild(artifactsEl); + + java.util.List dependencies = app.getAppConfig() + .getApplicationArtifact().getDependencies(); + for (Artifact.Dependency dependency : dependencies) { + Artifact artifact = dependency.getArtifact(); + String fullType = artifact.getType(); + String type = fullType != null && fullType.contains("/") ? fullType.split("/")[1] : fullType; + String artifactName = artifact.getName(); + if (artifactName == null) { + continue; + } + OMElement artifactEl = fac.createOMElement("artifact", null); + artifactEl.addAttribute("name", artifactName, null); + if (type != null) { + artifactEl.addAttribute("type", type, null); + } + artifactsEl.addChild(artifactEl); + } + return root; + } + } + return null; + } + + private OMElement getDataSourceConfigurationElement(String datasourceName) { + try { + DataSourceManager dsManager = DataSourceManager.getInstance(); + DataSourceRepository repo = dsManager.getDataSourceRepository(); + CarbonDataSource cds = repo.getDataSource(datasourceName); + if (cds != null) { + DataSourceMetaInfo meta = cds.getDSMInfo(); + org.w3c.dom.Element dsXml = (org.w3c.dom.Element) meta.getDefinition().getDsXMLConfiguration(); + String maskedXml = DataSourceUtils.elementToStringWithMaskedPasswords(dsXml); + return AXIOMUtil.stringToOM(maskedXml); + } + } catch (Exception e) { + LOG.warn("Error retrieving datasource configuration for: " + datasourceName, e); + } + return null; + } + + private OMElement getDataServiceConfigurationElement(SynapseConfiguration synapseConfig, String serviceName) { + try { + AxisConfiguration axisConfiguration = synapseConfig.getAxisConfiguration(); + if (axisConfiguration != null) { + AxisService axisService = axisConfiguration.getServiceForActivation(serviceName); + if (axisService == null) { + axisService = axisConfiguration.getService(serviceName); + } + if (axisService != null) { + Parameter dsParam = axisService.getParameter("DataService"); + if (dsParam != null && dsParam.getValue() instanceof DataService) { + DataService ds = (DataService) dsParam.getValue(); + return DataServiceSerializer.serializeDataService(ds); + } + } + } + } catch (Exception e) { + LOG.warn("Error retrieving data service configuration for: " + serviceName, e); + } + return null; + } + + private OMElement getTemplateConfigurationElement(SynapseConfiguration synapseConfig, String templateName) { + // Try sequence template first + TemplateMediator seqTemplate = synapseConfig.getSequenceTemplates().get(templateName); + if (seqTemplate != null) { + return new TemplateMediatorSerializer().serializeMediator(null, seqTemplate); + } + // Then endpoint template + org.apache.synapse.endpoints.Template epTemplate = synapseConfig.getEndpointTemplates().get(templateName); + if (epTemplate != null) { + return new TemplateSerializer().serializeEndpointTemplate(epTemplate, null); + } + return null; + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetInboundEndpointParamsResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetInboundEndpointParamsResource.java new file mode 100644 index 0000000000..9076c1e653 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetInboundEndpointParamsResource.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.lang3.StringUtils; +import org.apache.synapse.MessageContext; +import org.apache.synapse.config.SynapseConfiguration; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.apache.synapse.inbound.InboundEndpoint; +import org.apache.synapse.message.processor.MessageProcessor; +import org.apache.tomcat.jdbc.pool.DataSource; +import org.apache.tomcat.jdbc.pool.PoolConfiguration; +import org.json.JSONArray; +import org.json.JSONObject; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; +import org.wso2.micro.integrator.ndatasource.core.CarbonDataSource; +import org.wso2.micro.integrator.ndatasource.core.DataSourceManager; +import org.wso2.micro.integrator.ndatasource.core.DataSourceMetaInfo; +import org.wso2.micro.integrator.ndatasource.core.DataSourceRepository; +import org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils; +import org.wso2.micro.integrator.ndatasource.rdbms.RDBMSConfiguration; +import org.wso2.micro.integrator.ndatasource.rdbms.RDBMSDataSourceReader; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * ICP resource to fetch key-value parameters of supported artifacts. + * Supported types: inbound, message-processor, data-source + * Endpoint: /icp/artifacts/parameters?type=&name= + * Response: { "type": "", "name": "", "parameters": [ {"name": k, "value": v}, ... ] } + */ +public class ICPGetParamsResource extends APIResource { + + private static final Log LOG = LogFactory.getLog(ICPGetParamsResource.class); + + public ICPGetParamsResource(String urlTemplate) { + super(urlTemplate); + } + + @Override + public Set getMethods() { + Set methods = new HashSet<>(); + methods.add(Constants.HTTP_GET); + return methods; + } + + @Override + public boolean invoke(MessageContext messageContext) { + buildMessage(messageContext); + + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + + if (!messageContext.isDoingGET()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Only GET method is supported", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + String type = Utils.getQueryParameter(messageContext, Constants.TYPE); + String name = Utils.getQueryParameter(messageContext, Constants.NAME); + if (StringUtils.isBlank(name)) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: name", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + if (StringUtils.isBlank(type)) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: type", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + SynapseConfiguration configuration = messageContext.getConfiguration(); + if (configuration == null) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Synapse configuration is not available", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + + JSONObject resp = new JSONObject(); + resp.put(Constants.TYPE, type); + resp.put(Constants.NAME, name); + JSONArray paramsArray = new JSONArray(); + + switch (type.toLowerCase()) { + case "inbound": { + InboundEndpoint inbound = configuration.getInboundEndpoint(name); + if (inbound == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Inbound endpoint not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + try { + Map params = inbound.getParametersMap(); + if (params != null) { + for (Map.Entry e : params.entrySet()) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, e.getKey()); + p.put("value", Objects.toString(e.getValue(), "")); + paramsArray.put(p); + } + } + } catch (Exception ex) { + LOG.warn("Error reading parameters for inbound endpoint: " + name, ex); + } + break; + } + case "message-processor": { + MessageProcessor mp = configuration.getMessageProcessors().get(name); + if (mp == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Message processor not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + try { + Map params = mp.getParameters(); + if (params != null) { + for (Map.Entry e : params.entrySet()) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, e.getKey()); + p.put("value", Objects.toString(e.getValue(), "")); + paramsArray.put(p); + } + } + } catch (Exception ex) { + LOG.warn("Error reading parameters for message processor: " + name, ex); + } + break; + } + case "data-source": { + try { + DataSourceManager dsManager = DataSourceManager.getInstance(); + DataSourceRepository repo = dsManager.getDataSourceRepository(); + CarbonDataSource cds = repo.getDataSource(name); + if (cds == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Data source not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + + Object dsObj = cds.getDSObject(); + if (dsObj instanceof DataSource) { + PoolConfiguration pool = ((DataSource) dsObj).getPoolProperties(); + addParam(paramsArray, "driverClass", pool.getDriverClassName()); + addParam(paramsArray, "url", DataSourceUtils.maskURLPassword(pool.getUrl())); + addParam(paramsArray, "userName", pool.getUsername()); + addParam(paramsArray, "isDefaultAutoCommit", pool.isDefaultAutoCommit()); + addParam(paramsArray, "isDefaultReadOnly", pool.isDefaultReadOnly()); + addParam(paramsArray, "removeAbandoned", pool.isRemoveAbandoned()); + addParam(paramsArray, "validationQuery", pool.getValidationQuery()); + addParam(paramsArray, "validationQueryTimeout", pool.getValidationQueryTimeout()); + addParam(paramsArray, "maxActive", pool.getMaxActive()); + addParam(paramsArray, "maxIdle", pool.getMaxIdle()); + addParam(paramsArray, "maxWait", pool.getMaxWait()); + addParam(paramsArray, "maxAge", pool.getMaxAge()); + } else { + // External datasource definition + try { + DataSourceMetaInfo meta = cds.getDSMInfo(); + String dsXml = DataSourceUtils.elementToStringWithMaskedPasswords( + (org.w3c.dom.Element) meta.getDefinition().getDsXMLConfiguration()); + RDBMSConfiguration cfg = RDBMSDataSourceReader.loadConfig(dsXml); + addParam(paramsArray, "dataSourceClassName", cfg.getDataSourceClassName()); + for (RDBMSConfiguration.DataSourceProperty prop : cfg.getDataSourceProps()) { + if (!"password".equals(prop.getName())) { + addParam(paramsArray, prop.getName(), prop.getValue()); + } + } + } catch (Exception ex) { + LOG.warn("Error reading external datasource parameters: " + name, ex); + } + } + } catch (Exception ex) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Error reading data source parameters", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + break; + } + default: { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Unsupported type: " + type, axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + } + + resp.put("parameters", paramsArray); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + + private void addParam(JSONArray arr, String name, Object value) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, name); + p.put("value", Objects.toString(value, "")); + arr.put(p); + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetLocalEntryValueResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetLocalEntryValueResource.java new file mode 100644 index 0000000000..c76f4461e2 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetLocalEntryValueResource.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025, WSO2 Inc. + */ +package org.wso2.micro.integrator.management.apis; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.lang3.StringUtils; +import org.apache.synapse.MessageContext; +import org.apache.synapse.config.SynapseConfiguration; +import org.apache.synapse.config.Entry; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.json.JSONObject; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * ICP resource to fetch the value of a given Local Entry. + * Endpoint: /icp/artifacts/local-entry?name= + */ +public class ICPGetLocalEntryValueResource extends APIResource { + + private static final Log LOG = LogFactory.getLog(ICPGetLocalEntryValueResource.class); + + public ICPGetLocalEntryValueResource(String urlTemplate) { + super(urlTemplate); + } + + @Override + public Set getMethods() { + Set methods = new HashSet<>(); + methods.add(Constants.HTTP_GET); + return methods; + } + + @Override + public boolean invoke(MessageContext messageContext) { + buildMessage(messageContext); + + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + + if (!messageContext.isDoingGET()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Only GET method is supported", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + String name = Utils.getQueryParameter(messageContext, "name"); + if (StringUtils.isBlank(name)) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: name", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + SynapseConfiguration synapseConfig = messageContext.getConfiguration(); + if (synapseConfig == null) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Synapse configuration is not available", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + + Entry entry = synapseConfig.getDefinedEntries().get(name); + if (entry == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Local entry not found: " + name, axisMsgCtx, Constants.NOT_FOUND)); + return true; + } + + try { + switch (entry.getType()) { + case Entry.INLINE_TEXT: + case Entry.INLINE_XML: { + JSONObject resp = new JSONObject(); + resp.put(Constants.NAME, name); + resp.put("value", Objects.toString(entry.getValue(), "")); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + case Entry.URL_SRC: { + String content = fetchFromUrl(Objects.toString(entry.getValue(), "")); + JSONObject resp = new JSONObject(); + resp.put(Constants.NAME, name); + resp.put("value", content); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + case Entry.REMOTE_ENTRY: { + JSONObject resp = new JSONObject(); + resp.put(Constants.NAME, name); + resp.put("registryKey", Objects.toString(entry.getValue(), "")); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + default: { + JSONObject resp = new JSONObject(); + resp.put(Constants.NAME, name); + resp.put("value", Objects.toString(entry.getValue(), "")); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + } + } catch (Exception e) { + LOG.error("Failed to read local entry value for: " + name, e); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Failed to read local entry value", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + } + + private String fetchFromUrl(String urlStr) throws Exception { + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + sb.append(line).append('\n'); + } + return sb.toString(); + } + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetParamsResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetParamsResource.java new file mode 100644 index 0000000000..fbda0dfceb --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPGetParamsResource.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.lang3.StringUtils; +import org.apache.synapse.MessageContext; +import org.apache.synapse.config.SynapseConfiguration; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.apache.synapse.inbound.InboundEndpoint; +import org.apache.synapse.message.processor.MessageProcessor; +import org.apache.tomcat.jdbc.pool.DataSource; +import org.apache.tomcat.jdbc.pool.PoolConfiguration; +import org.json.JSONArray; +import org.json.JSONObject; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; +import org.wso2.micro.integrator.ndatasource.core.CarbonDataSource; +import org.wso2.micro.integrator.ndatasource.core.DataSourceManager; +import org.wso2.micro.integrator.ndatasource.core.DataSourceMetaInfo; +import org.wso2.micro.integrator.ndatasource.core.DataSourceRepository; +import org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils; +import org.wso2.micro.integrator.ndatasource.rdbms.RDBMSConfiguration; +import org.wso2.micro.integrator.ndatasource.rdbms.RDBMSDataSourceReader; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * ICP resource to fetch key-value parameters of supported artifacts. + * Supported types: inbound-endpoint, message-processor, data-source + * Endpoint: /icp/artifacts/parameters?type=&name= + * Response: { "type": "", "name": "", "parameters": [ {"name": k, "value": v}, ... ] } + */ +public class ICPGetParamsResource extends APIResource { + + private static final Log LOG = LogFactory.getLog(ICPGetParamsResource.class); + + public ICPGetParamsResource(String urlTemplate) { + super(urlTemplate); + } + + @Override + public Set getMethods() { + Set methods = new HashSet<>(); + methods.add(Constants.HTTP_GET); + return methods; + } + + @Override + public boolean invoke(MessageContext messageContext) { + buildMessage(messageContext); + + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + + if (!messageContext.isDoingGET()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Only GET method is supported", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + String type = Utils.getQueryParameter(messageContext, Constants.TYPE); + String name = Utils.getQueryParameter(messageContext, Constants.NAME); + if (StringUtils.isBlank(name)) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: name", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + if (StringUtils.isBlank(type)) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: type", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + SynapseConfiguration configuration = messageContext.getConfiguration(); + if (configuration == null) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Synapse configuration is not available", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + + JSONObject resp = new JSONObject(); + resp.put(Constants.TYPE, type); + resp.put(Constants.NAME, name); + JSONArray paramsArray = new JSONArray(); + + switch (type.toLowerCase()) { + case "inbound-endpoint": { + InboundEndpoint inbound = configuration.getInboundEndpoint(name); + if (inbound == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Inbound endpoint not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + try { + Map params = inbound.getParametersMap(); + if (params != null) { + for (Map.Entry e : params.entrySet()) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, e.getKey()); + p.put("value", Objects.toString(e.getValue(), "")); + paramsArray.put(p); + } + } + } catch (Exception ex) { + LOG.warn("Error reading parameters for inbound endpoint: " + name, ex); + } + break; + } + case "message-processor": { + MessageProcessor mp = configuration.getMessageProcessors().get(name); + if (mp == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Message processor not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + try { + Map params = mp.getParameters(); + if (params != null) { + for (Map.Entry e : params.entrySet()) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, e.getKey()); + p.put("value", Objects.toString(e.getValue(), "")); + paramsArray.put(p); + } + } + } catch (Exception ex) { + LOG.warn("Error reading parameters for message processor: " + name, ex); + } + break; + } + case "data-source": { + try { + DataSourceManager dsManager = DataSourceManager.getInstance(); + DataSourceRepository repo = dsManager.getDataSourceRepository(); + CarbonDataSource cds = repo.getDataSource(name); + if (cds == null) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Data source not found: " + name, axisMsgCtx, + Constants.NOT_FOUND)); + return true; + } + + Object dsObj = cds.getDSObject(); + if (dsObj instanceof DataSource) { + PoolConfiguration pool = ((DataSource) dsObj).getPoolProperties(); + addParam(paramsArray, "driverClass", pool.getDriverClassName()); + addParam(paramsArray, "url", DataSourceUtils.maskURLPassword(pool.getUrl())); + addParam(paramsArray, "userName", pool.getUsername()); + addParam(paramsArray, "isDefaultAutoCommit", pool.isDefaultAutoCommit()); + addParam(paramsArray, "isDefaultReadOnly", pool.isDefaultReadOnly()); + addParam(paramsArray, "removeAbandoned", pool.isRemoveAbandoned()); + addParam(paramsArray, "validationQuery", pool.getValidationQuery()); + addParam(paramsArray, "validationQueryTimeout", pool.getValidationQueryTimeout()); + addParam(paramsArray, "maxActive", pool.getMaxActive()); + addParam(paramsArray, "maxIdle", pool.getMaxIdle()); + addParam(paramsArray, "maxWait", pool.getMaxWait()); + addParam(paramsArray, "maxAge", pool.getMaxAge()); + } else { + // External datasource definition + try { + DataSourceMetaInfo meta = cds.getDSMInfo(); + String dsXml = DataSourceUtils.elementToStringWithMaskedPasswords( + (org.w3c.dom.Element) meta.getDefinition().getDsXMLConfiguration()); + RDBMSConfiguration cfg = RDBMSDataSourceReader.loadConfig(dsXml); + addParam(paramsArray, "dataSourceClassName", cfg.getDataSourceClassName()); + for (RDBMSConfiguration.DataSourceProperty prop : cfg.getDataSourceProps()) { + if (!"password".equals(prop.getName())) { + addParam(paramsArray, prop.getName(), prop.getValue()); + } + } + } catch (Exception ex) { + LOG.warn("Error reading external datasource parameters: " + name, ex); + } + } + } catch (Exception ex) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Error reading data source parameters", axisMsgCtx, + Constants.INTERNAL_SERVER_ERROR)); + return true; + } + break; + } + default: { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Unsupported type: " + type, axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + } + + resp.put("parameters", paramsArray); + Utils.setJsonPayLoad(axisMsgCtx, resp); + return true; + } + + private void addParam(JSONArray arr, String name, Object value) { + JSONObject p = new JSONObject(); + p.put(Constants.NAME, name); + p.put("value", Objects.toString(value, "")); + arr.put(p); + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPInternalApi.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPInternalApi.java new file mode 100644 index 0000000000..88aa5a26ac --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/ICPInternalApi.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +import org.apache.synapse.api.cors.CORSConfiguration; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; +import org.wso2.carbon.inbound.endpoint.internal.http.api.InternalAPI; +import org.wso2.carbon.inbound.endpoint.internal.http.api.InternalAPIHandler; + +import java.util.ArrayList; +import java.util.List; + +import static org.wso2.micro.integrator.management.apis.Constants.PREFIX_ARTIFACTS; +import static org.wso2.micro.integrator.management.apis.Constants.PREFIX_ICP; + +/** + * Internal API for ICP (Integrated Control Plane) endpoints. + * This API handles ICP-specific operations with dedicated authentication. + */ +public class ICPInternalApi implements InternalAPI { + + private String name; + private APIResource[] resources; + private List handlerList = null; + private CORSConfiguration apiCORSConfiguration = null; + + public ICPInternalApi() { + ArrayList resourcesList = new ArrayList<>(); + resourcesList.add(new ICPArtifactResource(PREFIX_ARTIFACTS)); + resourcesList.add(new WsdlResource(PREFIX_ARTIFACTS + "/wsdl")); + resourcesList.add(new ICPGetLocalEntryValueResource(PREFIX_ARTIFACTS + "/local-entry")); + resourcesList.add(new ICPGetParamsResource(PREFIX_ARTIFACTS + "/parameters")); + resources = resourcesList.toArray(new APIResource[resourcesList.size()]); + } + + @Override + public APIResource[] getResources() { + return resources; + } + + @Override + public String getContext() { + return PREFIX_ICP; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setHandlers(List handlerList) { + this.handlerList = handlerList; + } + + @Override + public List getHandlers() { + return this.handlerList; + } + + @Override + public void setCORSConfiguration(CORSConfiguration corsConfiguration) { + apiCORSConfiguration = corsConfiguration; + } + + @Override + public CORSConfiguration getCORSConfiguration() { + return apiCORSConfiguration; + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/SequenceWsdlResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/SequenceWsdlResource.java new file mode 100644 index 0000000000..8c269db359 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/SequenceWsdlResource.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +/** + * Deprecated placeholder. Kept intentionally to avoid breaking references; no implementation. + */ + diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/WsdlResource.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/WsdlResource.java new file mode 100644 index 0000000000..e48a104844 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/WsdlResource.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.util.AXIOMUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.synapse.MessageContext; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.apache.axis2.engine.AxisConfiguration; +import org.wso2.carbon.inbound.endpoint.internal.http.api.APIResource; +import org.wso2.micro.integrator.core.services.CarbonServerConfigurationService; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +/** + * ICP resource to fetch WSDL for a given data service or proxy service. + * Endpoints: + * - /icp/artifacts/wsdl?service={dataServiceName} + * - /icp/artifacts/wsdl?proxy={proxyServiceName} + */ +public class WsdlResource extends APIResource { + + private static final Log LOG = LogFactory.getLog(WsdlResource.class); + + private static final String PARAM_SERVICE = "service"; + private static final String PARAM_PROXY = "proxy"; + + public WsdlResource(String urlTemplate) { + super(urlTemplate); + } + + @Override + public Set getMethods() { + Set methods = new HashSet<>(); + methods.add(Constants.HTTP_GET); + return methods; + } + + @Override + public boolean invoke(MessageContext messageContext) { + buildMessage(messageContext); + + org.apache.axis2.context.MessageContext axisMsgCtx = ((Axis2MessageContext) messageContext) + .getAxis2MessageContext(); + + if (!messageContext.isDoingGET()) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Only GET method is supported", axisMsgCtx, Constants.BAD_REQUEST)); + return true; + } + + String serviceName = Utils.getQueryParameter(messageContext, PARAM_SERVICE); + String proxyName = Utils.getQueryParameter(messageContext, PARAM_PROXY); + if ((serviceName == null || serviceName.trim().isEmpty()) + && (proxyName == null || proxyName.trim().isEmpty())) { + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Missing required parameter: service or proxy", axisMsgCtx, + Constants.BAD_REQUEST)); + return true; + } + + final boolean isProxyRequest = (proxyName != null && !proxyName.trim().isEmpty()); + final String targetName = isProxyRequest ? proxyName : serviceName; + + // Validate that the specified Axis service exists (data service or proxy are Axis services) + AxisConfiguration axisConfiguration = axisMsgCtx.getConfigurationContext().getAxisConfiguration(); + org.apache.axis2.description.AxisService axisService = null; + if (axisConfiguration != null) { + try { + axisService = axisConfiguration.getServiceForActivation(targetName); + if (axisService == null) { + axisService = axisConfiguration.getService(targetName); + } + } catch (Exception ignored) { + } + } + if (Objects.isNull(axisService)) { + axisMsgCtx.setProperty(Constants.HTTP_STATUS_CODE, Constants.NOT_FOUND); + String kind = isProxyRequest ? "Proxy service" : "Data service"; + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError(kind + " not found: " + targetName, axisMsgCtx, Constants.NOT_FOUND)); + return true; + } + + try { + OMElement wsdl; + + // Try to fetch actual WSDL from service URL derived via internal config + String wsdlUrl = buildServiceWsdlUrl(axisMsgCtx, targetName); + String wsdlText = fetchUrl(wsdlUrl, 5000); + if (wsdlText != null && !wsdlText.isEmpty()) { + wsdl = AXIOMUtil.stringToOM(wsdlText); + } else { + wsdl = AXIOMUtil.stringToOM(""); + } + + // Set XML payload + if (axisMsgCtx.getEnvelope() == null) { + axisMsgCtx.setEnvelope(OMAbstractFactory.getSOAP11Factory().createSOAPEnvelope()); + axisMsgCtx.getEnvelope().addChild(OMAbstractFactory.getSOAP11Factory().createSOAPBody()); + } + axisMsgCtx.getEnvelope().getBody().addChild(wsdl); + + axisMsgCtx.setProperty(Constants.MESSAGE_TYPE, "application/xml"); + axisMsgCtx.setProperty(Constants.CONTENT_TYPE, "application/xml"); + axisMsgCtx.removeProperty(Constants.NO_ENTITY_BODY); + + } catch (Exception e) { + LOG.error("Error fetching WSDL for data service: " + serviceName, e); + Utils.setJsonPayLoad(axisMsgCtx, + Utils.createJsonError("Error fetching WSDL: " + e.getMessage(), + axisMsgCtx, Constants.INTERNAL_SERVER_ERROR)); + } + + return true; + } + + private String buildServiceWsdlUrl(org.apache.axis2.context.MessageContext axisMsgCtx, String serviceName) { + String host = deriveHost(axisMsgCtx); + int port = deriveHttpPort(); + String protocol = "http"; + return protocol + "://" + host + ":" + port + "/services/" + serviceName + "?wsdl"; + } + + private String deriveHost(org.apache.axis2.context.MessageContext axisMsgCtx) { + String host = null; + if (axisMsgCtx != null && axisMsgCtx.getConfigurationContext() != null && + axisMsgCtx.getConfigurationContext().getAxisConfiguration() != null && + axisMsgCtx.getConfigurationContext().getAxisConfiguration().getParameter("hostname") != null) { + Object val = axisMsgCtx.getConfigurationContext().getAxisConfiguration().getParameter("hostname") + .getValue(); + if (val != null) { + host = val.toString(); + } + } + if (host == null) { + String localIp = System.getProperty("carbon.local.ip"); + if (localIp != null && !localIp.isEmpty()) { + host = localIp; + } + } + if (host == null) { + host = "localhost"; + } + return host; + } + + private int deriveHttpPort() { + String portStr = CarbonServerConfigurationService.getInstance() + .getFirstProperty(CarbonServerConfigurationService.HTTP_PORT); + int port = 8290; + try { + if (portStr != null) { + port = Integer.parseInt(portStr.trim()); + } + } catch (Exception ignored) { + } + return port; + } + + private String fetchUrl(String urlStr, int timeoutMs) { + HttpURLConnection conn = null; + try { + URL url = new URL(urlStr); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(timeoutMs); + conn.setReadTimeout(timeoutMs); + int code = conn.getResponseCode(); + if (code >= 200 && code < 300) { + try (InputStream in = conn.getInputStream()) { + byte[] buf = in.readAllBytes(); + return new String(buf, StandardCharsets.UTF_8); + } + } else { + LOG.warn("WSDL fetch returned non-2xx: " + code + " for URL: " + urlStr); + return null; + } + } catch (Exception e) { + LOG.warn("Failed to fetch WSDL from URL: " + urlStr + ", " + e.getMessage()); + return null; + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/security/handler/ICPJWTSecurityHandler.java b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/security/handler/ICPJWTSecurityHandler.java new file mode 100644 index 0000000000..ba8fa22666 --- /dev/null +++ b/components/org.wso2.micro.integrator.extensions/org.wso2.micro.integrator.management.apis/src/main/java/org/wso2/micro/integrator/management/apis/security/handler/ICPJWTSecurityHandler.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.management.apis.security.handler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.synapse.MessageContext; +import org.wso2.micro.core.util.CarbonException; +import org.wso2.micro.integrator.management.apis.ManagementApiUndefinedException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Security handler for ICP endpoints that validates HMAC-based JWT tokens. + * Similar to JWTTokenSecurityHandler but uses HMAC secret authentication. + */ +public class ICPJWTSecurityHandler extends AuthenticationHandlerAdapter { + + private static final Log LOG = LogFactory.getLog(ICPJWTSecurityHandler.class); + private static final String DEFAULT_JWT_HMAC_SECRET = "default-secret-key-at-least-32-characters-long-for-hs256"; + + private String name; + private String jwtHmacSecret = DEFAULT_JWT_HMAC_SECRET; + + /** + * Constructor required by ConfigurationLoader. + * @param context The API context path + */ + public ICPJWTSecurityHandler(String context) throws CarbonException, XMLStreamException, IOException, + ManagementApiUndefinedException { + super(context); + LOG.info("ICPJWTSecurityHandler initialized for context: " + context); + } + + @Override + public Boolean invoke(MessageContext messageContext) { + return super.invoke(messageContext); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + protected Boolean authenticate(MessageContext messageContext, String authHeaderToken) { + // Validate HMAC JWT token + if (validateHMACJWT(authHeaderToken)) { + if (LOG.isDebugEnabled()) { + LOG.debug("HMAC JWT token validated successfully"); + } + return true; + } else { + LOG.warn("HMAC JWT token validation failed"); + return false; + } + } + + /** + * Validates HMAC-based JWT token. + */ + private boolean validateHMACJWT(String token) { + try { + // Split JWT into parts + String[] parts = token.split("\\."); + if (parts.length != 3) { + LOG.warn("Invalid JWT structure"); + return false; + } + + String headerPayload = parts[0] + "." + parts[1]; + String signature = parts[2]; + + // Verify signature using HMAC-SHA256 + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKeySpec = new SecretKeySpec( + jwtHmacSecret.getBytes(StandardCharsets.UTF_8), + "HmacSHA256" + ); + mac.init(secretKeySpec); + + byte[] expectedSignature = mac.doFinal(headerPayload.getBytes(StandardCharsets.UTF_8)); + String expectedSignatureBase64 = Base64.getUrlEncoder().withoutPadding() + .encodeToString(expectedSignature); + + if (!expectedSignatureBase64.equals(signature)) { + LOG.warn("JWT signature verification failed"); + return false; + } + + // Decode payload to check expiry + String payloadJson = new String( + Base64.getUrlDecoder().decode(parts[1]), + StandardCharsets.UTF_8 + ); + + // Extract exp claim (simple JSON parsing) + int expIndex = payloadJson.indexOf("\"exp\""); + if (expIndex != -1) { + int colonIndex = payloadJson.indexOf(":", expIndex); + int commaIndex = payloadJson.indexOf(",", colonIndex); + int braceIndex = payloadJson.indexOf("}", colonIndex); + + int endIndex = commaIndex != -1 ? + Math.min(commaIndex, braceIndex != -1 ? braceIndex : Integer.MAX_VALUE) : + braceIndex; + + if (endIndex != -1) { + String expStr = payloadJson.substring(colonIndex + 1, endIndex).trim(); + long exp = Long.parseLong(expStr); + long currentTime = System.currentTimeMillis() / 1000; + + if (currentTime >= exp) { + LOG.warn("JWT token has expired"); + return false; + } + } + } + + return true; + + } catch (Exception e) { + LOG.error("Error validating HMAC JWT", e); + return false; + } + } + + /** + * Sets the JWT HMAC secret. Called by the handler configuration parser. + * Can be configured in internal-apis.xml as: + * + * your-secret-here + * + */ + public void setJwtHmacSecret(String secret) { + if (secret != null && !secret.trim().isEmpty()) { + if (secret.getBytes(StandardCharsets.UTF_8).length < 32) { + LOG.warn("JWT HMAC secret should be at least 32 bytes. Using provided secret anyway."); + } + this.jwtHmacSecret = secret; + LOG.info("JWT HMAC secret configured from internal-apis.xml"); + } + } + + /** + * Gets the JWT HMAC secret. + */ + public String getJwtHmacSecret() { + return this.jwtHmacSecret; + } +} diff --git a/components/org.wso2.micro.integrator.initializer/pom.xml b/components/org.wso2.micro.integrator.initializer/pom.xml index 4c1059f4d6..662dd896d7 100755 --- a/components/org.wso2.micro.integrator.initializer/pom.xml +++ b/components/org.wso2.micro.integrator.initializer/pom.xml @@ -117,6 +117,10 @@ org.wso2.integration.transaction.counter transaction-count-handler + + org.wso2.orbit.com.nimbusds + nimbus-jose-jwt + @@ -138,6 +142,10 @@ org.wso2.micro.integrator.initializer.*;version="${project.version}" + com.nimbusds.jose;version="${nimbus-jose.orbit.imp.pkg.version}", + com.nimbusds.jose.jwk;version="${nimbus-jose.orbit.imp.pkg.version}", + com.nimbusds.jose.crypto;version="${nimbus-jose.orbit.imp.pkg.version}", + com.nimbusds.jwt;version="${nimbus-jose.orbit.imp.pkg.version}", org.wso2.micro.integrator.core.*;version="${project.version}", *;resolution:=optional diff --git a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/Constants.java b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/Constants.java index b0ee028397..5b0863697d 100644 --- a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/Constants.java +++ b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/Constants.java @@ -15,6 +15,40 @@ private Constants() { public static final String DASHBOARD_CONFIG_MANAGEMENT_HOSTNAME = "dashboard_config.management_hostname"; public static final String DASHBOARD_CONFIG_MANAGEMENT_PORT = "dashboard_config.management_port"; + // New ICP Configuration + public static final String ICP_API_DEFAULT_HOST = "localhost"; + public static final int ICP_API_DEFAULT_PORT = 9164; + public static final String PORT_OFFSET = "server.offset"; + public static final String HOSTNAME = "server.hostname"; + public static final String ICP_CONFIG_URL = "icp_config.icp_url"; + public static final String ICP_CONFIG_ENVIRONMENT = "icp_config.environment"; + public static final String ICP_CONFIG_PROJECT = "icp_config.project"; + public static final String ICP_CONFIG_COMPONENT = "icp_config.integration"; + public static final String ICP_CONFIG_RUNTIME = "icp_config.runtime"; + public static final String ICP_CONFIG_ENABLED = "icp_config.enabled"; + public static final String ICP_CONFIG_HEARTBEAT_INTERVAL = "icp_config.heartbeat_interval"; + + // JWT Configuration + public static final String ICP_JWT_ISSUER = "icp_config.jwt_issuer"; + public static final String ICP_JWT_AUDIENCE = "icp_config.jwt_audience"; + public static final String ICP_JWT_SCOPE = "icp_config.jwt_scope"; + public static final String ICP_JWT_EXPIRY_SECONDS = "icp_config.jwt_expiry_seconds"; + public static final String ICP_JWT_HMAC_SECRET = "icp_config.jwt_hmac_secret"; + + // Default ICP Configuration + public static final String DEFAULT_ENVIRONMENT = "production"; + public static final String DEFAULT_PROJECT = "default"; + public static final String DEFAULT_COMPONENT = "default"; + public static final String DEFAULT_ICP_URL = "https://localhost:9445"; + + public static final String DEFAULT_JWT_ISSUER = "icp-runtime-jwt-issuer"; + public static final String DEFAULT_JWT_AUDIENCE = "icp-server"; + public static final String DEFAULT_JWT_SCOPE = "runtime_agent"; + public static final long DEFAULT_JWT_EXPIRY_SECONDS = 3600; + public static final String DEFAULT_JWT_HMAC_SECRET = "default-secret-key-at-least-32-characters-long-for-hs256"; + public static final String RUNTIME_TYPE_MI = "MI"; + public static final String RUNTIME_STATUS_RUNNING = "RUNNING"; + public static final String DEFAULT_GROUP_ID = "default"; public static final long DEFAULT_HEARTBEAT_INTERVAL = 5; @@ -23,4 +57,11 @@ private Constants() { public static final String COLON = ":"; public static final String HTTPS_PREFIX = "https://"; public static final String MANAGEMENT = "management"; + + // ICP Endpoints + public static final String ICP_HEARTBEAT_ENDPOINT = "/icp/heartbeat"; + public static final String ICP_DELTA_HEARTBEAT_ENDPOINT = "/icp/deltaHeartbeat"; + + public static final String ENDPOINT_TEMPLATE_TYPE = "endpoint"; + public static final String SEQUENCE_TEMPLATE_TYPE = "sequence"; } diff --git a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HMACJWTTokenGenerator.java b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HMACJWTTokenGenerator.java new file mode 100644 index 0000000000..308bd281d8 --- /dev/null +++ b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HMACJWTTokenGenerator.java @@ -0,0 +1,56 @@ +package org.wso2.micro.integrator.initializer.dashboard; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class HMACJWTTokenGenerator { + + private final String hmacSecret; + + public HMACJWTTokenGenerator(String hmacSecret) { + if (hmacSecret == null || hmacSecret.getBytes(StandardCharsets.UTF_8).length < 32) { + throw new IllegalArgumentException("HMAC secret must be at least 256 bits (32 bytes)"); + } + this.hmacSecret = hmacSecret; + } + + /** + * Generate JWT Token with HMAC SHA256 + */ + public String generateToken(String issuer, String audience, String scope, long expiryTimeSeconds) + throws JOSEException { + + // Calculate expiry + long expiryMillis = System.currentTimeMillis() + (expiryTimeSeconds * 1000); + + // Build claims + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .issuer(issuer) + .audience(audience) + .expirationTime(new Date(expiryMillis)) + .issueTime(new Date()) + .claim("scope", scope) + .build(); + + // Create HMAC signer + JWSSigner signer = new MACSigner(hmacSecret.getBytes(StandardCharsets.UTF_8)); + + // Create and sign JWT + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.HS256).build(), + claimsSet + ); + + signedJWT.sign(signer); + return signedJWT.serialize(); + } + +} diff --git a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HeartBeatComponent.java b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HeartBeatComponent.java index 9f5b7d031a..769c776e31 100644 --- a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HeartBeatComponent.java +++ b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/HeartBeatComponent.java @@ -17,9 +17,9 @@ */ package org.wso2.micro.integrator.initializer.dashboard; +import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; @@ -71,7 +71,13 @@ private HeartBeatComponent(){ private static final Map configs = ConfigParser.getParsedConfigs(); public static void invokeHeartbeatExecutorService() { - + // Check if new ICP is configured + if (ICPHeartBeatComponent.isICPConfigured()) { + log.info("New ICP configuration detected. Starting ICP heartbeat service."); + ICPHeartBeatComponent.invokeICPHeartbeatExecutorService(); + return; + } + // Fall back to old dashboard heartbeat String heartbeatApiUrl = configs.get(DASHBOARD_CONFIG_URL) + "/heartbeat"; String groupId = getGroupId(); String nodeId = getNodeId(); @@ -174,14 +180,16 @@ private static String generateRandomId() { } public static boolean isDashboardConfigured() { - return configs.get(DASHBOARD_CONFIG_URL) != null; + // Check for either old dashboard config or new ICP config + return configs.get(DASHBOARD_CONFIG_URL) != null || ICPHeartBeatComponent.isICPConfigured(); } public static JsonObject getJsonResponse(CloseableHttpResponse response) { String stringResponse = getStringResponse(response); JsonObject responseObject = null; try { - responseObject = new JsonParser().parse(stringResponse).getAsJsonObject(); + Gson gson = new Gson(); + responseObject = gson.fromJson(stringResponse, JsonObject.class); } catch (JsonParseException e) { log.debug("Error occurred while parsing the heartbeat response.", e); } diff --git a/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/ICPHeartBeatComponent.java b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/ICPHeartBeatComponent.java new file mode 100644 index 0000000000..4d5f217be9 --- /dev/null +++ b/components/org.wso2.micro.integrator.initializer/src/main/java/org/wso2/micro/integrator/initializer/dashboard/ICPHeartBeatComponent.java @@ -0,0 +1,1589 @@ +/* + * Copyright (c) 2025, 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.micro.integrator.initializer.dashboard; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.EntityUtils; +import org.apache.synapse.api.API; +import org.apache.synapse.config.SynapseConfiguration; +import org.apache.synapse.core.axis2.ProxyService; +import org.apache.synapse.endpoints.Template; +import org.apache.synapse.mediators.template.TemplateMediator; +import org.apache.synapse.registry.Registry; +import org.json.JSONArray; +import org.json.JSONObject; +import org.apache.axis2.deployment.util.Utils; +import org.apache.axis2.description.AxisService; +import org.apache.axis2.description.Parameter; +import org.apache.axis2.engine.AxisConfiguration; +import org.wso2.config.mapper.ConfigParser; +import org.wso2.micro.application.deployer.CarbonApplication; +import org.wso2.micro.application.deployer.config.Artifact; +import org.wso2.micro.core.util.StringUtils; +import org.wso2.micro.integrator.core.util.MicroIntegratorBaseUtils; +import org.wso2.micro.integrator.initializer.deployment.application.deployer.CappDeployer; +import org.wso2.micro.integrator.registry.MicroIntegratorRegistry; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.time.Instant; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.wso2.micro.integrator.initializer.dashboard.Constants.*; + +/** + * Manages heartbeats from micro integrator to new ICP with JWT authentication, + * delta heartbeat optimization, and comprehensive artifact metadata. + */ +public class ICPHeartBeatComponent { + + private ICPHeartBeatComponent() { + } + + private static final Log log = LogFactory.getLog(ICPHeartBeatComponent.class); + private static final Map configs = ConfigParser.getParsedConfigs(); + private static String cachedJwtToken = null; + private static long jwtTokenExpiry = 0; + private static String runtimeIdFile = ".icp_runtime_id"; + private static String runtimeId = null; + // Track last runtime hash acknowledged by ICP to optimize delta heartbeats + private static String lastRuntimeHash = null; + + /** + * Lazily initializes and returns the runtime ID. + * Only initializes when ICP is configured. + * + * @return the runtime ID + * @throws IOException if there's an error reading or writing the runtime ID + * file + */ + private static synchronized String getRuntimeId() throws IOException { + // Prefer cached value if initialized + if (runtimeId != null && !runtimeId.trim().isEmpty()) { + return runtimeId; + } + + // Read from persisted file if present; else generate and persist + Path runtimeIdPath = Paths.get(runtimeIdFile); + if (Files.exists(runtimeIdPath)) { + String existingId = Files.readString(runtimeIdPath).trim(); + if (!existingId.isEmpty()) { + runtimeId = existingId; + return runtimeId; + } + } + + runtimeId = initRuntimeId(); + return runtimeId; + } + + /** + * Initializes the runtime ID from file or generates a new one. + * + * @return the runtime ID + * @throws IOException if there's an error reading or writing the runtime ID + * file + */ + private static String initRuntimeId() throws IOException { + // Use current working directory for the runtime ID file + Path runtimeIdPath = Paths.get(runtimeIdFile); + + // If file exists, prefer its value + if (Files.exists(runtimeIdPath)) { + String existingId = Files.readString(runtimeIdPath).trim(); + if (!existingId.isEmpty()) { + return existingId; + } + } + + // Generate new ID as: - if configured; else + String configuredPrefix = null; + Object configuredRuntimeId = configs.get(ICP_CONFIG_RUNTIME); + if (configuredRuntimeId != null) { + String cfgId = configuredRuntimeId.toString().trim(); + if (!cfgId.isEmpty()) { + configuredPrefix = cfgId; + } + } + + String newRuntimeId = (configuredPrefix != null ? configuredPrefix + "-" : "") + + UUID.randomUUID().toString(); + Files.writeString(runtimeIdPath, newRuntimeId); + return newRuntimeId; + } + + /** + * Starts the ICP heartbeat executor service that sends periodic delta + * heartbeats and full heartbeats when requested by the ICP. + */ + public static void invokeICPHeartbeatExecutorService() { + String icpUrl = getConfigValue(ICP_CONFIG_URL, DEFAULT_ICP_URL); + if (icpUrl == null) { + log.warn("ICP URL not configured. ICP heartbeat will not be started."); + return; + } + long interval = getInterval(); + log.info("Starting ICP heartbeat service. Interval: " + interval + "s"); + + ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + Runnable runnableTask = () -> { + try { + sendDeltaHeartbeat(icpUrl); + } catch (Exception e) { + log.error("Error occurred while sending delta heartbeat to ICP.", e); + } + }; + + // Initial delay of 5 seconds, then send at configured interval + scheduledExecutorService.scheduleAtFixedRate(runnableTask, 5, interval, TimeUnit.SECONDS); + } + + /** + * Sends a delta heartbeat to ICP with only runtime hash. + * If ICP detects a hash mismatch, it will respond with + * fullHeartbeatRequired=true. + */ + private static void sendDeltaHeartbeat(String icpUrl) { + try { + // Build full payload to calculate hash + JsonObject fullPayload = buildFullHeartbeatPayload(false); + String currentHash = fullPayload.get("runtimeHash").getAsString(); + + // Build delta payload + JsonObject deltaPayload = new JsonObject(); + deltaPayload.addProperty("runtime", getRuntimeId()); + deltaPayload.addProperty("runtimeHash", currentHash); + + // Create timestamp in Ballerina time:Utc format [seconds, nanoseconds_fraction] + deltaPayload.add("timestamp", createBallerinaTimestamp()); + + String deltaEndpoint = icpUrl + ICP_DELTA_HEARTBEAT_ENDPOINT; + JsonObject response = sendHeartbeatRequest(deltaEndpoint, deltaPayload); + + if (response != null && response.has("fullHeartbeatRequired") + && response.get("fullHeartbeatRequired").getAsBoolean()) { + log.info("ICP requested full heartbeat. Sending full heartbeat with all artifacts."); + sendFullHeartbeat(icpUrl); + lastRuntimeHash = currentHash; + } else if (response != null && response.has("acknowledged") + && response.get("acknowledged").getAsBoolean()) { + log.debug("Delta heartbeat acknowledged by ICP."); + lastRuntimeHash = currentHash; + } + } catch (Exception e) { + log.error("Error sending full heartbeat to ICP.", e); + } + } + + /** + * Sends a full heartbeat to ICP with all artifact metadata. + */ + private static void sendFullHeartbeat(String icpUrl) { + try { + JsonObject fullPayload = buildFullHeartbeatPayload(true); + String fullEndpoint = icpUrl + ICP_HEARTBEAT_ENDPOINT; + + JsonObject response = sendHeartbeatRequest(fullEndpoint, fullPayload); + + if (response != null && response.has("acknowledged") + && response.get("acknowledged").getAsBoolean()) { + log.info("Full heartbeat acknowledged by ICP."); + } else { + log.error("Unexpected response from ICP full heartbeat." + response.toString()); + } + } catch (Exception e) { + log.error("Error sending full heartbeat to ICP.", e); + } + } + + /** + * Sends HTTP request to ICP with JWT authentication. + */ + private static JsonObject sendHeartbeatRequest(String endpoint, JsonObject payload) { + try (CloseableHttpClient client = createHttpClient()) { + HttpPost httpPost = new HttpPost(endpoint); + + // Add JWT token to Authorization header + String jwtToken = ""; + try { + jwtToken = generateOrGetCachedJwtToken(); + } catch (Exception e) { + log.error("Error while jwtToken creation ", e); + } + httpPost.setHeader("Authorization", "Bearer " + jwtToken); + httpPost.setHeader("Accept", HEADER_VALUE_APPLICATION_JSON); + httpPost.setHeader("Content-type", HEADER_VALUE_APPLICATION_JSON); + + StringEntity entity = new StringEntity(payload.toString(), "UTF-8"); + httpPost.setEntity(entity); + + CloseableHttpResponse response = client.execute(httpPost); + return getJsonResponse(response); + } catch (Exception e) { + log.error("Error sending heartbeat request to ICP at: " + endpoint, e); + return null; + } + } + + /** + * Builds the full heartbeat payload with all artifact metadata. + */ + private static JsonObject buildFullHeartbeatPayload(boolean includeTimestamp) throws IOException { + JsonObject payload = new JsonObject(); + payload.addProperty("runtime", getRuntimeId()); + payload.addProperty("runtimeType", RUNTIME_TYPE_MI); + payload.addProperty("status", RUNTIME_STATUS_RUNNING); + payload.addProperty("environment", getEnvironment()); + payload.addProperty("project", getProject()); + payload.addProperty("component", getComponent()); + payload.addProperty("version", getMicroIntegratorVersion()); + // Optional management endpoint details (hostname and port) + String runtimeHost = getICPApiHostname(); + String runtimePort = getICPAPIPort(); + if (!StringUtils.isEmpty(runtimeHost)) { + payload.addProperty("runtimeHostname", runtimeHost); + } + if (!StringUtils.isEmpty(runtimePort)) { + payload.addProperty("runtimePort", runtimePort); + } + + // Node information + JsonObject nodeInfo = new JsonObject(); + nodeInfo.addProperty("platformName", "WSO2 Micro Integrator"); + nodeInfo.addProperty("platformVersion", getMicroIntegratorVersion()); + nodeInfo.addProperty("platformHome", System.getProperty("carbon.home")); + nodeInfo.addProperty("osName", System.getProperty("os.name")); + nodeInfo.addProperty("osVersion", System.getProperty("os.version")); + nodeInfo.addProperty("osArch", System.getProperty("os.arch")); + nodeInfo.addProperty("javaVersion", System.getProperty("java.version")); + nodeInfo.addProperty("carbonHome", System.getProperty("carbon.home")); + nodeInfo.addProperty("javaVendor", System.getProperty("java.vendor")); + + Runtime runtime = Runtime.getRuntime(); + nodeInfo.addProperty("totalMemory", runtime.totalMemory()); + nodeInfo.addProperty("freeMemory", runtime.freeMemory()); + nodeInfo.addProperty("maxMemory", runtime.maxMemory()); + nodeInfo.addProperty("usedMemory", runtime.totalMemory() - runtime.freeMemory()); + payload.add("nodeInfo", nodeInfo); + // Artifacts + JsonObject artifacts = collectArtifacts(); + payload.add("artifacts", artifacts); + + // Hash (exclude timestamp for hash calculation) + String hash = calculateHash(payload); + payload.addProperty("runtimeHash", hash); + + // Add timestamp if requested + if (includeTimestamp) { + payload.add("timestamp", createBallerinaTimestamp()); + } + + // Validate payload structure for ICP compatibility + payload = validateHeartbeatPayload(payload); + + log.info("Full heartbeat payload: " + payload.toString()); + return payload; + } + + private static String getICPApiHostname() { + try { + Object configured = configs.get(HOSTNAME); + if (configured != null && !StringUtils.isEmpty(configured.toString())) { + return configured.toString(); + } + String localIp = System.getProperty("carbon.local.ip"); + if (!StringUtils.isEmpty(localIp)) { + return localIp; + } + } catch (Exception ignored) { + // fall through to default + } + return ICP_API_DEFAULT_HOST; + } + + /** + * Resolves the ICP API port to report in the ICP heartbeat. + * Priority: + * 1) Calculated from `offset` (if provided) + * 2) Default ICP API port (9164) + */ + private static String getICPAPIPort() { + try { + // Read offset only from dashboard config (no legacy checks) + int offset = 0; + Object offsetCfg = configs.get(PORT_OFFSET); + if (offsetCfg != null && !StringUtils.isEmpty(offsetCfg.toString())) { + try { + offset = Integer.parseInt(offsetCfg.toString()); + } catch (NumberFormatException ignored) { + // keep offset = 0 if invalid + } + } + + if (ICP_API_DEFAULT_PORT > 0) { + if (offset != 0) { + int computed = ICP_API_DEFAULT_PORT - 10 + offset; + return String.valueOf(computed); + } + return String.valueOf(ICP_API_DEFAULT_PORT); + } + } catch (Exception ignored) { + // fall through + } + return String.valueOf(ICP_API_DEFAULT_PORT); + } + + /** + * Collects comprehensive artifact metadata from all MI Management API resources + */ + private static JsonObject collectArtifacts() { + JsonObject artifacts = new JsonObject(); + + try { + // Check if Synapse environment is available + if (MicroIntegratorBaseUtils.getSynapseEnvironment() == null) { + log.warn("Synapse environment is not available yet, returning empty artifacts"); + return createEmptyArtifactsStructure(); + } + + SynapseConfiguration synapseConfig = MicroIntegratorBaseUtils.getSynapseEnvironment() + .getSynapseConfiguration(); + + if (synapseConfig == null) { + log.warn("Synapse configuration is not available yet, returning empty artifacts"); + return createEmptyArtifactsStructure(); + } + + // Collect all artifact types as available in Management API + + // 1. REST APIs + artifacts.add("apis", collectRestApis(synapseConfig)); + + // 2. Proxy Services + artifacts.add("proxyServices", collectProxyServices(synapseConfig)); + + // 3. Endpoints + artifacts.add("endpoints", collectEndpoints(synapseConfig)); + + // 4. Inbound Endpoints + artifacts.add("inboundEndpoints", collectInboundEndpoints(synapseConfig)); + + // 5. Sequences + artifacts.add("sequences", collectSequences(synapseConfig)); + + // 6. Tasks + artifacts.add("tasks", collectTasks(synapseConfig)); + + // 7. Templates + artifacts.add("templates", collectTemplates(synapseConfig)); + + // 8. Message Stores + artifacts.add("messageStores", collectMessageStores(synapseConfig)); + + // 9. Message Processors + artifacts.add("messageProcessors", collectMessageProcessors(synapseConfig)); + + // 10. Local Entries + artifacts.add("localEntries", collectLocalEntries(synapseConfig)); + + // 11. Data Services (requires separate access) + artifacts.add("dataServices", collectDataServices(synapseConfig)); + + // 12. Carbon Applications (requires separate access) + artifacts.add("carbonApps", collectCarbonApps()); + + // 13. Data Sources (requires separate access) + artifacts.add("dataSources", collectDataSources()); + + // 14. Connectors (requires separate access) + artifacts.add("connectors", collectConnectors(synapseConfig)); + + // 15. Registry Resources (requires separate access) + artifacts.add("registryResources", collectRegistryResources(synapseConfig)); + + // 16. Log Files (requires filesystem access) + artifacts.add("logFiles", collectLogFiles()); + + } catch (Exception e) { + log.error("Error collecting artifacts from MI configuration.", e); + return createEmptyArtifactsStructure(); + } + + return artifacts; + } + + private static JsonObject createEmptyArtifactsStructure() { + JsonObject artifacts = new JsonObject(); + artifacts.add("apis", new JsonArray()); + artifacts.add("proxyServices", new JsonArray()); + artifacts.add("endpoints", new JsonArray()); + artifacts.add("inboundEndpoints", new JsonArray()); + artifacts.add("sequences", new JsonArray()); + artifacts.add("tasks", new JsonArray()); + artifacts.add("templates", new JsonArray()); + artifacts.add("messageStores", new JsonArray()); + artifacts.add("messageProcessors", new JsonArray()); + artifacts.add("localEntries", new JsonArray()); + artifacts.add("dataServices", new JsonArray()); + artifacts.add("carbonApps", new JsonArray()); + artifacts.add("dataSources", new JsonArray()); + artifacts.add("connectors", new JsonArray()); + artifacts.add("registryResources", new JsonArray()); + artifacts.add("logFiles", new JsonArray()); + artifacts.add("listeners", new JsonArray()); + artifacts.add("systemInfo", new JsonObject()); + return artifacts; + } + + /** + * Collects log files information similar to Management API LogFilesResource + * Fields: FileName, Size + */ + private static JsonArray collectLogFiles() { + JsonArray logs = new JsonArray(); + try { + String logsPath = getCarbonLogsPath(); + if (logsPath == null) { + return logs; + } + File folder = new File(logsPath); + File[] listOfFiles = folder.listFiles(); + if (listOfFiles == null || listOfFiles.length == 0) { + return logs; + } + for (File file : listOfFiles) { + try { + String filename = file.getName(); + if (filename.endsWith(".lck")) { + continue; + } + JsonObject logObj = new JsonObject(); + logObj.addProperty("FileName", filename); + logObj.addProperty("Size", humanReadableSize(file.length())); + logs.add(logObj); + } catch (Exception ignored) { + } + } + } catch (Exception e) { + log.debug("Error collecting log files info", e); + } + return logs; + } + + private static String getCarbonLogsPath() { + try { + String carbonLogsPath = System.getProperty("carbon.logs.path"); + if (carbonLogsPath == null) { + carbonLogsPath = System.getenv("CARBON_LOGS"); + if (carbonLogsPath == null) { + String carbonHome = System.getProperty("carbon.home"); + if (carbonHome == null) { + carbonHome = System.getenv("CARBON_HOME"); + } + if (carbonHome != null) { + return carbonHome + File.separator + "repository" + File.separator + "logs"; + } else { + return null; + } + } + } + return carbonLogsPath; + } catch (Exception e) { + return null; + } + } + + private static String humanReadableSize(long bytes) { + int unit = 1024; + if (bytes < unit) { + return bytes + " B"; + } + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = "KMGTPE".charAt(exp - 1) + ""; + return String.format(java.util.Locale.ROOT, "%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + /** + * Validates the entire heartbeat payload structure for Ballerina GraphQL API + * compatibility + */ + private static JsonObject validateHeartbeatPayload(JsonObject payload) { + try { + log.debug("Validating heartbeat payload structure for ICP GraphQL API compatibility"); + + // Ensure all required root-level properties exist and have correct types + if (!payload.has("runtime") || payload.get("runtime").isJsonNull()) { + payload.addProperty("runtime", UUID.randomUUID().toString()); + log.warn("Missing runtime, added default UUID"); + } + + if (!payload.has("runtimeType") || payload.get("runtimeType").isJsonNull()) { + payload.addProperty("runtimeType", "MI"); + log.warn("Missing runtimeType, added default 'MI'"); + } + + if (!payload.has("status") || payload.get("status").isJsonNull()) { + payload.addProperty("status", "RUNNING"); + log.warn("Missing status, added default 'RUNNING'"); + } + + if (!payload.has("environment") || payload.get("environment").isJsonNull()) { + payload.addProperty("environment", "dev"); + log.warn("Missing environment, added default 'dev'"); + } + + if (!payload.has("project") || payload.get("project").isJsonNull()) { + payload.addProperty("project", "default"); + log.warn("Missing project, added default 'default'"); + } + + if (!payload.has("component") || payload.get("component").isJsonNull()) { + payload.addProperty("component", "micro-integrator"); + log.warn("Missing component, added default 'micro-integrator'"); + } + + if (!payload.has("version") || payload.get("version").isJsonNull()) { + payload.addProperty("version", "4.4.0"); + log.warn("Missing version, added default '4.4.0'"); + } + + // Validate nodeInfo structure + if (!payload.has("nodeInfo") || payload.get("nodeInfo").isJsonNull() + || !payload.get("nodeInfo").isJsonObject()) { + JsonObject nodeInfo = new JsonObject(); + nodeInfo.addProperty("platformName", "wso2-mi"); + nodeInfo.addProperty("platformVersion", "4.4.0"); + nodeInfo.addProperty("platformHome", System.getProperty("carbon.home", "/opt/wso2mi")); + nodeInfo.addProperty("osName", System.getProperty("os.name", "unknown")); + nodeInfo.addProperty("osVersion", System.getProperty("os.version", "unknown")); + nodeInfo.addProperty("javaVersion", System.getProperty("java.version", "unknown")); + payload.add("nodeInfo", nodeInfo); + log.warn("Missing or invalid nodeInfo, added default nodeInfo structure"); + } + + // Validate artifacts structure + if (!payload.has("artifacts") || payload.get("artifacts").isJsonNull() + || !payload.get("artifacts").isJsonObject()) { + payload.add("artifacts", createEmptyArtifactsStructure()); + log.warn("Missing or invalid artifacts, added empty artifacts structure"); + } + + // Ensure runtimeHash exists + if (!payload.has("runtimeHash") || payload.get("runtimeHash").isJsonNull()) { + payload.addProperty("runtimeHash", ""); + log.warn("Missing runtimeHash, added empty string"); + } + + // Validate timestamp structure if present + if (payload.has("timestamp") && !payload.get("timestamp").isJsonNull()) { + if (!payload.get("timestamp").isJsonArray()) { + payload.add("timestamp", createBallerinaTimestamp()); + log.warn("Invalid timestamp format, replaced with valid Ballerina timestamp"); + } else { + JsonArray timestamp = payload.getAsJsonArray("timestamp"); + if (timestamp.size() != 2) { + payload.add("timestamp", createBallerinaTimestamp()); + log.warn("Invalid timestamp array size, replaced with valid Ballerina timestamp"); + } + } + } + + log.debug("Heartbeat payload validation completed successfully"); + return payload; + + } catch (Exception e) { + log.error("Error validating heartbeat payload structure, returning minimal payload", e); + + // Create minimal valid payload + JsonObject minimalPayload = new JsonObject(); + minimalPayload.addProperty("runtime", UUID.randomUUID().toString()); + minimalPayload.addProperty("runtimeType", "MI"); + minimalPayload.addProperty("status", "RUNNING"); + minimalPayload.addProperty("environment", "dev"); + minimalPayload.addProperty("project", "default"); + minimalPayload.addProperty("component", "micro-integrator"); + minimalPayload.addProperty("version", "4.4.0"); + minimalPayload.addProperty("runtimeHash", ""); + + JsonObject nodeInfo = new JsonObject(); + nodeInfo.addProperty("platformName", "wso2-mi"); + nodeInfo.addProperty("platformVersion", "4.4.0"); + minimalPayload.add("nodeInfo", nodeInfo); + + minimalPayload.add("artifacts", createEmptyArtifactsStructure()); + + return minimalPayload; + } + } + + /** + * Creates a Ballerina time:Utc compatible timestamp array [seconds, + * nanoseconds_fraction]. + */ + private static JsonArray createBallerinaTimestamp() { + Instant now = Instant.now(); + JsonArray timestampArray = new JsonArray(); + timestampArray.add(now.getEpochSecond()); // seconds since epoch as int + timestampArray.add(now.getNano() / 1_000_000_000.0); // nanoseconds as decimal fraction + return timestampArray; + } + + /** + * Calculates MD5 hash of the payload (excluding timestamp and dynamic memory + * values). + */ + private static String calculateHash(JsonObject payload) { + try { + // Create a copy and remove timestamp for consistent hashing + JsonObject payloadCopy = payload.deepCopy(); + payloadCopy.remove("timestamp"); + if (payloadCopy.has("nodeInfo") && payloadCopy.get("nodeInfo").isJsonObject()) { + JsonObject nodeInfo = payloadCopy.getAsJsonObject("nodeInfo"); + nodeInfo.remove("freeMemory"); + nodeInfo.remove("usedMemory"); + nodeInfo.remove("maxMemory"); + nodeInfo.remove("totalMemory"); + } + + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hashBytes = md.digest(payloadCopy.toString().getBytes("UTF-8")); + return Base64.getEncoder().encodeToString(hashBytes); + } catch (Exception e) { + log.error("Error calculating hash for heartbeat payload.", e); + return ""; + } + } + + /** + * Generates a new JWT token or returns cached token if still valid. + */ + private static String generateOrGetCachedJwtToken() throws Exception { + long currentTime = System.currentTimeMillis(); + // Return cached token if it's still valid (with 5 minute buffer) + if (cachedJwtToken != null && currentTime < (jwtTokenExpiry - 300000)) { + return cachedJwtToken; + } + String jwtHmacSecret = getConfigValue(ICP_JWT_HMAC_SECRET, DEFAULT_JWT_HMAC_SECRET); + HMACJWTTokenGenerator hmacJWTTokenGenerator = new HMACJWTTokenGenerator(jwtHmacSecret); + String issuer = getConfigValue(ICP_JWT_ISSUER, DEFAULT_JWT_ISSUER); + String audience = getConfigValue(ICP_JWT_AUDIENCE, DEFAULT_JWT_AUDIENCE); + String scope = getConfigValue(ICP_JWT_SCOPE, DEFAULT_JWT_SCOPE); + long expirySeconds = getJwtExpirySeconds(); + // Generate new token + cachedJwtToken = hmacJWTTokenGenerator.generateToken(issuer, audience, scope, expirySeconds); + jwtTokenExpiry = currentTime + (expirySeconds * 1000); + return cachedJwtToken; + } + + /** + * Creates an HTTP client with SSL support. + */ + private static CloseableHttpClient createHttpClient() throws Exception { + return HttpClients.custom() + .setSSLSocketFactory(new SSLConnectionSocketFactory( + SSLContexts.custom() + .loadTrustMaterial(null, (TrustStrategy) new TrustSelfSignedStrategy()) + .build(), + NoopHostnameVerifier.INSTANCE)) + .build(); + } + + /** + * Gets the environment name. + */ + private static String getEnvironment() { + return getConfigValue(ICP_CONFIG_ENVIRONMENT, DEFAULT_ENVIRONMENT); + } + + /** + * Gets the project name. + */ + private static String getProject() { + return getConfigValue(ICP_CONFIG_PROJECT, DEFAULT_PROJECT); + } + + /** + * Gets the component name. + */ + private static String getComponent() { + return getConfigValue(ICP_CONFIG_COMPONENT, DEFAULT_COMPONENT); + } + + /** + * Gets the heartbeat interval in seconds. + */ + private static long getInterval() { + long interval = DEFAULT_HEARTBEAT_INTERVAL; + Object configuredInterval = configs.get(ICP_CONFIG_HEARTBEAT_INTERVAL); + if (configuredInterval != null) { + interval = Integer.parseInt(configuredInterval.toString()); + } + return interval; + } + + /** + * Gets the JWT token expiry time in seconds. + */ + private static long getJwtExpirySeconds() { + Object expiry = configs.get(ICP_JWT_EXPIRY_SECONDS); + if (expiry != null) { + return Long.parseLong(expiry.toString()); + } + return DEFAULT_JWT_EXPIRY_SECONDS; + } + + /** + * Gets the MI version. + */ + private static String getMicroIntegratorVersion() { + return System.getProperty("product.version", "4.4.0"); + } + + /** + * Helper method to get configuration value with fallback. + */ + private static String getConfigValue(String key, String defaultValue) { + Object value = configs.get(key); + return (value != null) ? value.toString() : defaultValue; + } + + /** + * Checks if ICP is configured and enabled. + * Returns true only if explicitly enabled via configuration. + * + * @return true if ICP is enabled, false otherwise + */ + public static boolean isICPConfigured() { + Object icpEnabled = configs.get(ICP_CONFIG_ENABLED); + return icpEnabled != null && "true".equalsIgnoreCase(icpEnabled.toString()); + } + + /** + * Parses JSON response from HTTP response. + */ + private static JsonObject getJsonResponse(CloseableHttpResponse response) { + try { + HttpEntity entity = response.getEntity(); + String stringResponse = EntityUtils.toString(entity, "UTF-8"); + Gson gson = new Gson(); + return gson.fromJson(stringResponse, JsonObject.class); + } catch (Exception e) { + log.debug("Error parsing JSON response from ICP.", e); + return null; + } + } + + // ===== ARTIFACT COLLECTION METHODS ===== + + /** + * Collects REST API information from Synapse Configuration + */ + private static JsonArray collectRestApis(SynapseConfiguration synapseConfig) { + JsonArray apis = new JsonArray(); + try { + Collection apiCollection = synapseConfig.getAPIs(); + for (API api : apiCollection) { + JsonObject apiObj = new JsonObject(); + apiObj.addProperty("name", api.getName()); + apiObj.addProperty("context", api.getContext()); + apiObj.addProperty("version", api.getVersion()); + apiObj.addProperty("host", api.getHost()); + apiObj.addProperty("port", api.getPort()); + apiObj.addProperty("type", "API"); + apiObj.addProperty("state", "ENABLED"); + + // Collect API resources + JsonArray resources = new JsonArray(); + if (api.getResources() != null) { + for (org.apache.synapse.api.Resource resource : api.getResources()) { + JsonObject resourceObj = new JsonObject(); + + String resourcePath = ""; + try { + if (resource.getDispatcherHelper() != null) { + resourcePath = resource.getDispatcherHelper().getString(); + } + if (resourcePath == null || resourcePath.isEmpty()) { + resourcePath = "/*"; + } + } catch (Exception ex) { + resourcePath = "/*"; + } + resourceObj.addProperty("path", resourcePath); + + if (resource.getMethods() != null && resource.getMethods().length > 0) { + resourceObj.addProperty("methods", String.join(",", resource.getMethods())); + } + resources.add(resourceObj); + } + } + apiObj.add("resources", resources); + apis.add(apiObj); + } + } catch (Exception e) { + log.error("Error collecting REST APIs", e); + } + return apis; + } + + /** + * Collects Proxy Service information from Synapse Configuration + */ + private static JsonArray collectProxyServices(SynapseConfiguration synapseConfig) { + JsonArray proxies = new JsonArray(); + try { + Collection proxyCollection = synapseConfig.getProxyServices(); + for (ProxyService proxy : proxyCollection) { + JsonObject proxyObj = new JsonObject(); + proxyObj.addProperty("name", proxy.getName()); + proxyObj.addProperty("type", "ProxyService"); + // Align with Management API: lowercase enabled/disabled + proxyObj.addProperty("state", proxy.isRunning() ? "enabled" : "disabled"); + + // Tracing and statistics as per AspectConfiguration + try { + if (proxy.getAspectConfiguration() != null) { + String tracingState = proxy.getAspectConfiguration().isTracingEnabled() ? "enabled" : "disabled"; + String statsState = proxy.getAspectConfiguration().isStatisticsEnable() ? "enabled" : "disabled"; + proxyObj.addProperty("tracing", tracingState); + proxyObj.addProperty("statistics", statsState); + } + } catch (Throwable t) { + // ignore if aspect configuration not available for some proxies + } + + // Transport information + if (proxy.getTransports() != null) { + JsonArray transports = new JsonArray(); + for (Object transport : proxy.getTransports()) { + transports.add(transport.toString()); + } + proxyObj.add("transports", transports); + } + + proxies.add(proxyObj); + } + } catch (Exception e) { + log.error("Error collecting Proxy Services", e); + } + return proxies; + } + + /** + * Collects Endpoint information from Synapse Configuration + */ + private static JsonArray collectEndpoints(SynapseConfiguration synapseConfig) { + JsonArray endpoints = new JsonArray(); + try { + Map endpointMap = synapseConfig.getDefinedEndpoints(); + for (Map.Entry entry : endpointMap.entrySet()) { + JsonObject endpointObj = new JsonObject(); + org.apache.synapse.endpoints.Endpoint endpoint = entry.getValue(); + endpointObj.addProperty("name", entry.getKey()); + endpointObj.addProperty("type", endpoint.getClass().getSimpleName()); + endpoints.add(endpointObj); + } + } catch (Exception e) { + log.error("Error collecting Endpoints", e); + } + return endpoints; + } + + /** + * Collects Inbound Endpoint information + */ + private static JsonArray collectInboundEndpoints(SynapseConfiguration synapseConfig) { + JsonArray inboundEndpoints = new JsonArray(); + try { + Collection inboundCollection = synapseConfig + .getInboundEndpoints(); + for (org.apache.synapse.inbound.InboundEndpoint inbound : inboundCollection) { + JsonObject inboundObj = new JsonObject(); + inboundObj.addProperty("name", inbound.getName()); + inboundObj.addProperty("protocol", inbound.getProtocol()); + inboundObj.addProperty("state", "ENABLED"); + inboundEndpoints.add(inboundObj); + } + } catch (Exception e) { + log.error("Error collecting Inbound Endpoints", e); + } + return inboundEndpoints; + } + + /** + * Collects Sequence information from Synapse Configuration + */ + private static JsonArray collectSequences(SynapseConfiguration synapseConfig) { + JsonArray sequences = new JsonArray(); + try { + Map seqMap = synapseConfig + .getDefinedSequences(); + for (Map.Entry entry : seqMap.entrySet()) { + JsonObject seqObj = new JsonObject(); + seqObj.addProperty("name", entry.getKey()); + seqObj.addProperty("type", "Sequence"); + sequences.add(seqObj); + } + } catch (Exception e) { + log.error("Error collecting Sequences", e); + } + return sequences; + } + + /** + * Collects Task information from Synapse Configuration + */ + private static JsonArray collectTasks(SynapseConfiguration synapseConfig) { + JsonArray tasks = new JsonArray(); + try { + // Obtain SynapseEnvironment to access TaskDescriptionRepository + org.apache.synapse.core.SynapseEnvironment synapseEnv = MicroIntegratorBaseUtils.getSynapseEnvironment(); + + Collection startups = synapseConfig.getStartups(); + for (org.apache.synapse.Startup startup : startups) { + String name = startup.getName(); + String taskGroup = null; + try { + if (synapseEnv != null && synapseEnv.getTaskManager() != null + && synapseEnv.getTaskManager().getTaskDescriptionRepository() != null) { + org.apache.synapse.task.TaskDescription desc = synapseEnv.getTaskManager() + .getTaskDescriptionRepository().getTaskDescription(name); + if (desc != null) { + taskGroup = desc.getTaskGroup(); + } + } + } catch (Exception ignored) { + // If repository lookup fails, leave taskGroup null + } + + JsonObject taskObj = new JsonObject(); + taskObj.addProperty("name", name); + if (taskGroup != null) { + taskObj.addProperty("taskGroup", taskGroup); + } else { + taskObj.addProperty("taskGroup", ""); + } + tasks.add(taskObj); + } + } catch (Exception e) { + log.error("Error collecting Tasks", e); + } + return tasks; + } + + /** + * Collects Template information from Synapse Configuration + */ + private static JsonArray collectTemplates(SynapseConfiguration synapseConfig) { + JsonArray templates = new JsonArray(); + try { + // Endpoint Templates + Map endpointTemplates = synapseConfig.getEndpointTemplates(); + for (Map.Entry entry : endpointTemplates.entrySet()) { + JsonObject templateObj = new JsonObject(); + templateObj.addProperty("name", entry.getKey()); + templateObj.addProperty("type", Constants.ENDPOINT_TEMPLATE_TYPE); + templates.add(templateObj); + } + //Sequence Templates + Map sequenceTemplates = synapseConfig + .getSequenceTemplates(); + for (Map.Entry entry : sequenceTemplates.entrySet()) { + JsonObject templateObj = new JsonObject(); + templateObj.addProperty("name", entry.getKey()); + templateObj.addProperty("type", Constants.SEQUENCE_TEMPLATE_TYPE); + templates.add(templateObj); + } + } catch (Exception e) { + log.error("Error collecting Templates", e); + } + return templates; + } + + /** + * Collects Message Store information from Synapse Configuration + */ + private static JsonArray collectMessageStores(SynapseConfiguration synapseConfig) { + JsonArray messageStores = new JsonArray(); + try { + Map storeMap = synapseConfig.getMessageStores(); + for (Map.Entry entry : storeMap.entrySet()) { + JsonObject storeObj = new JsonObject(); + org.apache.synapse.message.store.MessageStore store = entry.getValue(); + storeObj.addProperty("name", entry.getKey()); + storeObj.addProperty("type", store.getClass().getSimpleName()); + storeObj.addProperty("size", store.size()); + messageStores.add(storeObj); + } + } catch (Exception e) { + log.error("Error collecting Message Stores", e); + } + return messageStores; + } + + /** + * Collects Message Processor information from Synapse Configuration + */ + private static JsonArray collectMessageProcessors(SynapseConfiguration synapseConfig) { + JsonArray messageProcessors = new JsonArray(); + try { + Map processorMap = synapseConfig + .getMessageProcessors(); + for (Map.Entry entry : processorMap + .entrySet()) { + JsonObject processorObj = new JsonObject(); + org.apache.synapse.message.processor.MessageProcessor processor = entry.getValue(); + processorObj.addProperty("name", entry.getKey()); + processorObj.addProperty("type", processor.getClass().getSimpleName()); + processorObj.addProperty("state", "ENABLED"); // Default state + // Include the associated Message Store name similar to Management API + try { + String messageStoreName = processor.getMessageStoreName(); + if (messageStoreName != null) { + processorObj.addProperty("messageStore", messageStoreName); + } else { + processorObj.addProperty("messageStore", ""); + } + } catch (Throwable ignore) { + // Be resilient: if any implementation throws, keep heartbeat flowing + } + messageProcessors.add(processorObj); + } + } catch (Exception e) { + log.error("Error collecting Message Processors", e); + } + return messageProcessors; + } + + /** + * Collects Local Entry information from Synapse Configuration + */ + private static JsonArray collectLocalEntries(SynapseConfiguration synapseConfig) { + JsonArray localEntries = new JsonArray(); + try { + Map definedEntries = synapseConfig.getDefinedEntries(); + for (Map.Entry e : definedEntries.entrySet()) { + String key = e.getKey(); + // Skip server-defined entries to match Management API behavior + if (org.apache.synapse.SynapseConstants.SERVER_IP.equals(key) || + org.apache.synapse.SynapseConstants.SERVER_HOST.equals(key)) { + continue; + } + org.apache.synapse.config.Entry entry = e.getValue(); + JsonObject entryObj = new JsonObject(); + entryObj.addProperty("name", key); + String entryType; + switch (entry.getType()) { + case org.apache.synapse.config.Entry.REMOTE_ENTRY: + entryType = "Registry Key"; + break; + case org.apache.synapse.config.Entry.INLINE_TEXT: + entryType = "Inline Text"; + break; + case org.apache.synapse.config.Entry.INLINE_XML: + entryType = "Inline XML"; + break; + case org.apache.synapse.config.Entry.URL_SRC: + entryType = "Source URL"; + break; + default: + entryType = "Unknown - " + entry.getType(); + break; + } + entryObj.addProperty("type", entryType); + localEntries.add(entryObj); + } + } catch (Exception e) { + log.error("Error collecting Local Entries", e); + } + return localEntries; + } + + /** + * Collects Data Service information from Synapse Configuration + */ + private static JsonArray collectDataServices(SynapseConfiguration synapseConfig) { + JsonArray dataServices = new JsonArray(); + try { + if (synapseConfig == null || synapseConfig.getAxisConfiguration() == null) { + log.debug("Synapse configuration or Axis configuration is not available for data services collection"); + return dataServices; + } + + AxisConfiguration axisConfiguration = synapseConfig.getAxisConfiguration(); + + // Get available data service names using DBUtils + String[] dataServiceNames = org.wso2.micro.integrator.dataservices.core.DBUtils + .getAvailableDS(axisConfiguration); + + for (String serviceName : dataServiceNames) { + JsonObject dsObj = new JsonObject(); + try { + dsObj.addProperty("name", serviceName); + dsObj.addProperty("type", "DataService"); + + // Try to get the DataService object for more details + AxisService axisService = axisConfiguration.getServiceForActivation(serviceName); + if (axisService != null) { + // Get DataService object from axis service parameter + Parameter dsParam = axisService.getParameter("DataService"); + if (dsParam != null && dsParam + .getValue() instanceof org.wso2.micro.integrator.dataservices.core.engine.DataService) { + org.wso2.micro.integrator.dataservices.core.engine.DataService dataService = (org.wso2.micro.integrator.dataservices.core.engine.DataService) dsParam + .getValue(); + + // Add service description if available + if (dataService.getDescription() != null) { + dsObj.addProperty("description", dataService.getDescription()); + } + + // Add config information + if (dataService.getConfigs() != null && !dataService.getConfigs().isEmpty()) { + JsonArray configs = new JsonArray(); + for (String configId : dataService.getConfigs().keySet()) { + configs.add(configId); + } + dsObj.add("configs", configs); + } + + // Add operation count + if (dataService.getOperationNames() != null) { + dsObj.addProperty("operationCount", dataService.getOperationNames().size()); + } + + // Add query count + if (dataService.getQueries() != null) { + dsObj.addProperty("queryCount", dataService.getQueries().size()); + } + } + + // Add service status + dsObj.addProperty("state", axisService.isActive() ? "ENABLED" : "DISABLED"); + } + + dataServices.add(dsObj); + } catch (Exception e) { + log.warn("Error processing data service: " + serviceName, e); + // Add basic info even if detailed processing fails + JsonObject basicDsObj = new JsonObject(); + basicDsObj.addProperty("name", serviceName); + basicDsObj.addProperty("type", "DataService"); + basicDsObj.addProperty("state", "UNKNOWN"); + dataServices.add(basicDsObj); + } + } + } catch (Exception e) { + log.error("Error collecting Data Services", e); + } + return dataServices; + } + + /** + * Collects Carbon Application information in the required heartbeat format + */ + private static JsonArray collectCarbonApps() { + JsonArray carbonApps = new JsonArray(); + try { + // Get active Carbon Applications + Collection activeApps = CappDeployer.getCarbonApps(); + for (CarbonApplication app : activeApps) { + JsonObject appObj = convertCarbonAppToHeartbeatFormat(app, "active", getRuntimeId()); + if (appObj != null) { + carbonApps.add(appObj); + } + } + + // Get faulty Carbon Applications + Collection faultyApps = CappDeployer.getFaultyCAppObjects(); + for (CarbonApplication app : faultyApps) { + JsonObject appObj = convertCarbonAppToHeartbeatFormat(app, "faulty", getRuntimeId()); + if (appObj != null) { + carbonApps.add(appObj); + } + } + + } catch (Exception e) { + log.error("Error collecting Carbon Apps", e); + } + return carbonApps; + } + + /** + * Converts a CarbonApplication to the required heartbeat format with name, + * nameIgnoreCase, and nodes structure + */ + private static JsonObject convertCarbonAppToHeartbeatFormat(CarbonApplication carbonApp, String status, + String runtimeId) { + if (carbonApp == null) { + return null; + } + + JsonObject appObj = new JsonObject(); + String appName = carbonApp.getAppName(); + + // Main structure + appObj.addProperty("name", appName); + appObj.addProperty("runtimeId", runtimeId); + appObj.addProperty("version", carbonApp.getAppVersion()); + appObj.addProperty("status", status); + + // Collect artifacts contained in this Carbon App + JsonArray artifacts = new JsonArray(); + if (carbonApp.getAppConfig() != null && + carbonApp.getAppConfig().getApplicationArtifact() != null && + carbonApp.getAppConfig().getApplicationArtifact().getDependencies() != null) { + List dependencies = carbonApp.getAppConfig().getApplicationArtifact() + .getDependencies(); + for (Artifact.Dependency dependency : dependencies) { + Artifact artifact = dependency.getArtifact(); + + if (artifact != null && artifact.getName() != null) { + JsonObject artifactObj = new JsonObject(); + artifactObj.addProperty("name", artifact.getName()); + + // Extract artifact type (remove prefix if present) + String artifactType = artifact.getType(); + if (artifactType != null && artifactType.contains("/")) { + artifactType = artifactType.split("/")[1]; + } + artifactObj.addProperty("type", artifactType); + + artifacts.add(artifactObj); + } + } + } + appObj.add("artifacts", artifacts); + return appObj; + } + + /** + * Collects Data Source information + */ + private static JsonArray collectDataSources() { + JsonArray dataSources = new JsonArray(); + try { + // Use Carbon DataSourceManager to list configured data sources + org.wso2.micro.integrator.ndatasource.core.DataSourceManager dsManager = + org.wso2.micro.integrator.ndatasource.core.DataSourceManager.getInstance(); + if (dsManager == null) { + log.warn("DataSourceManager instance is null; no data sources available"); + return dataSources; + } + + org.wso2.micro.integrator.ndatasource.core.DataSourceRepository repository = + dsManager.getDataSourceRepository(); + if (repository == null) { + log.warn("DataSourceRepository is null; no data sources available"); + return dataSources; + } + + java.util.Collection allDataSources = + repository.getAllDataSources(); + + for (org.wso2.micro.integrator.ndatasource.core.CarbonDataSource cds : allDataSources) { + try { + org.wso2.micro.integrator.ndatasource.core.DataSourceMetaInfo meta = cds.getDSMInfo(); + if (meta == null) { + continue; + } + JsonObject dsObj = new JsonObject(); + dsObj.addProperty("name", meta.getName()); + if (meta.getDefinition() != null) { + dsObj.addProperty("type", meta.getDefinition().getType()); + } else { + dsObj.addProperty("type", ""); + } + + // Try to extract driver, url (masked), and username + String driver = null; + String url = null; + String username = null; + + Object dsInstance = null; + try { + dsInstance = cds.getDSObject(); + } catch (Throwable t) { + if (log.isDebugEnabled()) { + log.debug("Unable to retrieve DSObject for datasource: " + meta.getName(), t); + } + } + + // Prefer extracting from live Tomcat JDBC DataSource + try { + if (dsInstance instanceof javax.sql.DataSource) { + if (dsInstance instanceof org.apache.tomcat.jdbc.pool.DataSource) { + org.apache.tomcat.jdbc.pool.DataSource tomcat = + (org.apache.tomcat.jdbc.pool.DataSource) dsInstance; + org.apache.tomcat.jdbc.pool.PoolConfiguration pool = tomcat.getPoolProperties(); + if (pool != null) { + if (pool.getDataSource() == null) { + // Normal JDBC pool + driver = pool.getDriverClassName(); + if (pool.getUrl() != null) { + url = org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils + .maskURLPassword(pool.getUrl()); + } + username = pool.getUsername(); + } + // If external DataSource factory is used (pool.getDataSource() != null), + // we'll fall back to reading XML definition below. + } + } + } + } catch (Throwable t) { + if (log.isDebugEnabled()) { + log.debug("Error extracting properties from live DataSource: " + meta.getName(), t); + } + } + + // Fallback: parse the XML definition to extract fields (works for external datasources too) + if ((driver == null && url == null && username == null) + && meta.getDefinition() != null + && meta.getDefinition().getDsXMLConfiguration() != null) { + try { + org.w3c.dom.Element root = + (org.w3c.dom.Element) meta.getDefinition().getDsXMLConfiguration(); + // direct children like , , + for (org.w3c.dom.Node child = root.getFirstChild(); child != null; child = child.getNextSibling()) { + if (!(child instanceof org.w3c.dom.Element)) { + continue; + } + String nodeName = child.getNodeName(); + String text = (child.getFirstChild() != null) ? child.getFirstChild().getNodeValue() : null; + if (text == null || text.trim().isEmpty()) { + continue; + } + if ("driverClassName".equals(nodeName) && driver == null) { + driver = text; + } else if ("url".equals(nodeName) && url == null) { + url = org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils + .maskURLPassword(text); + } else if ("username".equals(nodeName) && username == null) { + username = text; + } else if ("dataSourceProps".equals(nodeName)) { + // ... ... + for (org.w3c.dom.Node prop = child.getFirstChild(); prop != null; prop = prop.getNextSibling()) { + if (!(prop instanceof org.w3c.dom.Element)) { + continue; + } + org.w3c.dom.Element propEl = (org.w3c.dom.Element) prop; + org.w3c.dom.Node nameAttr = propEl.getAttributes() != null + ? propEl.getAttributes().getNamedItem("name") : null; + String propName = (nameAttr != null) ? nameAttr.getNodeValue() : null; + String propVal = (propEl.getFirstChild() != null) + ? propEl.getFirstChild().getNodeValue() : null; + if (propName == null || propVal == null) { + continue; + } + if (driver == null && ("driverClassName".equals(propName))) { + driver = propVal; + } else if (url == null && "url".equals(propName)) { + url = org.wso2.micro.integrator.ndatasource.core.utils.DataSourceUtils + .maskURLPassword(propVal); + } else if (username == null && ("username".equals(propName) || "user".equals(propName))) { + username = propVal; + } + } + } + } + } catch (Throwable t) { + if (log.isDebugEnabled()) { + log.debug("Error parsing datasource XML definition for: " + meta.getName(), t); + } + } + } + + if (driver != null) { + dsObj.addProperty("driver", driver); + } + if (url != null) { + dsObj.addProperty("url", url); + } + if (username != null) { + dsObj.addProperty("username", username); + } + + dataSources.add(dsObj); + } catch (Exception inner) { + log.debug("Skipping a datasource due to processing error", inner); + } + } + } catch (Exception e) { + log.error("Error collecting Data Sources", e); + } + return dataSources; + } + + /** + * Collects Connector information from Synapse Configuration + */ + private static JsonArray collectConnectors(SynapseConfiguration synapseConfig) { + JsonArray connectors = new JsonArray(); + try { + log.info("Starting connector collection for ICP heartbeat"); + + if (synapseConfig == null) { + log.warn("SynapseConfiguration is null, returning empty connector list"); + return connectors; + } + + // Get synapse libraries (which include connectors) + Map libraryMap = synapseConfig.getSynapseLibraries(); + + if (libraryMap == null || libraryMap.isEmpty()) { + log.info("No connectors/libraries found in SynapseConfiguration"); + return connectors; + } + + log.info("Found " + libraryMap.size() + " libraries/connectors to process"); + + int processedCount = 0; + int errorCount = 0; + + for (Map.Entry entry : libraryMap.entrySet()) { + try { + String qualifiedName = entry.getKey(); + org.apache.synapse.libraries.model.Library library = entry.getValue(); + + if (library instanceof org.apache.synapse.libraries.model.SynapseLibrary) { + org.apache.synapse.libraries.model.SynapseLibrary synapseLibrary = (org.apache.synapse.libraries.model.SynapseLibrary) library; + + JsonObject connectorObj = new JsonObject(); + connectorObj.addProperty("name", synapseLibrary.getName()); + connectorObj.addProperty("package", synapseLibrary.getPackage()); + // Add description if available + if (synapseLibrary.getDescription() != null) { + connectorObj.addProperty("description", synapseLibrary.getDescription()); + } + // Add status information + Boolean libStatus = synapseLibrary.getLibStatus(); + String status = (libStatus != null && libStatus) ? "enabled" : "disabled"; + connectorObj.addProperty("status", status); + connectors.add(connectorObj); + processedCount++; + if (log.isDebugEnabled()) { + log.debug("Processed connector: " + synapseLibrary.getName() + + " (package: " + synapseLibrary.getPackage() + ", status: " + status + ")"); + } + } else { + log.debug("Skipping non-SynapseLibrary entry: " + qualifiedName + + " (type: " + library.getClass().getSimpleName() + ")"); + } + } catch (Exception e) { + errorCount++; + log.warn("Error processing connector entry: " + entry.getKey(), e); + + // Add basic error entry + JsonObject errorConnectorObj = new JsonObject(); + errorConnectorObj.addProperty("name", "ERROR_" + entry.getKey()); + errorConnectorObj.addProperty("type", "Connector"); + errorConnectorObj.addProperty("status", "error"); + errorConnectorObj.addProperty("error", e.getMessage()); + connectors.add(errorConnectorObj); + } + } + + log.info("Connector collection completed. Processed: " + processedCount + + ", Errors: " + errorCount + ", Total connectors collected: " + connectors.size()); + + } catch (Exception e) { + log.error("Error collecting Connectors from SynapseConfiguration", e); + } + return connectors; + } + + /** + * Collects Registry Resources from MicroIntegratorRegistry + */ + private static JsonArray collectRegistryResources(SynapseConfiguration synapseConfig) { + JsonArray registryResources = new JsonArray(); + log.info("Starting registry resources collection for ICP heartbeat"); + if (synapseConfig == null) { + log.warn("SynapseConfiguration is null, returning empty registry resources list"); + return registryResources; + } + // Get the registry from synapse configuration + Registry synapseRegistry = synapseConfig.getRegistry(); + if (synapseRegistry == null) { + log.warn("No registry found in SynapseConfiguration, returning empty registry resources list"); + return registryResources; + } + if (!(synapseRegistry instanceof MicroIntegratorRegistry)) { + log.warn("Registry is not MicroIntegratorRegistry type, cannot collect resources. Type: " + + synapseRegistry.getClass().getSimpleName()); + return registryResources; + } + MicroIntegratorRegistry microIntegratorRegistry = (MicroIntegratorRegistry) synapseRegistry; + String regRoot = microIntegratorRegistry.getRegRoot(); + + if (regRoot == null || regRoot.trim().isEmpty()) { + log.warn("Registry root path is null or empty, returning empty registry resources list"); + return registryResources; + } + String registryPath = Utils.formatPath(regRoot + File.separator + "registry"); + File node = new File(registryPath); + if (node.exists() && node.isDirectory()) { + JSONArray childrenList = microIntegratorRegistry.getChildrenList(registryPath, regRoot); + for (int i = 0; i < childrenList.length(); i++) { + try { + JSONObject resource = childrenList.getJSONObject(i); + JsonObject resourceObj = new JsonObject(); + if (resource.has("name")) { + resourceObj.addProperty("name", resource.optString("name")); + } + if (resource.has("mediaType")) { + resourceObj.addProperty("type", resource.optString("mediaType")); + } + registryResources.add(resourceObj); + } catch (Exception e) { + log.warn("Error processing registry resource at index: " + i, e); + } + } + } else { + log.warn("Registry path does not exist or is not a directory: " + registryPath); + } + return registryResources; + } + +} diff --git a/deployment-icp-sample.toml b/deployment-icp-sample.toml new file mode 100644 index 0000000000..c9d95ec2f0 --- /dev/null +++ b/deployment-icp-sample.toml @@ -0,0 +1,67 @@ +# ======================================== +# WSO2 Micro Integrator - ICP Configuration Sample +# ======================================== + +# ---------------------------------------- +# New ICP Integration Configuration +# ---------------------------------------- +[server] +hostname = "localhost" +# offset = 10 + +[user_store] +type = "read_only_ldap" + +[keystore.primary] +file_name = "repository/resources/security/wso2carbon.jks" +password = "wso2carbon" +alias = "wso2carbon" +key_password = "wso2carbon" + +[truststore] +file_name = "repository/resources/security/client-truststore.jks" +password = "wso2carbon" +alias = "symmetric.key.value" +algorithm = "AES" + +# ---------------------------------------- +# ICP Configuration +# ---------------------------------------- +[icp_config] +enabled = true +# ICP connection details (only needed if enabled = true) +runtime = "mi-test" +environment = "prod" +project = "sample-project" +integration = "sample-mi-integration" +# icp_url = "https://icp-server:9443" + +# ---------------------------------------- +# Heartbeat Configuration (only used if enabled = true) +# ---------------------------------------- +# Interval between heartbeat transmissions (in seconds) +# Default: 5 seconds +# heartbeat_interval = 10 + +# ---------------------------------------- +# JWT Authentication Configuration +# ---------------------------------------- +# JWT token configuration (defaults shown) +jwt_hmac_secret = "default-secret-key-at-least-32-characters-long-for-hs256" +#jwt_issuer = "icp-runtime-jwt-issuer" +#jwt_audience = "icp-server" +#jwt_scope = "runtime_agent" +# Token validity in seconds (default: 3600 = 1 hour) +#jwt_expiry_seconds = 3600 + +# ---------------------------------------- +# Legacy Dashboard Configuration (Optional) +# Keep this for backward compatibility or fallback +# ---------------------------------------- +#[dashboard_config] +#dashboard_url = "https://dashboard-host:9743/dashboard/api/" +#group_id = "default" +#node_id = "node1" +#heartbeat_interval = 5 +#management_hostname = "mi-host.example.com" +#management_port = 9154 diff --git a/distribution/src/conf/internal-apis.xml b/distribution/src/conf/internal-apis.xml index c25cb2af85..550ad65ab3 100644 --- a/distribution/src/conf/internal-apis.xml +++ b/distribution/src/conf/internal-apis.xml @@ -51,6 +51,19 @@ Authorization, Content-Type + + + + default-secret-key-at-least-32-characters-long-for-hs256 + + + + true + * + Authorization, Content-Type + + diff --git a/distribution/src/conf/wrapper.conf b/distribution/src/conf/wrapper.conf index 0b69723957..99c96d4b98 100644 --- a/distribution/src/conf/wrapper.conf +++ b/distribution/src/conf/wrapper.conf @@ -130,4 +130,4 @@ wrapper.java.additional.49 = --add-opens=java.base/java.lang=ALL-UNNAMED wrapper.java.additional.50 = --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED wrapper.java.additional.51 = --add-opens=java.base/java.net=ALL-UNNAMED wrapper.java.additional.52 = --add-exports=jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED - +wrapper.java.additional.53 = -DenableICPApi=true diff --git a/distribution/src/resources/config-tool/templates/conf/internal-apis.xml.j2 b/distribution/src/resources/config-tool/templates/conf/internal-apis.xml.j2 index 5d1112abfb..f99c450478 100644 --- a/distribution/src/resources/config-tool/templates/conf/internal-apis.xml.j2 +++ b/distribution/src/resources/config-tool/templates/conf/internal-apis.xml.j2 @@ -105,6 +105,24 @@ {{management_api.cors.allowed_headers}} + {% if icp_config is defined and icp_config.enabled == true %} + + + + {% if icp_config.jwt_hmac_secret is defined %} + {{icp_config.jwt_hmac_secret}} + {% else %} + default-secret-key-at-least-32-characters-long-for-hs256 + {% endif %} + + + + true + * + Authorization, Content-Type + + + {% endif %} {% if internal_apis.file_user_store.enable == true %} diff --git a/distribution/src/scripts/micro-integrator.bat b/distribution/src/scripts/micro-integrator.bat index 1c074b1798..701072cf7e 100644 --- a/distribution/src/scripts/micro-integrator.bat +++ b/distribution/src/scripts/micro-integrator.bat @@ -227,7 +227,7 @@ if "%profileSet%" == "false" ( set profile=-Dprofile=micro-integrator-default ) -set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dlogfiles.home="%CARBON_HOME%\repository\logs" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=net.sf.saxon.xpath.XPathFactoryImpl -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableLivenessProbe=true -DenableManagementApi=true -DskipStartupExtensions=false -Dpolyglot.engine.WarnInterpreterOnly=false -Dautomation.mode.seq.car.name="%CAR_NAME%" -Dlog4j2.contextSelector="org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" -Dorg.ops4j.pax.logging.logReaderEnabled=false -Dorg.ops4j.pax.logging.eventAdminEnabled=false %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" --add-exports=jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true +set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dlogfiles.home="%CARBON_HOME%\repository\logs" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=net.sf.saxon.xpath.XPathFactoryImpl -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableLivenessProbe=true -DenableManagementApi=true -DenableICPApi=true -DskipStartupExtensions=false -Dpolyglot.engine.WarnInterpreterOnly=false -Dautomation.mode.seq.car.name="%CAR_NAME%" -Dlog4j2.contextSelector="org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" -Dorg.ops4j.pax.logging.logReaderEnabled=false -Dorg.ops4j.pax.logging.eventAdminEnabled=false %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" --add-exports=jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true :runJava rem echo JAVA_HOME environment variable is set to %JAVA_HOME% diff --git a/distribution/src/scripts/micro-integrator.sh b/distribution/src/scripts/micro-integrator.sh index c014fd477a..20c8a16208 100644 --- a/distribution/src/scripts/micro-integrator.sh +++ b/distribution/src/scripts/micro-integrator.sh @@ -390,6 +390,7 @@ do -DenableReadinessProbe=true \ -DenableLivenessProbe=true \ -DenableManagementApi=true \ + -DenableICPApi=true \ -DskipStartupExtensions=false \ -Dautomation.mode.seq.car.name="$CAR_NAME" \ -Dpolyglot.engine.WarnInterpreterOnly=false \ diff --git a/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.bat b/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.bat index 3e88d9e239..62f204498d 100644 --- a/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.bat +++ b/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.bat @@ -195,7 +195,7 @@ if "%profileSet%" == "false" ( set MI_HOST=localhost set MI_PORT=8290 -set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableManagementApi=true %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" +set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableManagementApi=true -DenableICPApi=true %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" :runJava rem echo JAVA_HOME environment variable is set to %JAVA_HOME% diff --git a/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.sh b/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.sh index 2d9b951789..52121b2a26 100755 --- a/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.sh +++ b/integration/mediation-tests/tests-other/src/test/resources/artifacts/ESB/serviceCatalog/micro-integrator.sh @@ -373,6 +373,7 @@ do -Dproperties.file.path=default \ -DenableReadinessProbe=true \ -DenableManagementApi=true \ + -DenableICPApi=true \ $NODE_PARAMS \ -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" \ org.wso2.micro.integrator.bootstrap.Bootstrap $* diff --git a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/passthru/transport/enableCorrelation/micro-integrator.sh b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/passthru/transport/enableCorrelation/micro-integrator.sh index 0ad5ba74ee..c302f16df2 100644 --- a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/passthru/transport/enableCorrelation/micro-integrator.sh +++ b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/passthru/transport/enableCorrelation/micro-integrator.sh @@ -373,6 +373,7 @@ do -DenableLivenessProbe=true \ -DenableCorrelationLogs=true \ -DenableManagementApi=true \ + -DenableICPApi=true \ -DskipStartupExtensions=false \ -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \ -Dorg.ops4j.pax.logging.logReaderEnabled=false \ diff --git a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.bat b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.bat index 48e2beb9ec..32710c3554 100644 --- a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.bat +++ b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.bat @@ -197,7 +197,7 @@ if "%profileSet%" == "false" ( set profile=-Dprofile=micro-integrator-default ) -set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dlogfiles.home="%CARBON_HOME%\repository\logs" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=net.sf.saxon.xpath.XPathFactoryImpl -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableLivenessProbe=true -DenableManagementApi=true -DskipStartupExtensions=false -Dautomation.mode.seq.car.name="%CAR_NAME%" -Dlog4j2.contextSelector="org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" -Dorg.ops4j.pax.logging.logReaderEnabled=false -Djsch.server_host_key=ssh-rsa,ssh-dss -Djsch.client_pubkey=ssh-rsa,ssh-dss -Dorg.ops4j.pax.logging.eventAdminEnabled=false %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" +set CMD_LINE_ARGS=-Xbootclasspath/a:%CARBON_XBOOTCLASSPATH% -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath="%CARBON_HOME%\repository\logs\heap-dump.hprof" -Dcom.sun.management.jmxremote -classpath %CARBON_CLASSPATH% %JAVA_OPTS% -Djava.endorsed.dirs=%JAVA_ENDORSED% -DandesConfig=broker.xml -Dcarbon.registry.root=/ -Dcarbon.home="%CARBON_HOME%" -Dlogfiles.home="%CARBON_HOME%\repository\logs" -Dwso2.server.standalone=true -Djava.command="%JAVA_HOME%\bin\java" -Djava.opts="%JAVA_OPTS%" -Djava.io.tmpdir="%CARBON_HOME%\tmp" -Dcatalina.base="%CARBON_HOME%\wso2\lib\tomcat" -Dwso2.carbon.xml="%CARBON_HOME%\conf\carbon.xml" -Dwso2.registry.xml="%CARBON_HOME%\conf\registry.xml" -Dwso2.user.mgt.xml="%CARBON_HOME%\conf\user-mgt.xml" -Dwso2.transports.xml="%CARBON_HOME%\conf\mgt-transports.xml" -Djava.util.logging.config.file="%CARBON_HOME%\conf\log4j.properties" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -DNonUserCoreMode=true -DNonRegistryMode=true -Dcarbon.logs.path="%CARBON_HOME%\repository\logs" -Dcomponents.repo="%CARBON_HOME%\wso2\components\plugins" -Dcarbon.config.dir.path="%CARBON_HOME%\conf" -Dcarbon.components.dir.path="%CARBON_HOME%\wso2\components" -Dcarbon.dropins.dir.path="%CARBON_HOME%\dropins" -Dcarbon.external.lib.dir.path="%CARBON_HOME%\lib" -Dcarbon.patches.dir.path="%CARBON_HOME%\patches" -Dcarbon.internal.lib.dir.path="%CARBON_HOME%\wso2\lib" -Dconf.location="%CARBON_HOME%\conf" -Dcom.atomikos.icatch.file="%CARBON_HOME%\wso2\lib\transactions.properties" -Dei.extendedURIBasedDispatcher=org.wso2.micro.integrator.core.handlers.IntegratorStatefulHandler -Dcom.atomikos.icatch.hide_init_file_path="true" -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true -Dcom.sun.jndi.ldap.connect.pool.authentication=simple -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 -Dorg.terracotta.quartz.skipUpdateCheck=true -Dcarbon.classpath=%CARBON_CLASSPATH% -Dfile.encoding=UTF8 -Dlogger.server.name="micro-integrator" -Dqpid.conf="\conf\advanced" -Dproperties.file.path=default -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=net.sf.saxon.xpath.XPathFactoryImpl -DavoidConfigHashRead=true -DenableReadinessProbe=true -DenableLivenessProbe=true -DenableManagementApi=true -DenableICPApi=true -DskipStartupExtensions=false -Dautomation.mode.seq.car.name="%CAR_NAME%" -Dlog4j2.contextSelector="org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" -Dorg.ops4j.pax.logging.logReaderEnabled=false -Djsch.server_host_key=ssh-rsa,ssh-dss -Djsch.client_pubkey=ssh-rsa,ssh-dss -Dorg.ops4j.pax.logging.eventAdminEnabled=false %JAVA_VER_BASED_OPTS% %profile% -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" :runJava rem echo JAVA_HOME environment variable is set to %JAVA_HOME% diff --git a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.sh b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.sh index bfa2908c4b..db2f5a2e75 100755 --- a/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.sh +++ b/integration/mediation-tests/tests-patches/src/test/resources/artifacts/ESB/vfs/micro-integrator.sh @@ -379,6 +379,7 @@ do -DenableReadinessProbe=true \ -DenableLivenessProbe=true \ -DenableManagementApi=true \ + -DenableICPApi=true \ -DskipStartupExtensions=false \ -Dautomation.mode.seq.car.name="$CAR_NAME" \ -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \ diff --git a/integration/mediation-tests/tests-platform/tests-userstore/src/test/resources/artifacts/ESB/server/bin/micro-integrator.sh b/integration/mediation-tests/tests-platform/tests-userstore/src/test/resources/artifacts/ESB/server/bin/micro-integrator.sh index b8800d84eb..4959f6b448 100755 --- a/integration/mediation-tests/tests-platform/tests-userstore/src/test/resources/artifacts/ESB/server/bin/micro-integrator.sh +++ b/integration/mediation-tests/tests-platform/tests-userstore/src/test/resources/artifacts/ESB/server/bin/micro-integrator.sh @@ -369,6 +369,7 @@ do -Dproperties.file.path=default \ -DenableReadinessProbe=true \ -DenableManagementApi=true \ + -DenableICPApi=true \ $NODE_PARAMS \ -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="*" \ org.wso2.micro.integrator.bootstrap.Bootstrap $*