1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.tomcat.util.modeler;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.InputStream;
22 import java.lang.management.ManagementFactory;
23 import java.net.URL;
24 import java.util.HashMap;
25 import java.util.Hashtable;
26 import java.util.List;
27 import java.util.Map;
28
29 import javax.management.DynamicMBean;
30 import javax.management.InstanceNotFoundException;
31 import javax.management.MBeanAttributeInfo;
32 import javax.management.MBeanInfo;
33 import javax.management.MBeanOperationInfo;
34 import javax.management.MBeanRegistration;
35 import javax.management.MBeanServer;
36 import javax.management.MBeanServerFactory;
37 import javax.management.MalformedObjectNameException;
38 import javax.management.ObjectName;
39
40 import org.apache.juli.logging.Log;
41 import org.apache.juli.logging.LogFactory;
42 import org.apache.tomcat.util.compat.JreCompat;
43 import org.apache.tomcat.util.modeler.modules.ModelerSource;
44 import org.apache.tomcat.util.res.StringManager;
45
46 /*
47    Issues:
48    - exceptions - too many "throws Exception"
49    - double check the interfaces
50    - start removing the use of the experimental methods in tomcat, then remove
51      the methods ( before 1.1 final )
52    - is the security enough to prevent Registry being used to avoid the
53      permission checks in the mbean server ?
54 */

55
56 /**
57  * Registry for modeler MBeans.
58  *
59  * This is the main entry point into modeler. It provides methods to create and
60  * manipulate model mbeans and simplify their use.
61  *
62  * This class is itself an mbean.
63  *
64  * IMPORTANT: public methods not marked with @since x.x are experimental or
65  * internal. Should not be used.
66  *
67  * @author Craig R. McClanahan
68  * @author Costin Manolache
69  */

