1 package org.apache.ojb.broker.metadata; 2 3 /* Copyright 2002-2005 The Apache Software Foundation 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * 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 18 import java.io.FileNotFoundException; 19 import java.io.InputStream; 20 import java.util.Hashtable; 21 import java.util.Iterator; 22 import java.util.List; 23 24 import org.apache.commons.lang.SerializationUtils; 25 import org.apache.ojb.broker.PBKey; 26 import org.apache.ojb.broker.core.PersistenceBrokerConfiguration; 27 import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator; 28 import org.apache.ojb.broker.util.logging.Logger; 29 import org.apache.ojb.broker.util.logging.LoggerFactory; 30 31 /** 32 * Central class for metadata operations/manipulations - manages OJB's 33 * metadata objects, in particular: 34 * <ul> 35 * <li>{@link org.apache.ojb.broker.metadata.DescriptorRepository} contains 36 * metadata of persistent objects</li> 37 * <li>{@link org.apache.ojb.broker.metadata.ConnectionRepository} contains 38 * all connection metadata information</li> 39 * </ul> 40 * 41 * This class allows transparent flexible metadata loading/manipulation at runtime. 42 * 43 * <p> 44 * <b>How to read/merge metadata</b><br/> 45 * Per default OJB loads default {@link org.apache.ojb.broker.metadata.DescriptorRepository} 46 * and {@link org.apache.ojb.broker.metadata.ConnectionRepository} instances, by reading the 47 * specified repository file. This is done first time the <code>MetadataManager</code> instance 48 * was used. 49 * <br/> 50 * To read metadata information at runtime use 51 * {@link #readDescriptorRepository readDescriptorRepository} and 52 * {@link #readConnectionRepository readConnectionRepository} 53 * methods. 54 * <br/> 55 * It is also possible to merge different repositories using 56 * {@link #mergeDescriptorRepository mergeDescriptorRepository} 57 * and {@link #mergeConnectionRepository mergeConnectionRepository} 58 * 59 * </p> 60 * 61 * <a name="perThread"/> 62 * <h3>Per thread handling of metadata</h3> 63 * <p> 64 * Per default the manager handle one global {@link org.apache.ojb.broker.metadata.DescriptorRepository} 65 * for all calling threads, but it is ditto possible to use different metadata <i>profiles</i> in a per thread 66 * manner - <i>profiles</i> means different copies of {@link org.apache.ojb.broker.metadata.DescriptorRepository} 67 * objects. 68 * <p/> 69 * 70 * <p> 71 * <a name="enablePerThreadMode"/> 72 * <b>Enable the per thread mode</b><br/> 73 * To enable the 'per thread' mode for {@link org.apache.ojb.broker.metadata.DescriptorRepository} 74 * instances: 75 * <pre> 76 * MetadataManager mm = MetadataManager.getInstance(); 77 * // tell the manager to use per thread mode 78 * mm.setEnablePerThreadChanges(true); 79 * ... 80 * </pre> 81 * This could be done e.g. at start up.<br/> 82 * Now it's possible to use dedicated <code>DescriptorRepository</code> instances 83 * per thread: 84 * <pre> 85 * // e.g we get a coppy of the global repository 86 * DescriptorRepository dr = mm.copyOfGlobalRepository(); 87 * // now we can manipulate the persistent object metadata of the copy 88 * ...... 89 * 90 * // set the changed repository for this thread 91 * mm.setDescriptor(dr); 92 * 93 * // now let this thread lookup a PersistenceBroker instance 94 * // with the modified metadata 95 * // all other threads use the global metadata 96 * PersistenceBroker broker = Persis...... 97 * </pre> 98 * Note: Change metadata <i>before</i> lookup the {@link org.apache.ojb.broker.PersistenceBroker} 99 * instance for current thread, because the metadata was bound to the PB at lookup. 100 * </p> 101 * 102 * <p> 103 * <b>How to use different metadata profiles</b><br/> 104 * MetadataManager was shipped with a simple mechanism to 105 * add, remove and load different persistent objects metadata 106 * profiles (different {@link org.apache.ojb.broker.metadata.DescriptorRepository} 107 * instances) in a per thread manner. Use 108 * <ul> 109 * <li>{@link #addProfile addProfile} add different persistent object metadata profiles</li> 110 * <li>{@link #removeProfile removeProfile} remove a persistent object metadata profiles</li> 111 * <li>{@link #loadProfile loadProfile} load a profile for the current thread</li> 112 * </ul> 113 * Note: method {@link #loadProfile loadProfile} only works if 114 * the <a href="#enablePerThreadMode">per thread mode</a> is enabled. 115 * </p> 116 * 117 * 118 * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a> 119 * @version $Id: MetadataManager.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $ 120 */ 121 public class MetadataManager 122 { 123 private static Logger log = LoggerFactory.getLogger(MetadataManager.class); 124 125 private static final String MSG_STR = "* Can't find DescriptorRepository for current thread, use default one *"; 126 private static ThreadLocal threadedRepository = new ThreadLocal(); 127 private static ThreadLocal currentProfileKey = new ThreadLocal(); 128 private static MetadataManager singleton; 129 130 private Hashtable metadataProfiles; 131 private DescriptorRepository globalRepository; 132 private ConnectionRepository connectionRepository; 133 private boolean enablePerThreadChanges; 134 private PBKey defaultPBKey; 135 136 // singleton 137 private MetadataManager() 138 { 139 init(); 140 } 141 142 private void init() 143 { 144 metadataProfiles = new Hashtable(); 145 final String repository = ((PersistenceBrokerConfiguration) OjbConfigurator.getInstance() 146 .getConfigurationFor(null)).getRepositoryFilename(); 147 try 148 { 149 globalRepository = new RepositoryPersistor().readDescriptorRepository(repository); 150 connectionRepository = new RepositoryPersistor().readConnectionRepository(repository); 151 } 152 catch (FileNotFoundException ex) 153 { 154 log.warn("Could not access '" + repository + "' or a DOCTYPE/DTD-dependency. " 155 + "(Check letter case for file names and HTTP-access if using DOCTYPE PUBLIC)" 156 + " Starting with empty metadata and connection configurations.", ex); 157 globalRepository = new DescriptorRepository(); 158 connectionRepository = new ConnectionRepository(); 159 } 160 catch (Exception ex) 161 { 162 throw new MetadataException("Can't read repository file '" + repository + "'", ex); 163 } 164 } 165 166 public void shutdown() 167 { 168 threadedRepository = null; 169 currentProfileKey = null; 170 globalRepository = null; 171 metadataProfiles = null; 172 singleton = null; 173 } 174 175 /** 176 * Returns an instance of this class. 177 */ 178 public static synchronized MetadataManager getInstance() 179 { 180 // lazy initialization 181 if (singleton == null) 182 { 183 singleton = new MetadataManager(); 184 } 185 return singleton; 186 } 187 188 /** 189 * Returns the current valid {@link org.apache.ojb.broker.metadata.DescriptorRepository} for 190 * the caller. This is the provided way to obtain the 191 * {@link org.apache.ojb.broker.metadata.DescriptorRepository}. 192 * <br> 193 * When {@link #isEnablePerThreadChanges per thread descriptor handling} is enabled 194 * it search for a specific {@link org.apache.ojb.broker.metadata.DescriptorRepository} 195 * for the calling thread, if none can be found the global descriptor was returned. 196 * 197 * @see MetadataManager#getGlobalRepository 198 * @see MetadataManager#copyOfGlobalRepository 199 */ 200 public DescriptorRepository getRepository() 201 { 202 DescriptorRepository repository; 203 if (enablePerThreadChanges) 204 { 205 repository = (DescriptorRepository) threadedRepository.get(); 206 if (repository == null) 207 { 208 repository = getGlobalRepository(); 209 log.info(MSG_STR); 210 } 211 // arminw: 212 // TODO: Be more strict in per thread mode and throw a exception when not find descriptor for calling thread? 213 // if (repository == null) 214 // { 215 // throw new MetadataException("Can't find a DescriptorRepository for current thread, don't forget" + 216 // " to set a DescriptorRepository if enable per thread changes before perform other action"); 217 // } 218 return repository; 219 } 220 else 221 { 222 return globalRepository; 223 } 224 } 225 226 /** 227 * Returns explicit the global {@link org.apache.ojb.broker.metadata.DescriptorRepository} - use with 228 * care, because it ignores the {@link #isEnablePerThreadChanges per thread mode}. 229 * 230 * @see MetadataManager#getRepository 231 * @see MetadataManager#copyOfGlobalRepository 232 */ 233 public DescriptorRepository getGlobalRepository() 234 { 235 return globalRepository; 236 } 237 238 /** 239 * Returns the {@link ConnectionRepository}. 240 */ 241 public ConnectionRepository connectionRepository() 242 { 243 return connectionRepository; 244 } 245 246 /** 247 * Merge the given {@link ConnectionRepository} with the existing one (without making 248 * a deep copy of the containing connection descriptors). 249 * @see #mergeConnectionRepository(ConnectionRepository targetRepository, ConnectionRepository sourceRepository, boolean deep) 250 */ 251 public void mergeConnectionRepository(ConnectionRepository repository) 252 { 253 mergeConnectionRepository(connectionRepository(), repository, false); 254 } 255 256 /** 257 * Merge the given source {@link ConnectionRepository} with the 258 * existing target. If parameter 259 * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made. 260 * <br/> 261 * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized 262 * by using the default class loader to resolve classes. This can be problematic 263 * when classes are loaded by a context class loader. 264 * <p> 265 * Note: All classes within the repository structure have to implement 266 * <code>java.io.Serializable</code> to be able to create a cloned copy. 267 */ 268 public void mergeConnectionRepository( 269 ConnectionRepository targetRepository, ConnectionRepository sourceRepository, boolean deep) 270 { 271 List list = sourceRepository.getAllDescriptor(); 272 for (Iterator iterator = list.iterator(); iterator.hasNext();) 273 { 274 JdbcConnectionDescriptor jcd = (JdbcConnectionDescriptor) iterator.next(); 275 if (deep) 276 { 277 //TODO: adopt copy/clone methods for metadata classes? 278 jcd = (JdbcConnectionDescriptor) SerializationUtils.clone(jcd); 279 } 280 targetRepository.addDescriptor(jcd); 281 } 282 } 283 284 /** 285 * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository} 286 * (without making a deep copy of containing class-descriptor objects) with the 287 * global one, returned by method {@link #getRepository()} - keep 288 * in mind if running in <a href="#perThread">per thread mode</a> 289 * merge maybe only takes effect on current thread. 290 * 291 * @see #mergeDescriptorRepository(DescriptorRepository targetRepository, DescriptorRepository sourceRepository, boolean deep) 292 */ 293 public void mergeDescriptorRepository(DescriptorRepository repository) 294 { 295 mergeDescriptorRepository(getRepository(), repository, false); 296 } 297 298 /** 299 * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository} 300 * files, the source objects will be pushed to the target repository. If parameter 301 * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made. 302 * <br/> 303 * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized 304 * by using the default class loader to resolve classes. This can be problematic 305 * when classes are loaded by a context class loader. 306 * <p> 307 * Note: All classes within the repository structure have to implement 308 * <code>java.io.Serializable</code> to be able to create a cloned copy. 309 * 310 * @see #isEnablePerThreadChanges 311 * @see #setEnablePerThreadChanges 312 */ 313 public void mergeDescriptorRepository( 314 DescriptorRepository targetRepository, DescriptorRepository sourceRepository, boolean deep) 315 { 316 Iterator it = sourceRepository.iterator(); 317 while (it.hasNext()) 318 { 319 ClassDescriptor cld = (ClassDescriptor) it.next(); 320 if (deep) 321 { 322 //TODO: adopt copy/clone methods for metadata classes? 323 cld = (ClassDescriptor) SerializationUtils.clone(cld); 324 } 325 targetRepository.put(cld.getClassOfObject(), cld); 326 cld.setRepository(targetRepository); 327 } 328 } 329 330 /** 331 * Read ClassDescriptors from the given repository file. 332 * @see #mergeDescriptorRepository 333 */ 334 public DescriptorRepository readDescriptorRepository(String fileName) 335 { 336 try 337 { 338 RepositoryPersistor persistor = new RepositoryPersistor(); 339 return persistor.readDescriptorRepository(fileName); 340 } 341 catch (Exception e) 342 { 343 throw new MetadataException("Can not read repository " + fileName, e); 344 } 345 } 346 347 /** 348 * Read ClassDescriptors from the given InputStream. 349 * @see #mergeDescriptorRepository 350 */ 351 public DescriptorRepository readDescriptorRepository(InputStream inst) 352 { 353 try 354 { 355 RepositoryPersistor persistor = new RepositoryPersistor(); 356 return persistor.readDescriptorRepository(inst); 357 } 358 catch (Exception e) 359 { 360 throw new MetadataException("Can not read repository " + inst, e); 361 } 362 } 363 364 /** 365 * Read JdbcConnectionDescriptors from the given repository file. 366 * 367 * @see #mergeConnectionRepository 368 */ 369 public ConnectionRepository readConnectionRepository(String fileName) 370 { 371 try 372 { 373 RepositoryPersistor persistor = new RepositoryPersistor(); 374 return persistor.readConnectionRepository(fileName); 375 } 376 catch (Exception e) 377 { 378 throw new MetadataException("Can not read repository " + fileName, e); 379 } 380 } 381 382 /** 383 * Read JdbcConnectionDescriptors from this InputStream. 384 * 385 * @see #mergeConnectionRepository 386 */ 387 public ConnectionRepository readConnectionRepository(InputStream inst) 388 { 389 try 390 { 391 RepositoryPersistor persistor = new RepositoryPersistor(); 392 return persistor.readConnectionRepository(inst); 393 } 394 catch (Exception e) 395 { 396 throw new MetadataException("Can not read repository from " + inst, e); 397 } 398 } 399 400 /** 401 * Set the {@link org.apache.ojb.broker.metadata.DescriptorRepository} - if <i>global</i> was true, the 402 * given descriptor aquire global availability (<i>use with care!</i>), 403 * else the given descriptor was associated with the calling thread. 404 * 405 * @see #isEnablePerThreadChanges 406 * @see #setEnablePerThreadChanges 407 */ 408 public void setDescriptor(DescriptorRepository repository, boolean global) 409 { 410 if (global) 411 { 412 if (log.isDebugEnabled()) log.debug("Set new global repository: " + repository); 413 globalRepository = repository; 414 } 415 else 416 { 417 if (log.isDebugEnabled()) log.debug("Set new threaded repository: " + repository); 418 threadedRepository.set(repository); 419 } 420 } 421 422 /** 423 * Set {@link DescriptorRepository} for the current thread. 424 * Convenience method for 425 * {@link #setDescriptor(DescriptorRepository repository, boolean global) setDescriptor(repository, false)}. 426 */ 427 public void setDescriptor(DescriptorRepository repository) 428 { 429 setDescriptor(repository, false); 430 } 431 432 /** 433 * Convenience method for 434 * {@link #setDescriptor setDescriptor(repository, false)}. 435 * @deprecated use {@link #setDescriptor} 436 */ 437 public void setPerThreadDescriptor(DescriptorRepository repository) 438 { 439 setDescriptor(repository, false); 440 } 441 442 /** 443 * Returns a copy of the current global 444 * {@link org.apache.ojb.broker.metadata.DescriptorRepository} 445 * <p> 446 * Note: All classes within the repository structure have to implement 447 * <code>java.io.Serializable</code> to be able to create a cloned copy. 448 * 449 * @see MetadataManager#getGlobalRepository 450 * @see MetadataManager#getRepository 451 */ 452 public DescriptorRepository copyOfGlobalRepository() 453 { 454 return (DescriptorRepository) SerializationUtils.clone(globalRepository); 455 } 456 457 /** 458 * If returns <i>true</i> if <a href="#perThread">per thread</a> runtime 459 * changes of the {@link org.apache.ojb.broker.metadata.DescriptorRepository} 460 * is enabled and the {@link #getRepository} method returns a threaded 461 * repository file if set, or the global if no threaded was found. 462 * <br> 463 * If returns <i>false</i> the {@link #getRepository} method return 464 * always the {@link #getGlobalRepository() global} repository. 465 * 466 * @see #setEnablePerThreadChanges 467 */ 468 public boolean isEnablePerThreadChanges() 469 { 470 return enablePerThreadChanges; 471 } 472 473 /** 474 * Enable the possibility of making <a href="#perThread">per thread</a> runtime changes 475 * of the {@link org.apache.ojb.broker.metadata.DescriptorRepository}. 476 * 477 * @see #isEnablePerThreadChanges 478 */ 479 public void setEnablePerThreadChanges(boolean enablePerThreadChanges) 480 { 481 this.enablePerThreadChanges = enablePerThreadChanges; 482 } 483 484 /** 485 * Add a metadata profile. 486 * @see #loadProfile 487 */ 488 public void addProfile(Object key, DescriptorRepository repository) 489 { 490 if (metadataProfiles.contains(key)) 491 { 492 throw new MetadataException("Duplicate profile key. Key '" + key + "' already exists."); 493 } 494 metadataProfiles.put(key, repository); 495 } 496 497 /** 498 * Load the given metadata profile for the current thread. 499 * 500 */ 501 public void loadProfile(Object key) 502 { 503 if (!isEnablePerThreadChanges()) 504 { 505 throw new MetadataException("Can not load profile with disabled per thread mode"); 506 } 507 DescriptorRepository rep = (DescriptorRepository) metadataProfiles.get(key); 508 if (rep == null) 509 { 510 throw new MetadataException("Can not find profile for key '" + key + "'"); 511 } 512 currentProfileKey.set(key); 513 setDescriptor(rep); 514 } 515 516 /** 517 * Returns the last activated profile key. 518 * @return the last activated profile key or null if no profile has been loaded 519 * @throws MetadataException if per-thread changes has not been activated 520 * @see #loadProfile(Object) 521 */ 522 public Object getCurrentProfileKey() throws MetadataException 523 { 524 if (!isEnablePerThreadChanges()) 525 { 526 throw new MetadataException("Call to this method is undefined, since per-thread mode is disabled."); 527 } 528 return currentProfileKey.get(); 529 } 530 531 /** 532 * Remove the given metadata profile. 533 */ 534 public DescriptorRepository removeProfile(Object key) 535 { 536 return (DescriptorRepository) metadataProfiles.remove(key); 537 } 538 539 /** 540 * Remove all metadata profiles. 541 */ 542 public void clearProfiles() 543 { 544 metadataProfiles.clear(); 545 currentProfileKey.set(null); 546 } 547 548 /** 549 * Remove all profiles 550 * 551 * @see #removeProfile 552 * @see #addProfile 553 */ 554 public void removeAllProfiles() 555 { 556 metadataProfiles.clear(); 557 currentProfileKey.set(null); 558 } 559 560 /** 561 * Return the default {@link PBKey} used in convinience method 562 * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}. 563 * <br/> 564 * If in {@link JdbcConnectionDescriptor} the 565 * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection} 566 * is enabled, OJB will detect the default {@link org.apache.ojb.broker.PBKey} by itself. 567 * 568 * @see #setDefaultPBKey 569 */ 570 public PBKey getDefaultPBKey() 571 { 572 if(defaultPBKey == null) 573 { 574 defaultPBKey = buildDefaultKey(); 575 } 576 return defaultPBKey; 577 } 578 579 /** 580 * Set the {@link PBKey} used in convinience method 581 * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}. 582 * <br/> 583 * It's only allowed to use one {@link JdbcConnectionDescriptor} with enabled 584 * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection}. In this case 585 * OJB will automatically set the default key. 586 * <br/> 587 * Note: It's recommended to set this key only once and not to change at runtime 588 * of OJB to avoid side-effects. 589 * If set more then one time a warning will be logged. 590 * @throws MetadataException if key was set more than one time 591 */ 592 public void setDefaultPBKey(PBKey defaultPBKey) 593 { 594 if(this.defaultPBKey != null) 595 { 596 log.warn("The used default PBKey change. Current key is " + this.defaultPBKey + ", new key will be " + defaultPBKey); 597 } 598 this.defaultPBKey = defaultPBKey; 599 log.info("Set default PBKey for convenience broker creation: " + defaultPBKey); 600 } 601 602 /** 603 * Try to build an default PBKey for convenience PB create method. 604 * 605 * @return PBKey or <code>null</code> if default key was not declared in 606 * metadata 607 */ 608 private PBKey buildDefaultKey() 609 { 610 List descriptors = connectionRepository().getAllDescriptor(); 611 JdbcConnectionDescriptor descriptor; 612 PBKey result = null; 613 for (Iterator iterator = descriptors.iterator(); iterator.hasNext();) 614 { 615 descriptor = (JdbcConnectionDescriptor) iterator.next(); 616 if (descriptor.isDefaultConnection()) 617 { 618 if(result != null) 619 { 620 log.error("Found additional connection descriptor with enabled 'default-connection' " 621 + descriptor.getPBKey() + ". This is NOT allowed. Will use the first found descriptor " + result 622 + " as default connection"); 623 } 624 else 625 { 626 result = descriptor.getPBKey(); 627 } 628 } 629 } 630 631 if(result == null) 632 { 633 log.info("No 'default-connection' attribute set in jdbc-connection-descriptors," + 634 " thus it's currently not possible to use 'defaultPersistenceBroker()' " + 635 " convenience method to lookup PersistenceBroker instances. But it's possible"+ 636 " to enable this at runtime using 'setDefaultKey' method."); 637 } 638 return result; 639 } 640 }