001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jmx.access;
018
019import java.io.IOException;
020import java.net.MalformedURLException;
021import java.util.Arrays;
022import java.util.Map;
023
024import javax.management.MBeanServerConnection;
025import javax.management.ObjectName;
026import javax.management.remote.JMXServiceURL;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.beans.factory.DisposableBean;
032import org.springframework.beans.factory.InitializingBean;
033import org.springframework.jmx.JmxException;
034import org.springframework.jmx.MBeanServerNotFoundException;
035import org.springframework.jmx.support.NotificationListenerHolder;
036import org.springframework.lang.Nullable;
037import org.springframework.util.CollectionUtils;
038
039/**
040 * Registrar object that associates a specific {@link javax.management.NotificationListener}
041 * with one or more MBeans in an {@link javax.management.MBeanServer}
042 * (typically via a {@link javax.management.MBeanServerConnection}).
043 *
044 * @author Juergen Hoeller
045 * @since 2.5.2
046 * @see #setServer
047 * @see #setMappedObjectNames
048 * @see #setNotificationListener
049 */
050public class NotificationListenerRegistrar extends NotificationListenerHolder
051                implements InitializingBean, DisposableBean {
052
053        /** Logger available to subclasses. */
054        protected final Log logger = LogFactory.getLog(getClass());
055
056        private final ConnectorDelegate connector = new ConnectorDelegate();
057
058        @Nullable
059        private MBeanServerConnection server;
060
061        @Nullable
062        private JMXServiceURL serviceUrl;
063
064        @Nullable
065        private Map<String, ?> environment;
066
067        @Nullable
068        private String agentId;
069
070        @Nullable
071        private ObjectName[] actualObjectNames;
072
073
074        /**
075         * Set the {@code MBeanServerConnection} used to connect to the
076         * MBean which all invocations are routed to.
077         */
078        public void setServer(MBeanServerConnection server) {
079                this.server = server;
080        }
081
082        /**
083         * Specify the environment for the JMX connector.
084         * @see javax.management.remote.JMXConnectorFactory#connect(javax.management.remote.JMXServiceURL, java.util.Map)
085         */
086        public void setEnvironment(@Nullable Map<String, ?> environment) {
087                this.environment = environment;
088        }
089
090        /**
091         * Allow Map access to the environment to be set for the connector,
092         * with the option to add or override specific entries.
093         * <p>Useful for specifying entries directly, for example via
094         * "environment[myKey]". This is particularly useful for
095         * adding or overriding entries in child bean definitions.
096         */
097        @Nullable
098        public Map<String, ?> getEnvironment() {
099                return this.environment;
100        }
101
102        /**
103         * Set the service URL of the remote {@code MBeanServer}.
104         */
105        public void setServiceUrl(String url) throws MalformedURLException {
106                this.serviceUrl = new JMXServiceURL(url);
107        }
108
109        /**
110         * Set the agent id of the {@code MBeanServer} to locate.
111         * <p>Default is none. If specified, this will result in an
112         * attempt being made to locate the attendant MBeanServer, unless
113         * the {@link #setServiceUrl "serviceUrl"} property has been set.
114         * @see javax.management.MBeanServerFactory#findMBeanServer(String)
115         * <p>Specifying the empty String indicates the platform MBeanServer.
116         */
117        public void setAgentId(String agentId) {
118                this.agentId = agentId;
119        }
120
121
122        @Override
123        public void afterPropertiesSet() {
124                if (getNotificationListener() == null) {
125                        throw new IllegalArgumentException("Property 'notificationListener' is required");
126                }
127                if (CollectionUtils.isEmpty(this.mappedObjectNames)) {
128                        throw new IllegalArgumentException("Property 'mappedObjectName' is required");
129                }
130                prepare();
131        }
132
133        /**
134         * Registers the specified {@code NotificationListener}.
135         * <p>Ensures that an {@code MBeanServerConnection} is configured and attempts
136         * to detect a local connection if one is not supplied.
137         */
138        public void prepare() {
139                if (this.server == null) {
140                        this.server = this.connector.connect(this.serviceUrl, this.environment, this.agentId);
141                }
142                try {
143                        this.actualObjectNames = getResolvedObjectNames();
144                        if (this.actualObjectNames != null) {
145                                if (logger.isDebugEnabled()) {
146                                        logger.debug("Registering NotificationListener for MBeans " + Arrays.asList(this.actualObjectNames));
147                                }
148                                for (ObjectName actualObjectName : this.actualObjectNames) {
149                                        this.server.addNotificationListener(
150                                                        actualObjectName, getNotificationListener(), getNotificationFilter(), getHandback());
151                                }
152                        }
153                }
154                catch (IOException ex) {
155                        throw new MBeanServerNotFoundException(
156                                        "Could not connect to remote MBeanServer at URL [" + this.serviceUrl + "]", ex);
157                }
158                catch (Exception ex) {
159                        throw new JmxException("Unable to register NotificationListener", ex);
160                }
161        }
162
163        /**
164         * Unregisters the specified {@code NotificationListener}.
165         */
166        @Override
167        public void destroy() {
168                try {
169                        if (this.server != null && this.actualObjectNames != null) {
170                                for (ObjectName actualObjectName : this.actualObjectNames) {
171                                        try {
172                                                this.server.removeNotificationListener(
173                                                                actualObjectName, getNotificationListener(), getNotificationFilter(), getHandback());
174                                        }
175                                        catch (Exception ex) {
176                                                if (logger.isDebugEnabled()) {
177                                                        logger.debug("Unable to unregister NotificationListener", ex);
178                                                }
179                                        }
180                                }
181                        }
182                }
183                finally {
184                        this.connector.close();
185                }
186        }
187
188}