70 public class Registry implements RegistryMBean, MBeanRegistration {
71
72     /**
73      * The Log instance to which we will write our log messages.
74      */

75     private static final Log log = LogFactory.getLog(Registry.class);
76     private static final StringManager sm = StringManager.getManager(Registry.class);
77
78     // Support for the factory methods
79
80     /**
81      * The registry instance created by our factory method the first time it is
82      * called.
83      */

84     private static Registry registry = null;
85
86     // Per registry fields
87
88     /**
89      * The <code>MBeanServer</code> instance that we will use to register
90      * management beans.
91      */

92     private volatile MBeanServer server = null;
93     private final Object serverLock = new Object();
94
95     /**
96      * The set of ManagedBean instances for the beans this registry knows about,
97      * keyed by name.
98      */

99     private Map<String, ManagedBean> descriptors = new HashMap<>();
100
101     /**
102      * List of managed beans, keyed by class name
103      */

104     private Map<String, ManagedBean> descriptorsByClass = new HashMap<>();
105
106     // map to avoid duplicated searching or loading descriptors
107     private Map<String, URL> searchedPaths = new HashMap<>();
108
109     private Object guard;
110
111     // Id - small ints to use array access. No reset on stop()
112     // Used for notifications
113     private final Hashtable<String, Hashtable<String, Integer>> idDomains = new Hashtable<>();
114     private final Hashtable<String, int[]> ids = new Hashtable<>();
115
116
117     // ----------------------------------------------------------- Constructors
118
119     protected Registry() {
120         super();
121     }
122
123
124     // -------------------- Static methods --------------------
125     // Factories
126
127     /**
128      * Factory method to create (if necessary) and return our
129      * <code>Registry</code> instance.
130      *
131      * @param key Unused
132      * @param guard Prevent access to the registry by untrusted components
133      *
134      * @return the registry
135      * @since 1.1
136      */

137     public static synchronized Registry getRegistry(Object key, Object guard) {
138         if (registry == null) {
139             if (JreCompat.isGraalAvailable()) {
140                 disableRegistry();
141             } else {
142                 registry = new Registry();
143             }
144         }
145         if (registry.guard != null && registry.guard != guard) {
146             return null;
147         }
148         return registry;
149     }
150
151
152     public static synchronized void disableRegistry() {
153         if (registry == null) {
154             registry = new NoDescriptorRegistry();
155         } else if (!(registry instanceof NoDescriptorRegistry)) {
156             log.warn(sm.getString("registry.noDisable"));
157         }
158     }
159
160
161     // -------------------- Generic methods --------------------
162
163     /**
164      * Lifecycle method - clean up the registry metadata. Called from
165      * resetMetadata().
166      *
167      * @since 1.1
168      */

169     @Override
170     public void stop() {
171         descriptorsByClass = new HashMap<>();
172         descriptors = new HashMap<>();
173         searchedPaths = new HashMap<>();
174     }
175
176
177     /**
178      * Register a bean by creating a modeler mbean and adding it to the
179      * MBeanServer.
180      *
181      * If metadata is not loaded, we'll look up and read a file named
182      * "mbeans-descriptors.ser" or "mbeans-descriptors.xml" in the same package
183      * or parent.
184      *
185      * If the bean is an instance of DynamicMBean. it's metadata will be
186      * converted to a model mbean and we'll wrap it - so modeler services will
187      * be supported
188      *
189      * If the metadata is still not found, introspection will be used to extract
190      * it automatically.
191      *
192      * If an mbean is already registered under this name, it'll be first
193      * unregistered.
194      *
195      * If the component implements MBeanRegistration, the methods will be
196      * called. If the method has a method "setRegistry" that takes a
197      * RegistryMBean as parameter, it'll be called with the current registry.
198      *
199      *
200      * @param bean Object to be registered
201      * @param oname Name used for registration
202      * @param type The type of the mbean, as declared in mbeans-descriptors. If
203      *            null, the name of the class will be used. This can be used as
204      *            a hint or by subclasses.
205      * @throws Exception if a registration error occurred
206      * @since 1.1
207      */

208     @Override
209     public void registerComponent(Object bean, String oname, String type) throws Exception {
210         registerComponent(bean, new ObjectName(oname), type);
211     }
212
213
214     /**
215      * Unregister a component. We'll first check if it is registered, and mask
216      * all errors. This is mostly a helper.
217      *
218      * @param oname Name used for unregistration
219      *
220      * @since 1.1
221      */

222     @Override
223     public void unregisterComponent(String oname) {
224         try {
225             unregisterComponent(new ObjectName(oname));
226         } catch (MalformedObjectNameException e) {
227             log.info(sm.getString("registry.objectNameCreateError"), e);
228         }
229     }
230
231
232     /**
233      * Invoke a operation on a list of mbeans. Can be used to implement
234      * lifecycle operations.
235      *
236      * @param mbeans list of ObjectName on which we'll invoke the operations
237      * @param operation  Name of the operation ( init, start, stop, etc)
238      * @param failFirst  If false, exceptions will be ignored
239      * @throws Exception Error invoking operation
240      * @since 1.1
241      */

242     @Override
243     public void invoke(List<ObjectName> mbeans, String operation, boolean failFirst)
244             throws Exception {
245
246         if (mbeans == null) {
247             return;
248         }
249         for (ObjectName current : mbeans) {
250             try {
251                 if (current == null) {
252                     continue;
253                 }
254                 if (getMethodInfo(current, operation) == null) {
255                     continue;
256                 }
257                 getMBeanServer().invoke(current, operation, new Object[] {}, new String[] {});
258
259             } catch (Exception t) {
260                 if (failFirst)
261                     throw t;
262                 log.info(sm.getString("registry.initError"), t);
263             }
264         }
265     }
266
267     // -------------------- ID registry --------------------
268
269     /**
270      * Return an int ID for faster access. Will be used for notifications and
271      * for other operations we want to optimize.
272      *
273      * @param domain Namespace
274      * @param name Type of the notification
275      * @return A unique id for the domain:name combination
276      * @since 1.1
277      */

278     @Override
279     public synchronized int getId(String domain, String name) {
280         if (domain == null) {
281             domain = "";
282         }
283         Hashtable<String, Integer> domainTable = idDomains.get(domain);
284         if (domainTable == null) {
285             domainTable = new Hashtable<>();
286             idDomains.put(domain, domainTable);
287         }
288         if (name == null) {
289             name = "";
290         }
291         Integer i = domainTable.get(name);
292
293         if (i != null) {
294             return i.intValue();
295         }
296
297         int id[] = ids.get(domain);
298         if (id == null) {
299             id = new int[1];
300             ids.put(domain, id);
301         }
302         int code = id[0]++;
303         domainTable.put(name, Integer.valueOf(code));
304         return code;
305     }
306
307
308     // -------------------- Metadata --------------------
309     // methods from 1.0
310
311     /**
312      * Add a new bean metadata to the set of beans known to this registry. This
313      * is used by internal components.
314      *
315      * @param bean The managed bean to be added
316      * @since 1.0
317      */

318     public void addManagedBean(ManagedBean bean) {
319         // XXX Use group + name
320         descriptors.put(bean.getName(), bean);
321         if (bean.getType() != null) {
322             descriptorsByClass.put(bean.getType(), bean);
323         }
324     }
325
326
327     /**
328      * Find and return the managed bean definition for the specified bean name,
329      * if any; otherwise return <code>null</code>.
330      *
331      * @param name Name of the managed bean to be returned. Since 1.1, both
332      *            short names or the full name of the class can be used.
333      * @return the managed bean
334      * @since 1.0
335      */

336     public ManagedBean findManagedBean(String name) {
337         // XXX Group ?? Use Group + Type
338         ManagedBean mb = descriptors.get(name);
339         if (mb == null)
340             mb = descriptorsByClass.get(name);
341         return mb;
342     }
343
344
345     // -------------------- Helpers --------------------
346
347     /**
348      * Get the type of an attribute of the object, from the metadata.
349      *
350      * @param oname The bean name
351      * @param attName The attribute name
352      * @return null if metadata about the attribute is not found
353      * @since 1.1
354      */

355     public String getType(ObjectName oname, String attName) {
356         String type = null;
357         MBeanInfo info = null;
358         try {
359             info = getMBeanServer().getMBeanInfo(oname);
360         } catch (Exception e) {
361             log.info(sm.getString("registry.noMetadata", oname));
362             return null;
363         }
364
365         MBeanAttributeInfo attInfo[] = info.getAttributes();
366         for (int i = 0; i < attInfo.length; i++) {
367             if (attName.equals(attInfo[i].getName())) {
368                 type = attInfo[i].getType();
369                 return type;
370             }
371         }
372         return null;
373     }
374
375
376     /**
377      * Find the operation info for a method
378      *
379      * @param oname The bean name
380      * @param opName The operation name
381      * @return the operation info for the specified operation
382      */

383     public MBeanOperationInfo getMethodInfo(ObjectName oname, String opName) {
384         MBeanInfo info = null;
385         try {
386             info = getMBeanServer().getMBeanInfo(oname);
387         } catch (Exception e) {
388             log.info(sm.getString("registry.noMetadata", oname));
389             return null;
390         }
391         MBeanOperationInfo attInfo[] = info.getOperations();
392         for (int i = 0; i < attInfo.length; i++) {
393             if (opName.equals(attInfo[i].getName())) {
394                 return attInfo[i];
395             }
396         }
397         return null;
398     }
399
400     /**
401      * Find the operation info for a method.
402      *
403      * @param oname The bean name
404      * @param opName The operation name
405      * @param argCount The number of arguments to the method
406      * @return the operation info for the specified operation
407      * @throws InstanceNotFoundException If the object name is not bound to an MBean
408      */

409     public MBeanOperationInfo getMethodInfo(ObjectName oname, String opName, int argCount)
410         throws InstanceNotFoundException
411     {
412         MBeanInfo info = null;
413         try {
414             info = getMBeanServer().getMBeanInfo(oname);
415         } catch (InstanceNotFoundException infe) {
416             throw infe;
417         } catch (Exception e) {
418             log.warn(sm.getString("registry.noMetadata", oname), e);
419             return null;
420         }
421         MBeanOperationInfo attInfo[] = info.getOperations();
422         for (int i = 0; i < attInfo.length; i++) {
423             if (opName.equals(attInfo[i].getName())
424                 && argCount == attInfo[i].getSignature().length) {
425                 return attInfo[i];
426             }
427         }
428         return null;
429     }
430
431     /**
432      * Unregister a component. This is just a helper that avoids exceptions by
433      * checking if the mbean is already registered
434      *
435      * @param oname The bean name
436      */

437     public void unregisterComponent(ObjectName oname) {
438         try {
439             if (oname != null && getMBeanServer().isRegistered(oname)) {
440                 getMBeanServer().unregisterMBean(oname);
441             }
442         } catch (Throwable t) {
443             log.error(sm.getString("registry.unregisterError"), t);
444         }
445     }
446
447
448     /**
449      * Factory method to create (if necessary) and return our
450      * <code>MBeanServer</code> instance.
451      *
452      * @return the MBean server
453      */

454     public MBeanServer getMBeanServer() {
455         if (server == null) {
456             synchronized (serverLock) {
457                 if (server == null) {
458                     long t1 = System.currentTimeMillis();
459                     if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
460                         server = MBeanServerFactory.findMBeanServer(null).get(0);
461                         if (log.isDebugEnabled()) {
462                             log.debug("Using existing MBeanServer " + (System.currentTimeMillis() - t1));
463                         }
464                     } else {
465                         server = ManagementFactory.getPlatformMBeanServer();
466                         if (log.isDebugEnabled()) {
467                             log.debug("Creating MBeanServer" + (System.currentTimeMillis() - t1));
468                         }
469                     }
470                 }
471             }
472         }
473         return server;
474     }
475
476
477     /**
478      * Find or load metadata.
479      *
480      * @param bean The bean
481      * @param beanClass The bean class
482      * @param type The registry type
483      * @return the managed bean
484      * @throws Exception An error occurred
485      */

