1/* 2 * Copyright (c) 1999, 2017, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * 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.Collections; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.List; 36import java.util.concurrent.locks.ReentrantReadWriteLock; 37import java.lang.System.Logger.Level; 38import java.util.Map; 39import java.util.Set; 40import javax.management.DynamicMBean; 41import javax.management.InstanceAlreadyExistsException; 42import javax.management.InstanceNotFoundException; 43import javax.management.ObjectName; 44import javax.management.QueryExp; 45import javax.management.RuntimeOperationsException; 46 47/** 48 * This repository does not support persistency. 49 * 50 * @since 1.5 51 */ 52public class Repository { 53 54 /** 55 * An interface that allows the caller to get some control 56 * over the registration. 57 * @see #addMBean 58 * @see #remove 59 */ 60 public interface RegistrationContext { 61 /** 62 * Called by {@link #addMBean}. 63 * Can throw a RuntimeOperationsException to cancel the 64 * registration. 65 */ 66 public void registering(); 67 68 /** 69 * Called by {@link #remove}. 70 * Any exception thrown by this method will be ignored. 71 */ 72 public void unregistered(); 73 } 74 75 // Private fields --------------------------------------------> 76 77 /** 78 * The structure for storing the objects is very basic. 79 * A Hashtable is used for storing the different domains 80 * For each domain, a hashtable contains the instances with 81 * canonical key property list string as key and named object 82 * aggregated from given object name and mbean instance as value. 83 */ 84 private final Map<String,Map<String,NamedObject>> domainTb; 85 86 /** 87 * Number of elements contained in the Repository 88 */ 89 private volatile int nbElements = 0; 90 91 /** 92 * Domain name of the server the repository is attached to. 93 * It is quicker to store the information in the repository rather 94 * than querying the framework each time the info is required. 95 */ 96 private final String domain; 97 98 /** 99 * We use a global reentrant read write lock to protect the repository. 100 * This seems safer and more efficient: we are using Maps of Maps, 101 * Guaranteing consistency while using Concurent objects at each level 102 * may be more difficult. 103 **/ 104 private final ReentrantReadWriteLock lock; 105 106 // Private fields <============================================= 107 108 // Private methods ---------------------------------------------> 109 110 /* This class is used to match an ObjectName against a pattern. */ 111 private final static class ObjectNamePattern { 112 private final String[] keys; 113 private final String[] values; 114 private final String properties; 115 private final boolean isPropertyListPattern; 116 private final boolean isPropertyValuePattern; 117 118 /** 119 * The ObjectName pattern against which ObjectNames are matched. 120 **/ 121 public final ObjectName pattern; 122 123 /** 124 * Builds a new ObjectNamePattern object from an ObjectName pattern. 125 * @param pattern The ObjectName pattern under examination. 126 **/ 127 public ObjectNamePattern(ObjectName pattern) { 128 this(pattern.isPropertyListPattern(), 129 pattern.isPropertyValuePattern(), 130 pattern.getCanonicalKeyPropertyListString(), 131 pattern.getKeyPropertyList(), 132 pattern); 133 } 134 135 /** 136 * Builds a new ObjectNamePattern object from an ObjectName pattern 137 * constituents. 138 * @param propertyListPattern pattern.isPropertyListPattern(). 139 * @param propertyValuePattern pattern.isPropertyValuePattern(). 140 * @param canonicalProps pattern.getCanonicalKeyPropertyListString(). 141 * @param keyPropertyList pattern.getKeyPropertyList(). 142 * @param pattern The ObjectName pattern under examination. 143 **/ 144 ObjectNamePattern(boolean propertyListPattern, 145 boolean propertyValuePattern, 146 String canonicalProps, 147 Map<String,String> keyPropertyList, 148 ObjectName pattern) { 149 this.isPropertyListPattern = propertyListPattern; 150 this.isPropertyValuePattern = propertyValuePattern; 151 this.properties = canonicalProps; 152 final int len = keyPropertyList.size(); 153 this.keys = new String[len]; 154 this.values = new String[len]; 155 int i = 0; 156 for (Map.Entry<String,String> entry : keyPropertyList.entrySet()) { 157 keys[i] = entry.getKey(); 158 values[i] = entry.getValue(); 159 i++; 160 } 161 this.pattern = pattern; 162 } 163 164 /** 165 * Return true if the given ObjectName matches the ObjectName pattern 166 * for which this object has been built. 167 * WARNING: domain name is not considered here because it is supposed 168 * not to be wildcard when called. PropertyList is also 169 * supposed not to be zero-length. 170 * @param name The ObjectName we want to match against the pattern. 171 * @return true if <code>name</code> matches the pattern. 172 **/ 173 public boolean matchKeys(ObjectName name) { 174 // If key property value pattern but not key property list 175 // pattern, then the number of key properties must be equal 176 // 177 if (isPropertyValuePattern && 178 !isPropertyListPattern && 179 (name.getKeyPropertyList().size() != keys.length)) 180 return false; 181 182 // If key property value pattern or key property list pattern, 183 // then every property inside pattern should exist in name 184 // 185 if (isPropertyValuePattern || isPropertyListPattern) { 186 for (int i = keys.length - 1; i >= 0 ; i--) { 187 // Find value in given object name for key at current 188 // index in receiver 189 // 190 String v = name.getKeyProperty(keys[i]); 191 // Did we find a value for this key ? 192 // 193 if (v == null) return false; 194 // If this property is ok (same key, same value), go to next 195 // 196 if (isPropertyValuePattern && 197 pattern.isPropertyValuePattern(keys[i])) { 198 // wildmatch key property values 199 // values[i] is the pattern; 200 // v is the string 201 if (Util.wildmatch(v,values[i])) 202 continue; 203 else 204 return false; 205 } 206 if (v.equals(values[i])) continue; 207 return false; 208 } 209 return true; 210 } 211 212 // If no pattern, then canonical names must be equal 213 // 214 final String p1 = name.getCanonicalKeyPropertyListString(); 215 final String p2 = properties; 216 return (p1.equals(p2)); 217 } 218 } 219 220 /** 221 * Add all the matching objects from the given hashtable in the 222 * result set for the given ObjectNamePattern 223 * Do not check whether the domains match (only check for matching 224 * key property lists - see <i>matchKeys()</i>) 225 **/ 226 private void addAllMatching(final Map<String,NamedObject> moiTb, 227 final Set<NamedObject> result, 228 final ObjectNamePattern pattern) { 229 synchronized (moiTb) { 230 for (NamedObject no : moiTb.values()) { 231 final ObjectName on = no.getName(); 232 // if all couples (property, value) are contained 233 if (pattern.matchKeys(on)) result.add(no); 234 } 235 } 236 } 237 238 private void addNewDomMoi(final DynamicMBean object, 239 final String dom, 240 final ObjectName name, 241 final RegistrationContext context) { 242 final Map<String,NamedObject> moiTb = 243 new HashMap<String,NamedObject>(); 244 final String key = name.getCanonicalKeyPropertyListString(); 245 addMoiToTb(object,name,key,moiTb,context); 246 domainTb.put(dom, moiTb); 247 nbElements++; 248 } 249 250 private void registering(RegistrationContext context) { 251 if (context == null) return; 252 try { 253 context.registering(); 254 } catch (RuntimeOperationsException x) { 255 throw x; 256 } catch (RuntimeException x) { 257 throw new RuntimeOperationsException(x); 258 } 259 } 260 261 private void unregistering(RegistrationContext context, ObjectName name) { 262 if (context == null) return; 263 try { 264 context.unregistered(); 265 } catch (Exception x) { 266 // shouldn't come here... 267 MBEANSERVER_LOGGER.log(Level.DEBUG, 268 "Unexpected exception while unregistering "+name, 269 x); 270 } 271 } 272 273 private void addMoiToTb(final DynamicMBean object, 274 final ObjectName name, 275 final String key, 276 final Map<String,NamedObject> moiTb, 277 final RegistrationContext context) { 278 registering(context); 279 moiTb.put(key,new NamedObject(name, object)); 280 } 281 282 /** 283 * Retrieves the named object contained in repository 284 * from the given objectname. 285 */ 286 private NamedObject retrieveNamedObject(ObjectName name) { 287 288 // No patterns inside reposit 289 if (name.isPattern()) return null; 290 291 // Extract the domain name. 292 String dom = name.getDomain().intern(); 293 294 // Default domain case 295 if (dom.length() == 0) { 296 dom = domain; 297 } 298 299 Map<String,NamedObject> moiTb = domainTb.get(dom); 300 if (moiTb == null) { 301 return null; // No domain containing registered object names 302 } 303 304 return moiTb.get(name.getCanonicalKeyPropertyListString()); 305 } 306 307 // Private methods <============================================= 308 309 // Protected methods ---------------------------------------------> 310 311 // Protected methods <============================================= 312 313 // Public methods ---------------------------------------------> 314 315 /** 316 * Construct a new repository with the given default domain. 317 */ 318 public Repository(String domain) { 319 this(domain,true); 320 } 321 322 /** 323 * Construct a new repository with the given default domain. 324 */ 325 public Repository(String domain, boolean fairLock) { 326 lock = new ReentrantReadWriteLock(fairLock); 327 328 domainTb = new HashMap<String,Map<String,NamedObject>>(5); 329 330 if (domain != null && domain.length() != 0) 331 this.domain = domain.intern(); // we use == domain later on... 332 else 333 this.domain = ServiceName.DOMAIN; 334 335 // Creates a new hashtable for the default domain 336 domainTb.put(this.domain, new HashMap<String,NamedObject>()); 337 } 338 339 /** 340 * Returns the list of domains in which any MBean is currently 341 * registered. 342 * 343 */ 344 public String[] getDomains() { 345 346 lock.readLock().lock(); 347 final List<String> result; 348 try { 349 // Temporary list 350 result = new ArrayList<String>(domainTb.size()); 351 for (Map.Entry<String,Map<String,NamedObject>> entry : 352 domainTb.entrySet()) { 353 // Skip domains that are in the table but have no 354 // MBean registered in them 355 // in particular the default domain may be like this 356 Map<String,NamedObject> t = entry.getValue(); 357 if (t != null && t.size() != 0) 358 result.add(entry.getKey()); 359 } 360 } finally { 361 lock.readLock().unlock(); 362 } 363 364 // Make an array from result. 365 return result.toArray(new String[result.size()]); 366 } 367 368 /** 369 * Stores an MBean associated with its object name in the repository. 370 * 371 * @param object MBean to be stored in the repository. 372 * @param name MBean object name. 373 * @param context A registration context. If non null, the repository 374 * will call {@link RegistrationContext#registering() 375 * context.registering()} from within the repository 376 * lock, when it has determined that the {@code object} 377 * can be stored in the repository with that {@code name}. 378 * If {@link RegistrationContext#registering() 379 * context.registering()} throws an exception, the 380 * operation is abandonned, the MBean is not added to the 381 * repository, and a {@link RuntimeOperationsException} 382 * is thrown. 383 */ 384 public void addMBean(final DynamicMBean object, ObjectName name, 385 final RegistrationContext context) 386 throws InstanceAlreadyExistsException { 387 388 if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) { 389 MBEANSERVER_LOGGER.log(Level.TRACE, "name = " + name); 390 } 391 392 // Extract the domain name. 393 String dom = name.getDomain().intern(); 394 boolean to_default_domain = false; 395 396 // Set domain to default if domain is empty and not already set 397 if (dom.length() == 0) 398 name = Util.newObjectName(domain + name.toString()); 399 400 // Do we have default domain ? 401 if (dom == domain) { // ES: OK (dom & domain are interned) 402 to_default_domain = true; 403 dom = domain; 404 } else { 405 to_default_domain = false; 406 } 407 408 // Validate name for an object 409 if (name.isPattern()) { 410 throw new RuntimeOperationsException( 411 new IllegalArgumentException("Repository: cannot add mbean for " + 412 "pattern name " + name.toString())); 413 } 414 415 lock.writeLock().lock(); 416 try { 417 // Domain cannot be JMImplementation if entry does not exist 418 if ( !to_default_domain && 419 dom.equals("JMImplementation") && 420 domainTb.containsKey("JMImplementation")) { 421 throw new RuntimeOperationsException( 422 new IllegalArgumentException( 423 "Repository: domain name cannot be JMImplementation")); 424 } 425 426 // If domain does not already exist, add it to the hash table 427 final Map<String,NamedObject> moiTb = domainTb.get(dom); 428 if (moiTb == null) { 429 addNewDomMoi(object, dom, name, context); 430 return; 431 } else { 432 // Add instance if not already present 433 String cstr = name.getCanonicalKeyPropertyListString(); 434 NamedObject elmt= moiTb.get(cstr); 435 if (elmt != null) { 436 throw new InstanceAlreadyExistsException(name.toString()); 437 } else { 438 nbElements++; 439 addMoiToTb(object,name,cstr,moiTb,context); 440 } 441 } 442 443 } finally { 444 lock.writeLock().unlock(); 445 } 446 } 447 448 /** 449 * Checks whether an MBean of the name specified is already stored in 450 * the repository. 451 * 452 * @param name name of the MBean to find. 453 * 454 * @return true if the MBean is stored in the repository, 455 * false otherwise. 456 */ 457 public boolean contains(ObjectName name) { 458 if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) { 459 MBEANSERVER_LOGGER.log(Level.TRACE, "name = " + name); 460 } 461 lock.readLock().lock(); 462 try { 463 return (retrieveNamedObject(name) != null); 464 } finally { 465 lock.readLock().unlock(); 466 } 467 } 468 469 /** 470 * Retrieves the MBean of the name specified from the repository. The 471 * object name must match exactly. 472 * 473 * @param name name of the MBean to retrieve. 474 * 475 * @return The retrieved MBean if it is contained in the repository, 476 * null otherwise. 477 */ 478 public DynamicMBean retrieve(ObjectName name) { 479 if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) { 480 MBEANSERVER_LOGGER.log(Level.TRACE, "name = " + name); 481 } 482 483 // Calls internal retrieve method to get the named object 484 lock.readLock().lock(); 485 try { 486 NamedObject no = retrieveNamedObject(name); 487 if (no == null) return null; 488 else return no.getObject(); 489 } finally { 490 lock.readLock().unlock(); 491 } 492 } 493 494 /** 495 * Selects and retrieves the list of MBeans whose names match the specified 496 * object name pattern and which match the specified query expression 497 * (optionally). 498 * 499 * @param pattern The name of the MBean(s) to retrieve - may be a specific 500 * object or a name pattern allowing multiple MBeans to be selected. 501 * @param query query expression to apply when selecting objects - this 502 * parameter will be ignored when the Repository Service does not 503 * support filtering. 504 * 505 * @return The list of MBeans selected. There may be zero, one or many 506 * MBeans returned in the set. 507 */ 508 public Set<NamedObject> query(ObjectName pattern, QueryExp query) { 509 510 final Set<NamedObject> result = new HashSet<NamedObject>(); 511 512 // The following filter cases are considered: 513 // null, "", "*:*" : names in all domains 514 // ":*", ":[key=value],*" : names in defaultDomain 515 // "domain:*", "domain:[key=value],*" : names in the specified domain 516 517 // Surely one of the most frequent cases ... query on the whole world 518 ObjectName name; 519 if (pattern == null || 520 pattern.getCanonicalName().length() == 0 || 521 pattern.equals(ObjectName.WILDCARD)) 522 name = ObjectName.WILDCARD; 523 else name = pattern; 524 525 lock.readLock().lock(); 526 try { 527 528 // If pattern is not a pattern, retrieve this mbean ! 529 if (!name.isPattern()) { 530 final NamedObject no = retrieveNamedObject(name); 531 if (no != null) result.add(no); 532 return result; 533 } 534 535 // All names in all domains 536 if (name == ObjectName.WILDCARD) { 537 for (Map<String,NamedObject> moiTb : domainTb.values()) { 538 result.addAll(moiTb.values()); 539 } 540 return result; 541 } 542 543 final String canonical_key_property_list_string = 544 name.getCanonicalKeyPropertyListString(); 545 final boolean allNames = 546 (canonical_key_property_list_string.length()==0); 547 final ObjectNamePattern namePattern = 548 (allNames?null:new ObjectNamePattern(name)); 549 550 // All names in default domain 551 if (name.getDomain().length() == 0) { 552 final Map<String,NamedObject> moiTb = domainTb.get(domain); 553 if (allNames) 554 result.addAll(moiTb.values()); 555 else 556 addAllMatching(moiTb, result, namePattern); 557 return result; 558 } 559 560 if (!name.isDomainPattern()) { 561 final Map<String,NamedObject> moiTb = domainTb.get(name.getDomain()); 562 if (moiTb == null) return Collections.emptySet(); 563 if (allNames) 564 result.addAll(moiTb.values()); 565 else 566 addAllMatching(moiTb, result, namePattern); 567 return result; 568 } 569 570 // Pattern matching in the domain name (*, ?) 571 final String dom2Match = name.getDomain(); 572 for (String dom : domainTb.keySet()) { 573 if (Util.wildmatch(dom, dom2Match)) { 574 final Map<String,NamedObject> moiTb = domainTb.get(dom); 575 if (allNames) 576 result.addAll(moiTb.values()); 577 else 578 addAllMatching(moiTb, result, namePattern); 579 } 580 } 581 return result; 582 } finally { 583 lock.readLock().unlock(); 584 } 585 } 586 587 /** 588 * Removes an MBean from the repository. 589 * 590 * @param name name of the MBean to remove. 591 * @param context A registration context. If non null, the repository 592 * will call {@link RegistrationContext#unregistered() 593 * context.unregistered()} from within the repository 594 * lock, just after the mbean associated with 595 * {@code name} is removed from the repository. 596 * If {@link RegistrationContext#unregistered() 597 * context.unregistered()} is not expected to throw any 598 * exception. If it does, the exception is logged 599 * and swallowed. 600 * 601 * @exception InstanceNotFoundException The MBean does not exist in 602 * the repository. 603 */ 604 public void remove(final ObjectName name, 605 final RegistrationContext context) 606 throws InstanceNotFoundException { 607 608 // Debugging stuff 609 if (MBEANSERVER_LOGGER.isLoggable(Level.TRACE)) { 610 MBEANSERVER_LOGGER.log(Level.TRACE, "name = " + name); 611 } 612 613 // Extract domain name. 614 String dom= name.getDomain().intern(); 615 616 // Default domain case 617 if (dom.length() == 0) dom = domain; 618 619 lock.writeLock().lock(); 620 try { 621 // Find the domain subtable 622 final Map<String,NamedObject> moiTb = domainTb.get(dom); 623 if (moiTb == null) { 624 throw new InstanceNotFoundException(name.toString()); 625 } 626 627 // Remove the corresponding element 628 if (moiTb.remove(name.getCanonicalKeyPropertyListString())==null) { 629 throw new InstanceNotFoundException(name.toString()); 630 } 631 632 // We removed it ! 633 nbElements--; 634 635 // No more object for this domain, we remove this domain hashtable 636 if (moiTb.isEmpty()) { 637 domainTb.remove(dom); 638 639 // set a new default domain table (always present) 640 // need to reinstantiate a hashtable because of possible 641 // big buckets array size inside table, never cleared, 642 // thus the new ! 643 if (dom == domain) // ES: OK dom and domain are interned. 644 domainTb.put(domain, new HashMap<String,NamedObject>()); 645 } 646 647 unregistering(context,name); 648 649 } finally { 650 lock.writeLock().unlock(); 651 } 652 } 653 654 /** 655 * Gets the number of MBeans stored in the repository. 656 * 657 * @return Number of MBeans. 658 */ 659 public Integer getCount() { 660 return nbElements; 661 } 662 663 /** 664 * Gets the name of the domain currently used by default in the 665 * repository. 666 * 667 * @return A string giving the name of the default domain name. 668 */ 669 public String getDefaultDomain() { 670 return domain; 671 } 672 673 // Public methods <============================================= 674} 675