net.ageto.gyrex.persistence.carbonado.storage.internal.SchemaSupportJob.java Source code

Java tutorial

Introduction

Here is the source code for net.ageto.gyrex.persistence.carbonado.storage.internal.SchemaSupportJob.java

Source

/*******************************************************************************
 * Copyright (c) 2013 <enter-company-name-here> 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:
 *     <enter-developer-name-here> - initial API and implementation
 *******************************************************************************/
package net.ageto.gyrex.persistence.carbonado.storage.internal;

import java.sql.Connection;
import java.util.Collection;

import org.eclipse.gyrex.persistence.storage.content.RepositoryContentType;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;

import org.apache.commons.lang.exception.ExceptionUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.ageto.gyrex.persistence.carbonado.internal.CarbonadoActivator;
import net.ageto.gyrex.persistence.carbonado.storage.CarbonadoRepository;

import com.amazon.carbonado.Repository;
import com.amazon.carbonado.repo.jdbc.JDBCConnectionCapability;

/**
 * Abstract base class for operations on database schemas.
 */
public abstract class SchemaSupportJob extends Job {

    private static final class RepositoryRule implements ISchedulingRule {
        private final String repositoryId;

        public RepositoryRule(final CarbonadoRepositoryImpl repository) {
            repositoryId = repository.getRepositoryId();
        }

        @Override
        public boolean contains(final ISchedulingRule rule) {
            return this == rule;
        }

        @Override
        public boolean isConflicting(final ISchedulingRule rule) {
            return (rule instanceof RepositoryRule) && repositoryId.equals(((RepositoryRule) rule).repositoryId);
        }
    }

    private static final Logger LOG = LoggerFactory.getLogger(SchemaMigrationJob.class);

    private final CarbonadoRepositoryImpl repository;
    private final Collection<RepositoryContentType> contentTypes;
    private final boolean commitWhenDone;

    private IStatus schemaStatus;

    public static final Status NOT_AVAILABLE = new Status(IStatus.INFO, CarbonadoActivator.SYMBOLIC_NAME,
            "Status information not available.");

    public SchemaSupportJob(final CarbonadoRepositoryImpl repository,
            final Collection<RepositoryContentType> contentTypes, final boolean commitWhenDone, final String name) {
        super(name);
        this.repository = repository;
        this.contentTypes = contentTypes;
        this.commitWhenDone = commitWhenDone;
        setPriority(LONG);
        setRule(new RepositoryRule(repository));
    }

    IStatus buildStatusInternal(final int severity, final String message) {
        return new Status(severity, CarbonadoActivator.SYMBOLIC_NAME, message);
    }

    public Collection<RepositoryContentType> getContentTypes() {
        return contentTypes;
    }

    public CarbonadoRepository getRepository() {
        return repository;
    }

    /**
     * The {@link #schemaStatus} is updated at every touch of the repository.
     * 
     * @return the current schemaStatus or
     *         {@link SchemaMigrationJob#NOT_AVAILABLE} if none is set
     */
    public IStatus getSchemaStatus() {
        final IStatus status = schemaStatus;
        return null != status ? status : NOT_AVAILABLE;
    }

    protected abstract IStatus processSchema(RepositoryContentType contentType, Connection connection,
            IProgressMonitor monitor);

