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.19.2.7 2005/12/21 22:26:10 tomdz Exp $
120: */
121: public class MetadataManager {
122: private static Logger log = LoggerFactory
123: .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: init();
139: }
140:
141: private void init() {
142: metadataProfiles = new Hashtable();
143: final String repository = ((PersistenceBrokerConfiguration) OjbConfigurator
144: .getInstance().getConfigurationFor(null))
145: .getRepositoryFilename();
146: try {
147: globalRepository = new RepositoryPersistor()
148: .readDescriptorRepository(repository);
149: connectionRepository = new RepositoryPersistor()
150: .readConnectionRepository(repository);
151: } catch (FileNotFoundException ex) {
152: log
153: .warn(
154: "Could not access '"
155: + repository
156: + "' or a DOCTYPE/DTD-dependency. "
157: + "(Check letter case for file names and HTTP-access if using DOCTYPE PUBLIC)"
158: + " Starting with empty metadata and connection configurations.",
159: ex);
160: globalRepository = new DescriptorRepository();
161: connectionRepository = new ConnectionRepository();
162: } catch (Exception ex) {
163: throw new MetadataException("Can't read repository file '"
164: + repository + "'", ex);
165: }
166: }
167:
168: public void shutdown() {
169: threadedRepository = null;
170: currentProfileKey = null;
171: globalRepository = null;
172: metadataProfiles = null;
173: singleton = null;
174: }
175:
176: /**
177: * Returns an instance of this class.
178: */
179: public static synchronized MetadataManager getInstance() {
180: // lazy initialization
181: if (singleton == null) {
182: singleton = new MetadataManager();
183: }
184: return singleton;
185: }
186:
187: /**
188: * Returns the current valid {@link org.apache.ojb.broker.metadata.DescriptorRepository} for
189: * the caller. This is the provided way to obtain the
190: * {@link org.apache.ojb.broker.metadata.DescriptorRepository}.
191: * <br>
192: * When {@link #isEnablePerThreadChanges per thread descriptor handling} is enabled
193: * it search for a specific {@link org.apache.ojb.broker.metadata.DescriptorRepository}
194: * for the calling thread, if none can be found the global descriptor was returned.
195: *
196: * @see MetadataManager#getGlobalRepository
197: * @see MetadataManager#copyOfGlobalRepository
198: */
199: public DescriptorRepository getRepository() {
200: DescriptorRepository repository;
201: if (enablePerThreadChanges) {
202: repository = (DescriptorRepository) threadedRepository
203: .get();
204: if (repository == null) {
205: repository = getGlobalRepository();
206: log.info(MSG_STR);
207: }
208: // arminw:
209: // TODO: Be more strict in per thread mode and throw a exception when not find descriptor for calling thread?
210: // if (repository == null)
211: // {
212: // throw new MetadataException("Can't find a DescriptorRepository for current thread, don't forget" +
213: // " to set a DescriptorRepository if enable per thread changes before perform other action");
214: // }
215: return repository;
216: } else {
217: return globalRepository;
218: }
219: }
220:
221: /**
222: * Returns explicit the global {@link org.apache.ojb.broker.metadata.DescriptorRepository} - use with
223: * care, because it ignores the {@link #isEnablePerThreadChanges per thread mode}.
224: *
225: * @see MetadataManager#getRepository
226: * @see MetadataManager#copyOfGlobalRepository
227: */
228: public DescriptorRepository getGlobalRepository() {
229: return globalRepository;
230: }
231:
232: /**
233: * Returns the {@link ConnectionRepository}.
234: */
235: public ConnectionRepository connectionRepository() {
236: return connectionRepository;
237: }
238:
239: /**
240: * Merge the given {@link ConnectionRepository} with the existing one (without making
241: * a deep copy of the containing connection descriptors).
242: * @see #mergeConnectionRepository(ConnectionRepository targetRepository, ConnectionRepository sourceRepository, boolean deep)
243: */
244: public void mergeConnectionRepository(
245: ConnectionRepository repository) {
246: mergeConnectionRepository(connectionRepository(), repository,
247: false);
248: }
249:
250: /**
251: * Merge the given source {@link ConnectionRepository} with the
252: * existing target. If parameter
253: * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made.
254: * <br/>
255: * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized
256: * by using the default class loader to resolve classes. This can be problematic
257: * when classes are loaded by a context class loader.
258: * <p>
259: * Note: All classes within the repository structure have to implement
260: * <code>java.io.Serializable</code> to be able to create a cloned copy.
261: */
262: public void mergeConnectionRepository(
263: ConnectionRepository targetRepository,
264: ConnectionRepository sourceRepository, boolean deep) {
265: List list = sourceRepository.getAllDescriptor();
266: for (Iterator iterator = list.iterator(); iterator.hasNext();) {
267: JdbcConnectionDescriptor jcd = (JdbcConnectionDescriptor) iterator
268: .next();
269: if (deep) {
270: //TODO: adopt copy/clone methods for metadata classes?
271: jcd = (JdbcConnectionDescriptor) SerializationUtils
272: .clone(jcd);
273: }
274: targetRepository.addDescriptor(jcd);
275: }
276: }
277:
278: /**
279: * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository}
280: * (without making a deep copy of containing class-descriptor objects) with the
281: * global one, returned by method {@link #getRepository()} - keep
282: * in mind if running in <a href="#perThread">per thread mode</a>
283: * merge maybe only takes effect on current thread.
284: *
285: * @see #mergeDescriptorRepository(DescriptorRepository targetRepository, DescriptorRepository sourceRepository, boolean deep)
286: */
287: public void mergeDescriptorRepository(
288: DescriptorRepository repository) {
289: mergeDescriptorRepository(getRepository(), repository, false);
290: }
291:
292: /**
293: * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository}
294: * files, the source objects will be pushed to the target repository. If parameter
295: * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made.
296: * <br/>
297: * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized
298: * by using the default class loader to resolve classes. This can be problematic
299: * when classes are loaded by a context class loader.
300: * <p>
301: * Note: All classes within the repository structure have to implement
302: * <code>java.io.Serializable</code> to be able to create a cloned copy.
303: *
304: * @see #isEnablePerThreadChanges
305: * @see #setEnablePerThreadChanges
306: */
307: public void mergeDescriptorRepository(
308: DescriptorRepository targetRepository,
309: DescriptorRepository sourceRepository, boolean deep) {
310: Iterator it = sourceRepository.iterator();
311: while (it.hasNext()) {
312: ClassDescriptor cld = (ClassDescriptor) it.next();
313: if (deep) {
314: //TODO: adopt copy/clone methods for metadata classes?
315: cld = (ClassDescriptor) SerializationUtils.clone(cld);
316: }
317: targetRepository.put(cld.getClassOfObject(), cld);
318: cld.setRepository(targetRepository);
319: }
320: }
321:
322: /**
323: * Read ClassDescriptors from the given repository file.
324: * @see #mergeDescriptorRepository
325: */
326: public DescriptorRepository readDescriptorRepository(String fileName) {
327: try {
328: RepositoryPersistor persistor = new RepositoryPersistor();
329: return persistor.readDescriptorRepository(fileName);
330: } catch (Exception e) {
331: throw new MetadataException("Can not read repository "
332: + fileName, e);
333: }
334: }
335:
336: /**
337: * Read ClassDescriptors from the given InputStream.
338: * @see #mergeDescriptorRepository
339: */
340: public DescriptorRepository readDescriptorRepository(
341: InputStream inst) {
342: try {
343: RepositoryPersistor persistor = new RepositoryPersistor();
344: return persistor.readDescriptorRepository(inst);
345: } catch (Exception e) {
346: throw new MetadataException("Can not read repository "
347: + inst, e);
348: }
349: }
350:
351: /**
352: * Read JdbcConnectionDescriptors from the given repository file.
353: *
354: * @see #mergeConnectionRepository
355: */
356: public ConnectionRepository readConnectionRepository(String fileName) {
357: try {
358: RepositoryPersistor persistor = new RepositoryPersistor();
359: return persistor.readConnectionRepository(fileName);
360: } catch (Exception e) {
361: throw new MetadataException("Can not read repository "
362: + fileName, e);
363: }
364: }
365:
366: /**
367: * Read JdbcConnectionDescriptors from this InputStream.
368: *
369: * @see #mergeConnectionRepository
370: */
371: public ConnectionRepository readConnectionRepository(
372: InputStream inst) {
373: try {
374: RepositoryPersistor persistor = new RepositoryPersistor();
375: return persistor.readConnectionRepository(inst);
376: } catch (Exception e) {
377: throw new MetadataException("Can not read repository from "
378: + inst, e);
379: }
380: }
381:
382: /**
383: * Set the {@link org.apache.ojb.broker.metadata.DescriptorRepository} - if <i>global</i> was true, the
384: * given descriptor aquire global availability (<i>use with care!</i>),
385: * else the given descriptor was associated with the calling thread.
386: *
387: * @see #isEnablePerThreadChanges
388: * @see #setEnablePerThreadChanges
389: */
390: public void setDescriptor(DescriptorRepository repository,
391: boolean global) {
392: if (global) {
393: if (log.isDebugEnabled())
394: log.debug("Set new global repository: " + repository);
395: globalRepository = repository;
396: } else {
397: if (log.isDebugEnabled())
398: log.debug("Set new threaded repository: " + repository);
399: threadedRepository.set(repository);
400: }
401: }
402:
403: /**
404: * Set {@link DescriptorRepository} for the current thread.
405: * Convenience method for
406: * {@link #setDescriptor(DescriptorRepository repository, boolean global) setDescriptor(repository, false)}.
407: */
408: public void setDescriptor(DescriptorRepository repository) {
409: setDescriptor(repository, false);
410: }
411:
412: /**
413: * Convenience method for
414: * {@link #setDescriptor setDescriptor(repository, false)}.
415: * @deprecated use {@link #setDescriptor}
416: */
417: public void setPerThreadDescriptor(DescriptorRepository repository) {
418: setDescriptor(repository, false);
419: }
420:
421: /**
422: * Returns a copy of the current global
423: * {@link org.apache.ojb.broker.metadata.DescriptorRepository}
424: * <p>
425: * Note: All classes within the repository structure have to implement
426: * <code>java.io.Serializable</code> to be able to create a cloned copy.
427: *
428: * @see MetadataManager#getGlobalRepository
429: * @see MetadataManager#getRepository
430: */
431: public DescriptorRepository copyOfGlobalRepository() {
432: return (DescriptorRepository) SerializationUtils
433: .clone(globalRepository);
434: }
435:
436: /**
437: * If returns <i>true</i> if <a href="#perThread">per thread</a> runtime
438: * changes of the {@link org.apache.ojb.broker.metadata.DescriptorRepository}
439: * is enabled and the {@link #getRepository} method returns a threaded
440: * repository file if set, or the global if no threaded was found.
441: * <br>
442: * If returns <i>false</i> the {@link #getRepository} method return
443: * always the {@link #getGlobalRepository() global} repository.
444: *
445: * @see #setEnablePerThreadChanges
446: */
447: public boolean isEnablePerThreadChanges() {
448: return enablePerThreadChanges;
449: }
450:
451: /**
452: * Enable the possibility of making <a href="#perThread">per thread</a> runtime changes
453: * of the {@link org.apache.ojb.broker.metadata.DescriptorRepository}.
454: *
455: * @see #isEnablePerThreadChanges
456: */
457: public void setEnablePerThreadChanges(boolean enablePerThreadChanges) {
458: this .enablePerThreadChanges = enablePerThreadChanges;
459: }
460:
461: /**
462: * Add a metadata profile.
463: * @see #loadProfile
464: */
465: public void addProfile(Object key, DescriptorRepository repository) {
466: if (metadataProfiles.contains(key)) {
467: throw new MetadataException("Duplicate profile key. Key '"
468: + key + "' already exists.");
469: }
470: metadataProfiles.put(key, repository);
471: }
472:
473: /**
474: * Load the given metadata profile for the current thread.
475: *
476: */
477: public void loadProfile(Object key) {
478: if (!isEnablePerThreadChanges()) {
479: throw new MetadataException(
480: "Can not load profile with disabled per thread mode");
481: }
482: DescriptorRepository rep = (DescriptorRepository) metadataProfiles
483: .get(key);
484: if (rep == null) {
485: throw new MetadataException(
486: "Can not find profile for key '" + key + "'");
487: }
488: currentProfileKey.set(key);
489: setDescriptor(rep);
490: }
491:
492: /**
493: * Returns the last activated profile key.
494: * @return the last activated profile key or null if no profile has been loaded
495: * @throws MetadataException if per-thread changes has not been activated
496: * @see #loadProfile(Object)
497: */
498: public Object getCurrentProfileKey() throws MetadataException {
499: if (!isEnablePerThreadChanges()) {
500: throw new MetadataException(
501: "Call to this method is undefined, since per-thread mode is disabled.");
502: }
503: return currentProfileKey.get();
504: }
505:
506: /**
507: * Remove the given metadata profile.
508: */
509: public DescriptorRepository removeProfile(Object key) {
510: return (DescriptorRepository) metadataProfiles.remove(key);
511: }
512:
513: /**
514: * Remove all metadata profiles.
515: */
516: public void clearProfiles() {
517: metadataProfiles.clear();
518: currentProfileKey.set(null);
519: }
520:
521: /**
522: * Remove all profiles
523: *
524: * @see #removeProfile
525: * @see #addProfile
526: */
527: public void removeAllProfiles() {
528: metadataProfiles.clear();
529: currentProfileKey.set(null);
530: }
531:
532: /**
533: * Return the default {@link PBKey} used in convinience method
534: * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}.
535: * <br/>
536: * If in {@link JdbcConnectionDescriptor} the
537: * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection}
538: * is enabled, OJB will detect the default {@link org.apache.ojb.broker.PBKey} by itself.
539: *
540: * @see #setDefaultPBKey
541: */
542: public PBKey getDefaultPBKey() {
543: if (defaultPBKey == null) {
544: defaultPBKey = buildDefaultKey();
545: }
546: return defaultPBKey;
547: }
548:
549: /**
550: * Set the {@link PBKey} used in convinience method
551: * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}.
552: * <br/>
553: * It's only allowed to use one {@link JdbcConnectionDescriptor} with enabled
554: * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection}. In this case
555: * OJB will automatically set the default key.
556: * <br/>
557: * Note: It's recommended to set this key only once and not to change at runtime
558: * of OJB to avoid side-effects.
559: * If set more then one time a warning will be logged.
560: * @throws MetadataException if key was set more than one time
561: */
562: public void setDefaultPBKey(PBKey defaultPBKey) {
563: if (this .defaultPBKey != null) {
564: log.warn("The used default PBKey change. Current key is "
565: + this .defaultPBKey + ", new key will be "
566: + defaultPBKey);
567: }
568: this .defaultPBKey = defaultPBKey;
569: log.info("Set default PBKey for convenience broker creation: "
570: + defaultPBKey);
571: }
572:
573: /**
574: * Try to build an default PBKey for convenience PB create method.
575: *
576: * @return PBKey or <code>null</code> if default key was not declared in
577: * metadata
578: */
579: private PBKey buildDefaultKey() {
580: List descriptors = connectionRepository().getAllDescriptor();
581: JdbcConnectionDescriptor descriptor;
582: PBKey result = null;
583: for (Iterator iterator = descriptors.iterator(); iterator
584: .hasNext();) {
585: descriptor = (JdbcConnectionDescriptor) iterator.next();
586: if (descriptor.isDefaultConnection()) {
587: if (result != null) {
588: log
589: .error("Found additional connection descriptor with enabled 'default-connection' "
590: + descriptor.getPBKey()
591: + ". This is NOT allowed. Will use the first found descriptor "
592: + result + " as default connection");
593: } else {
594: result = descriptor.getPBKey();
595: }
596: }
597: }
598:
599: if (result == null) {
600: log
601: .info("No 'default-connection' attribute set in jdbc-connection-descriptors,"
602: + " thus it's currently not possible to use 'defaultPersistenceBroker()' "
603: + " convenience method to lookup PersistenceBroker instances. But it's possible"
604: + " to enable this at runtime using 'setDefaultKey' method.");
605: }
606: return result;
607: }
608: }
|