View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.jmx;
20  
21  import java.io.IOException;
22  import java.util.Locale;
23  import java.util.Map;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import javax.management.InstanceNotFoundException;
29  import javax.management.MBeanRegistrationException;
30  import javax.management.MBeanServer;
31  import javax.management.ObjectName;
32  
33  import org.eclipse.jetty.util.component.Container;
34  import org.eclipse.jetty.util.component.ContainerLifeCycle;
35  import org.eclipse.jetty.util.component.Dumpable;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  
39  /**
40   * Container class for the MBean instances
41   */
42  public class MBeanContainer implements Container.InheritedListener, Dumpable
43  {
44      private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName());
45      private final static ConcurrentMap<String, AtomicInteger> __unique = new ConcurrentHashMap<>();
46  
47      public static void resetUnique()
48      {
49          __unique.clear();
50      }
51  
52      private final MBeanServer _mbeanServer;
53      private final Map<Object, ObjectName> _beans = new ConcurrentHashMap<>();
54      private String _domain = null;
55  
56      /**
57       * Lookup an object name by instance
58       *
59       * @param object instance for which object name is looked up
60       * @return object name associated with specified instance, or null if not found
61       */
62      public ObjectName findMBean(Object object)
63      {
64          return _beans.get(object);
65      }
66  
67      /**
68       * Lookup an instance by object name
69       *
70       * @param objectName object name of instance
71       * @return instance associated with specified object name, or null if not found
72       */
73      public Object findBean(ObjectName objectName)
74      {
75          for (Map.Entry<Object, ObjectName> entry : _beans.entrySet())
76          {
77              if (entry.getKey().equals(objectName))
78                  return entry.getValue();
79          }
80          return null;
81      }
82  
83      /**
84       * Constructs MBeanContainer
85       *
86       * @param server instance of MBeanServer for use by container
87       */
88      public MBeanContainer(MBeanServer server)
89      {
90          _mbeanServer = server;
91      }
92  
93      /**
94       * Retrieve instance of MBeanServer used by container
95       *
96       * @return instance of MBeanServer
97       */
98      public MBeanServer getMBeanServer()
99      {
100         return _mbeanServer;
101     }
102 
103     /**
104      * Set domain to be used to add MBeans
105      *
106      * @param domain domain name
107      */
108     public void setDomain(String domain)
109     {
110         _domain = domain;
111     }
112 
113     /**
114      * Retrieve domain name used to add MBeans
115      *
116      * @return domain name
117      */
118     public String getDomain()
119     {
120         return _domain;
121     }
122 
123 
124     @Override
125     public void beanAdded(Container parent, Object obj)
126     {
127         if (LOG.isDebugEnabled())
128             LOG.debug("beanAdded {}->{}", parent, obj);
129 
130         // Is there an object name for the parent ?
131         ObjectName parentObjectName = null;
132         if (parent != null)
133         {
134             parentObjectName = findMBean(parent);
135             if (parentObjectName == null)
136             {
137                 // Create the parent bean.
138                 beanAdded(null, parent);
139                 parentObjectName = findMBean(parent);
140             }
141         }
142 
143         // Does the mbean already exist ?
144         if (obj == null || _beans.containsKey(obj))
145             return;
146 
147         try
148         {
149             // Create an MBean for the object.
150             Object mbean = ObjectMBean.mbeanFor(obj);
151             if (mbean == null)
152                 return;
153 
154             ObjectName objectName = null;
155             if (mbean instanceof ObjectMBean)
156             {
157                 ((ObjectMBean)mbean).setMBeanContainer(this);
158                 objectName = ((ObjectMBean)mbean).getObjectName();
159             }
160 
161             // No override of the mbean's ObjectName, so make a generic one.
162             if (objectName == null)
163             {
164                 // If no explicit domain, create one.
165                 String domain = _domain;
166                 if (domain == null)
167                     domain = obj.getClass().getPackage().getName();
168 
169                 String type = obj.getClass().getName().toLowerCase(Locale.ENGLISH);
170                 int dot = type.lastIndexOf('.');
171                 if (dot >= 0)
172                     type = type.substring(dot + 1);
173 
174                 StringBuilder buf = new StringBuilder();
175 
176                 String context = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectContextBasis()) : null;
177                 if (context == null && parentObjectName != null)
178                     context = parentObjectName.getKeyProperty("context");
179 
180                 if (context != null && context.length() > 1)
181                     buf.append("context=").append(context).append(",");
182 
183                 buf.append("type=").append(type);
184 
185                 String name = (mbean instanceof ObjectMBean) ? makeName(((ObjectMBean)mbean).getObjectNameBasis()) : context;
186                 if (name != null && name.length() > 1)
187                     buf.append(",").append("name=").append(name);
188 
189                 String basis = buf.toString();
190 
191                 AtomicInteger count = __unique.get(basis);
192                 if (count == null)
193                 {
194                     count = new AtomicInteger();
195                     AtomicInteger existing = __unique.putIfAbsent(basis, count);
196                     if (existing != null)
197                         count = existing;
198                 }
199 
200                 objectName = ObjectName.getInstance(domain + ":" + basis + ",id=" + count.getAndIncrement());
201             }
202 
203             _mbeanServer.registerMBean(mbean, objectName);
204             if (LOG.isDebugEnabled())
205                 LOG.debug("Registered {}", objectName);
206 
207             _beans.put(obj, objectName);
208         }
209         catch (Throwable x)
210         {
211             LOG.warn("bean: " + obj, x);
212         }
213     }
214 
215     @Override
216     public void beanRemoved(Container parent, Object obj)
217     {
218         if (LOG.isDebugEnabled())
219             LOG.debug("beanRemoved {}", obj);
220 
221         ObjectName objectName = _beans.remove(obj);
222 
223         if (objectName != null)
224             unregister(objectName);
225     }
226 
227     /**
228      * @param basis name to strip of special characters.
229      * @return normalized name
230      */
231     public String makeName(String basis)
232     {
233         if (basis == null)
234             return null;
235         return basis
236                 .replace(':', '_')
237                 .replace('*', '_')
238                 .replace('?', '_')
239                 .replace('=', '_')
240                 .replace(',', '_')
241                 .replace(' ', '_');
242     }
243 
244     @Override
245     public void dump(Appendable out, String indent) throws IOException
246     {
247         ContainerLifeCycle.dumpObject(out,this);
248         ContainerLifeCycle.dump(out, indent, _beans.entrySet());
249     }
250 
251     @Override
252     public String dump()
253     {
254         return ContainerLifeCycle.dump(this);
255     }
256 
257     public void destroy()
258     {
259         _beans.values().stream()
260                 .filter(objectName -> objectName != null)
261                 .forEach(this::unregister);
262     }
263 
264     private void unregister(ObjectName objectName)
265     {
266         try
267         {
268             getMBeanServer().unregisterMBean(objectName);
269             if (LOG.isDebugEnabled())
270                 LOG.debug("Unregistered {}", objectName);
271         }
272         catch (MBeanRegistrationException | InstanceNotFoundException x)
273         {
274             LOG.ignore(x);
275         }
276         catch (Throwable x)
277         {
278             LOG.warn(x);
279         }
280     }
281 }