org.eclipse.gyrex.persistence.storage.Repository.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.persistence.storage.Repository.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2011 Gunnar Wagenknecht and others.
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Contributors:
 *     Gunnar Wagenknecht - initial API and implementation
 *******************************************************************************/
package org.eclipse.gyrex.persistence.storage;

import java.text.MessageFormat;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.gyrex.common.identifiers.IdHelper;
import org.eclipse.gyrex.monitoring.metrics.MetricSet;
import org.eclipse.gyrex.persistence.internal.PersistenceActivator;
import org.eclipse.gyrex.persistence.internal.storage.RepositoryPreferencesBasedMetadata;
import org.eclipse.gyrex.persistence.internal.storage.RepositoryRegistry;
import org.eclipse.gyrex.persistence.storage.content.BasicRepositoryContentTypeSupport;
import org.eclipse.gyrex.persistence.storage.content.RepositoryContentTypeSupport;
import org.eclipse.gyrex.persistence.storage.provider.RepositoryProvider;
import org.eclipse.gyrex.persistence.storage.registry.IRepositoryDefinition;

import org.eclipse.core.runtime.PlatformObject;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;

import org.apache.commons.lang.StringUtils;

/**
 * A repository is a concrete instance of a data store where a particular set of
 * data will be persisted.
 * <p>
 * In Gyrex, repositories are an abstraction for an actual data store accessed
 * using a defined persistence technology (eg. a MySQL database accessed via
 * JDBC, a flat CSV file, a distributed in-memory hast table, etc.). A specific
 * repository may only be accessed using the technology defined by the
 * repository implementation. Typically, they are configured whitin Gyrex
 * through an administrative interface. Their implementation is contributed to
 * Gyrex by {@link RepositoryProvider repository providers}.
 * </p>
 * <p>
 * In order to simplify operation of Gyrex repositories will support
 * administrative tasks. For example, it will be possible to transfer a
 * repository into a read-only mode or disabled it completely for maintenance
 * purposes. It will also be possible to dump a repository (or portions of it)
 * into a repository provider independent format. The dump may be loaded later
 * into another repository.
 * </p>
 * <p>
 * This class must be subclassed by clients that contribute a repository
 * implementation to Gyrex. As such it is considered part of a service provider
 * API which may evolve faster than the general API. Please get in touch with
 * the development team through the prefered channels listed on <a
 * href="http://www.eclipse.org/gyrex">the Gyrex website</a> to stay up-to-date
 * of possible changes.
 * </p>
 */
public abstract class Repository extends PlatformObject implements IRepositoryContstants {

    /**
     * Utility method to create a well formated metrics id based on a repository
     * provider and a specified repository id.
     * 
     * @param repositoryProvider
     *            the repository provider
     * @param repositoryId
     *            the repository id
     * @return a well formatted metrics id
     */
    protected static String createMetricsId(final RepositoryProvider repositoryProvider,
            final String repositoryId) {
        return repositoryProvider.getProviderId() + "." + repositoryId + ".metrics";
    }

    /** the repository id */
    private final String repositoryId;

    /** the repository provider */
    private final RepositoryProvider repositoryProvider;

    /** indicates if the repository has been closed */
    private final AtomicBoolean closed = new AtomicBoolean();

    /** the repository metrics */
    private final MetricSet metrics;

    /** the repository metrics registration */
    private volatile ServiceRegistration<?> metricsRegistration;

    /** the content type support */
    private volatile RepositoryContentTypeSupport repositoryContentTypeSupport;

