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 }