/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.jbossas7;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.rhq.core.domain.configuration.ConfigurationUpdateStatus.FAILURE;
import static org.rhq.core.domain.configuration.ConfigurationUpdateStatus.NOCHANGE;
import static org.rhq.core.domain.configuration.ConfigurationUpdateStatus.SUCCESS;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.modules.plugins.jbossas7.json.Address;
import org.rhq.modules.plugins.jbossas7.json.ComplexResult;
import org.rhq.modules.plugins.jbossas7.json.CompositeOperation;
import org.rhq.modules.plugins.jbossas7.json.Operation;
import org.rhq.modules.plugins.jbossas7.json.ReadResource;
import org.rhq.modules.plugins.jbossas7.json.Result;
import org.rhq.modules.plugins.jbossas7.json.WriteAttribute;
/**
* Handle Datasources (possibly jdbc-driver) related stuff
* @author Heiko W. Rupp
*/
public class DatasourceComponent extends BaseComponent<DatasourcesComponent> {
private static final String ALLOW_MULTIPLE_USERS_ATTRIBUTE = "allow-multiple-users";
private static final String TRACK_STATEMENTS_ATTRIBUTE = "track-statements";
private static final String MAX_POOL_SIZE_ATTRIBUTE = "max-pool-size";
private static final String MIN_POOL_SIZE_ATTRIBUTE = "min-pool-size";
/**
* list of metrics where we can expect expresion value instead of just number (such expression has to be resolved)
*/
private static final Set<String> EXPRESSION_METRICS = Collections.unmodifiableSet(new HashSet<String>(Arrays
.asList(MAX_POOL_SIZE_ATTRIBUTE, MIN_POOL_SIZE_ATTRIBUTE)));
private static final Set<String> UNSET_FORBIDDEN_ATTRIBUTES = Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList(MAX_POOL_SIZE_ATTRIBUTE, MIN_POOL_SIZE_ATTRIBUTE, "pool-prefill", "pool-use-strict-min",
"blocking-timeout-wait-millis", "idle-timeout-minutes", "background-validation-millis",
"background-validation")));
static final String ENABLED_ATTRIBUTE = "enabled";
static final String ENABLE_OPERATION = "enable";
static final String DISABLE_OPERATION = "disable";
static final String CONNECTION_PROPERTIES_ATTRIBUTE = "*1";
static final String XA_DATASOURCE_PROPERTIES_ATTRIBUTE = "*2";
@Override
public OperationResult invokeOperation(String name, Configuration parameters) throws Exception {
if (name.equals(ENABLE_OPERATION)) {
Operation operation = new Operation(ENABLE_OPERATION, getAddress());
Boolean persistent = Boolean.valueOf(parameters.getSimpleValue("persistent", TRUE.toString()));
operation.addAdditionalProperty("persistent", persistent);
Result res = getASConnection().execute(operation);
if (res.isSuccess()) {
return new OperationResult();
} else {
OperationResult operationResult = new OperationResult();
operationResult.setErrorMessage(res.getFailureDescription());
return operationResult;
}
}
if (name.equals(DISABLE_OPERATION)) {
Operation operation = new Operation(DISABLE_OPERATION, getAddress());
boolean allowResourceServiceRestart = Boolean.parseBoolean(parameters.getSimpleValue(
"allow-resource-service-restart", FALSE.toString()));
if (allowResourceServiceRestart) {
operation.allowResourceServiceRestart();
}
Result res = getASConnection().execute(operation);
if (res.isSuccess()) {
return new OperationResult();
} else {
OperationResult operationResult = new OperationResult();
operationResult.setErrorMessage(res.getFailureDescription());
return operationResult;
}
}
return super.invokeOperation(name, parameters);
}
@Override
public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> requests) throws Exception {
Set<MeasurementScheduleRequest> metrics = new HashSet<MeasurementScheduleRequest>(requests.size());
for (MeasurementScheduleRequest request : requests) {
if (request.getName().equals("connectionAvailable")) {
report.addData(getConnectionAvailable(request));
} else if (request.getName().equals(MAX_POOL_SIZE_ATTRIBUTE)) {
getRCAsMetric(report, request);
} else if (request.getName().equals(MIN_POOL_SIZE_ATTRIBUTE)) {
getRCAsMetric(report, request);
} else {
metrics.add(request);
}
}
/*
* Remainder here are metrics that can be read from the resource.
* Those
*/
ReadResource op = new ReadResource(address);
op.includeRuntime(true);
op.recursive(true);
ComplexResult res = getASConnection().executeComplex(op);
if (!res.isSuccess())
return;
Map<String, Object> results = new HashMap<String, Object>();
@SuppressWarnings("unchecked")
Map<String, Map<String, Object>> statistics = (Map<String, Map<String, Object>>) res.getResult().get(
"statistics");
if (statistics != null) {
results.putAll(statistics.get("pool"));
results.putAll(statistics.get("jdbc"));
for (MeasurementScheduleRequest metric : metrics) {
String name = metric.getName();
Object o = results.get(name);
if (o != null) {
Double val;
if(o instanceof Integer) {
val = ((Integer) o).doubleValue();
} else if(o instanceof Double) {
val = (Double) o;
} else {
String tmp = (String) o;
val = Double.valueOf(tmp);
}
MeasurementDataNumeric data = new MeasurementDataNumeric(metric, val);
report.addData(data);
}
}
}
}
@Override
public void updateResourceConfiguration(final ConfigurationUpdateReport configurationUpdateReport) {
ResourceType resourceType = context.getResourceType();
ConfigurationDefinition configDef = resourceType.getResourceConfigurationDefinition();
Configuration newConfig = configurationUpdateReport.getConfiguration();
// remove special property being possibly sent from server in case EAP requires reload/restart
newConfig.remove("__OOB");
// 1. First of all, read the current configuration
ConfigurationLoadDelegate readDelegate = new ConfigurationLoadDelegate(configDef, getASConnection(), address,
includeRuntime);
Configuration currentConfig;
try {
currentConfig = readDelegate.loadResourceConfiguration();
} catch (Exception e) {
getLog().error("Could not read current configuration before update", e);
configurationUpdateReport.setStatus(FAILURE);
configurationUpdateReport.setErrorMessage("Could not read current configuration before update: "
+ ThrowableUtil.getRootMessage(e));
return;
}
// 2. We will wrap all property-simple and connection properties changes in a composite operation
CompositeOperation updateOperation = new CompositeOperation();
// 3. Capture property-simple changes
Map<String, PropertySimple> newConfigSimpleProperties = newConfig.getSimpleProperties();
Map<String, PropertySimple> currentConfigSimpleProperties = currentConfig.getSimpleProperties();
Set<String> allSimplePropertyNames = new HashSet<String>(newConfigSimpleProperties.size()
+ currentConfigSimpleProperties.size());
allSimplePropertyNames.addAll(newConfigSimpleProperties.keySet());
allSimplePropertyNames.addAll(currentConfigSimpleProperties.keySet());
// Read-only
allSimplePropertyNames.remove(ENABLED_ATTRIBUTE);
if (getServerComponent().getServerPluginConfiguration().getProductType() == JBossProductType.AS) {
// Only supported on EAP
allSimplePropertyNames.remove(ALLOW_MULTIPLE_USERS_ATTRIBUTE);
allSimplePropertyNames.remove(TRACK_STATEMENTS_ATTRIBUTE);
}
for (String simplePropertyName : allSimplePropertyNames) {
PropertySimple newConfigPropertySimple = newConfigSimpleProperties.get(simplePropertyName);
String newConfigPropertySimpleValue = newConfigPropertySimple == null ? null : newConfigPropertySimple
.getStringValue();
PropertySimple currentConfigPropertySimple = currentConfigSimpleProperties.get(simplePropertyName);
String currentConfigPropertySimpleValue = currentConfigPropertySimple == null ? null
: currentConfigPropertySimple.getStringValue();
boolean canUnset = !UNSET_FORBIDDEN_ATTRIBUTES.contains(simplePropertyName);
if (newConfigPropertySimpleValue == null) {
if (currentConfigPropertySimpleValue != null) {
String val;
if (canUnset) {
val = null;
} else {
val = configDef.getPropertyDefinitionSimple(simplePropertyName).getDefaultValue();
}
updateOperation.addStep(new WriteAttribute(getAddress(), simplePropertyName, val));
}
} else if (!newConfigPropertySimpleValue.equals(currentConfigPropertySimpleValue)) {
updateOperation.addStep(new WriteAttribute(getAddress(), simplePropertyName,
newConfigPropertySimpleValue));
}
}
// 4. Capture connection property changes
String connPropAttributeNameOnServer, connPropPluginConfigPropertyName, keyName;
if (isXADatasourceResource(resourceType)) {
connPropAttributeNameOnServer = "xa-datasource-properties";
connPropPluginConfigPropertyName = XA_DATASOURCE_PROPERTIES_ATTRIBUTE;
keyName = "key";
} else {
connPropAttributeNameOnServer = "connection-properties";
connPropPluginConfigPropertyName = CONNECTION_PROPERTIES_ATTRIBUTE;
keyName = "pname";
}
Map<String, String> newConfigConnectionProperties = getConnectionPropertiesAsMap(
newConfig.getList(connPropPluginConfigPropertyName), keyName);
Map<String, String> currentConfigConnectionProperties = getConnectionPropertiesAsMap(
currentConfig.getList(connPropPluginConfigPropertyName), keyName);
Set<String> allConnectionPropertyNames = new HashSet<String>(newConfigConnectionProperties.size()
+ currentConfigConnectionProperties.size());
allConnectionPropertyNames.addAll(newConfigConnectionProperties.keySet());
allConnectionPropertyNames.addAll(currentConfigConnectionProperties.keySet());
for (String connectionPropertyName : allConnectionPropertyNames) {
Address propertyAddress = new Address(getAddress());
propertyAddress.add(connPropAttributeNameOnServer, connectionPropertyName);
String newConfigConnectionPropertyValue = newConfigConnectionProperties.get(connectionPropertyName);
String currentConfigConnectionPropertyValue = currentConfigConnectionProperties.get(connectionPropertyName);
if (newConfigConnectionPropertyValue == null) {
updateOperation.addStep(new Operation("remove", propertyAddress));
} else if (currentConfigConnectionPropertyValue == null) {
Operation addOperation = new Operation("add", propertyAddress);
addOperation.addAdditionalProperty("value", newConfigConnectionPropertyValue);
updateOperation.addStep(addOperation);
} else if (!newConfigConnectionPropertyValue.equals(currentConfigConnectionPropertyValue)) {
updateOperation.addStep(new Operation("remove", propertyAddress));
Operation addOperation = new Operation("add", propertyAddress);
addOperation.addAdditionalProperty("value", newConfigConnectionPropertyValue);
updateOperation.addStep(addOperation);
}
}
// 5. Update config if needed
if (updateOperation.numberOfSteps() > 0) {
Result res = getASConnection().execute(updateOperation);
if (res.isSuccess()) {
configurationUpdateReport.setStatus(SUCCESS);
} else {
configurationUpdateReport.setStatus(FAILURE);
configurationUpdateReport.setErrorMessage(res.getFailureDescription());
}
} else {
configurationUpdateReport.setStatus(NOCHANGE);
}
}
private void getRCAsMetric(MeasurementReport report, MeasurementScheduleRequest request) {
ReadMetricResult result = getMetricValue(report, request, EXPRESSION_METRICS);
if (result.equals(ReadMetricResult.Null)) { // server
Double val = Double.valueOf(-1);
if (request.getName().equals(MAX_POOL_SIZE_ATTRIBUTE))
val = Double.valueOf(20); // The default value
else if (request.getName().equals(MIN_POOL_SIZE_ATTRIBUTE))
val = Double.valueOf(0); // The default value
MeasurementDataNumeric data = new MeasurementDataNumeric(request, val);
report.addData(data);
}
}
private MeasurementDataTrait getConnectionAvailable(MeasurementScheduleRequest request) {
Result res = getASConnection().execute(new Operation("test-connection-in-pool", getAddress()));
return new MeasurementDataTrait(request, String.valueOf(res.isSuccess()));
}
static boolean isXADatasourceResource(ResourceType resourceType) {
return resourceType.getName().toLowerCase().contains("xa");
}
static Map<String, String> getConnectionPropertiesAsMap(PropertyList listPropertyWrapper, String keyName) {
if (listPropertyWrapper == null) {
return Collections.emptyMap();
}
List<Property> propertyList = listPropertyWrapper.getList();
if (propertyList.size() == 0) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<String, String>(propertyList.size());
for (Property p : propertyList) {
PropertyMap map = (PropertyMap) p;
String key = map.getSimpleValue(keyName, null);
String value = map.getSimpleValue("value", null);
if (key == null || value == null) {
continue;
}
result.put(key, value);
}
return result;
}
}