com.emc.ecs.sync.source.AtmosSource.java Source code

Java tutorial

Introduction

Here is the source code for com.emc.ecs.sync.source.AtmosSource.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.source;

import com.emc.atmos.AtmosException;
import com.emc.atmos.api.*;
import com.emc.atmos.api.bean.DirectoryEntry;
import com.emc.atmos.api.bean.Metadata;
import com.emc.atmos.api.bean.ServiceInformation;
import com.emc.atmos.api.jersey.AtmosApiClient;
import com.emc.atmos.api.request.ListDirectoryRequest;
import com.emc.ecs.sync.filter.SyncFilter;
import com.emc.ecs.sync.model.object.AtmosSyncObject;
import com.emc.ecs.sync.target.SyncTarget;
import com.emc.ecs.sync.util.*;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.dbcp.BasicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.File;
import java.net.URI;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Reads objects from an Atmos system.
 */
public class AtmosSource extends SyncSource<AtmosSyncObject> {
    private static final Logger log = LoggerFactory.getLogger(AtmosSource.class);

    public static final String SOURCE_OIDLIST_OPTION = "source-oid-list";
    public static final String SOURCE_OIDLIST_DESC = "The file containing the list of OIDs to copy (newline separated).  Use - to read the list from standard input.  Not compatible with source-namespace";
    public static final String SOURCE_OIDLIST_ARG_NAME = "filename";

    public static final String SOURCE_NAMELIST_OPTION = "source-name-list";
    public static final String SOURCE_NAMELIST_DESC = "The file containing the list of namespace paths to copy (newline separated).  Use - to read the list from standard input.  Not compatible with source-namespace";
    public static final String SOURCE_NAMELIST_ARG_NAME = "filename";

    public static final String SOURCE_SQLQUERY_OPTION = "source-sql-query";
    public static final String SOURCE_SQLQUERY_DESC = "The SQL query to use to select the OIDs to copy from a database.  This query is assumed to return a raw OID (not a URL) as the first column in each result.  Not compatible with any other source options.  If specified, all of the query-* options must also be specified.";
    public static final String SOURCE_SQLQUERY_ARG_NAME = "sql-query";

    public static final String JDBC_URL_OPT = "query-jdbc-url";
    public static final String JDBC_URL_DESC = "The URL to the database (used in conjunction with source-sql-query)";
    public static final String JDBC_URL_ARG_NAME = "jdbc-url";

    public static final String JDBC_DRIVER_OPT = "query-jdbc-driver-class";
    public static final String JDBC_DRIVER_DESC = "The JDBC database driver class (used in conjunction with source-sql-query)";
    public static final String JDBC_DRIVER_ARG_NAME = "java-class-name";

    public static final String JDBC_USER_OPT = "query-user";
    public static final String JDBC_USER_DESC = "The database userid (used in conjunction with source-sql-query)";
    public static final String JDBC_USER_ARG_NAME = "userid";

    public static final String JDBC_PASSWORD_OPT = "query-password";
    public static final String JDBC_PASSWORD_DESC = "The database password (used in conjunction with source-sql-query)";
    public static final String JDBC_PASSWORD_ARG_NAME = "password";

    public static final String DELETE_TAGS_OPT = "remove-tags-on-delete";
    public static final String DELETE_TAGS_DESC = "When used with the DeleteSourceTarget or when specifying --delete-source, this will attempt to remove listable tags from objects before deleting them.";

    // timed operations
    public static final String OPERATION_LIST_DIRECTORY = "AtmosListDirectory";
    public static final String OPERATION_GET_ALL_META = "AtmosGetAllMeta";
    public static final String OPERATION_GET_OBJECT_INFO = "AtmosGetObjectInfo";
    public static final String OPERATION_GET_OBJECT_STREAM = "AtmosGetObjectStream";
    public static final String OPERATION_GET_USER_META = "AtmosGetUserMeta";
    public static final String OPERATION_DELETE_USER_META = "AtmosDeleteUserMeta";
    public static final String OPERATION_DELETE_OBJECT = "AtmosDeleteObject";

    private List<URI> endpoints;
    private String uid;
    private String secret;
    private AtmosApi atmos;
    private DataSource dataSource;

    private String namespaceRoot;
    private String oidFile;
    private String query;
    private String nameFile;
    private boolean deleteTags = false;

