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