    private IStatus processSchemas(final Collection<RepositoryContentType> contentTypes,
            final JDBCConnectionCapability jdbcConnectionCapability, final IProgressMonitor monitor)
            throws Exception {
        // spin the migration loop
        Connection connection = null;
        boolean wasAutoCommit = true; // default to auto-commit
        try {
            // get connection
            connection = jdbcConnectionCapability.getConnection();
            // remember auto-commit state
            wasAutoCommit = connection.getAutoCommit();

            // collect result
            final MultiStatus result = new MultiStatus(CarbonadoActivator.SYMBOLIC_NAME, 0, String.format(
                    "Database schema verification result for database %s.", repository.getDescription()), null);

            // verify schemas
            final SubMonitor subMonitor = SubMonitor.convert(monitor, contentTypes.size());
            for (final RepositoryContentType contentType : contentTypes) {
                result.add(processSchema(contentType, connection, subMonitor.newChild(1)));
            }

            // commit any pending changes if migration was allowed
            if (commitWhenDone) {
                connection.commit();
            } else {
                connection.rollback();
            }

            return result;
        } finally {
            if (null != connection) {
                try {
                    // verify that auto-commit state was not modified
                    if (wasAutoCommit != connection.getAutoCommit()) {
                        // Carbonado uses auto-commit to detect if a transaction
                        // was in progress whan the connection was acquired previously
                        // in this case it does not close the connection, which is fine;
                        // however, if any schema-support implementation removed the auto-commit flag
                        // Carbonado will no longer close the connection because it thinks a
                        // transaction is in progress;
                        // thus we need to reset the auto-commit flag in this case!
                        LOG.debug(
                                "Resetting auto-commit flag on connection {} due to modifications during schema migration",
                                connection);
                        connection.setAutoCommit(wasAutoCommit);
                    }
                    jdbcConnectionCapability.yieldConnection(connection);
                } catch (final Exception e) {
                    throw new IllegalStateException(
                            "Unable to properly return a database connection to the pool. This will lead to resource leaks! "
                                    + e.getMessage(),
                            e);
                }
            }
        }
    }

    @Override
    public IStatus run(final IProgressMonitor progressMonitor) {
        final SubMonitor monitor = SubMonitor.convert(progressMonitor,
                String.format("Verifying database schema for '%s'...", repository.getRepositoryId()), 100);

        Repository cRepository;
        try {
            cRepository = repository.getOrCreateRepository();
        } catch (final Exception e) {
            LOG.error("Failed to open repository {}. {}",
                    new Object[] { repository.getRepositoryId(), ExceptionUtils.getRootCauseMessage(e), e });
            final String message = String.format("Failed to open repository. Please check server logs. %s",
                    ExceptionUtils.getRootCauseMessage(e));
            repository.setError(message);
            schemaStatus = buildStatusInternal(IStatus.CANCEL, message);
            return Status.CANCEL_STATUS;
        }

        // check if JDBC database
        final JDBCConnectionCapability jdbcConnectionCapability = cRepository
                .getCapability(JDBCConnectionCapability.class);
        if (jdbcConnectionCapability == null)
            // non JDBC repository means auto-updating schema
            return schemaStatus = Status.OK_STATUS;

        // check if transaction in progress
        if (cRepository.getTransactionIsolationLevel() != null) {
            LOG.warn(
                    "Carbonado repository '{}' (using database {}) is configured with a default transaction level. This is not supported by the current schema migration implementation.",
                    repository.getRepositoryId(), repository.getDescription());
        }

        // quick check
        if (contentTypes.isEmpty()) {
            // don't fail although this should be a programming error
            LOG.debug("No content types assigned to repository '{}'.", repository.getDescription());
            repository.setError(null);
            return schemaStatus = Status.OK_STATUS;
        }

        // verify schemas
        try {
            schemaStatus = processSchemas(contentTypes, jdbcConnectionCapability, monitor.newChild(50));
        } catch (final Exception e) {
            LOG.error("Failed to verify database schema for database {} (repository {}). {}",
                    new Object[] { cRepository.getName(), repository.getRepositoryId(),
                            ExceptionUtils.getRootCauseMessage(e), e });
            final String message = String.format("Unable to verify database schema. Please check server logs. %s",
                    ExceptionUtils.getRootCauseMessage(e));
            repository.setError(message);
            schemaStatus = buildStatusInternal(IStatus.CANCEL, message);
            return Status.CANCEL_STATUS;
        }

        // done
        if ((null != schemaStatus) && schemaStatus.matches(IStatus.ERROR)) {
            repository.setError(String.format("Database schema verification failed. Please check database %s.",
                    repository.getDescription()));
        } else {
            repository.setError(null);
            schemaStatus = Status.OK_STATUS;
        }
        return Status.OK_STATUS;
    }

}