486     public ManagedBean findManagedBean(Object bean, Class<?> beanClass, String type)
487             throws Exception {
488
489         if (bean != null && beanClass == null) {
490             beanClass = bean.getClass();
491         }
492
493         if (type == null) {
494             type = beanClass.getName();
495         }
496
497         // first look for existing descriptor
498         ManagedBean managed = findManagedBean(type);
499
500         // Search for a descriptor in the same package
501         if (managed == null) {
502             // check package and parent packages
503             if (log.isDebugEnabled()) {
504                 log.debug("Looking for descriptor ");
505             }
506             findDescriptor(beanClass, type);
507
508             managed = findManagedBean(type);
509         }
510
511         // Still not found - use introspection
512         if (managed == null) {
513             if (log.isDebugEnabled()) {
514                 log.debug("Introspecting ");
515             }
516
517             // introspection
518             load("MbeansDescriptorsIntrospectionSource", beanClass, type);
519
520             managed = findManagedBean(type);
521             if (managed == null) {
522                 log.warn(sm.getString("registry.noTypeMetadata", type));
523                 return null;
524             }
525             managed.setName(type);
526             addManagedBean(managed);
527         }
528         return managed;
529     }
530
531
532     /**
533      * EXPERIMENTAL Convert a string to object, based on type. Used by several
534      * components. We could provide some pluggability. It is here to keep things
535      * consistent and avoid duplication in other tasks
536      *
537      * @param type Fully qualified class name of the resulting value
538      * @param value String value to be converted
539      * @return Converted value
540      */

