blob: e33492d945e25dff913e5cb53cb76eb8d168633f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jmx.mbeanserver;
27
28import com.sun.jmx.defaults.ServiceName;
29import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
30
31import java.util.ArrayList;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import java.util.concurrent.locks.ReentrantReadWriteLock;
36import java.util.logging.Level;
37import java.util.Map;
38import java.util.Set;
39import javax.management.DynamicMBean;
40import javax.management.InstanceAlreadyExistsException;
41import javax.management.InstanceNotFoundException;
42import javax.management.MalformedObjectNameException;
43import javax.management.ObjectName;
44import javax.management.QueryExp;
45import javax.management.RuntimeOperationsException;
46
47/**
48 * The RepositorySupport implements the Repository interface.
49 * This repository does not support persistency.
50 *
51 * @since 1.5
52 */
53public class Repository {
54
55 // Private fields -------------------------------------------->
56
57 /**
58 * The structure for storing the objects is very basic.
59 * A Hashtable is used for storing the different domains
60 * For each domain, a hashtable contains the instances with
61 * canonical key property list string as key and named object
62 * aggregated from given object name and mbean instance as value.
63 */
64 private final Map<String,Map<String,NamedObject>> domainTb;
65
66 /**
67 * Number of elements contained in the Repository
68 */
69 private volatile int nbElements = 0;
70
71 /**
72 * Domain name of the server the repository is attached to.
73 * It is quicker to store the information in the repository rather
74 * than querying the framework each time the info is required.
75 */
76 private final String domain;
77
78 /**
79 * We use a global reentrant read write lock to protect the repository.
80 * This seems safer and more efficient: we are using Maps of Maps,
81 * Guaranteing consistency while using Concurent objects at each level
82 * may be more difficult.
83 **/
84 private final ReentrantReadWriteLock lock;
85
86 // Private fields <=============================================
87
88 // Private methods --------------------------------------------->
89
90 /* This class is used to match an ObjectName against a pattern. */
91 private final static class ObjectNamePattern {
92 private final char[] domain;
93 private final String[] keys;
94 private final String[] values;
95 private final String properties;
96 private final boolean isPropertyListPattern;
97 private final boolean isPropertyValuePattern;
98
99 /**
100 * The ObjectName pattern against which ObjectNames are matched.
101 **/
102 public final ObjectName pattern;
103
104 /**
105 * Builds a new ObjectNamePattern object from an ObjectName pattern.
106 * @param pattern The ObjectName pattern under examination.
107 **/
108 public ObjectNamePattern(ObjectName pattern) {
109 this(pattern.getDomain(),
110 pattern.isPropertyListPattern(),
111 pattern.isPropertyValuePattern(),
112 pattern.getCanonicalKeyPropertyListString(),
113 pattern.getKeyPropertyList(),
114 pattern);
115 }
116
117 /**
118 * Builds a new ObjectNamePattern object from an ObjectName pattern
119 * constituents.
120 * @param domain pattern.getDomain().
121 * @param propertyListPattern pattern.isPropertyListPattern().
122 * @param propertyValuePattern pattern.isPropertyValuePattern().
123 * @param canonicalProps pattern.getCanonicalKeyPropertyListString().
124 * @param keyPropertyList pattern.getKeyPropertyList().
125 * @param pattern The ObjectName pattern under examination.
126 **/
127 ObjectNamePattern(String domain,
128 boolean propertyListPattern,
129 boolean propertyValuePattern,
130 String canonicalProps,
131 Map<String,String> keyPropertyList,
132 ObjectName pattern) {
133 this.domain = domain.toCharArray();
134 this.isPropertyListPattern = propertyListPattern;
135 this.isPropertyValuePattern = propertyValuePattern;
136 this.properties = canonicalProps;
137 final int len = keyPropertyList.size();
138 this.keys = new String[len];
139 this.values = new String[len];
140 int i = 0;
141 for (Map.Entry<String,String> entry : keyPropertyList.entrySet()) {
142 keys[i] = entry.getKey();
143 values[i] = entry.getValue();
144 i++;
145 }
146 this.pattern = pattern;
147 }
148
149 /**
150 * Return true if the given ObjectName matches the ObjectName pattern
151 * for which this object has been built.
152 * WARNING: domain name is not considered here because it is supposed
153 * not to be wildcard when called. PropertyList is also
154 * supposed not to be zero-length.
155 * @param name The ObjectName we want to match against the pattern.
156 * @return true if <code>name</code> matches the pattern.
157 **/
158 public boolean matchKeys(ObjectName name) {
159 // If key property value pattern but not key property list
160 // pattern, then the number of key properties must be equal
161 //
162 if (isPropertyValuePattern &&
163 !isPropertyListPattern &&
164 (name.getKeyPropertyList().size() != keys.length))
165 return false;
166
167 // If key property value pattern or key property list pattern,
168 // then every property inside pattern should exist in name
169 //
170 if (isPropertyValuePattern || isPropertyListPattern) {
171 for (int i = keys.length - 1; i >= 0 ; i--) {
172 // Find value in given object name for key at current
173 // index in receiver
174 //
175 String v = name.getKeyProperty(keys[i]);
176 // Did we find a value for this key ?
177 //
178 if (v == null) return false;
179 // If this property is ok (same key, same value), go to next
180 //
181 if (isPropertyValuePattern &&
182 pattern.isPropertyValuePattern(keys[i])) {
183 // wildmatch key property values
184 final char[] val_pattern = values[i].toCharArray();
185 final char[] val_string = v.toCharArray();
186 if (wildmatch(val_string,val_pattern))
187 continue;
188 else
189 return false;
190 }
191 if (v.equals(values[i])) continue;
192 return false;
193 }
194 return true;
195 }
196
197 // If no pattern, then canonical names must be equal
198 //
199 final String p1 = name.getCanonicalKeyPropertyListString();
200 final String p2 = properties;
201 return (p1.equals(p2));
202 }
203 }
204
205 /**
206 * Add all the matching objects from the given hashtable in the
207 * result set for the given ObjectNamePattern
208 * Do not check whether the domains match (only check for matching
209 * key property lists - see <i>matchKeys()</i>)
210 **/
211 private void addAllMatching(final Map<String,NamedObject> moiTb,
212 final Set<NamedObject> result,
213 final ObjectNamePattern pattern) {
214 synchronized (moiTb) {
215 for (NamedObject no : moiTb.values()) {
216 final ObjectName on = no.getName();
217 // if all couples (property, value) are contained
218 if (pattern.matchKeys(on)) result.add(no);
219 }
220 }
221 }
222
223 private void addNewDomMoi(final DynamicMBean object, final String dom,
224 final ObjectName name) {
225 final Map<String,NamedObject> moiTb =
226 new HashMap<String,NamedObject>();
227 moiTb.put(name.getCanonicalKeyPropertyListString(),
228 new NamedObject(name, object));
229 domainTb.put(dom, moiTb);
230 nbElements++;
231 }
232
233 /** Match a string against a shell-style pattern. The only pattern
234 characters recognised are <code>?</code>, standing for any one
235 character, and <code>*</code>, standing for any string of
236 characters, including the empty string.
237
238 @param str the string to match, as a character array.
239 @param pat the pattern to match the string against, as a
240 character array.
241
242 @return true if and only if the string matches the pattern.
243 */
244 /* The algorithm is a classical one. We advance pointers in
245 parallel through str and pat. If we encounter a star in pat,
246 we remember its position and continue advancing. If at any
247 stage we get a mismatch between str and pat, we look to see if
248 there is a remembered star. If not, we fail. If so, we
249 retreat pat to just past that star and str to the position
250 after the last one we tried, and we let the match advance
251 again.
252
253 Even though there is only one remembered star position, the
254 algorithm works when there are several stars in the pattern.
255 When we encounter the second star, we forget the first one.
256 This is OK, because if we get to the second star in A*B*C
257 (where A etc are arbitrary strings), we have already seen AXB.
258 We're therefore setting up a match of *C against the remainder
259 of the string, which will match if that remainder looks like
260 YC, so the whole string looks like AXBYC.
261 */
262 public static boolean wildmatch(char[] str, char[] pat) {
263 int stri; // index in str
264 int pati; // index in pat
265 int starstri; // index for backtrack if "*" attempt fails
266 int starpati; // index for backtrack if "*" attempt fails, +1
267 final int strlen = str.length;
268 final int patlen = pat.length;
269
270 stri = pati = 0;
271 starstri = starpati = -1;
272
273 /* On each pass through this loop, we either advance pati,
274 or we backtrack pati and advance starstri. Since starstri
275 is only ever assigned from pati, the loop must terminate. */
276 while (true) {
277 if (pati < patlen) {
278 final char patc = pat[pati];
279 switch (patc) {
280 case '?':
281 if (stri == strlen)
282 break;
283 stri++;
284 pati++;
285 continue;
286 case '*':
287 pati++;
288 starpati = pati;
289 starstri = stri;
290 continue;
291 default:
292 if (stri < strlen && str[stri] == patc) {
293 stri++;
294 pati++;
295 continue;
296 }
297 break;
298 }
299 } else if (stri == strlen)
300 return true;
301
302 // Mismatched, can we backtrack to a "*"?
303 if (starpati < 0 || starstri == strlen)
304 return false;
305
306 // Retry the match one position later in str
307 pati = starpati;
308 starstri++;
309 stri = starstri;
310 }
311 }
312
313 /**
314 * Retrieves the named object contained in repository
315 * from the given objectname.
316 */
317 private NamedObject retrieveNamedObject(ObjectName name) {
318
319 // No patterns inside reposit
320 if (name.isPattern()) return null;
321
322 // Extract the domain name.
323 String dom= name.getDomain().intern();
324
325 // Default domain case
326 if (dom.length() == 0) {
327 dom = domain;
328 }
329
330 Map<String,NamedObject> moiTb = domainTb.get(dom);
331 if (moiTb == null) {
332 return null; // No domain containing registered object names
333 }
334
335 return moiTb.get(name.getCanonicalKeyPropertyListString());
336 }
337
338 // Private methods <=============================================
339
340 // Protected methods --------------------------------------------->
341
342 // Protected methods <=============================================
343
344 // Public methods --------------------------------------------->
345
346 /**
347 * Construct a new repository with the given default domain.
348 */
349 public Repository(String domain) {
350 this(domain,true);
351 }
352
353 /**
354 * Construct a new repository with the given default domain.
355 */
356 public Repository(String domain, boolean fairLock) {
357 lock = new ReentrantReadWriteLock(fairLock);
358
359 domainTb = new HashMap<String,Map<String,NamedObject>>(5);
360
361 if (domain != null && domain.length() != 0)
362 this.domain = domain;
363 else
364 this.domain = ServiceName.DOMAIN;
365
366 // Creates an new hastable for the default domain
367 domainTb.put(this.domain.intern(), new HashMap<String,NamedObject>());
368 }
369
370 /**
371 * Returns the list of domains in which any MBean is currently
372 * registered.
373 *
374 */
375 public String[] getDomains() {
376
377 lock.readLock().lock();
378 final List<String> result;
379 try {
380 // Temporary list
381 result = new ArrayList<String>(domainTb.size());
382 for (Map.Entry<String,Map<String,NamedObject>> entry :
383 domainTb.entrySet()) {
384 // Skip domains that are in the table but have no
385 // MBean registered in them
386 // in particular the default domain may be like this
387 Map<String,NamedObject> t = entry.getValue();
388 if (t != null && t.size() != 0)
389 result.add(entry.getKey());
390 }
391 } finally {
392 lock.readLock().unlock();
393 }
394
395 // Make an array from result.
396 return result.toArray(new String[result.size()]);
397 }
398
399 /**
400 * Stores an MBean associated with its object name in the repository.
401 *
402 * @param object MBean to be stored in the repository.
403 * @param name MBean object name.
404 */
405 public void addMBean(final DynamicMBean object, ObjectName name)
406 throws InstanceAlreadyExistsException {
407
408 if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
409 MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(),
410 "addMBean", "name = " + name);
411 }
412
413 // Extract the domain name.
414 String dom = name.getDomain().intern();
415 boolean to_default_domain = false;
416
417 // Set domain to default if domain is empty and not already set
418 if (dom.length() == 0) {
419 try {
420 name = new ObjectName(domain + name.toString());
421 } catch (MalformedObjectNameException e) {
422 if (MBEANSERVER_LOGGER.isLoggable(Level.FINEST)) {
423 MBEANSERVER_LOGGER.logp(Level.FINEST,
424 Repository.class.getName(), "addMBean",
425 "Unexpected MalformedObjectNameException", e);
426 }
427 }
428 }
429
430 // Do we have default domain ?
431 if (dom == domain) {
432 to_default_domain = true;
433 dom = domain;
434 } else {
435 to_default_domain = false;
436 }
437
438 // Validate name for an object
439 if (name.isPattern()) {
440 throw new RuntimeOperationsException(
441 new IllegalArgumentException("Repository: cannot add mbean for " +
442 "pattern name " + name.toString()));
443 }
444
445 lock.writeLock().lock();
446 try {
447 // Domain cannot be JMImplementation if entry does not exists
448 if ( !to_default_domain &&
449 dom.equals("JMImplementation") &&
450 domainTb.containsKey("JMImplementation")) {
451 throw new RuntimeOperationsException(
452 new IllegalArgumentException(
453 "Repository: domain name cannot be JMImplementation"));
454 }
455
456 // If domain not already exists, add it to the hash table
457 final Map<String,NamedObject> moiTb = domainTb.get(dom);
458 if (moiTb == null) {
459 addNewDomMoi(object, dom, name);
460 return;
461 }
462
463 // Add instance if not already present
464 String cstr = name.getCanonicalKeyPropertyListString();
465 NamedObject elmt= moiTb.get(cstr);
466 if (elmt != null) {
467 throw new InstanceAlreadyExistsException(name.toString());
468 } else {
469 nbElements++;
470 moiTb.put(cstr, new NamedObject(name, object));
471 }
472
473 } finally {
474 lock.writeLock().unlock();
475 }
476 }
477
478 /**
479 * Checks whether an MBean of the name specified is already stored in
480 * the repository.
481 *
482 * @param name name of the MBean to find.
483 *
484 * @return true if the MBean is stored in the repository,
485 * false otherwise.
486 */
487 public boolean contains(ObjectName name) {
488 if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
489 MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(),
490 "contains", " name = " + name);
491 }
492 lock.readLock().lock();
493 try {
494 return (retrieveNamedObject(name) != null);
495 } finally {
496 lock.readLock().unlock();
497 }
498 }
499
500 /**
501 * Retrieves the MBean of the name specified from the repository. The
502 * object name must match exactly.
503 *
504 * @param name name of the MBean to retrieve.
505 *
506 * @return The retrieved MBean if it is contained in the repository,
507 * null otherwise.
508 */
509 public DynamicMBean retrieve(ObjectName name) {
510 if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
511 MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(),
512 "retrieve", "name = " + name);
513 }
514
515 // Calls internal retrieve method to get the named object
516 lock.readLock().lock();
517 try {
518 NamedObject no = retrieveNamedObject(name);
519 if (no == null) return null;
520 else return no.getObject();
521 } finally {
522 lock.readLock().unlock();
523 }
524 }
525
526 /**
527 * Selects and retrieves the list of MBeans whose names match the specified
528 * object name pattern and which match the specified query expression
529 * (optionally).
530 *
531 * @param pattern The name of the MBean(s) to retrieve - may be a specific
532 * object or a name pattern allowing multiple MBeans to be selected.
533 * @param query query expression to apply when selecting objects - this
534 * parameter will be ignored when the Repository Service does not
535 * support filtering.
536 *
537 * @return The list of MBeans selected. There may be zero, one or many
538 * MBeans returned in the set.
539 */
540 public Set<NamedObject> query(ObjectName pattern, QueryExp query) {
541
542 final Set<NamedObject> result = new HashSet<NamedObject>();
543
544 // The following filter cases are considered:
545 // null, "", "*:*" : names in all domains
546 // ":*", ":[key=value],*" : names in defaultDomain
547 // "domain:*", "domain:[key=value],*" : names in the specified domain
548
549 // Surely one of the most frequent case ... query on the whole world
550 ObjectName name = null;
551 if (pattern == null ||
552 pattern.getCanonicalName().length() == 0 ||
553 pattern.equals(ObjectName.WILDCARD))
554 name = ObjectName.WILDCARD;
555 else name = pattern;
556
557 lock.readLock().lock();
558 try {
559
560 // If pattern is not a pattern, retrieve this mbean !
561 if (!name.isPattern()) {
562 final NamedObject no;
563 no = retrieveNamedObject(name);
564 if (no != null) result.add(no);
565 return result;
566 }
567
568 // All names in all domains
569 if (name == ObjectName.WILDCARD) {
570 for (Map<String,NamedObject> moiTb : domainTb.values()) {
571 result.addAll(moiTb.values());
572 }
573 return result;
574 }
575
576 final String canonical_key_property_list_string =
577 name.getCanonicalKeyPropertyListString();
578 final boolean allNames =
579 (canonical_key_property_list_string.length()==0);
580 final ObjectNamePattern namePattern =
581 (allNames?null:new ObjectNamePattern(name));
582
583 // All names in default domain
584 if (name.getDomain().length() == 0) {
585 final Map<String,NamedObject> moiTb = domainTb.get(domain);
586 if (allNames)
587 result.addAll(moiTb.values());
588 else
589 addAllMatching(moiTb, result, namePattern);
590 return result;
591 }
592
593 // Pattern matching in the domain name (*, ?)
594 char[] dom2Match = name.getDomain().toCharArray();
595 for (String domain : domainTb.keySet()) {
596 char[] theDom = domain.toCharArray();
597 if (wildmatch(theDom, dom2Match)) {
598 final Map<String,NamedObject> moiTb = domainTb.get(domain);
599 if (allNames)
600 result.addAll(moiTb.values());
601 else
602 addAllMatching(moiTb, result, namePattern);
603 }
604 }
605 return result;
606 } finally {
607 lock.readLock().unlock();
608 }
609 }
610
611 /**
612 * Removes an MBean from the repository.
613 *
614 * @param name name of the MBean to remove.
615 *
616 * @exception InstanceNotFoundException The MBean does not exist in
617 * the repository.
618 */
619 public void remove(final ObjectName name)
620 throws InstanceNotFoundException {
621
622 // Debugging stuff
623 if (MBEANSERVER_LOGGER.isLoggable(Level.FINER)) {
624 MBEANSERVER_LOGGER.logp(Level.FINER, Repository.class.getName(),
625 "remove", "name = " + name);
626 }
627
628 // Extract domain name.
629 String dom= name.getDomain().intern();
630
631 // Default domain case
632 if (dom.length() == 0) dom = domain;
633
634 lock.writeLock().lock();
635 try {
636 // Find the domain subtable
637 final Map<String,NamedObject> moiTb = domainTb.get(dom);
638 if (moiTb == null) {
639 throw new InstanceNotFoundException(name.toString());
640 }
641
642 // Remove the corresponding element
643 if (moiTb.remove(name.getCanonicalKeyPropertyListString())==null) {
644 throw new InstanceNotFoundException(name.toString());
645 }
646
647 // We removed it !
648 nbElements--;
649
650 // No more object for this domain, we remove this domain hashtable
651 if (moiTb.isEmpty()) {
652 domainTb.remove(dom);
653
654 // set a new default domain table (always present)
655 // need to reinstantiate a hashtable because of possible
656 // big buckets array size inside table, never cleared,
657 // thus the new !
658 if (dom == domain)
659 domainTb.put(domain, new HashMap<String,NamedObject>());
660 }
661 } finally {
662 lock.writeLock().unlock();
663 }
664 }
665
666 /**
667 * Gets the number of MBeans stored in the repository.
668 *
669 * @return Number of MBeans.
670 */
671 public Integer getCount() {
672 return new Integer(nbElements);
673 }
674
675 /**
676 * Gets the name of the domain currently used by default in the
677 * repository.
678 *
679 * @return A string giving the name of the default domain name.
680 */
681 public String getDefaultDomain() {
682 return domain;
683 }
684
685 // Public methods <=============================================
686}