001 package org.apache.ojb.broker.metadata;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.io.FileNotFoundException;
019 import java.io.InputStream;
020 import java.util.Hashtable;
021 import java.util.Iterator;
022 import java.util.List;
023
024 import org.apache.commons.lang.SerializationUtils;
025 import org.apache.ojb.broker.PBKey;
026 import org.apache.ojb.broker.core.PersistenceBrokerConfiguration;
027 import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
028 import org.apache.ojb.broker.util.logging.Logger;
029 import org.apache.ojb.broker.util.logging.LoggerFactory;
030
031 /**
032 * Central class for metadata operations/manipulations - manages OJB's
033 * metadata objects, in particular:
034 * <ul>
035 * <li>{@link org.apache.ojb.broker.metadata.DescriptorRepository} contains
036 * metadata of persistent objects</li>
037 * <li>{@link org.apache.ojb.broker.metadata.ConnectionRepository} contains
038 * all connection metadata information</li>
039 * </ul>
040 *
041 * This class allows transparent flexible metadata loading/manipulation at runtime.
042 *
043 * <p>
044 * <b>How to read/merge metadata</b><br/>
045 * Per default OJB loads default {@link org.apache.ojb.broker.metadata.DescriptorRepository}
046 * and {@link org.apache.ojb.broker.metadata.ConnectionRepository} instances, by reading the
047 * specified repository file. This is done first time the <code>MetadataManager</code> instance
048 * was used.
049 * <br/>
050 * To read metadata information at runtime use
051 * {@link #readDescriptorRepository readDescriptorRepository} and
052 * {@link #readConnectionRepository readConnectionRepository}
053 * methods.
054 * <br/>
055 * It is also possible to merge different repositories using
056 * {@link #mergeDescriptorRepository mergeDescriptorRepository}
057 * and {@link #mergeConnectionRepository mergeConnectionRepository}
058 *
059 * </p>
060 *
061 * <a name="perThread"/>
062 * <h3>Per thread handling of metadata</h3>
063 * <p>
064 * Per default the manager handle one global {@link org.apache.ojb.broker.metadata.DescriptorRepository}
065 * for all calling threads, but it is ditto possible to use different metadata <i>profiles</i> in a per thread
066 * manner - <i>profiles</i> means different copies of {@link org.apache.ojb.broker.metadata.DescriptorRepository}
067 * objects.
068 * <p/>
069 *
070 * <p>
071 * <a name="enablePerThreadMode"/>
072 * <b>Enable the per thread mode</b><br/>
073 * To enable the 'per thread' mode for {@link org.apache.ojb.broker.metadata.DescriptorRepository}
074 * instances:
075 * <pre>
076 * MetadataManager mm = MetadataManager.getInstance();
077 * // tell the manager to use per thread mode
078 * mm.setEnablePerThreadChanges(true);
079 * ...
080 * </pre>
081 * This could be done e.g. at start up.<br/>
082 * Now it's possible to use dedicated <code>DescriptorRepository</code> instances
083 * per thread:
084 * <pre>
085 * // e.g we get a coppy of the global repository
086 * DescriptorRepository dr = mm.copyOfGlobalRepository();
087 * // now we can manipulate the persistent object metadata of the copy
088 * ......
089 *
090 * // set the changed repository for this thread
091 * mm.setDescriptor(dr);
092 *
093 * // now let this thread lookup a PersistenceBroker instance
094 * // with the modified metadata
095 * // all other threads use the global metadata
096 * PersistenceBroker broker = Persis......
097 * </pre>
098 * Note: Change metadata <i>before</i> lookup the {@link org.apache.ojb.broker.PersistenceBroker}
099 * 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 }