blob: e7c48a6bc7d737b7532d32a38b5eac6b7d10a1a0 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.sqoop.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPListParseEngine;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapred.JobConf;
import org.apache.sqoop.mapreduce.JobBase;
import org.apache.sqoop.mapreduce.db.DBConfiguration;
import org.apache.sqoop.mapreduce.mainframe.MainframeConfiguration;
import org.apache.sqoop.mapreduce.mainframe.MainframeDatasetPath;
/**
* Utility methods used when accessing a mainframe server through FTP client.
*/
public final class MainframeFTPClientUtils {
private static final Log LOG = LogFactory.getLog(
MainframeFTPClientUtils.class.getName());
private static FTPClient mockFTPClient = null; // Used for unit testing
private MainframeFTPClientUtils() {
}
public static List<String> listSequentialDatasets(String pdsName, Configuration conf) throws IOException {
List<String> datasets = new ArrayList<String>();
String dsName = pdsName;
String fileName = "";
MainframeDatasetPath p = null;
String dsType = conf.get(MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE);
if (dsType == null) {
// default dataset type to partitioned dataset
conf.set(MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE,MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE_PARTITIONED);
dsType = conf.get(MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE);
}
try {
p = new MainframeDatasetPath(dsName,conf);
} catch (Exception e) {
LOG.error(e.getMessage());
LOG.error("MainframeDatasetPath helper class incorrectly initialised");
e.printStackTrace();
}
boolean isSequentialDs = false;
boolean isGDG = false;
if (dsType != null && p != null) {
isSequentialDs = p.getMainframeDatasetType().toString().equals(MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE_SEQUENTIAL);
isGDG = p.getMainframeDatasetType().toString().equals(MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE_GDG);
pdsName = p.getMainframeDatasetFolder();
fileName = p.getMainframeDatasetFileName();
}
FTPClient ftp = null;
try {
ftp = getFTPConnection(conf);
if (ftp != null) {
ftp.changeWorkingDirectory("'" + pdsName + "'");
FTPFile[] ftpFiles = null;
if (!MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE_PARTITIONED.equals(dsType)) {
FTPListParseEngine parser = null;
if (MainframeConfiguration.MAINFRAME_INPUT_DATASET_TYPE_GDG.equals(dsType)) {
// use GDG specific parser to filter out non GDG datasets
parser = ftp.initiateListParsing(MainframeConfiguration.MAINFRAME_FTP_FILE_GDG_ENTRY_PARSER_CLASSNAME, "");
} else {
// excepting partitioned datasets, use the MainframeFTPFileEntryParser, default doesn't match larger datasets
parser = ftp.initiateListParsing(MainframeConfiguration.MAINFRAME_FTP_FILE_ENTRY_PARSER_CLASSNAME, "");
}
List<FTPFile> listing = new ArrayList<FTPFile>();
while(parser.hasNext()) {
FTPFile[] files = parser.getNext(25);
for (FTPFile file : files) {
if (file != null) {
listing.add(file);
LOG.info(String.format("Name: %s Type: %s", file.getName(), file.getType()));
}
// skip nulls returned from parser
}
ftpFiles = new FTPFile[listing.size()];
for (int i = 0;i < listing.size(); i++) {
ftpFiles[i] = listing.get(i);
}
LOG.info("Files returned from mainframe parser:-");
for (FTPFile f : ftpFiles) {
LOG.info(String.format("Name: %s, Type: %s",f.getName(),f.getType()));
}
}
}
else {
// partitioned datasets have a different FTP listing structure
LOG.info("Dataset is a partitioned dataset, using default FTP list parsing");
ftpFiles = ftp.listFiles();
}
if (!isGDG) {
for (FTPFile f : ftpFiles) {
LOG.info(String.format("Name: %s Type: %s",f.getName(), f.getType()));
if (f.getType() == FTPFile.FILE_TYPE) {
// only add datasets if default behaviour of partitioned data sets
// or if it is a sequential data set, only add if the file name matches exactly
if (!isSequentialDs || isSequentialDs && f.getName().equals(fileName) && !fileName.equals("")) {
datasets.add(f.getName());
}
}
}
} else {
LOG.info("GDG branch. File list:-");
for (FTPFile f : ftpFiles) {
LOG.info(String.format("Name: %s Type: %s",f.getName(), f.getType()));
}
if (ftpFiles.length > 0 && ftpFiles[ftpFiles.length-1].getType() == FTPFile.FILE_TYPE) {
// for GDG - add the last file in the collection
datasets.add(ftpFiles[ftpFiles.length-1].getName());
}
}
}
} catch(IOException ioe) {
throw new IOException ("Could not list datasets from " + pdsName + ":"
+ ioe.toString());
} finally {
if (ftp != null) {
closeFTPConnection(ftp);
}
}
return datasets;
}
public static FTPClient getFTPConnection(Configuration conf)
throws IOException {
FTPClient ftp = null;
try {
String username = conf.get(DBConfiguration.USERNAME_PROPERTY);
String password;
if (username == null) {
username = "anonymous";
password = "";
}
else {
password = DBConfiguration.getPassword((JobConf) conf);
}
String connectString = conf.get(DBConfiguration.URL_PROPERTY);
String server = connectString;
int port = 0;
String[] parts = connectString.split(":");
if (parts.length == 2) {
server = parts[0];
try {
port = Integer.parseInt(parts[1]);
} catch(NumberFormatException e) {
LOG.warn("Invalid port number: " + e.toString());
}
}
if (null != mockFTPClient) {
ftp = mockFTPClient;
} else {
ftp = new FTPClient();
}
FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_MVS);
ftp.configure(config);
if (conf.getBoolean(JobBase.PROPERTY_VERBOSE, false)) {
ftp.addProtocolCommandListener(new PrintCommandListener(
new PrintWriter(System.out), true));
}
try {
if (port > 0) {
ftp.connect(server, port);
} else {
ftp.connect(server);
}
} catch(IOException ioexp) {
throw new IOException("Could not connect to server " + server, ioexp);
}
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
throw new IOException("FTP server " + server
+ " refused connection:" + ftp.getReplyString());
}
LOG.info("Connected to " + server + " on " +
(port>0 ? port : ftp.getDefaultPort()));
if (!ftp.login(username, password)) {
ftp.logout();
throw new IOException("Could not login to server " + server
+ ":" + ftp.getReplyString());
}
// set transfer mode
String transferMode = conf.get(MainframeConfiguration.MAINFRAME_FTP_TRANSFER_MODE);
if (StringUtils.equalsIgnoreCase(MainframeConfiguration.MAINFRAME_FTP_TRANSFER_MODE_BINARY,transferMode)) {
LOG.info("Setting FTP transfer mode to binary");
// ftp.setFileTransferMode(FTP.BINARY_FILE_TYPE) doesn't work for MVS, it throws a syntax error
ftp.sendCommand("TYPE I");
// this is IMPORTANT - otherwise it will convert 0x0d0a to 0x0a = dropping bytes
ftp.setFileType(FTP.BINARY_FILE_TYPE);
} else {
LOG.info("Defaulting FTP transfer mode to ascii");
ftp.setFileTransferMode(FTP.ASCII_FILE_TYPE);
}
// Use passive mode as default.
ftp.enterLocalPassiveMode();
LOG.info("System type detected: " + ftp.getSystemType());
} catch(IOException ioe) {
if (ftp != null && ftp.isConnected()) {
try {
ftp.disconnect();
} catch(IOException f) {
// do nothing
}
}
ftp = null;
throw ioe;
}
return ftp;
}
public static boolean closeFTPConnection(FTPClient ftp) {
boolean success = true;
try {
ftp.noop(); // check that control connection is working OK
ftp.logout();
} catch(FTPConnectionClosedException e) {
success = false;
LOG.warn("Server closed connection: " + e.toString());
} catch(IOException e) {
success = false;
LOG.warn("Server closed connection: " + e.toString());
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch(IOException f) {
success = false;
}
}
}
return success;
}
// Used for testing only
public static void setMockFTPClient(FTPClient FTPClient) {
mockFTPClient = FTPClient;
}
}