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