    /**
     * Creates and returns a new repository instance.
     * <p>
     * Subclasses must call this constructor to initialize the base repository
     * instance.
     * </p>
     * <p>
     * The provided metrics will be registered with Gyrex on behalf of the
     * bundle which loaded this class.
     * </p>
     * 
     * @param repositoryId
     *            the repository id (may not be <code>null</code>, must conform
     *            to {@link IdHelper#isValidId(String)})
     * @param repositoryProvider
     *            the repository provider (may not be <code>null</code>)
     * @param metrics
     *            the metrics used by this repository (may not be
     *            <code>null</code>)
     * @throws IllegalArgumentException
     *             if an invalid argument was specified
     */
    protected Repository(final String repositoryId, final RepositoryProvider repositoryProvider,
            final MetricSet metrics) throws IllegalArgumentException {
        if (null == repositoryId) {
            throw new IllegalArgumentException("repository id must not be null");
        }
        if (null == repositoryProvider) {
            throw new IllegalArgumentException("repository provider must not be null");
        }
        if (null == metrics) {
            throw new IllegalArgumentException("metrics must not be null");
        }

        if (!IdHelper.isValidId(repositoryId)) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "repository id \"{0}\" is invalid; valid chars are US-ASCII a-z / A-Z / 0-9 / '.' / '-' / '_'",
                    repositoryId));
        }

        this.repositoryId = repositoryId;
        this.repositoryProvider = repositoryProvider;
        this.metrics = metrics;

        // register the services (this is the last call)
        registerMetrics();
    }

    /**
     * Called by the platform when a repository is no longer needed and all held
     * resources must be released.
     * <p>
     * The implementation sets the {@link #isClosed() closed} state, calls
     * {@link #doClose()} and unregisters the repository metrics. Subclasses
     * should extend {@link #doClose()} and permanently release any resources.
     * </p>
     * 
     * @noreference This method is not intended to be referenced by clients.
     */
    public final void close() {
        if (!closed.compareAndSet(false, true)) {
            return;
        }

        try {
            doClose();
        } finally {
            // unregister metrics
            final ServiceRegistration<?> registration = metricsRegistration;
            if (null != registration) {
                try {
                    registration.unregister();
                } catch (final IllegalStateException ignored) {
                    // already unregistered
                }
                metricsRegistration = null;
            }
        }
    }

    /**
     * Called by the platform when a repository is no longer needed and all held
     * resources should be released.
     * <p>
     * The default implementation does nothing. Subclasses may overwrite and
     * extend.
     * </p>
     * 
     * @noreference This method is not intended to be referenced by clients.
     */
    protected void doClose() {
        // empty
    }

    private BundleContext getBundleContext() {
        // TODO: we might need to wrap this into a privileged call
        final Bundle bundle = FrameworkUtil.getBundle(getClass());
        final BundleContext bundleContext = null != bundle ? bundle.getBundleContext() : null;
        if (null == bundleContext) {
            throw new IllegalStateException("Unable to determin bundle context for class '" + getClass().getName()
                    + "'. Please ensure that this class was loaded by a bundle which is either STARTING, ACTIVE or STOPPING.");
        }
        return bundleContext;
    }

    /**
     * Returns the {@link RepositoryContentTypeSupport} for the repository.
     * <p>
     * The default implementation returns a
     * {@link BasicRepositoryContentTypeSupport basic support strategy}.
     * Subclasses should override to provide a more sophisticated support
     * strategy.
     * </p>
     * 
     * @return the {@link RepositoryContentTypeSupport} (may not be
     *         <code>null</code>)
     */
    public RepositoryContentTypeSupport getContentTypeSupport() {
        if (null != repositoryContentTypeSupport) {
            return repositoryContentTypeSupport;
        }
        return repositoryContentTypeSupport = new BasicRepositoryContentTypeSupport(this);
    }

    /**
     * Returns a human readable description of the repository that can be
     * displayed to administrators, etc. An empty String is returned if no
     * description is available.
     * <p>
     * The default implementation returns an empty String.
     * </p>
     * 
     * @return a human readable description
     */
    public String getDescription() {
        return "";
    }

    /**
     * Returns the repository metadata with the specified id.
     * <p>
     * This is a handle only method. The returned metadata may not exist.
     * </p>
     * <p>
     * The default implementation returns an instance which stores metadata in
     * the repository preferences. Subclasses may override and provide a custom
     * implementation.
     * </p>
     * 
     * @param id
     *            the metadata identifier (must validate using
     *            {@link IdHelper#isValidId(String)})
     * @return the {@link RepositoryMetadata}
     * @see RepositoryMetadata
     */
    public RepositoryMetadata getMetadata(final String id) {
        final RepositoryRegistry registry = PersistenceActivator.getInstance().getRepositoriesManager();
        final IRepositoryDefinition definition = registry.getRepositoryDefinition(repositoryId);
        if (definition == null) {
            throw new IllegalStateException(
                    String.format("Repository definition not found for repository %s.", repositoryId));
        }
        return new RepositoryPreferencesBasedMetadata(definition.getRepositoryPreferences(), id, repositoryId);
    }

    /**
     * Returns the repository metrics.
     * 
     * @return the metrics
     */
    protected final MetricSet getMetrics() {
        return metrics;
    }

    /**
     * Returns the name of the repository. This is the name of the class without
     * the package name and used by {@link #toString()}.
     * 
     * @return the name of the repository
     */
    private String getName() {
        String string = getClass().getName();
        final int index = string.lastIndexOf('.');
        if (index != -1) {
            string = string.substring(index + 1, string.length());
        }
        return string;
    }

    /**
     * Returns the repository identifier.
     * <p>
     * The identifier is unique within Gyrex and persistent across session.
     * </p>
     * 
     * @return the repository identifier
     */
    public final String getRepositoryId() {
        return repositoryId;
    }

    /**
     * Returns the repository provider.
     * <p>
     * The repository provider defines the underlying persistence technology. It
     * gives advices on what is support by this repository and what not.
     * </p>
     * 
     * @return the repository provider
     */
    public final RepositoryProvider getRepositoryProvider() {
        return repositoryProvider;
    }

    /**
     * Indicates if this <code>Repository</code> object has been closed.
     * <p>
     * This method is guaranteed to return <code>true</code> only when it is
     * called after the method {@link #close()} has been called.
     * </p>
     * 
     * @return <code>true</code> if the repository is closed; <code>false</code>
     *         otherwise
     */
    public final boolean isClosed() {
        return closed.get();
    }

    /**
     * Registers the metrics on behalf of the bundle which loaded this class.
     * 
     * @throws IllegalArgumentException
     *             if the class was not loaded by a bundle class loader
     * @throws IllegalStateException
     *             if the bundle which loaded the class has no valid bundle
     *             context
     */
    private void registerMetrics() throws IllegalArgumentException, IllegalStateException {
        // get bundle context
        final BundleContext bundleContext = getBundleContext();

        // create metrics properties
        final Dictionary<String, Object> metricsProps = new Hashtable<String, Object>(4);
        metricsProps.put(Constants.SERVICE_VENDOR, getName() + "[" + getRepositoryId() + "]");
        metricsProps.put(Constants.SERVICE_DESCRIPTION, "Metrics for repository '" + getRepositoryId() + "'.");
        metricsProps.put(SERVICE_PROPERTY_REPOSITORY_ID, getRepositoryId());
        try {
            final String description = getDescription();
            if (StringUtils.isNotBlank(description)) {
                metricsProps.put(SERVICE_PROPERTY_REPOSITORY_DESCRIPTION, description);
            }
        } catch (final Exception e) {
            // registerMetrics is called during object construction, therefore #getDescription might not be ready yet.
        }

        // register metrics
        metricsRegistration = bundleContext.registerService(MetricSet.SERVICE_NAME, metrics, metricsProps);
    }

    /**
     * Returns a string containing a concise, human-readable description of the
     * repository including information about the repository implementation and
     * the repository id.
     * 
     * @return a string representation of the repository
     */
    @Override
    public String toString() {
        return getName() + " {" + repositoryId + "}";
    }
}