541     public Object convertValue(String type, String value) {
542         Object objValue = value;
543
544         if (type == null || "java.lang.String".equals(type)) {
545             // string is default
546             objValue = value;
547         } else if ("javax.management.ObjectName".equals(type) || "ObjectName".equals(type)) {
548             try {
549                 objValue = new ObjectName(value);
550             } catch (MalformedObjectNameException e) {
551                 return null;
552             }
553         } else if ("java.lang.Integer".equals(type) || "int".equals(type)) {
554             objValue = Integer.valueOf(value);
555         } else if ("java.lang.Long".equals(type) || "long".equals(type)) {
556             objValue = Long.valueOf(value);
557         } else if ("java.lang.Boolean".equals(type) || "boolean".equals(type)) {
558             objValue = Boolean.valueOf(value);
559         }
560         return objValue;
561     }
562
563
564     /**
565      * Experimental. Load descriptors.
566      *
567      * @param sourceType The source type
568      * @param source The bean
569      * @param param A type to load
570      * @return List of descriptors
571      * @throws Exception Error loading descriptors
572      */

573     public List<ObjectName> load(String sourceType, Object source, String param) throws Exception {
574         if (log.isTraceEnabled()) {
575             log.trace("load " + source);
576         }
577         String location = null;
578         String type = null;
579         Object inputsource = null;
580
581         if (source instanceof URL) {
582             URL url = (URL) source;
583             location = url.toString();
584             type = param;
585             inputsource = url.openStream();
586             if (sourceType == null && location.endsWith(".xml")) {
587                 sourceType = "MbeansDescriptorsDigesterSource";
588             }
589         } else if (source instanceof File) {
590             location = ((File) source).getAbsolutePath();
591             inputsource = new FileInputStream((File) source);
592             type = param;
593             if (sourceType == null && location.endsWith(".xml")) {
594                 sourceType = "MbeansDescriptorsDigesterSource";
595             }
596         } else if (source instanceof InputStream) {
597             type = param;
598             inputsource = source;
599         } else if (source instanceof Class<?>) {
600             location = ((Class<?>) source).getName();
601             type = param;
602             inputsource = source;
603             if (sourceType == null) {
604                 sourceType = "MbeansDescriptorsIntrospectionSource";
605             }
606         }
607
608         if (sourceType == null) {
609             sourceType = "MbeansDescriptorsDigesterSource";
610         }
611         ModelerSource ds = getModelerSource(sourceType);
612         List<ObjectName> mbeans = ds.loadDescriptors(this, type, inputsource);
613
614         return mbeans;
615     }
616
617
618     /**
619      * Register a component
620      *
621      * @param bean The bean
622      * @param oname The object name
623      * @param type The registry type
624      * @throws Exception Error registering component
625      */