    @Override
    public Options getCustomOptions() {
        Options opts = new Options();
        opts.addOption(Option.builder().longOpt(SOURCE_OIDLIST_OPTION).desc(SOURCE_OIDLIST_DESC).hasArg()
                .argName(SOURCE_OIDLIST_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(SOURCE_NAMELIST_OPTION).desc(SOURCE_NAMELIST_DESC).hasArg()
                .argName(SOURCE_NAMELIST_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(SOURCE_SQLQUERY_OPTION).desc(SOURCE_SQLQUERY_DESC).hasArg()
                .argName(SOURCE_SQLQUERY_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(JDBC_URL_OPT).desc(JDBC_URL_DESC).hasArg()
                .argName(JDBC_URL_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(JDBC_DRIVER_OPT).desc(JDBC_DRIVER_DESC).hasArg()
                .argName(JDBC_DRIVER_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(JDBC_USER_OPT).desc(JDBC_USER_DESC).hasArg()
                .argName(JDBC_USER_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(JDBC_PASSWORD_OPT).desc(JDBC_PASSWORD_DESC).hasArg()
                .argName(JDBC_PASSWORD_ARG_NAME).build());
        opts.addOption(Option.builder().longOpt(DELETE_TAGS_OPT).desc(DELETE_TAGS_DESC).build());
        return opts;
    }

    @Override
    public boolean canHandleSource(String sourceUri) {
        return sourceUri.startsWith(AtmosUtil.URI_PREFIX);
    }

    @Override
    public void parseCustomOptions(CommandLine line) {
        AtmosUtil.AtmosUri atmosUri = AtmosUtil.parseUri(sourceUri);
        endpoints = atmosUri.endpoints;
        uid = atmosUri.uid;
        secret = atmosUri.secret;
        namespaceRoot = atmosUri.rootPath;

        if (line.hasOption(SOURCE_OIDLIST_OPTION))
            oidFile = line.getOptionValue(SOURCE_OIDLIST_OPTION);

        if (line.hasOption(SOURCE_NAMELIST_OPTION))
            nameFile = line.getOptionValue(SOURCE_NAMELIST_OPTION);

        if (line.hasOption(SOURCE_SQLQUERY_OPTION)) {
            query = line.getOptionValue(SOURCE_SQLQUERY_OPTION);

            // Initialize a connection pool
            BasicDataSource ds = new BasicDataSource();
            ds.setUrl(line.getOptionValue(JDBC_URL_OPT));
            if (line.hasOption(JDBC_DRIVER_OPT))
                ds.setDriverClassName(line.getOptionValue(JDBC_DRIVER_OPT));
            ds.setUsername(line.getOptionValue(JDBC_USER_OPT));
            ds.setPassword(line.getOptionValue(JDBC_PASSWORD_OPT));
            ds.setMaxActive(200);
            ds.setMaxOpenPreparedStatements(180);
            setDataSource(ds);
        }

        deleteTags = line.hasOption(DELETE_TAGS_OPT);
    }

    @Override
    public void configure(SyncSource source, Iterator<SyncFilter> filters, SyncTarget target) {
        if (atmos == null) {
            if (endpoints == null || uid == null || secret == null)
                throw new ConfigurationException("Must specify endpoints, uid and secret key");
            atmos = new AtmosApiClient(new AtmosConfig(uid, secret, endpoints.toArray(new URI[endpoints.size()])));
        }

        // Check authentication
        ServiceInformation info = atmos.getServiceInformation();
        log.info("Connected to Atmos {} on {}", info.getAtmosVersion(), endpoints);

        boolean namespace = namespaceRoot != null;
        boolean objectlist = oidFile != null;
        boolean namelist = nameFile != null;
        boolean sqllist = query != null;

        int optCount = 0;
        if (namespace)
            optCount++;
        if (objectlist)
            optCount++;
        if (namelist)
            optCount++;
        if (sqllist)
            optCount++;

        if (optCount > 1) {
            throw new ConfigurationException(
                    MessageFormat.format("Only one of ({0}, --{1}, --{2}, --{3}) is allowed", "namespace-path",
                            SOURCE_OIDLIST_OPTION, SOURCE_NAMELIST_OPTION, SOURCE_SQLQUERY_OPTION));
        }
        if (optCount < 1) {
            throw new ConfigurationException(
                    MessageFormat.format("One of ({0}, --{1}, --{2}, --{3}) must be specified", "namespace-path",
                            SOURCE_OIDLIST_OPTION, SOURCE_NAMELIST_OPTION, SOURCE_SQLQUERY_OPTION));
        }

        if (objectlist && !"-".equals(oidFile)) {
            // Verify file
            File f = new File(oidFile);
            if (!f.exists()) {
                throw new ConfigurationException(
                        MessageFormat.format("The OID list file {0} does not exist", oidFile));
            }
        }

        if (namelist && !"-".equals(nameFile)) {
            // Verify file
            File f = new File(nameFile);
            if (!f.exists()) {
                throw new ConfigurationException(
                        MessageFormat.format("The pathname list file {0} does not exist", nameFile));
            }
        }

        if (namespace) {
            if (!namespaceRoot.startsWith("/"))
                namespaceRoot = "/" + namespaceRoot;
            if (!namespaceRoot.equals("/")) {
                namespaceRoot = namespaceRoot.replaceFirst("/$", "");

                // does namespaceRoot exist?
                try {
                    Metadata typeMeta = atmos.getSystemMetadata(new ObjectPath(namespaceRoot))
                            .get(AtmosUtil.TYPE_KEY);
                    if (AtmosUtil.DIRECTORY_TYPE.equals(typeMeta.getValue()))
                        namespaceRoot += "/";
                } catch (AtmosException e) {
                    if (e.getErrorCode() == 1003)
                        throw new ConfigurationException("specified path does not exist in the cloud");
                    throw e;
                }
            }
        }
    }

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

    @Override
    public String getDocumentation() {
        return "The Atmos source plugin is triggered by the source pattern:\n" + AtmosUtil.PATTERN_DESC + "\n"
                + "Note that the uid should be the 'full token ID' including the "
                + "subtenant ID and the uid concatenated by a slash\n"
                + "If you want to software load balance across multiple hosts, "
                + "you can provide a comma-delimited list of hostnames or IPs " + "in the host part of the URI.";
    }

    @Override
    public Iterator<AtmosSyncObject> iterator() {
        if (namespaceRoot != null) {
            ObjectPath objectPath = new ObjectPath(namespaceRoot);
            return Collections
                    .singletonList(new AtmosSyncObject(this, atmos, objectPath, getRelativePath(objectPath)))
                    .iterator();
        } else if (oidFile != null) {
            return oidFileIterator();
        } else if (query != null && dataSource != null) {
            return sqlQueryIterator();
        } else if (nameFile != null) {
            return nameFileIterator();
        } else {
            throw new IllegalStateException("One of namespaceRoot, oidFile, query or nameFile must be set");
        }
    }

    @Override
    public Iterator<AtmosSyncObject> childIterator(AtmosSyncObject syncObject) {
        if (syncObject.isDirectory())
            return new AtmosDirectoryIterator((ObjectPath) syncObject.getRawSourceIdentifier(),
                    syncObject.getRelativePath());
        else
            return null;
    }

    @Override
    public void delete(final AtmosSyncObject syncObject) {
        if (deleteTags) {
            // get all tags for the object
            Map<String, Boolean> tags = time(new Function<Map<String, Boolean>>() {
                @Override
                public Map<String, Boolean> call() {
                    return atmos.getUserMetadataNames(syncObject.getRawSourceIdentifier());
                }
            }, OPERATION_GET_USER_META);
            for (final String name : tags.keySet()) {
                // if a tag is listable, delete it
                if (tags.get(name))
                    time(new Function<Void>() {
                        @Override
                        public Void call() {
                            atmos.deleteUserMetadata(syncObject.getRawSourceIdentifier(), name);
                            return null;
                        }
                    }, OPERATION_DELETE_USER_META);
            }
        }
        try {
            // delete the object
            time(new Function<Void>() {
                @Override
                public Void call() {
                    atmos.delete(syncObject.getRawSourceIdentifier());
                    return null;
                }
            }, OPERATION_DELETE_OBJECT);
        } catch (AtmosException e) {
            if (e.getErrorCode() == 1023)
                log.warn("could not delete non-empty directory {}", syncObject.getRawSourceIdentifier());
            else
                throw e;
        }
    }

    private Iterator<AtmosSyncObject> sqlQueryIterator() {
        return new ReadOnlyIterator<AtmosSyncObject>() {
            private Connection con;
            private PreparedStatement stmt;
            private ResultSet rs;

            @Override
            protected AtmosSyncObject getNextObject() {
                try {
                    if (rs == null) {
                        con = dataSource.getConnection();
                        stmt = con.prepareStatement(query);
                        rs = stmt.executeQuery();
                    }

                    if (rs.next()) {
                        ObjectId objectId = new ObjectId(rs.getString(1));
                        return new AtmosSyncObject(AtmosSource.this, atmos, objectId, getRelativePath(objectId));
                    } else {
                        log.info("Reached end of query");
                        try {
                            rs.close();
                            stmt.close();
                            con.close();
                        } catch (SQLException e) {
                            log.warn("Error closing resources: " + e, e);
                        }
                        return null;
                    }
                } catch (SQLException e) {
                    throw new RuntimeException("Error while querying the database", e);
                }
            }
        };
    }

    private Iterator<AtmosSyncObject> oidFileIterator() {
        final Iterator<String> fileIterator = new FileLineIterator(oidFile);

        return new ReadOnlyIterator<AtmosSyncObject>() {
            @Override
            protected AtmosSyncObject getNextObject() {
                if (fileIterator.hasNext()) {
                    ObjectId objectId = new ObjectId(fileIterator.next());
                    return new AtmosSyncObject(AtmosSource.this, atmos, objectId, getRelativePath(objectId));
                }
                return null;
            }
        };
    }

    private Iterator<AtmosSyncObject> nameFileIterator() {
        final Iterator<String> fileIterator = new FileLineIterator(nameFile);

        return new ReadOnlyIterator<AtmosSyncObject>() {
            @Override
            protected AtmosSyncObject getNextObject() {
                if (fileIterator.hasNext()) {
                    ObjectPath objectPath = new ObjectPath(fileIterator.next().trim());
                    return new AtmosSyncObject(AtmosSource.this, atmos, objectPath, getRelativePath(objectPath));
                }
                return null;
            }
        };
    }

    private String getRelativePath(ObjectIdentifier identifier) {
        if (identifier instanceof ObjectPath) {
            String path = identifier.toString();

            if (namespaceRoot != null && path.startsWith(namespaceRoot))
                path = path.substring(namespaceRoot.length());

            if (path.startsWith("/"))
                path = path.substring(1);

            return path;

        } else {
            return identifier.toString();
        }
    }

    protected class AtmosDirectoryIterator extends ReadOnlyIterator<AtmosSyncObject> {
        private ObjectPath directory;
        private String relativePath;
        private ListDirectoryRequest listRequest;
        private Iterator<DirectoryEntry> atmosIterator;

        public AtmosDirectoryIterator(ObjectPath directory, String relativePath) {
            this.directory = directory;
            this.relativePath = relativePath;
            listRequest = new ListDirectoryRequest().path(directory).includeMetadata(true);
        }

        @Override
        protected AtmosSyncObject getNextObject() {
            if (getAtmosIterator().hasNext()) {
                DirectoryEntry entry = getAtmosIterator().next();
                ObjectPath objectPath = new ObjectPath(directory, entry);
                String childPath = relativePath + "/" + entry.getFilename();
                childPath = childPath.replaceFirst("^/", "");
                Long size = null;
                if (entry.getSystemMetadataMap() != null && entry.getSystemMetadataMap().get("size") != null)
                    size = Long.parseLong(entry.getSystemMetadataMap().get("size").getValue());
                return new AtmosSyncObject(AtmosSource.this, atmos, objectPath, childPath, size);
            }
            return null;
        }

        private synchronized Iterator<DirectoryEntry> getAtmosIterator() {
            if (atmosIterator == null || (!atmosIterator.hasNext() && listRequest.getToken() != null)) {
                atmosIterator = getNextBlock().iterator();
            }
            return atmosIterator;
        }

        private List<DirectoryEntry> getNextBlock() {
            return time(new Function<List<DirectoryEntry>>() {
                @Override
                public List<DirectoryEntry> call() {
                    return atmos.listDirectory(listRequest).getEntries();
                }
            }, OPERATION_LIST_DIRECTORY);
        }
    }

    public List<URI> getEndpoints() {
        return endpoints;
    }

    public void setEndpoints(List<URI> endpoints) {
        this.endpoints = endpoints;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public AtmosApi getAtmos() {
        return atmos;
    }

    public void setAtmos(AtmosApi atmos) {
        this.atmos = atmos;
    }

    /**
     * @return the namespaceRoot
     */
    public String getNamespaceRoot() {
        return namespaceRoot;
    }

    /**
     * @param namespaceRoot the namespaceRoot to set
     */
    public void setNamespaceRoot(String namespaceRoot) {
        this.namespaceRoot = namespaceRoot;
    }

    /**
     * @return the oidFile
     */
    public String getOidFile() {
        return oidFile;
    }

    /**
     * @param oidFile the oidFile to set
     */
    public void setOidFile(String oidFile) {
        this.oidFile = oidFile;
    }

    public String getNameFile() {
        return nameFile;
    }

    public void setNameFile(String nameFile) {
        this.nameFile = nameFile;
    }

    /**
     * @return the query
     */
    public String getQuery() {
        return query;
    }

    /**
     * @param query the query to set
     */
    public void setQuery(String query) {
        this.query = query;
    }

    /**
     * @return the dataSource
     */
    public DataSource getDataSource() {
        return dataSource;
    }

    /**
     * @param dataSource the dataSource to set
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public boolean isDeleteTags() {
        return deleteTags;
    }

    public void setDeleteTags(boolean deleteTags) {
        this.deleteTags = deleteTags;
    }
}