com.emc.ecs.sync.service.DbService.java Source code

Java tutorial

Introduction

Here is the source code for com.emc.ecs.sync.service.DbService.java

Source

/*
 * Copyright 2013-2015 EMC Corporation. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * or in the "license" file accompanying this file. This file 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.
 */
package com.emc.ecs.sync.service;

import com.emc.ecs.sync.SyncPlugin;
import com.emc.ecs.sync.filter.SyncFilter;
import com.emc.ecs.sync.model.ObjectStatus;
import com.emc.ecs.sync.model.object.SyncObject;
import com.emc.ecs.sync.source.SyncSource;
import com.emc.ecs.sync.target.SyncTarget;
import com.emc.ecs.sync.util.Function;
import com.emc.ecs.sync.util.TimingUtil;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.io.Closeable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public abstract class DbService implements Closeable {
    private static final Logger log = LoggerFactory.getLogger(DbService.class);

    public static final String OPERATION_OBJECT_QUERY = "ObjectQuery";
    public static final String OPERATION_OBJECT_UPDATE = "ObjectUpdate";
    public static final String OPERATION_LIST_ERRORS = "ListErrors";

    public static final String DEFAULT_OBJECTS_TABLE_NAME = "objects";
    public static final int DEFAULT_MAX_ERROR_SIZE = 2048;

    protected SyncPlugin timingPlugin = new DatabasePlugin();
    protected String objectsTableName = DEFAULT_OBJECTS_TABLE_NAME;
    protected int maxErrorSize = DEFAULT_MAX_ERROR_SIZE;
    private JdbcTemplate jdbcTemplate;
    private boolean initialized = false;

    /**
     * Implementations must provide a method to wipe out their database. This may mean deleting a .db file (Sqlite) or
     * dropping relevant tables, etc. Be aware that this operation may be done after {@link #close()} is called
     */
    public abstract void deleteDatabase();

    protected abstract JdbcTemplate createJdbcTemplate();

    protected abstract void createTable();

    /**
     * Be sure we close resources before GC
     */
    @Override
    protected void finalize() throws Throwable {
        try {
            close();
        } finally {
            super.finalize();
        }
    }

    public void setStatus(final SyncObject object, final ObjectStatus status, final String error,
            final boolean newRow) {
        initCheck();
        final String dateField = getDateFieldForStatus(status);
        final Date dateValue = getDateValueForStatus(status);
        Long contentLength = null;
        Date mtime = null;
        try {
            contentLength = object.getMetadata().getContentLength();
            mtime = object.getMetadata().getModificationTime();
        } catch (Throwable t) {
            log.info("could not pull metadata from object {}: {}", object, t.toString());
        }
        final Long fContentLength = contentLength;
        final Date fMtime = mtime;
        TimingUtil.time(timingPlugin, OPERATION_OBJECT_UPDATE, new Function<Void>() {
            @Override
            public Void call() {
                if (newRow) {
                    String insert = SyncRecord.insert(objectsTableName, SyncRecord.SOURCE_ID, SyncRecord.TARGET_ID,
                            SyncRecord.IS_DIRECTORY, SyncRecord.SIZE, SyncRecord.MTIME, SyncRecord.STATUS,
                            dateField, SyncRecord.RETRY_COUNT, SyncRecord.ERROR_MESSAGE);
                    getJdbcTemplate().update(insert, object.getSourceIdentifier(), object.getTargetIdentifier(),
                            object.isDirectory(), fContentLength, getDateParam(fMtime), status.getValue(),
                            getDateParam(dateValue), object.getFailureCount(), fitString(error, maxErrorSize));
                } else {
                    // don't want to overwrite last error message unless there is a new error message
                    List<String> fields = new ArrayList<>(
                            Arrays.asList(SyncRecord.TARGET_ID, SyncRecord.IS_DIRECTORY, SyncRecord.SIZE,
                                    SyncRecord.MTIME, SyncRecord.STATUS, dateField, SyncRecord.RETRY_COUNT));
                    if (error != null)
                        fields.add(SyncRecord.ERROR_MESSAGE);
                    String update = SyncRecord.updateBySourceId(objectsTableName,
                            fields.toArray(new String[fields.size()]));

                    List<Object> params = new ArrayList<>(Arrays.asList(object.getTargetIdentifier(),
                            object.isDirectory(), fContentLength, getDateParam(fMtime), status.getValue(),
                            getDateParam(dateValue), object.getFailureCount()));
                    if (error != null)
                        params.add(fitString(error, maxErrorSize));
                    params.add(object.getSourceIdentifier());

                    getJdbcTemplate().update(update, params.toArray());
                }
                return null;
            }
        });
    }

    public SyncRecord getSyncRecord(final SyncObject object) {
        initCheck();
        return TimingUtil.time(timingPlugin, OPERATION_OBJECT_QUERY, new Function<SyncRecord>() {
            @Override
            public SyncRecord call() {
                try {
                    return getJdbcTemplate().queryForObject(SyncRecord.selectBySourceId(objectsTableName),
                            new Mapper(), object.getSourceIdentifier());
                } catch (IncorrectResultSizeDataAccessException e) {
                    return null;
                }
            }
        });
    }

    public Iterable<SyncRecord> getSyncErrors() {
        initCheck();
        return TimingUtil.time(timingPlugin, OPERATION_LIST_ERRORS, new Function<Iterable<SyncRecord>>() {
            @Override
            public Iterable<SyncRecord> call() {
                return new Iterable<SyncRecord>() {
                    @Override
                    public Iterator<SyncRecord> iterator() {
                        return new RowIterator<>(getJdbcTemplate().getDataSource(), new Mapper(),
                                SyncRecord.selectErrors(objectsTableName));
                    }
                };
            }
        });
    }

    protected synchronized void initCheck() {
        if (!initialized) {
            jdbcTemplate = createJdbcTemplate();
            createTable();
            initialized = true;
        }
    }

    /**
     * Be sure to override in implementations to close the datasource completely, then call super.close()
     */
    @Override
    public void close() {
        jdbcTemplate = null;
    }

    protected String getDateFieldForStatus(ObjectStatus status) {
        if (status == ObjectStatus.InTransfer)
            return "transfer_start";
        else if (status == ObjectStatus.Transferred)
            return "transfer_complete";
        else if (status == ObjectStatus.InVerification)
            return "verify_start";
        else
            return "verify_complete";
    }

    protected Date getDateValueForStatus(ObjectStatus status) {
        if (Arrays.asList(ObjectStatus.InTransfer, ObjectStatus.Transferred, ObjectStatus.InVerification,
                ObjectStatus.Verified).contains(status))
            return new Date();
        else
            return null;
    }

    protected String fitString(String string, int size) {
        if (string == null)
            return null;
        if (string.length() > size) {
            return string.substring(0, size);
        }
        return string;
    }

    protected JdbcTemplate getJdbcTemplate() {
        if (jdbcTemplate == null)
            throw new UnsupportedOperationException("this service has closed all DB connections");
        return jdbcTemplate;
    }

    protected boolean hasColumn(ResultSet rs, String name) {
        try {
            rs.findColumn(name);
            return true;
        } catch (SQLException e) {
            return false;
        }
    }

    protected boolean hasStringColumn(ResultSet rs, String name) throws SQLException {
        if (hasColumn(rs, name)) {
            String value = rs.getString(name);
            return !rs.wasNull() && value != null;
        }
        return false;
    }

    protected boolean hasLongColumn(ResultSet rs, String name) throws SQLException {
        if (hasColumn(rs, name)) {
            rs.getLong(name);
            return !rs.wasNull();
        }
        return false;
    }

    protected boolean hasBooleanColumn(ResultSet rs, String name) throws SQLException {
        if (hasColumn(rs, name)) {
            rs.getBoolean(name);
            return !rs.wasNull();
        }
        return false;
    }

    protected boolean hasDateColumn(ResultSet rs, String name) throws SQLException {
        if (hasColumn(rs, name)) {
            rs.getDate(name);
            return !rs.wasNull();
        }
        return false;
    }

    protected Date getResultDate(ResultSet rs, String name) throws SQLException {
        return rs.getDate(name);
    }

    protected Object getDateParam(Date date) {
        return date;
    }

    public String getObjectsTableName() {
        return objectsTableName;
    }

    public void setObjectsTableName(String objectsTableName) {
        this.objectsTableName = objectsTableName;
    }

    public int getMaxErrorSize() {
        return maxErrorSize;
    }

    public void setMaxErrorSize(int maxErrorSize) {
        this.maxErrorSize = maxErrorSize;
    }

    public DbService withObjectsTableName(String objectsTableName) {
        setObjectsTableName(objectsTableName);
        return this;
    }

    public DbService withMaxErrorSize(int maxErrorSize) {
        setMaxErrorSize(maxErrorSize);
        return this;
    }

    /**
     * Uses best-effort to populate fields based on the available columns in the result set.  If a field
     * is not present in the result set, the field is left null or whatever its default value is.
     */
    public class Mapper implements RowMapper<SyncRecord> {
        @Override
        public SyncRecord mapRow(ResultSet rs, int rowNum) throws SQLException {
            SyncRecord record = new SyncRecord();

            if (!hasColumn(rs, SyncRecord.SOURCE_ID))
                throw new IllegalArgumentException(
                        "result set does not have a column named " + SyncRecord.SOURCE_ID);
            record.setSourceId(rs.getString(SyncRecord.SOURCE_ID));

            if (hasStringColumn(rs, SyncRecord.TARGET_ID))
                record.setTargetId(rs.getString(SyncRecord.TARGET_ID));
            if (hasBooleanColumn(rs, SyncRecord.IS_DIRECTORY))
                record.setIsDirectory(rs.getBoolean(SyncRecord.IS_DIRECTORY));
            if (hasLongColumn(rs, SyncRecord.SIZE))
                record.setSize(rs.getLong(SyncRecord.SIZE));
            if (hasDateColumn(rs, SyncRecord.MTIME))
                record.setMtime(getResultDate(rs, SyncRecord.MTIME));
            if (hasStringColumn(rs, SyncRecord.STATUS))
                record.setStatus(ObjectStatus.fromValue(rs.getString(SyncRecord.STATUS)));
            if (hasDateColumn(rs, SyncRecord.TRANSFER_START))
                record.setTransferStart(getResultDate(rs, SyncRecord.TRANSFER_START));
            if (hasDateColumn(rs, SyncRecord.TRANSFER_COMPLETE))
                record.setTransferComplete(getResultDate(rs, SyncRecord.TRANSFER_COMPLETE));
            if (hasDateColumn(rs, SyncRecord.VERIFY_START))
                record.setVerifyStart(getResultDate(rs, SyncRecord.VERIFY_START));
            if (hasDateColumn(rs, SyncRecord.VERIFY_COMPLETE))
                record.setVerifyComplete(getResultDate(rs, SyncRecord.VERIFY_COMPLETE));
            if (hasLongColumn(rs, SyncRecord.RETRY_COUNT))
                record.setRetryCount(rs.getInt(SyncRecord.RETRY_COUNT));
            if (hasStringColumn(rs, SyncRecord.ERROR_MESSAGE))
                record.setErrorMessage(rs.getString(SyncRecord.ERROR_MESSAGE));

            return record;
        }
    }

    // purely for timing functions
    protected class DatabasePlugin extends SyncFilter {
        @Override
        public String getActivationName() {
            return null;
        }

        @Override
        public void filter(SyncObject obj) {
        }

        @Override
        public SyncObject reverseFilter(SyncObject obj) {
            return null;
        }

        @Override
        public String getName() {
            return "Database";
        }

        @Override
        public String getDocumentation() {
            return null;
        }

        @Override
        public Options getCustomOptions() {
            return null;
        }

        @Override
        protected void parseCustomOptions(CommandLine line) {
        }

        @Override
        public void configure(SyncSource source, Iterator<SyncFilter> filters, SyncTarget target) {
        }
    }
}