org.apache.ojb.broker.metadata.MetadataManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ojb.broker.metadata.MetadataManager.java

Source

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;
    }
}