OS400FTPEntryParser.java
/*
* 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.commons.net.ftp.parser;
import java.io.File;
import java.text.ParseException;
import java.util.Locale;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
/**
* <pre>
* Example *FILE/*MEM FTP entries, when the current
* working directory is a file of file system QSYS:
* ------------------------------------------------
*
* $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
* 250-NAMEFMT set to 1.
* 250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
* $ dir
* 227 Entering Passive Mode (10,200,36,33,40,249).
* 125 List started.
* QPGMR 135168 22.06.13 13:18:19 *FILE
* QPGMR *MEM MKCMD.MBR
* QPGMR *MEM RUCALLTST.MBR
* QPGMR *MEM RUCMDHLP.MBR
* QPGMR *MEM RUCRTTST.MBR
* 250 List completed.
*
*
* Example *FILE entry of an OS/400 save file:
* ---------------------------------------------------
*
* $ cwd /qsys.lib/rpgunit.lib
* 250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
* $ dir rpgunit.file
* 227 Entering Passive Mode (10,200,36,33,188,106).
* 125 List started.
* QPGMR 16347136 29.06.13 15:45:09 *FILE RPGUNIT.SAVF
* 250 List completed.
*
*
* Example *STMF/*DIR FTP entries, when the
* current working directory is in file system "root":
* ---------------------------------------------------
*
* $ cwd /home/raddatz
* 250 "/home/raddatz" is current directory.
* $ dir test*
* 227 Entering Passive Mode (10,200,36,33,200,189).
* 125 List started.
* RADDATZ 200 21.05.11 12:31:18 *STMF TEST_RG_02_CRLF.properties
* RADDATZ 187 08.05.11 12:31:40 *STMF TEST_RG_02_LF.properties
* RADDATZ 187 08.05.11 12:31:52 *STMF TEST_RG_02_CR.properties
* RADDATZ 8192 04.07.13 09:04:14 *DIR testDir1/
* RADDATZ 8192 04.07.13 09:04:17 *DIR testDir2/
* 250 List completed.
*
*
* Example 1, using ANT to list specific members of a file:
* --------------------------------------------------------
*
* <echo/>
* <echo>Listing members of a file:</echo>
*
* <ftp action="list"
* server="${ftp.server}"
* userid="${ftp.user}"
* password="${ftp.password}"
* binary="false"
* verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
* systemTypeKey="OS/400"
* listing="ftp-listing.txt"
* >
* <fileset dir="./i5-downloads-file" casesensitive="false">
* <include name="run*.mbr" />
* </fileset>
* </ftp>
*
* Output:
* -------
*
* [echo] Listing members of a file:
* [ftp] listing files
* [ftp] listing RUN.MBR
* [ftp] listing RUNNER.MBR
* [ftp] listing RUNNERBND.MBR
* [ftp] 3 files listed
*
*
* Example 2, using ANT to list specific members of all files of a library:
* ------------------------------------------------------------------------
*
* <echo/>
* <echo>Listing members of all files of a library:</echo>
*
* <ftp action="list"
* server="${ftp.server}"
* userid="${ftp.user}"
* password="${ftp.password}"
* binary="false"
* verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB"
* systemTypeKey="OS/400"
* listing="ftp-listing.txt"
* >
* <fileset dir="./i5-downloads-lib" casesensitive="false">
* <include name="**\run*.mbr" />
* </fileset>
* </ftp>
*
* Output:
* -------
*
* [echo] Listing members of all files of a library:
* [ftp] listing files
* [ftp] listing RPGUNIT1.FILE\RUN.MBR
* [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR
* [ftp] listing RPGUNITT1.FILE\RUNT.MBR
* [ftp] listing RPGUNITY1.FILE\RUN.MBR
* [ftp] listing RPGUNITY1.FILE\RUNNER.MBR
* [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR
* [ftp] 6 files listed
*
*
* Example 3, using ANT to download specific members of a file:
* ------------------------------------------------------------
*
* <echo/>
* <echo>Downloading members of a file:</echo>
*
* <ftp action="get"
* server="${ftp.server}"
* userid="${ftp.user}"
* password="${ftp.password}"
* binary="false"
* verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
* systemTypeKey="OS/400"
* >
* <fileset dir="./i5-downloads-file" casesensitive="false">
* <include name="run*.mbr" />
* </fileset>
* </ftp>
*
* Output:
* -------
*
* [echo] Downloading members of a file:
* [ftp] getting files
* [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUN.MBR
* [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNER.MBR
* [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNERBND.MBR
* [ftp] 3 files retrieved
*
*
* Example 4, using ANT to download specific members of all files of a library:
* ----------------------------------------------------------------------------
*
* <echo/>
* <echo>Downloading members of all files of a library:</echo>
*
* <ftp action="get"
* server="${ftp.server}"
* userid="${ftp.user}"
* password="${ftp.password}"
* binary="false"
* verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB"
* systemTypeKey="OS/400"
* >
* <fileset dir="./i5-downloads-lib" casesensitive="false">
* <include name="**\run*.mbr" />
* </fileset>
* </ftp>
*
* Output:
* -------
*
* [echo] Downloading members of all files of a library:
* [ftp] getting files
* [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR
* [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR
* [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR
* [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR
* [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR
* [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR
* [ftp] 6 files retrieved
*
*
* Example 5, using ANT to download a save file of a library:
* ----------------------------------------------------------
*
* <ftp action="get"
* server="${ftp.server}"
* userid="${ftp.user}"
* password="${ftp.password}"
* binary="true"
* verbose="true"
* remotedir="/QSYS.LIB/RPGUNIT.LIB"
* systemTypeKey="OS/400"
* >
* <fileset dir="./i5-downloads-savf" casesensitive="false">
* <include name="RPGUNIT.SAVF" />
* </fileset>
* </ftp>
*
* Output:
* -------
* [echo] Downloading save file:
* [ftp] getting files
* [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF
* [ftp] 1 files retrieved
*
* </pre>
*/
public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl {
private static final String DEFAULT_DATE_FORMAT = "yy/MM/dd HH:mm:ss"; // 01/11/09 12:30:24
private static final String REGEX = "(\\S+)\\s+" // user
+ "(?:(\\d+)\\s+)?" // size, empty for members
+ "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members
+ "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM
+ "((\\S+\\s*)+)?"; // file name, missing, when CWD is a *FILE
/**
* The default constructor for a OS400FTPEntryParser object.
*
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
* sign that <code>REGEX</code> is not a valid regular expression.
*/
public OS400FTPEntryParser() {
this(null);
}
/**
* This constructor allows the creation of an OS400FTPEntryParser object with something other than the default configuration.
*
* @param config The {@link FTPClientConfig configuration} object used to configure this parser.
* @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
* sign that <code>REGEX</code> is not a valid regular expression.
* @since 1.4
*/
public OS400FTPEntryParser(final FTPClientConfig config) {
super(REGEX);
configure(config);
}
/**
* Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified.
*
* @return the default configuration for this parser.
*/
@Override
protected FTPClientConfig getDefaultConfiguration() {
return new FTPClientConfig(FTPClientConfig.SYST_OS400, DEFAULT_DATE_FORMAT, null);
}
/**
*
* @param string String value that is checked for {@code null} or empty.
* @return {@code true} for {@code null} or empty values, else {@code false}.
*/
private boolean isNullOrEmpty(final String string) {
return string == null || string.isEmpty();
}
@Override
public FTPFile parseFTPEntry(final String entry) {
final FTPFile file = new FTPFile();
file.setRawListing(entry);
final int type;
if (matches(entry)) {
final String usr = group(1);
final String filesize = group(2);
String datestr = "";
if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) {
datestr = group(3) + " " + group(4);
}
final String typeStr = group(5);
String name = group(6);
boolean mustScanForPathSeparator = true;
try {
file.setTimestamp(super.parseTimestamp(datestr));
} catch (final ParseException e) {
// intentionally do nothing
}
if (typeStr.equalsIgnoreCase("*STMF")) {
type = FTPFile.FILE_TYPE;
if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) {
return null;
}
} else if (typeStr.equalsIgnoreCase("*DIR")) {
type = FTPFile.DIRECTORY_TYPE;
if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) {
return null;
}
} else if (typeStr.equalsIgnoreCase("*FILE")) {
// File, defines the structure of the data (columns of a row)
// but the data is stored in one or more members. Typically, a
// source file contains multiple members whereas it is
// recommended (but not enforced) to use one member per data
// file.
// Save files are a special type of files which are used
// to save objects, e.g. for backups.
if (name == null || !name.toUpperCase(Locale.ROOT).endsWith(".SAVF")) {
return null;
}
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
} else if (typeStr.equalsIgnoreCase("*MEM")) {
mustScanForPathSeparator = false;
type = FTPFile.FILE_TYPE;
if (isNullOrEmpty(name)) {
return null;
}
if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) {
return null;
}
// Quick and dirty bug fix to make SelectorUtils work.
// Class SelectorUtils uses 'File.separator' to splitt
// a given path into pieces. But actually it had to
// use the separator of the FTP server, which is a forward
// slash in case of an AS/400.
name = name.replace('/', File.separatorChar);
} else {
type = FTPFile.UNKNOWN_TYPE;
}
file.setType(type);
file.setUser(usr);
try {
file.setSize(Long.parseLong(filesize));
} catch (final NumberFormatException e) {
// intentionally do nothing
}
if (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
if (mustScanForPathSeparator) {
final int pos = name.lastIndexOf('/');
if (pos > -1) {
name = name.substring(pos + 1);
}
}
file.setName(name);
return file;
}
return null;
}
}