001package org.javasimon.jmx;
002
003import org.javasimon.Counter;
004import org.javasimon.Manager;
005import org.javasimon.Simon;
006import org.javasimon.Stopwatch;
007import org.javasimon.callback.CallbackSkeleton;
008
009import java.lang.management.ManagementFactory;
010import java.util.HashSet;
011import java.util.Iterator;
012import java.util.Set;
013
014import javax.management.JMException;
015import javax.management.MBeanServer;
016import javax.management.ObjectName;
017
018/**
019 * Callback that registers MXBeans for Simons after their creation. It is
020 * advisable to register the callback as soon as possible otherwise MX Beans
021 * for some Simons may not be created. Class can be extended in order to
022 * override {@link #constructObjectName(Simon)}.
023 *
024 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
025 */
026public class JmxRegisterCallback extends CallbackSkeleton {
027
028        /** Domain part of the JMX object name - protected for subclasses. */
029        protected String domain;
030
031        /** MBean server instance specified for this callback (or default platform one) - protected for subclasses. */
032        protected MBeanServer mBeanServer;
033
034        /** Names of all beans registered for separate Simons */
035        private Set<String> registeredNames = new HashSet<>();
036
037        /** Whether all existing Simons from Manager should be registered after callback is added to a Manager. */
038        private boolean registerExisting;
039
040        private Manager manager;
041
042        /**
043         * Default constructor uses default MBeanServer.
044         *
045         * @param domain domain part of the object name
046         */
047        public JmxRegisterCallback(String domain) {
048                this(ManagementFactory.getPlatformMBeanServer(), domain);
049        }
050
051        /**
052         * Constructor using specific MBeanServer.
053         *
054         * @param mBeanServer specific MBeanServer
055         * @param domain domain part of the object name
056         */
057        public JmxRegisterCallback(MBeanServer mBeanServer, String domain) {
058                assert domain != null && !(domain.isEmpty());
059                this.mBeanServer = mBeanServer;
060                this.domain = domain;
061        }
062
063        @Override
064        public synchronized void initialize(Manager manager) {
065                if (this.manager != null) {
066                        throw new IllegalStateException("Callback was already initialized");
067                }
068                this.manager = manager;
069                if (registerExisting) {
070                        for (Simon simon : manager.getSimons(null)) {
071                                register(simon);
072                        }
073                }
074        }
075
076        /**
077         * After Simon is created respective MX bean is registered for it according to its type.
078         *
079         * @param simon created Simon
080         */
081        @Override
082        public final void onSimonCreated(Simon simon) {
083                if (simon.getName() == null) {
084                        return;
085                }
086                register(simon);
087        }
088
089        /**
090         * When the Simon is destroyed, its MX bean is unregistered.
091         *
092         * @param simon destroyed Simon
093         */
094        @Override
095        public final void onSimonDestroyed(Simon simon) {
096                String name = constructObjectName(simon);
097                unregisterSimon(name);
098        }
099
100        private synchronized void unregisterSimon(String name) {
101                try {
102                        ObjectName objectName = new ObjectName(name);
103                        mBeanServer.unregisterMBean(objectName);
104                        registeredNames.remove(name);
105                        onManagerMessage("Unregistered Simon with the name: " + objectName);
106                } catch (JMException e) {
107                        onManagerWarning("JMX unregistration failed for: " + name, e);
108                }
109        }
110
111        /** When the manager is cleared, all MX beans for its Simons are unregistered. */
112        @Override
113        public final void onManagerClear() {
114                unregisterAllSimons();
115        }
116
117        /** Unregister all previously registered Simons. */
118        private synchronized void unregisterAllSimons() {
119                Iterator<String> namesIter = registeredNames.iterator();
120                while (namesIter.hasNext()) {
121                        String name = namesIter.next();
122                        try {
123                                ObjectName objectName = new ObjectName(name);
124                                mBeanServer.unregisterMBean(objectName);
125                                // I have to use iterator.remove() - hence no common method for clearManager() and simonDestroyed(simon)
126                                namesIter.remove();
127                                onManagerMessage("Unregistered Simon with the name: " + objectName);
128                        } catch (JMException e) {
129                                onManagerWarning("JMX unregistration failed for: " + name, e);
130                        }
131                }
132        }
133
134        /** Stop operation and clear all registered beans. Callback should be removed from Simon Manager. */
135        public void cleanup() {
136                unregisterAllSimons();
137        }
138
139        /**
140         * Method registering Simon MX Bean - can not be overridden, but can be used in subclasses.
141         *
142         * @param simon Simon MX Bean to be registered
143         * @since 3.1
144         */
145        protected final void register(Simon simon) {
146                Object mBean = constructObject(simon);
147                String name = constructObjectName(simon);
148                registerSimonBean(mBean, name);
149        }
150
151        private synchronized void registerSimonBean(Object simonBean, String name) {
152                if (simonBean != null && name != null) {
153                        try {
154                                ObjectName objectName = new ObjectName(name);
155                                if (mBeanServer.isRegistered(objectName)) {
156                                        mBeanServer.unregisterMBean(objectName);
157                                } else {
158                                        registeredNames.add(name);
159                                }
160                                mBeanServer.registerMBean(simonBean, objectName);
161                                onManagerMessage("Simon registered under the name: " + objectName);
162                        } catch (JMException e) {
163                                onManagerWarning("JMX registration failed for: " + name, e);
164                                registeredNames.remove(name);
165                        }
166                }
167        }
168
169        /**
170         * Constructs JMX object from Simon object. Method can be overridden.
171         *
172         * @param simon Simon object
173         * @return JMX object (=MBean) representing the Simon
174         */
175        protected SimonSuperMXBean constructObject(Simon simon) {
176                SimonSuperMXBean simonMxBean;
177                if (simon instanceof Counter) {
178                        simonMxBean = new CounterMXBeanImpl((Counter) simon);
179                } else if (simon instanceof Stopwatch) {
180                        simonMxBean = new StopwatchMXBeanImpl((Stopwatch) simon);
181                } else {
182                        onManagerWarning("Unknown type of Simon! " + simon, null);
183                        simonMxBean = null;
184                }
185                return simonMxBean;
186        }
187
188        /**
189         * Constructs JMX object name from Simon object. Method can be overridden.
190         *
191         * @param simon Simon object
192         * @return object name in String form
193         */
194        protected String constructObjectName(Simon simon) {
195                return domain + ":type=" + simonType(simon) + ",name=" + simon.getName();
196        }
197
198        /**
199         * Returns type of the simon as defined in {@link SimonInfo#COUNTER},
200         * {@link SimonInfo#STOPWATCH} or {@link SimonInfo#UNKNOWN}.
201         *
202         * @param simon Simon object
203         * @return type of the Simon as String
204         */
205        protected String simonType(Simon simon) {
206                String type = SimonInfo.UNKNOWN;
207                if (simon instanceof Counter) {
208                        type = SimonInfo.COUNTER;
209                } else if (simon instanceof Stopwatch) {
210                        type = SimonInfo.STOPWATCH;
211                }
212                return type;
213        }
214
215        String getDomain() {
216                return domain;
217        }
218
219        MBeanServer getBeanServer() {
220                return mBeanServer;
221        }
222
223        /**
224         * If set to true before initialization callback registers all Simons already existing in the Manager
225         * during initialization.
226         *
227         * @param registerExisting true if all already existing simons should be registered
228         */
229        public void setRegisterExisting(boolean registerExisting) {
230                this.registerExisting = registerExisting;
231        }
232}