Java tutorial
package org.apache.ojb.broker.metadata; /* Copyright 2002-2005 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.SerializationUtils; import org.apache.ojb.broker.PBKey; import org.apache.ojb.broker.core.PersistenceBrokerConfiguration; import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator; import org.apache.ojb.broker.util.logging.Logger; import org.apache.ojb.broker.util.logging.LoggerFactory; /** * Central class for metadata operations/manipulations - manages OJB's * metadata objects, in particular: * <ul> * <li>{@link org.apache.ojb.broker.metadata.DescriptorRepository} contains * metadata of persistent objects</li> * <li>{@link org.apache.ojb.broker.metadata.ConnectionRepository} contains * all connection metadata information</li> * </ul> * * This class allows transparent flexible metadata loading/manipulation at runtime. * * <p> * <b>How to read/merge metadata</b><br/> * Per default OJB loads default {@link org.apache.ojb.broker.metadata.DescriptorRepository} * and {@link org.apache.ojb.broker.metadata.ConnectionRepository} instances, by reading the * specified repository file. This is done first time the <code>MetadataManager</code> instance * was used. * <br/> * To read metadata information at runtime use * {@link #readDescriptorRepository readDescriptorRepository} and * {@link #readConnectionRepository readConnectionRepository} * methods. * <br/> * It is also possible to merge different repositories using * {@link #mergeDescriptorRepository mergeDescriptorRepository} * and {@link #mergeConnectionRepository mergeConnectionRepository} * * </p> * * <a name="perThread"/> * <h3>Per thread handling of metadata</h3> * <p> * Per default the manager handle one global {@link org.apache.ojb.broker.metadata.DescriptorRepository} * for all calling threads, but it is ditto possible to use different metadata <i>profiles</i> in a per thread * manner - <i>profiles</i> means different copies of {@link org.apache.ojb.broker.metadata.DescriptorRepository} * objects. * <p/> * * <p> * <a name="enablePerThreadMode"/> * <b>Enable the per thread mode</b><br/> * To enable the 'per thread' mode for {@link org.apache.ojb.broker.metadata.DescriptorRepository} * instances: * <pre> * MetadataManager mm = MetadataManager.getInstance(); * // tell the manager to use per thread mode * mm.setEnablePerThreadChanges(true); * ... * </pre> * This could be done e.g. at start up.<br/> * Now it's possible to use dedicated <code>DescriptorRepository</code> instances * per thread: * <pre> * // e.g we get a coppy of the global repository * DescriptorRepository dr = mm.copyOfGlobalRepository(); * // now we can manipulate the persistent object metadata of the copy * ...... * * // set the changed repository for this thread * mm.setDescriptor(dr); * * // now let this thread lookup a PersistenceBroker instance * // with the modified metadata * // all other threads use the global metadata * PersistenceBroker broker = Persis...... * </pre> * Note: Change metadata <i>before</i> lookup the {@link org.apache.ojb.broker.PersistenceBroker} * instance for current thread, because the metadata was bound to the PB at lookup. * </p> * * <p> * <b>How to use different metadata profiles</b><br/> * MetadataManager was shipped with a simple mechanism to * add, remove and load different persistent objects metadata * profiles (different {@link org.apache.ojb.broker.metadata.DescriptorRepository} * instances) in a per thread manner. Use * <ul> * <li>{@link #addProfile addProfile} add different persistent object metadata profiles</li> * <li>{@link #removeProfile removeProfile} remove a persistent object metadata profiles</li> * <li>{@link #loadProfile loadProfile} load a profile for the current thread</li> * </ul> * Note: method {@link #loadProfile loadProfile} only works if * the <a href="#enablePerThreadMode">per thread mode</a> is enabled. * </p> * * * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a> * @version $Id: MetadataManager.java,v 1.19.2.7 2005/12/21 22:26:10 tomdz Exp $ */ public class MetadataManager { private static Logger log = LoggerFactory.getLogger(MetadataManager.class); private static final String MSG_STR = "* Can't find DescriptorRepository for current thread, use default one *"; private static ThreadLocal threadedRepository = new ThreadLocal(); private static ThreadLocal currentProfileKey = new ThreadLocal(); private static MetadataManager singleton; private Hashtable metadataProfiles; private DescriptorRepository globalRepository; private ConnectionRepository connectionRepository; private boolean enablePerThreadChanges; private PBKey defaultPBKey; // singleton private MetadataManager() { init(); } private void init() { metadataProfiles = new Hashtable(); final String repository = ((PersistenceBrokerConfiguration) OjbConfigurator.getInstance() .getConfigurationFor(null)).getRepositoryFilename(); try { globalRepository = new RepositoryPersistor().readDescriptorRepository(repository); connectionRepository = new RepositoryPersistor().readConnectionRepository(repository); } catch (FileNotFoundException ex) { log.warn("Could not access '" + repository + "' or a DOCTYPE/DTD-dependency. " + "(Check letter case for file names and HTTP-access if using DOCTYPE PUBLIC)" + " Starting with empty metadata and connection configurations.", ex); globalRepository = new DescriptorRepository(); connectionRepository = new ConnectionRepository(); } catch (Exception ex) { throw new MetadataException("Can't read repository file '" + repository + "'", ex); } } public void shutdown() { threadedRepository = null; currentProfileKey = null; globalRepository = null; metadataProfiles = null; singleton = null; } /** * Returns an instance of this class. */ public static synchronized MetadataManager getInstance() { // lazy initialization if (singleton == null) { singleton = new MetadataManager(); } return singleton; } /** * Returns the current valid {@link org.apache.ojb.broker.metadata.DescriptorRepository} for * the caller. This is the provided way to obtain the * {@link org.apache.ojb.broker.metadata.DescriptorRepository}. * <br> * When {@link #isEnablePerThreadChanges per thread descriptor handling} is enabled * it search for a specific {@link org.apache.ojb.broker.metadata.DescriptorRepository} * for the calling thread, if none can be found the global descriptor was returned. * * @see MetadataManager#getGlobalRepository * @see MetadataManager#copyOfGlobalRepository */ public DescriptorRepository getRepository() { DescriptorRepository repository; if (enablePerThreadChanges) { repository = (DescriptorRepository) threadedRepository.get(); if (repository == null) { repository = getGlobalRepository(); log.info(MSG_STR); } // arminw: // TODO: Be more strict in per thread mode and throw a exception when not find descriptor for calling thread? // if (repository == null) // { // throw new MetadataException("Can't find a DescriptorRepository for current thread, don't forget" + // " to set a DescriptorRepository if enable per thread changes before perform other action"); // } return repository; } else { return globalRepository; } } /** * Returns explicit the global {@link org.apache.ojb.broker.metadata.DescriptorRepository} - use with * care, because it ignores the {@link #isEnablePerThreadChanges per thread mode}. * * @see MetadataManager#getRepository * @see MetadataManager#copyOfGlobalRepository */ public DescriptorRepository getGlobalRepository() { return globalRepository; } /** * Returns the {@link ConnectionRepository}. */ public ConnectionRepository connectionRepository() { return connectionRepository; } /** * Merge the given {@link ConnectionRepository} with the existing one (without making * a deep copy of the containing connection descriptors). * @see #mergeConnectionRepository(ConnectionRepository targetRepository, ConnectionRepository sourceRepository, boolean deep) */ public void mergeConnectionRepository(ConnectionRepository repository) { mergeConnectionRepository(connectionRepository(), repository, false); } /** * Merge the given source {@link ConnectionRepository} with the * existing target. If parameter * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made. * <br/> * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized * by using the default class loader to resolve classes. This can be problematic * when classes are loaded by a context class loader. * <p> * Note: All classes within the repository structure have to implement * <code>java.io.Serializable</code> to be able to create a cloned copy. */ public void mergeConnectionRepository(ConnectionRepository targetRepository, ConnectionRepository sourceRepository, boolean deep) { List list = sourceRepository.getAllDescriptor(); for (Iterator iterator = list.iterator(); iterator.hasNext();) { JdbcConnectionDescriptor jcd = (JdbcConnectionDescriptor) iterator.next(); if (deep) { //TODO: adopt copy/clone methods for metadata classes? jcd = (JdbcConnectionDescriptor) SerializationUtils.clone(jcd); } targetRepository.addDescriptor(jcd); } } /** * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository} * (without making a deep copy of containing class-descriptor objects) with the * global one, returned by method {@link #getRepository()} - keep * in mind if running in <a href="#perThread">per thread mode</a> * merge maybe only takes effect on current thread. * * @see #mergeDescriptorRepository(DescriptorRepository targetRepository, DescriptorRepository sourceRepository, boolean deep) */ public void mergeDescriptorRepository(DescriptorRepository repository) { mergeDescriptorRepository(getRepository(), repository, false); } /** * Merge the given {@link org.apache.ojb.broker.metadata.DescriptorRepository} * files, the source objects will be pushed to the target repository. If parameter * <tt>deep</tt> is set <code>true</code> deep copies of source objects were made. * <br/> * Note: Using <tt>deep copy mode</tt> all descriptors will be serialized * by using the default class loader to resolve classes. This can be problematic * when classes are loaded by a context class loader. * <p> * Note: All classes within the repository structure have to implement * <code>java.io.Serializable</code> to be able to create a cloned copy. * * @see #isEnablePerThreadChanges * @see #setEnablePerThreadChanges */ public void mergeDescriptorRepository(DescriptorRepository targetRepository, DescriptorRepository sourceRepository, boolean deep) { Iterator it = sourceRepository.iterator(); while (it.hasNext()) { ClassDescriptor cld = (ClassDescriptor) it.next(); if (deep) { //TODO: adopt copy/clone methods for metadata classes? cld = (ClassDescriptor) SerializationUtils.clone(cld); } targetRepository.put(cld.getClassOfObject(), cld); cld.setRepository(targetRepository); } } /** * Read ClassDescriptors from the given repository file. * @see #mergeDescriptorRepository */ public DescriptorRepository readDescriptorRepository(String fileName) { try { RepositoryPersistor persistor = new RepositoryPersistor(); return persistor.readDescriptorRepository(fileName); } catch (Exception e) { throw new MetadataException("Can not read repository " + fileName, e); } } /** * Read ClassDescriptors from the given InputStream. * @see #mergeDescriptorRepository */ public DescriptorRepository readDescriptorRepository(InputStream inst) { try { RepositoryPersistor persistor = new RepositoryPersistor(); return persistor.readDescriptorRepository(inst); } catch (Exception e) { throw new MetadataException("Can not read repository " + inst, e); } } /** * Read JdbcConnectionDescriptors from the given repository file. * * @see #mergeConnectionRepository */ public ConnectionRepository readConnectionRepository(String fileName) { try { RepositoryPersistor persistor = new RepositoryPersistor(); return persistor.readConnectionRepository(fileName); } catch (Exception e) { throw new MetadataException("Can not read repository " + fileName, e); } } /** * Read JdbcConnectionDescriptors from this InputStream. * * @see #mergeConnectionRepository */ public ConnectionRepository readConnectionRepository(InputStream inst) { try { RepositoryPersistor persistor = new RepositoryPersistor(); return persistor.readConnectionRepository(inst); } catch (Exception e) { throw new MetadataException("Can not read repository from " + inst, e); } } /** * Set the {@link org.apache.ojb.broker.metadata.DescriptorRepository} - if <i>global</i> was true, the * given descriptor aquire global availability (<i>use with care!</i>), * else the given descriptor was associated with the calling thread. * * @see #isEnablePerThreadChanges * @see #setEnablePerThreadChanges */ public void setDescriptor(DescriptorRepository repository, boolean global) { if (global) { if (log.isDebugEnabled()) log.debug("Set new global repository: " + repository); globalRepository = repository; } else { if (log.isDebugEnabled()) log.debug("Set new threaded repository: " + repository); threadedRepository.set(repository); } } /** * Set {@link DescriptorRepository} for the current thread. * Convenience method for * {@link #setDescriptor(DescriptorRepository repository, boolean global) setDescriptor(repository, false)}. */ public void setDescriptor(DescriptorRepository repository) { setDescriptor(repository, false); } /** * Convenience method for * {@link #setDescriptor setDescriptor(repository, false)}. * @deprecated use {@link #setDescriptor} */ public void setPerThreadDescriptor(DescriptorRepository repository) { setDescriptor(repository, false); } /** * Returns a copy of the current global * {@link org.apache.ojb.broker.metadata.DescriptorRepository} * <p> * Note: All classes within the repository structure have to implement * <code>java.io.Serializable</code> to be able to create a cloned copy. * * @see MetadataManager#getGlobalRepository * @see MetadataManager#getRepository */ public DescriptorRepository copyOfGlobalRepository() { return (DescriptorRepository) SerializationUtils.clone(globalRepository); } /** * If returns <i>true</i> if <a href="#perThread">per thread</a> runtime * changes of the {@link org.apache.ojb.broker.metadata.DescriptorRepository} * is enabled and the {@link #getRepository} method returns a threaded * repository file if set, or the global if no threaded was found. * <br> * If returns <i>false</i> the {@link #getRepository} method return * always the {@link #getGlobalRepository() global} repository. * * @see #setEnablePerThreadChanges */ public boolean isEnablePerThreadChanges() { return enablePerThreadChanges; } /** * Enable the possibility of making <a href="#perThread">per thread</a> runtime changes * of the {@link org.apache.ojb.broker.metadata.DescriptorRepository}. * * @see #isEnablePerThreadChanges */ public void setEnablePerThreadChanges(boolean enablePerThreadChanges) { this.enablePerThreadChanges = enablePerThreadChanges; } /** * Add a metadata profile. * @see #loadProfile */ public void addProfile(Object key, DescriptorRepository repository) { if (metadataProfiles.contains(key)) { throw new MetadataException("Duplicate profile key. Key '" + key + "' already exists."); } metadataProfiles.put(key, repository); } /** * Load the given metadata profile for the current thread. * */ public void loadProfile(Object key) { if (!isEnablePerThreadChanges()) { throw new MetadataException("Can not load profile with disabled per thread mode"); } DescriptorRepository rep = (DescriptorRepository) metadataProfiles.get(key); if (rep == null) { throw new MetadataException("Can not find profile for key '" + key + "'"); } currentProfileKey.set(key); setDescriptor(rep); } /** * Returns the last activated profile key. * @return the last activated profile key or null if no profile has been loaded * @throws MetadataException if per-thread changes has not been activated * @see #loadProfile(Object) */ public Object getCurrentProfileKey() throws MetadataException { if (!isEnablePerThreadChanges()) { throw new MetadataException("Call to this method is undefined, since per-thread mode is disabled."); } return currentProfileKey.get(); } /** * Remove the given metadata profile. */ public DescriptorRepository removeProfile(Object key) { return (DescriptorRepository) metadataProfiles.remove(key); } /** * Remove all metadata profiles. */ public void clearProfiles() { metadataProfiles.clear(); currentProfileKey.set(null); } /** * Remove all profiles * * @see #removeProfile * @see #addProfile */ public void removeAllProfiles() { metadataProfiles.clear(); currentProfileKey.set(null); } /** * Return the default {@link PBKey} used in convinience method * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}. * <br/> * If in {@link JdbcConnectionDescriptor} the * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection} * is enabled, OJB will detect the default {@link org.apache.ojb.broker.PBKey} by itself. * * @see #setDefaultPBKey */ public PBKey getDefaultPBKey() { if (defaultPBKey == null) { defaultPBKey = buildDefaultKey(); } return defaultPBKey; } /** * Set the {@link PBKey} used in convinience method * {@link org.apache.ojb.broker.PersistenceBrokerFactory#defaultPersistenceBroker}. * <br/> * It's only allowed to use one {@link JdbcConnectionDescriptor} with enabled * {@link JdbcConnectionDescriptor#isDefaultConnection() default connection}. In this case * OJB will automatically set the default key. * <br/> * Note: It's recommended to set this key only once and not to change at runtime * of OJB to avoid side-effects. * If set more then one time a warning will be logged. * @throws MetadataException if key was set more than one time */ public void setDefaultPBKey(PBKey defaultPBKey) { if (this.defaultPBKey != null) { log.warn("The used default PBKey change. Current key is " + this.defaultPBKey + ", new key will be " + defaultPBKey); } this.defaultPBKey = defaultPBKey; log.info("Set default PBKey for convenience broker creation: " + defaultPBKey); } /** * Try to build an default PBKey for convenience PB create method. * * @return PBKey or <code>null</code> if default key was not declared in * metadata */ private PBKey buildDefaultKey() { List descriptors = connectionRepository().getAllDescriptor(); JdbcConnectionDescriptor descriptor; PBKey result = null; for (Iterator iterator = descriptors.iterator(); iterator.hasNext();) { descriptor = (JdbcConnectionDescriptor) iterator.next(); if (descriptor.isDefaultConnection()) { if (result != null) { log.error("Found additional connection descriptor with enabled 'default-connection' " + descriptor.getPBKey() + ". This is NOT allowed. Will use the first found descriptor " + result + " as default connection"); } else { result = descriptor.getPBKey(); } } } if (result == null) { log.info("No 'default-connection' attribute set in jdbc-connection-descriptors," + " thus it's currently not possible to use 'defaultPersistenceBroker()' " + " convenience method to lookup PersistenceBroker instances. But it's possible" + " to enable this at runtime using 'setDefaultKey' method."); } return result; } }