| /** |
| * 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 java.util.concurrent.ExecutorService; |
| 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. Use platform to connect to local JVM. |
| */ |
| @UriPath |
| private String serverURL; |
| |
| /** |
| * The domain for the mbean you're connecting to |
| */ |
| @UriParam @Metadata(required = "true") |
| private String objectDomain; |
| |
| /** |
| * 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; |
| |
| /** |
| * The attribute to observe for the monitor bean or consumer. |
| */ |
| @UriParam |
| private String observedAttribute; |
| |
| /** |
| * The frequency to poll the bean to check the monitor (monitor types only). |
| */ |
| @UriParam(defaultValue = "10000") |
| private long granularityPeriod = 10000; |
| |
| /** |
| * The type of monitor to create. One of string, gauge, counter (monitor types only). |
| */ |
| @UriParam(enums = "counter,gauge,string") |
| private String monitorType; |
| |
| /** |
| * Initial threshold for the monitor. The value must exceed this before notifications are fired (counter monitor only). |
| */ |
| @UriParam(label = "counter") |
| private int initThreshold; |
| |
| /** |
| * The amount to increment the threshold after it's been exceeded (counter monitor only). |
| */ |
| @UriParam(label = "counter") |
| private int offset; |
| |
| /** |
| * The value at which the counter is reset to zero (counter monitor only). |
| */ |
| @UriParam(label = "counter") |
| private int modulus; |
| |
| /** |
| * If true, then the value reported in the notification is the difference from the threshold as opposed to the value itself (counter and gauge monitor only). |
| */ |
| @UriParam(label = "counter,gauge") |
| private boolean differenceMode; |
| |
| /** |
| * If true, the gauge will fire a notification when the high threshold is exceeded (gauge monitor only). |
| */ |
| @UriParam(label = "gauge") |
| private boolean notifyHigh; |
| |
| /** |
| * If true, the gauge will fire a notification when the low threshold is exceeded (gauge monitor only). |
| */ |
| @UriParam(label = "gauge") |
| private boolean notifyLow; |
| |
| /** |
| * Value for the gauge's high threshold (gauge monitor only). |
| */ |
| @UriParam(label = "gauge") |
| private Double thresholdHigh; |
| |
| /** |
| * Value for the gauge's low threshold (gauge monitor only). |
| */ |
| @UriParam(label = "gauge") |
| private Double thresholdLow; |
| |
| /** |
| * If true, will fire a notification when the string attribute differs from the string to compare (string monitor or consumer). |
| * By default the consumer will notify match if observed attribute and string to compare has been configured. |
| */ |
| @UriParam(label = "consumer,string") |
| private boolean notifyDiffer; |
| |
| /** |
| * If true, will fire a notification when the string attribute matches the string to compare (string monitor or consumer). |
| * By default the consumer will notify match if observed attribute and string to compare has been configured. |
| */ |
| @UriParam(label = "consumer,string") |
| private boolean notifyMatch; |
| |
| /** |
| * Value for attribute to compare (string monitor or consumer). |
| * By default the consumer will notify match if observed attribute and string to compare has been configured. |
| */ |
| @UriParam(label = "consumer,string") |
| private String stringToCompare; |
| |
| /** |
| * 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"; |
| |
| /** |
| * Credentials for making a remote connection |
| */ |
| @UriParam(label = "security", secret = true) |
| private String user; |
| |
| /** |
| * Credentials for making a remote connection |
| */ |
| @UriParam(label = "security", secret = true) |
| private String password; |
| |
| /** |
| * Reference to a bean that implements the NotificationFilter. |
| */ |
| @UriParam(label = "advanced") |
| private NotificationFilter notificationFilter; |
| |
| /** |
| * 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; |
| |
| /** |
| * 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(label = "advanced", defaultValue = "true") |
| private boolean testConnectionOnStartup = true; |
| |
| /** |
| * 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; |
| |
| /** |
| * The number of seconds to wait before attempting to retry establishment of the initial connection or attempt to reconnect a lost connection |
| */ |
| @UriParam(label = "advanced", defaultValue = "10") |
| private int reconnectDelay = 10; |
| |
| /** |
| * 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; |
| |
| /** |
| * To use a custom shared thread pool for the consumers. By default each consume has their own thread-pool to process and route notifications. |
| */ |
| @UriParam(label = "advanced") |
| private ExecutorService executorService; |
| |
| /** |
| * 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; |
| } |
| |
| public ExecutorService getExecutorService() { |
| return executorService; |
| } |
| |
| public void setExecutorService(ExecutorService executorService) { |
| this.executorService = executorService; |
| } |
| |
| 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<>(); |
| ht.putAll(getObjectProperties()); |
| objectName = new ObjectName(getObjectDomain(), ht); |
| } |
| return objectName; |
| } |
| } |