626     public void registerComponent(Object bean, ObjectName oname, String type) throws Exception {
627         if (log.isDebugEnabled()) {
628             log.debug("Managed= " + oname);
629         }
630
631         if (bean == null) {
632             log.error(sm.getString("registry.nullBean", oname));
633             return;
634         }
635
636         try {
637             if (type == null) {
638                 type = bean.getClass().getName();
639             }
640
641             ManagedBean managed = findManagedBean(null, bean.getClass(), type);
642
643             // The real mbean is created and registered
644             DynamicMBean mbean = managed.createMBean(bean);
645
646             if (getMBeanServer().isRegistered(oname)) {
647                 if (log.isDebugEnabled()) {
648                     log.debug("Unregistering existing component " + oname);
649                 }
650                 getMBeanServer().unregisterMBean(oname);
651             }
652
653             getMBeanServer().registerMBean(mbean, oname);
654         } catch (Exception ex) {
655             log.error(sm.getString("registry.registerError", oname), ex);
656             throw ex;
657         }
658     }
659
660
661     /**
662      * Lookup the component descriptor in the package and in the parent
663      * packages.
664      *
665      * @param packageName The package name
666      * @param classLoader The class loader
667      */

668     public void loadDescriptors(String packageName, ClassLoader classLoader) {
669         String res = packageName.replace('.', '/');
670
671         if (log.isTraceEnabled()) {
672             log.trace("Finding descriptor " + res);
673         }
674
675         if (searchedPaths.get(packageName) != null) {
676             return;
677         }
678
679         String descriptors = res + "/mbeans-descriptors.xml";
680         URL dURL = classLoader.getResource(descriptors);
681
682         if (dURL == null) {
683             return;
684         }
685
686         log.debug("Found " + dURL);
687         searchedPaths.put(packageName, dURL);
688         try {
689             load("MbeansDescriptorsDigesterSource", dURL, null);
690         } catch (Exception ex) {
691             log.error(sm.getString("registry.loadError", dURL));
692         }
693     }
694
695
696     /**
697      * Lookup the component descriptor in the package and in the parent
698      * packages.
699      */

700     private void findDescriptor(Class<?> beanClass, String type) {
701         if (type == null) {
702             type = beanClass.getName();
703         }
704         ClassLoader classLoader = null;
705         if (beanClass != null) {
706             classLoader = beanClass.getClassLoader();
707         }
708         if (classLoader == null) {
709             classLoader = Thread.currentThread().getContextClassLoader();
710         }
711         if (classLoader == null) {
712             classLoader = this.getClass().getClassLoader();
713         }
714
715         String className = type;
716         String pkg = className;
717         while (pkg.indexOf(".") > 0) {
718             int lastComp = pkg.lastIndexOf(".");
719             if (lastComp <= 0)
720                 return;
721             pkg = pkg.substring(0, lastComp);
722             if (searchedPaths.get(pkg) != null) {
723                 return;
724             }
725             loadDescriptors(pkg, classLoader);
726         }
727     }
728
729
730     private ModelerSource getModelerSource(String type) throws Exception {
731         if (type == null)
732             type = "MbeansDescriptorsDigesterSource";
733         if (!type.contains(".")) {
734             type = "org.apache.tomcat.util.modeler.modules." + type;
735         }
736
737         Class<?> c = Class.forName(type);
738         ModelerSource ds = (ModelerSource) c.getConstructor().newInstance();
739         return ds;
740     }
741
742
743     // -------------------- Registration --------------------
744
745     @Override
746     public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
747         synchronized (serverLock) {
748             this.server = server;
749         }
750         return name;
751     }
752
753
754     @Override
755     public void postRegister(Boolean registrationDone) {
756     }
757
758
759     @Override
760     public void preDeregister() throws Exception {
761     }
762
763
764     @Override
765     public void postDeregister() {
766     }
767 }
768