ARTEMIS-4025 - trap failure to apply properties as errors and report via the broker status in a configuration/properties/errors status field
This commit is contained in:
parent
b900a1e4bd
commit
03ef286cc8
|
@ -360,6 +360,55 @@ public final class JsonUtil {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static JsonObject mergeAndUpdate(JsonObject source, JsonObject update) {
|
||||
// all immutable so we need to create new merged instance
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
for (Map.Entry<String, JsonValue> entry : source.entrySet()) {
|
||||
jsonObjectBuilder.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// apply any updates
|
||||
for (String updateKey : update.keySet()) {
|
||||
JsonValue updatedValue = update.get(updateKey);
|
||||
if (updatedValue != null) {
|
||||
if (!source.containsKey(updateKey)) {
|
||||
jsonObjectBuilder.add(updateKey, updatedValue);
|
||||
} else {
|
||||
// recursively merge into new value
|
||||
if (updatedValue.getValueType() == JsonValue.ValueType.OBJECT) {
|
||||
jsonObjectBuilder.add(updateKey, mergeAndUpdate(source.getJsonObject(updateKey), updatedValue.asJsonObject()));
|
||||
|
||||
} else if (updatedValue.getValueType() == JsonValue.ValueType.ARRAY) {
|
||||
JsonArrayBuilder jsonArrayBuilder = JsonLoader.createArrayBuilder();
|
||||
|
||||
// update wins
|
||||
JsonArray updatedArrayValue = update.getJsonArray(updateKey);
|
||||
JsonArray sourceArrayValue = source.getJsonArray(updateKey);
|
||||
|
||||
for (int i = 0; i < updatedArrayValue.size(); i++) {
|
||||
if (i < sourceArrayValue.size()) {
|
||||
JsonValue element = updatedArrayValue.get(i);
|
||||
if (element.getValueType() == JsonValue.ValueType.OBJECT) {
|
||||
jsonArrayBuilder.add(mergeAndUpdate(sourceArrayValue.getJsonObject(i), updatedArrayValue.getJsonObject(i)));
|
||||
} else {
|
||||
// take the update
|
||||
jsonArrayBuilder.add(element);
|
||||
}
|
||||
} else {
|
||||
jsonArrayBuilder.add(updatedArrayValue.get(i));
|
||||
}
|
||||
}
|
||||
jsonObjectBuilder.add(updateKey, jsonArrayBuilder.build());
|
||||
} else {
|
||||
// update wins!
|
||||
jsonObjectBuilder.add(updateKey, updatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jsonObjectBuilder.build();
|
||||
}
|
||||
|
||||
private static class NullableJsonString implements JsonValue, JsonString {
|
||||
|
||||
private final String value;
|
||||
|
|
|
@ -127,4 +127,89 @@ public class JsonUtilTest {
|
|||
|
||||
Assert.assertEquals(input, notTruncated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeEqual() {
|
||||
|
||||
final byte[] bytesA = {0x0a, 0x0b};
|
||||
final byte[] bytesB = {0x1a, 0x1b};
|
||||
final byte[] bytesAB = {0x0a, 0x0b, 0x1a, 0x1b};
|
||||
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("a", "a", jsonObjectBuilder);
|
||||
JsonUtil.addToObject("byteArray", bytesA, jsonObjectBuilder);
|
||||
JsonUtil.addToObject("null", null, jsonObjectBuilder);
|
||||
|
||||
JsonObject sourceOne = jsonObjectBuilder.build();
|
||||
|
||||
JsonObjectBuilder jsonTargetObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("a", "a", jsonTargetObjectBuilder);
|
||||
JsonUtil.addToObject("byteArray", bytesB, jsonTargetObjectBuilder);
|
||||
JsonUtil.addToObject("null", null, jsonTargetObjectBuilder);
|
||||
|
||||
JsonObject sourceTwo = jsonTargetObjectBuilder.build();
|
||||
|
||||
JsonObjectBuilder jsonMergedObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("a", "a", jsonMergedObjectBuilder);
|
||||
// update wins
|
||||
JsonUtil.addToObject("byteArray", bytesB, jsonMergedObjectBuilder);
|
||||
JsonUtil.addToObject("null", null, jsonMergedObjectBuilder);
|
||||
|
||||
JsonObject mergedExpected = jsonMergedObjectBuilder.build();
|
||||
|
||||
Assert.assertEquals(mergedExpected, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeArray() {
|
||||
|
||||
final byte[] bytesA = {0x0a, 0x0b};
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
|
||||
jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("byteArray", bytesA, jsonObjectBuilder);
|
||||
|
||||
JsonObject sourceOne = jsonObjectBuilder.build();
|
||||
|
||||
JsonObjectBuilder jsonTargetObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("byteArray", bytesA, jsonTargetObjectBuilder);
|
||||
|
||||
JsonObject sourceTwo = jsonTargetObjectBuilder.build();
|
||||
|
||||
JsonObjectBuilder jsonMergedObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("byteArray", bytesA, jsonMergedObjectBuilder);
|
||||
|
||||
JsonObject mergedExpected = jsonMergedObjectBuilder.build();
|
||||
|
||||
Assert.assertEquals(mergedExpected, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeDuplicate() {
|
||||
|
||||
// merge duplicate attribute value, two wins
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("dup", "a", jsonObjectBuilder);
|
||||
JsonObject sourceOne = jsonObjectBuilder.build();
|
||||
|
||||
JsonObjectBuilder jsonTargetObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("dup", "b", jsonTargetObjectBuilder);
|
||||
JsonObject sourceTwo = jsonTargetObjectBuilder.build();
|
||||
|
||||
Assert.assertEquals(sourceTwo, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeEmpty() {
|
||||
|
||||
// merge into empty
|
||||
JsonObject sourceOne = JsonLoader.createObjectBuilder().build();
|
||||
|
||||
JsonObjectBuilder jsonTargetObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
JsonUtil.addToObject("dup", "b", jsonTargetObjectBuilder);
|
||||
JsonObject sourceTwo = jsonTargetObjectBuilder.build();
|
||||
|
||||
Assert.assertEquals(sourceTwo, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.apache.activemq.artemis.core.config.impl;
|
||||
|
||||
import java.beans.IndexedPropertyDescriptor;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -52,6 +53,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.JsonUtil;
|
||||
import org.apache.activemq.artemis.api.core.Pair;
|
||||
import org.apache.activemq.artemis.api.core.QueueConfiguration;
|
||||
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||
|
@ -99,8 +101,12 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlug
|
|||
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
|
||||
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
|
||||
import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
|
||||
import org.apache.activemq.artemis.json.JsonArrayBuilder;
|
||||
import org.apache.activemq.artemis.json.JsonObject;
|
||||
import org.apache.activemq.artemis.json.JsonObjectBuilder;
|
||||
import org.apache.activemq.artemis.utils.ByteUtil;
|
||||
import org.apache.activemq.artemis.utils.Env;
|
||||
import org.apache.activemq.artemis.utils.JsonLoader;
|
||||
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.XMLUtil;
|
||||
|
@ -109,6 +115,7 @@ import org.apache.activemq.artemis.utils.uri.BeanSupport;
|
|||
import org.apache.commons.beanutils.BeanUtilsBean;
|
||||
import org.apache.commons.beanutils.ConvertUtilsBean;
|
||||
import org.apache.commons.beanutils.Converter;
|
||||
import org.apache.commons.beanutils.DynaBean;
|
||||
import org.apache.commons.beanutils.MappedPropertyDescriptor;
|
||||
import org.apache.commons.beanutils.MethodUtils;
|
||||
import org.apache.commons.beanutils.PropertyUtilsBean;
|
||||
|
@ -116,6 +123,7 @@ import org.apache.commons.beanutils.expression.DefaultResolver;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import org.apache.commons.beanutils.expression.Resolver;
|
||||
|
||||
public class ConfigurationImpl implements Configuration, Serializable {
|
||||
|
||||
|
@ -403,7 +411,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
* Parent folder for all data folders.
|
||||
*/
|
||||
private File artemisInstance;
|
||||
private String status;
|
||||
private transient volatile JsonObject status = JsonLoader.createObjectBuilder().build();
|
||||
|
||||
@Override
|
||||
public String getJournalRetentionDirectory() {
|
||||
|
@ -538,7 +546,146 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
|
||||
public void populateWithProperties(Map<String, Object> beanProperties) throws InvocationTargetException, IllegalAccessException {
|
||||
CollectionAutoFillPropertiesUtil autoFillCollections = new CollectionAutoFillPropertiesUtil();
|
||||
BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), autoFillCollections);
|
||||
BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), autoFillCollections) {
|
||||
// override to treat missing properties as errors, not skip as the default impl does
|
||||
@Override
|
||||
public void setProperty(final Object bean, String name, final Object value) throws InvocationTargetException, IllegalAccessException {
|
||||
{
|
||||
if (logger.isTraceEnabled()) {
|
||||
final StringBuilder sb = new StringBuilder(" setProperty(");
|
||||
sb.append(bean);
|
||||
sb.append(", ");
|
||||
sb.append(name);
|
||||
sb.append(", ");
|
||||
if (value == null) {
|
||||
sb.append("<NULL>");
|
||||
} else if (value instanceof String) {
|
||||
sb.append((String) value);
|
||||
} else if (value instanceof String[]) {
|
||||
final String[] values = (String[]) value;
|
||||
sb.append('[');
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(values[i]);
|
||||
}
|
||||
sb.append(']');
|
||||
} else {
|
||||
sb.append(value.toString());
|
||||
}
|
||||
sb.append(')');
|
||||
logger.trace(sb.toString());
|
||||
}
|
||||
|
||||
// Resolve any nested expression to get the actual target bean
|
||||
Object target = bean;
|
||||
final Resolver resolver = getPropertyUtils().getResolver();
|
||||
while (resolver.hasNested(name)) {
|
||||
try {
|
||||
target = getPropertyUtils().getProperty(target, resolver.next(name));
|
||||
if (target == null) {
|
||||
throw new InvocationTargetException(null, "Resolved nested property for:" + name + ", on: " + bean + " was null");
|
||||
}
|
||||
name = resolver.remove(name);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new InvocationTargetException(e, "No getter for property:" + name + ", on: " + bean);
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(" Target bean = " + target);
|
||||
logger.trace(" Target name = " + name);
|
||||
}
|
||||
|
||||
// Declare local variables we will require
|
||||
final String propName = resolver.getProperty(name); // Simple name of target property
|
||||
Class<?> type = null; // Java type of target property
|
||||
final int index = resolver.getIndex(name); // Indexed subscript value (if any)
|
||||
final String key = resolver.getKey(name); // Mapped key value (if any)
|
||||
|
||||
// Calculate the property type
|
||||
if (target instanceof DynaBean) {
|
||||
throw new InvocationTargetException(null, "Cannot determine DynaBean type to access: " + name + " on: " + target);
|
||||
} else if (target instanceof Map) {
|
||||
type = Object.class;
|
||||
} else if (target != null && target.getClass().isArray() && index >= 0) {
|
||||
type = Array.get(target, index).getClass();
|
||||
} else {
|
||||
PropertyDescriptor descriptor = null;
|
||||
try {
|
||||
descriptor = getPropertyUtils().getPropertyDescriptor(target, name);
|
||||
if (descriptor == null) {
|
||||
throw new InvocationTargetException(null, "No accessor method descriptor for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new InvocationTargetException(e, "Failed to get descriptor for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
if (descriptor instanceof MappedPropertyDescriptor) {
|
||||
if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
|
||||
throw new InvocationTargetException(null, "No mapped Write method for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
type = ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
|
||||
} else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
|
||||
if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
|
||||
throw new InvocationTargetException(null, "No indexed Write method for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
type = ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
|
||||
} else if (index >= 0 && List.class.isAssignableFrom(descriptor.getPropertyType())) {
|
||||
type = Object.class;
|
||||
} else if (key != null) {
|
||||
if (descriptor.getReadMethod() == null) {
|
||||
throw new InvocationTargetException(null, "No Read method for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
type = (value == null) ? Object.class : value.getClass();
|
||||
} else {
|
||||
if (descriptor.getWriteMethod() == null) {
|
||||
throw new InvocationTargetException(null, "No Write method for: " + name + " on: " + target.getClass());
|
||||
}
|
||||
type = descriptor.getPropertyType();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the specified value to the required type
|
||||
Object newValue = null;
|
||||
if (type.isArray() && (index < 0)) { // Scalar value into array
|
||||
if (value == null) {
|
||||
final String[] values = new String[1];
|
||||
values[0] = null;
|
||||
newValue = getConvertUtils().convert(values, type);
|
||||
} else if (value instanceof String) {
|
||||
newValue = getConvertUtils().convert(value, type);
|
||||
} else if (value instanceof String[]) {
|
||||
newValue = getConvertUtils().convert((String[]) value, type);
|
||||
} else {
|
||||
newValue = convert(value, type);
|
||||
}
|
||||
} else if (type.isArray()) { // Indexed value into array
|
||||
if (value instanceof String || value == null) {
|
||||
newValue = getConvertUtils().convert((String) value, type.getComponentType());
|
||||
} else if (value instanceof String[]) {
|
||||
newValue = getConvertUtils().convert(((String[]) value)[0], type.getComponentType());
|
||||
} else {
|
||||
newValue = convert(value, type.getComponentType());
|
||||
}
|
||||
} else { // Value into scalar
|
||||
if (value instanceof String) {
|
||||
newValue = getConvertUtils().convert((String) value, type);
|
||||
} else if (value instanceof String[]) {
|
||||
newValue = getConvertUtils().convert(((String[]) value)[0], type);
|
||||
} else {
|
||||
newValue = convert(value, type);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the setter method
|
||||
try {
|
||||
getPropertyUtils().setProperty(target, name, newValue);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw new InvocationTargetException(e, "Cannot set: " + propName + " on: " + target.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
autoFillCollections.setBeanUtilsBean(beanUtils);
|
||||
// nested property keys delimited by . and enclosed by '"' if they key's themselves contain dots
|
||||
beanUtils.getPropertyUtils().setResolver(new SurroundResolver(getBrokerPropertiesKeySurround(beanProperties)));
|
||||
|
@ -599,7 +746,46 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
|
||||
BeanSupport.customise(beanUtils);
|
||||
|
||||
beanUtils.populate(this, beanProperties);
|
||||
logger.debug("populate:" + this + ", " + beanProperties);
|
||||
|
||||
HashMap<String, String> errors = new HashMap<>();
|
||||
// Loop through the property name/value pairs to be set
|
||||
for (final Map.Entry<String, ? extends Object> entry : beanProperties.entrySet()) {
|
||||
// Identify the property name and value(s) to be assigned
|
||||
final String name = entry.getKey();
|
||||
try {
|
||||
// Perform the assignment for this property
|
||||
beanUtils.setProperty(this, name, entry.getValue());
|
||||
} catch (InvocationTargetException invocationTargetException) {
|
||||
logger.trace("failed to populate property key: " + name, invocationTargetException);
|
||||
Throwable toLog = invocationTargetException;
|
||||
if (invocationTargetException.getCause() != null) {
|
||||
toLog = invocationTargetException.getCause();
|
||||
}
|
||||
trackError(errors, entry, toLog);
|
||||
|
||||
} catch (Exception oops) {
|
||||
trackError(errors, entry, oops);
|
||||
}
|
||||
}
|
||||
updateStatus(errors);
|
||||
}
|
||||
|
||||
private void trackError(HashMap<String, String> errors, Map.Entry<String,?> entry, Throwable oops) {
|
||||
logger.debug("failed to populate property entry(" + entry.toString() + "), reason: " + oops.getLocalizedMessage(), oops);
|
||||
errors.put(entry.toString(), oops.getLocalizedMessage());
|
||||
}
|
||||
|
||||
private void updateStatus(HashMap<String, String> errors) {
|
||||
|
||||
JsonArrayBuilder errorsObjectArray = JsonLoader.createArrayBuilder();
|
||||
for (Map.Entry<String, String> entry : errors.entrySet()) {
|
||||
errorsObjectArray.add(JsonLoader.createObjectBuilder().add("value", entry.getKey()).add("reason", entry.getValue()));
|
||||
}
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
jsonObjectBuilder.add("properties", JsonLoader.createObjectBuilder().add("errors", errorsObjectArray));
|
||||
|
||||
status = JsonUtil.mergeAndUpdate(status, jsonObjectBuilder.build());
|
||||
}
|
||||
|
||||
private String getBrokerPropertiesKeySurround(Map<String, Object> propertiesToApply) {
|
||||
|
@ -2852,13 +3038,14 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return status;
|
||||
public synchronized String getStatus() {
|
||||
return status.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
public synchronized void setStatus(String status) {
|
||||
JsonObject update = JsonUtil.readJsonObject(status);
|
||||
this.status = JsonUtil.mergeAndUpdate(this.status, update);
|
||||
}
|
||||
|
||||
// extend property utils with ability to auto-fill and locate from collections
|
||||
|
@ -2904,7 +3091,14 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
Object resolved = getNestedProperty(bean, name);
|
||||
Object resolved = null;
|
||||
|
||||
try {
|
||||
resolved = getNestedProperty(bean, name);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
// to avoid it being swallowed by caller wrap
|
||||
throw new InvocationTargetException(e, "Cannot access property with key: " + name);
|
||||
}
|
||||
|
||||
return trackCollectionOrMap(name, resolved, bean);
|
||||
}
|
||||
|
@ -3015,7 +3209,12 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
// create one and initialise with name
|
||||
try {
|
||||
Object instance = candidate.getParameterTypes()[candidate.getParameterCount() - 1].getDeclaredConstructor().newInstance();
|
||||
beanUtilsBean.setProperty(instance, "name", name);
|
||||
|
||||
try {
|
||||
beanUtilsBean.setProperty(instance, "name", name);
|
||||
} catch (Throwable ignored) {
|
||||
// for maps a name attribute is not mandatory
|
||||
}
|
||||
|
||||
// this is always going to be a little hacky b/c our config is not natively property friendly
|
||||
if (instance instanceof TransportConfiguration) {
|
||||
|
|
|
@ -4499,7 +4499,7 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
|
|||
}
|
||||
checkStarted();
|
||||
|
||||
return server.getConfiguration().getStatus();
|
||||
return server.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -167,6 +167,8 @@ public interface ActiveMQServer extends ServiceComponent {
|
|||
|
||||
CriticalAnalyzer getCriticalAnalyzer();
|
||||
|
||||
void updateStatus(String component, String statusJson);
|
||||
|
||||
/**
|
||||
* it will release hold a lock for the activation.
|
||||
*/
|
||||
|
|
|
@ -403,6 +403,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
|||
}
|
||||
};
|
||||
|
||||
private final ServerStatus serverStatus;
|
||||
|
||||
public ActiveMQServerImpl() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
@ -481,6 +483,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
|||
|
||||
this.serviceRegistry = serviceRegistry == null ? new ServiceRegistryImpl() : serviceRegistry;
|
||||
|
||||
this.serverStatus = new ServerStatus(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -608,6 +611,16 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
|||
propertiesFileUrl = fileUrltoBrokerProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return serverStatus.asJson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(String component, String statusJson) {
|
||||
serverStatus.update(component, statusJson);
|
||||
}
|
||||
|
||||
private void internalStart() throws Exception {
|
||||
if (state != SERVER_STATE.STOPPED) {
|
||||
logger.debug("Server already started!");
|
||||
|
@ -615,6 +628,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
|||
}
|
||||
|
||||
configuration.parseProperties(propertiesFileUrl);
|
||||
updateStatus("configuration", configuration.getStatus());
|
||||
|
||||
initializeExecutorServices();
|
||||
|
||||
|
@ -4388,6 +4402,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
|||
configurationReloadDeployed.set(false);
|
||||
if (isActive()) {
|
||||
configuration.parseProperties(propertiesFileUrl);
|
||||
updateStatus("configuration", configuration.getStatus());
|
||||
deployReloadableConfigFromConfiguration();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.activemq.artemis.core.server.impl;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.JsonUtil;
|
||||
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||
import org.apache.activemq.artemis.json.JsonObject;
|
||||
import org.apache.activemq.artemis.json.JsonObjectBuilder;
|
||||
import org.apache.activemq.artemis.utils.JsonLoader;
|
||||
|
||||
public class ServerStatus {
|
||||
|
||||
private final ActiveMQServerImpl server;
|
||||
private final HashMap<String, String> immutableStateValues = new HashMap<>();
|
||||
private JsonObject globalStatus = JsonLoader.createObjectBuilder().build();
|
||||
|
||||
public ServerStatus(ActiveMQServerImpl activeMQServer) {
|
||||
this.server = activeMQServer;
|
||||
immutableStateValues.put("version", server.getVersion().getFullVersion());
|
||||
}
|
||||
|
||||
public synchronized String asJson() {
|
||||
updateServerStatus();
|
||||
return globalStatus.toString();
|
||||
}
|
||||
|
||||
private synchronized void updateServerStatus() {
|
||||
HashMap<String, String> snapshotOfServerStatusAttributes = new HashMap<>();
|
||||
snapshotOfServerStatusAttributes.putAll(immutableStateValues);
|
||||
snapshotOfServerStatusAttributes.put("identity", server.getIdentity());
|
||||
SimpleString nodeId = server.getNodeID();
|
||||
snapshotOfServerStatusAttributes.put("nodeId", nodeId == null ? null : nodeId.toString());
|
||||
snapshotOfServerStatusAttributes.put("uptime", server.getUptime());
|
||||
snapshotOfServerStatusAttributes.put("state", server.getState().toString());
|
||||
|
||||
update("server", JsonUtil.toJsonObject(snapshotOfServerStatusAttributes));
|
||||
}
|
||||
|
||||
public synchronized void update(String component, String statusJson) {
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
jsonObjectBuilder.add(component, JsonUtil.readJsonObject(statusJson));
|
||||
globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build());
|
||||
}
|
||||
|
||||
public synchronized void update(String component, JsonObject componentStatus) {
|
||||
JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder();
|
||||
jsonObjectBuilder.add(component, componentStatus);
|
||||
globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build());
|
||||
}
|
||||
|
||||
}
|
|
@ -644,6 +644,7 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
|
|||
}
|
||||
}
|
||||
}
|
||||
properties.remove("status"); // this is not a simple symmetric property
|
||||
// now parse
|
||||
configuration.parsePrefixedProperties(properties, null);
|
||||
|
||||
|
@ -1289,6 +1290,55 @@ public class ConfigurationImplTest extends ActiveMQTestBase {
|
|||
Assert.assertEquals(SimpleString.toSimpleString("moreImportant"), configuration.getAddressSettings().get("Name.With.Dots").getExpiryAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusOnErrorApplyingProperties() throws Exception {
|
||||
ConfigurationImpl configuration = new ConfigurationImpl();
|
||||
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.put("clusterConfigurations.cc.bonkers", "bla");
|
||||
|
||||
properties.put("notValid.#.expiryAddress", "sharedExpiry");
|
||||
properties.put("addressSettings.#.bla", "bla");
|
||||
properties.put("addressSettings.#.expiryAddress", "good");
|
||||
|
||||
String SHA = "34311";
|
||||
// status field json blob gives possibility for two-way interaction
|
||||
// this value is reflected back but can be augmented
|
||||
properties.put("status", "{ \"properties\": { \"sha\": \"" + SHA + "\"}}");
|
||||
|
||||
configuration.parsePrefixedProperties(properties, null);
|
||||
|
||||
String jsonStatus = configuration.getStatus();
|
||||
|
||||
// errors reported
|
||||
assertTrue(jsonStatus.contains("notValid"));
|
||||
assertTrue(jsonStatus.contains("Unknown"));
|
||||
assertTrue(jsonStatus.contains("bonkers"));
|
||||
assertTrue(jsonStatus.contains("bla"));
|
||||
|
||||
// input status reflected
|
||||
assertTrue(jsonStatus.contains(SHA));
|
||||
// only errors reported, good property goes unmentioned
|
||||
assertFalse(jsonStatus.contains("good"));
|
||||
|
||||
// apply again with only good values, new sha.... verify no errors
|
||||
properties.clear();
|
||||
|
||||
String UPDATED_SHA = "66666";
|
||||
// status field json blob gives possibility for two-way interaction
|
||||
// this value is reflected back but can be augmented
|
||||
properties.put("status", "{ \"properties\": { \"sha\": \"" + UPDATED_SHA + "\"}}");
|
||||
properties.put("addressSettings.#.expiryAddress", "changed");
|
||||
|
||||
configuration.parsePrefixedProperties(properties, null);
|
||||
|
||||
jsonStatus = configuration.getStatus();
|
||||
|
||||
assertTrue(jsonStatus.contains(UPDATED_SHA));
|
||||
assertFalse(jsonStatus.contains(SHA));
|
||||
}
|
||||
|
||||
/**
|
||||
* To test ARTEMIS-926
|
||||
* @throws Throwable
|
||||
|
|
|
@ -95,8 +95,6 @@ public class ConfigurationTest extends ActiveMQTestBase {
|
|||
|
||||
Assert.assertTrue(propsFile.exists());
|
||||
|
||||
System.out.println("props: " + propsFile.getAbsolutePath());
|
||||
|
||||
ActiveMQServer server = getActiveMQServer("duplicate-queues.xml");
|
||||
server.setProperties(propsFile.getAbsolutePath());
|
||||
try {
|
||||
|
@ -127,6 +125,10 @@ public class ConfigurationTest extends ActiveMQTestBase {
|
|||
// verify round trip apply
|
||||
Assert.assertTrue(server.getActiveMQServerControl().getStatus().contains("2"));
|
||||
|
||||
// verify some server attributes
|
||||
Assert.assertNotNull(server.getActiveMQServerControl().getStatus().contains("version"));
|
||||
Assert.assertNotNull(server.getActiveMQServerControl().getStatus().contains("uptime"));
|
||||
|
||||
} finally {
|
||||
try {
|
||||
server.stop();
|
||||
|
|
Loading…
Reference in New Issue