View Javadoc

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 }