/**
* 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
*
* 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.apache.camel.component.jmx;
import java.util.Hashtable;
import java.util.Map;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationFilter;
import javax.management.ObjectName;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultEndpoint;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.util.ObjectHelper;
/**
* The jmx component allows to receive JMX notifications.
*
* Endpoint that describes a connection to an mbean.
* <p/>
* The component can connect to the local platform mbean server with the following URI:
* <p/>
* <code>jmx://platform?options</code>
* <p/>
* A remote mbean server url can be provided following the initial JMX scheme like so:
* <p/>
* <code>jmx:service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi?options</code>
* <p/>
* You can append query options to the URI in the following format, ?options=value&option2=value&...
*/
@UriEndpoint(firstVersion = "2.6.0", scheme = "jmx", title = "JMX", syntax = "jmx:serverURL", consumerOnly = true, consumerClass = JMXConsumer.class, label = "monitoring")
public class JMXEndpoint extends DefaultEndpoint {
// error messages as constants so they can be asserted on from unit tests
protected static final String ERR_PLATFORM_SERVER = "Monitor type consumer only supported on platform server.";
protected static final String ERR_THRESHOLD_LOW = "ThresholdLow must be set when monitoring a gauge attribute.";
protected static final String ERR_THRESHOLD_HIGH = "ThresholdHigh must be set when monitoring a gauge attribute.";
protected static final String ERR_GAUGE_NOTIFY = "One or both of NotifyHigh and NotifyLow must be true when monitoring a gauge attribute.";
protected static final String ERR_STRING_NOTIFY = "One or both of NotifyDiffer and NotifyMatch must be true when monitoring a string attribute.";
protected static final String ERR_STRING_TO_COMPARE = "StringToCompare must be specified when monitoring a string attribute.";
protected static final String ERR_OBSERVED_ATTRIBUTE = "Observed attribute must be specified";
/**
* server url comes from the remaining endpoint
*/
@UriPath
private String serverURL;
/**
* URI Property: [monitor types only] The attribute to observe for the monitor bean.
*/
@UriParam
private String observedAttribute;
/**
* URI Property: [monitor types only] The frequency to poll the bean to check the monitor.
*/
@UriParam(defaultValue = "10000")
private long granularityPeriod = 10000;
/**
* URI Property: [monitor types only] The type of monitor to create. One of string, gauge, counter.
*/
@UriParam(enums = "counter,gauge,string")
private String monitorType;
/**
* URI Property: [counter monitor only] Initial threshold for the monitor. The value must exceed this before notifications are fired.
*/
@UriParam(label = "counter")
private int initThreshold;
/**
* URI Property: [counter monitor only] The amount to increment the threshold after it's been exceeded.
*/
@UriParam(label = "counter")
private int offset;
/**
* URI Property: [counter monitor only] The value at which the counter is reset to zero
*/
@UriParam(label = "counter")
private int modulus;
/**
* URI Property: [counter + gauge monitor only] If true, then the value reported in the notification is the difference from the threshold as opposed to the value itself.
*/
@UriParam(label = "counter,gauge")
private boolean differenceMode;
/**
* URI Property: [gauge monitor only] If true, the gauge will fire a notification when the high threshold is exceeded
*/
@UriParam(label = "gauge")
private boolean notifyHigh;
/**
* URI Property: [gauge monitor only] If true, the gauge will fire a notification when the low threshold is exceeded
*/
@UriParam(label = "gauge")
private boolean notifyLow;
/**
* URI Property: [gauge monitor only] Value for the gauge's high threshold
*/
@UriParam(label = "gauge")
private Double thresholdHigh;
/**
* URI Property: [gauge monitor only] Value for the gauge's low threshold
*/
@UriParam(label = "gauge")
private Double thresholdLow;
/**
* URI Property: [string monitor only] If true, the string monitor will fire a notification when the string attribute differs from the string to compare.
*/
@UriParam(label = "string")
private boolean notifyDiffer;
/**
* URI Property: [string monitor only] If true, the string monitor will fire a notification when the string attribute matches the string to compare.
*/
@UriParam(label = "string")
private boolean notifyMatch;
/**
* URI Property: [string monitor only] Value for the string monitor's string to compare.
*/
@UriParam(label = "string")
private String stringToCompare;
/**
* URI Property: Format for the message body. Either "xml" or "raw". If xml, the notification is serialized to xml. If raw, then the raw java object is set as the body.
*/
@UriParam(defaultValue = "xml", enums = "xml,raw")
private String format = "xml";
/**
* URI Property: credentials for making a remote connection
*/
@UriParam(label = "security", secret = true)
private String user;
/**
* URI Property: credentials for making a remote connection
*/
@UriParam(label = "security", secret = true)
private String password;
/**
* URI Property: The domain for the mbean you're connecting to
*/
@UriParam @Metadata(required = "true")
private String objectDomain;
/**
* URI Property: The name key for the mbean you're connecting to. This value is mutually exclusive with the object properties that get passed.
*/
@UriParam
private String objectName;
/**
* URI Property: Reference to a bean that implements the NotificationFilter.
*/
@UriParam(label = "advanced")
private NotificationFilter notificationFilter;
/**
* URI Property: Value to handback to the listener when a notification is received. This value will be put in the message header with the key "jmx.handback"
*/
@UriParam(label = "advanced")
private Object handback;
/**
* URI Property: If true the consumer will throw an exception if unable to establish the JMX connection upon startup. If false, the consumer will attempt
* to establish the JMX connection every 'x' seconds until the connection is made -- where 'x' is the configured reconnectionDelay
*/
@UriParam(defaultValue = "true", label = "advanced")
private boolean testConnectionOnStartup = true;
/**
* URI Property: If true the consumer will attempt to reconnect to the JMX server when any connection failure occurs. The consumer will attempt
* to re-establish the JMX connection every 'x' seconds until the connection is made-- where 'x' is the configured reconnectionDelay
*/
@UriParam(label = "advanced")
private boolean reconnectOnConnectionFailure;
/**
* URI Property: The number of seconds to wait before attempting to retry establishment of the initial connection or attempt to reconnect a lost connection
*/
@UriParam(defaultValue = "10", label = "advanced")
private int reconnectDelay = 10;
/**
* URI Property: properties for the object name. These values will be used if the objectName param is not set
*/
@UriParam(label = "advanced", prefix = "key.", multiValue = true)
private Map<String, String> objectProperties;
/**
* cached object name that was built from the objectName param or the hashtable
*/
private transient ObjectName jmxObjectName;
public JMXEndpoint(String aEndpointUri, JMXComponent aComponent) {
super(aEndpointUri, aComponent);
}
public Consumer createConsumer(Processor aProcessor) throws Exception {
// validate that all of the endpoint is configured properly
if (getMonitorType() != null) {
if (!isPlatformServer()) {
throw new IllegalArgumentException(ERR_PLATFORM_SERVER);
}
if (ObjectHelper.isEmpty(getObservedAttribute())) {
throw new IllegalArgumentException(ERR_OBSERVED_ATTRIBUTE);
}
if (getMonitorType().equals("string")) {
if (ObjectHelper.isEmpty(getStringToCompare())) {
throw new IllegalArgumentException(ERR_STRING_TO_COMPARE);
}
if (!isNotifyDiffer() && !isNotifyMatch()) {
throw new IllegalArgumentException(ERR_STRING_NOTIFY);
}
} else if (getMonitorType().equals("gauge")) {
if (!isNotifyHigh() && !isNotifyLow()) {
throw new IllegalArgumentException(ERR_GAUGE_NOTIFY);
}
if (getThresholdHigh() == null) {
throw new IllegalArgumentException(ERR_THRESHOLD_HIGH);
}
if (getThresholdLow() == null) {
throw new IllegalArgumentException(ERR_THRESHOLD_LOW);
}
}
JMXMonitorConsumer answer = new JMXMonitorConsumer(this, aProcessor);
configureConsumer(answer);
return answer;
} else {
// shouldn't need any other validation.
JMXConsumer answer = new JMXConsumer(this, aProcessor);
configureConsumer(answer);
return answer;
}
}
public Producer createProducer() throws Exception {
throw new UnsupportedOperationException("producing JMX notifications is not supported");
}
public boolean isSingleton() {
return false;
}
public String getFormat() {
return format;
}
public void setFormat(String aFormat) {
format = aFormat;
}
public boolean isXML() {
return "xml".equals(getFormat());
}
public boolean isPlatformServer() {
return "platform".equals(getServerURL());
}
public String getUser() {
return user;
}
public void setUser(String aUser) {
user = aUser;
}
public String getPassword() {
return password;
}
public void setPassword(String aPassword) {
password = aPassword;
}
public String getObjectDomain() {
return objectDomain;
}
public void setObjectDomain(String aObjectDomain) {
objectDomain = aObjectDomain;
}
public String getObjectName() {
return objectName;
}
public void setObjectName(String aObjectName) {
if (getObjectProperties() != null) {
throw new IllegalArgumentException("Cannot set both objectName and objectProperties");
}
objectName = aObjectName;
}
protected String getServerURL() {
return serverURL;
}
protected void setServerURL(String aServerURL) {
serverURL = aServerURL;
}
public NotificationFilter getNotificationFilter() {
return notificationFilter;
}
public void setNotificationFilter(NotificationFilter aFilterRef) {
notificationFilter = aFilterRef;
}
public Object getHandback() {
return handback;
}
public void setHandback(Object aHandback) {
handback = aHandback;
}
public Map<String, String> getObjectProperties() {
return objectProperties;
}
/**
* Setter for the ObjectProperties is either called by reflection when
* processing the URI or manually by the component.
* <p/>
* If the URI contained a value with a reference like "objectProperties=#myHashtable"
* then the Hashtable will be set in place.
* <p/>
* If there are extra properties that begin with "key." then the component will
* create a Hashtable with these values after removing the "key." prefix.
*/
public void setObjectProperties(Map<String, String> objectProperties) {
if (getObjectName() != null) {
throw new IllegalArgumentException("Cannot set both objectName and objectProperties");
}
this.objectProperties = objectProperties;
}
protected ObjectName getJMXObjectName() throws MalformedObjectNameException {
if (jmxObjectName == null) {
ObjectName on = buildObjectName();
setJMXObjectName(on);
}
return jmxObjectName;
}
protected void setJMXObjectName(ObjectName aCachedObjectName) {
jmxObjectName = aCachedObjectName;
}
public String getObservedAttribute() {
return observedAttribute;
}
public void setObservedAttribute(String aObservedAttribute) {
observedAttribute = aObservedAttribute;
}
public long getGranularityPeriod() {
return granularityPeriod;
}
public void setGranularityPeriod(long aGranularityPeriod) {
granularityPeriod = aGranularityPeriod;
}
public String getMonitorType() {
return monitorType;
}
public void setMonitorType(String aMonitorType) {
monitorType = aMonitorType;
}
public int getInitThreshold() {
return initThreshold;
}
public void setInitThreshold(int aInitThreshold) {
initThreshold = aInitThreshold;
}
public int getOffset() {
return offset;
}
public void setOffset(int aOffset) {
offset = aOffset;
}
public int getModulus() {
return modulus;
}
public void setModulus(int aModulus) {
modulus = aModulus;
}
public boolean isDifferenceMode() {
return differenceMode;
}
public void setDifferenceMode(boolean aDifferenceMode) {
differenceMode = aDifferenceMode;
}
public boolean isNotifyHigh() {
return notifyHigh;
}
public void setNotifyHigh(boolean aNotifyHigh) {
notifyHigh = aNotifyHigh;
}
public boolean isNotifyLow() {
return notifyLow;
}
public void setNotifyLow(boolean aNotifyLow) {
notifyLow = aNotifyLow;
}
public Double getThresholdHigh() {
return thresholdHigh;
}
public void setThresholdHigh(Double aThresholdHigh) {
thresholdHigh = aThresholdHigh;
}
public Double getThresholdLow() {
return thresholdLow;
}
public void setThresholdLow(Double aThresholdLow) {
thresholdLow = aThresholdLow;
}
public boolean isNotifyDiffer() {
return notifyDiffer;
}
public void setNotifyDiffer(boolean aNotifyDiffer) {
notifyDiffer = aNotifyDiffer;
}
public boolean isNotifyMatch() {
return notifyMatch;
}
public void setNotifyMatch(boolean aNotifyMatch) {
notifyMatch = aNotifyMatch;
}
public String getStringToCompare() {
return stringToCompare;
}
public void setStringToCompare(String aStringToCompare) {
stringToCompare = aStringToCompare;
}
public boolean getTestConnectionOnStartup() {
return this.testConnectionOnStartup;
}
public void setTestConnectionOnStartup(boolean testConnectionOnStartup) {
this.testConnectionOnStartup = testConnectionOnStartup;
}
public boolean getReconnectOnConnectionFailure() {
return this.reconnectOnConnectionFailure;
}
public void setReconnectOnConnectionFailure(boolean reconnectOnConnectionFailure) {
this.reconnectOnConnectionFailure = reconnectOnConnectionFailure;
}
public int getReconnectDelay() {
return this.reconnectDelay;
}
public void setReconnectDelay(int reconnectDelay) {
this.reconnectDelay = reconnectDelay;
}
private ObjectName buildObjectName() throws MalformedObjectNameException {
ObjectName objectName;
if (getObjectProperties() == null) {
StringBuilder sb = new StringBuilder(getObjectDomain()).append(':').append("name=").append(getObjectName());
objectName = new ObjectName(sb.toString());
} else {
Hashtable<String, String> ht = new Hashtable<String, String>();
ht.putAll(getObjectProperties());
objectName = new ObjectName(getObjectDomain(), ht);
}
return objectName;
}
}