目前开发的这个项目中需要从远程服务器上下载数据,采用了开源的commons.net.ftp包。在实际应用中发现了一个问题,在测试服务器上调用ftpClient.listFiles()方法可以返回包含文件名的数组,而在现网服务器上此方法返回NULL。我被这个问题困扰了好久,下面把我的处理思路陈述如下:
(1)首先发现2个服务器的区别:测试服务器为solaris服务器,而现网服务器为hp服务器,会不会是平台差异所致呢?带着这个问题,下载了common包的源码,通过源码进行调试。
(2)FTPListParseEngine负责处理通过socket来获取远程服务器的信息。大概执行了ls –l
操作,并把结果一行行放入一个linkedlist中。代码如下:
 1
private void readStream(InputStream stream, String encoding) throws IOException
 2
    
{
 3
        BufferedReader reader;
 4
        if (encoding == null)
 5
        
{
 6
            reader = new BufferedReader(new InputStreamReader(stream));
 7
        }
 8
        else
 9
        
{
10
            reader = new BufferedReader(new InputStreamReader(stream, encoding));
11
        }
12
        
13
        String line = this.parser.readNextEntry(reader);
14
15
        while (line != null)
16
        
{
17
            this.entries.add(line);
18
            line = this.parser.readNextEntry(reader);
19
        }
20
        reader.close();
21
    }
22
 
 
(3)这个时候发现问题了,传入line中的字符串中有乱码!正常的应该为:
    
        
            | 
             drwxr-xr-x 11 daladmin   daladmin      1024 2004年9月18日 mqm 
             | 
        
    
其中时间那部分为乱码。                  
(4)处理:在调用listFiles()之前先调用ftpClient.setControlEncoding("GBK");这样line就能正常显示了,但是listFiles() 返回依然为空!!! 继续.....
(5) 发现继续运行的时候有一个正则表达式匹配不成功,代码如下:
 1
 public boolean matches(String s)
 2
    
{
 3
        this.result = null;
 4
        if (_matcher_.matches(s.trim(), this.pattern))
 5
        
{
 6
            this.result = _matcher_.getMatch();
 7
        }
 8
        return null != this.result;
 9
    }
10
 
 
s即为(3)中的line,追踪正则表达式,是在具体的子类UnixFTPEntryParser中写死的。如下:
 1
private static final String REGEX =
 2
        "([bcdlfmpSs-])"
 3
        +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+"
 4
        + "(\\d+)\\s+"
 5
        + "(\\S+)\\s+"
 6
        + "(?:(\\S+)\\s+)?"
 7
        + "(\\d+)\\s+"
 8
        
 9
        /**//*
10
          numeric or standard format date
11
        */
12
        //问题出在此处,这个匹配只匹配2中形式:
13
        //(1)2008-08-03
14
        //(2)Jan  9或4月 26
15
        //而出错的hp机器下的显示为 8月20日(没有空格分开)
16
        //故无法匹配而报错
17
        //将下面字符串改为:
18
        //((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+)|(?:\\S+))\\s+
19
        //便可以成功匹配
20
        + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+"
21
        
22
        /**//* 
23
           year (for non-recent standard format) 
24
           or time (for numeric or recent standard format  
25
        */
26
        + "(\\d+(?::\\d+)?)\\s+"
27
        
28
        + "(\\S*)(\\s*.*)";
29
 
 
(6)做上面修改后,能够解析出来,但是接着又会报异常,错误发生在UnixFTPEntryParser类的parseFTPEntry方法中,common.net对中文支持的实在是不够:
 1
 try
 2
            
{
 3
                file.setTimestamp(super.parseTimestamp(datestr));
 4
            }
 5
            catch (ParseException e)
 6
            
{
 7
            //注释掉
 8
                return null;  // this is a parsing failure too.
 9
            }
10
 
 
这个错误的原因是创建simpleDateFormat类时(详情请见jdkAPI文档)
public SimpleDateFormat(String pattern, Locale locale) 
 
locale为EN,解决方案是创建一个新类,继承ConfigurableFTPFileEntryParserImpl。其中的属性defaultDateFormat和recentDateFormat 用Locale.CHINA初始化。而我目前的程序用不到取文件的修改时间,所以直接省事将上段代码中的异常吞掉,即注释掉return null 。网上有个解决方案(http://hi.baidu.com/hzwei206/blog/item/7c901d2debf7e136359bf7cd.html),是用了另一种方案,粘贴如下:
  
 
    
        
            | 
             commons-net-1.4.1.jar包中ftp应用的几点问题 
             
            一、异常: 
                     从http://commons.apache.com网站下载了commons-net-1.4.1包后添加到自己的工程中,调用FtpClient类的listFiles(String pathName)方法时,抛如下异常: 
                     Exception in thread "main" java.lang.NoClassDefFoundError :  
                         org/apache/oro/text/regex/MalformedPatternException 
                           at org.apache.commons.net.ftp.parser.RegexFTPFileEntryParserImpl.<init> (RegexFTPFileEntryParserImpl.java:75) 
                           at org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl.<init>(ConfigurableFTPFileEntryParserImpl.java:57) 
                           at org.apache.commons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:136) 
                           at org.apache.commons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:119) 
                           at  org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createUnixFTPEntryParser(DefaultFTPFileEntryParserFactory.java:169) 
                           at org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createFileEntryParser(DefaultFTPFileEntryParserFactory.java:94) 
                           at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:2358) 
                           at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2141) 
                           at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2188) 
                           ................. 
            以上异常是由于缺少辅助的包jakarta-oro-2.0.8.jar引起的,去http://commons.apache.com网站下载该包后放入工程的lib下,并加载到classpath中,重新编译运行,OK! 
            二、调用FtpClient类的listFiles(String pathName)方法失效的问题: 
                  一般是由于ftp服务器(主要是小型机)的操作系统不同语言环境的时间格式造成的,在中文环境下,文件或文件夹的时间格式为"m月d日 hh:mm"或"yyyy年m月 d",而E文环境下时间格式为"MMM d yyyy"或"MMM d HH:mm",于是,在中文环境下,ftp包中的FTPTimestampParserImpl类将时间字符串Date化时抛异常,因为commons-net-1.4.1包不支持中文。 
            解决办法(两种办法): 
                    1. 将ftp服务器操作系统语言环境设为英文; 
                     2. 修改ftp包的代码:将FTPTimestampParserImpl类进行扩展,使之支持中文 
            下面针对第2种解决办法来实现: 
            (1)    新建类FTPTimestampParserImplExZH类: 
             
              1  /** *//** 
              2 * FTPTimestampParserImpl的扩展类,使之支持中文环境的时间格式 
              3 * Date:2007-8-15 
              4 */ 
              5 package org.apache.commons.net.ftp.parser; 
              6  
              7 import java.text.ParseException; 
              8 import java.text.ParsePosition; 
              9 import java.text.SimpleDateFormat; 
             10 import java.util.Calendar; 
             11 import java.util.Date; 
             12  
             13  /** *//** 
             14 * @author hzwei206 
             15 * FTPTimestampParserImpl的扩展类,使之支持中文环境的时间格式 
             16 */ 
             17 public class FTPTimestampParserImplExZH extends FTPTimestampParserImpl 
             18   { 
             19       private SimpleDateFormat defaultDateFormat = new SimpleDateFormat("mm d hh:mm"); 
             20       private SimpleDateFormat recentDateFormat = new SimpleDateFormat("yyyy mm d"); 
             21  
             22        /** *//** 
             23        * @author hzwei206 
             24        * 将中文环境的时间格式进行转换 
             25        */ 
             26       private String formatDate_Zh2En(String timeStrZh) 
             27         { 
             28           if (timeStrZh == null) 
             29             { 
             30               return ""; 
             31           } 
             32          
             33           int len = timeStrZh.length(); 
             34           StringBuffer sb = new StringBuffer(len); 
             35           char ch = ' '; 
             36           for (int i = 0;i < len;i++) 
             37             { 
             38               ch = timeStrZh.charAt(i); 
             39               if ((ch >= '0' && ch <= '9') || ch == ' ' || ch == ':') 
             40                 { 
             41                   sb.append(ch); 
             42               } 
             43           } 
             44          
             45           return sb.toString(); 
             46       } 
             47      
             48        /** *//**  
             49        * Implements the one {@link    FTPTimestampParser#parseTimestamp(String)    method} 
             50        * in the {@link    FTPTimestampParser    FTPTimestampParser} interface  
             51        * according to this algorithm: 
             52        *  
             53        * If the recentDateFormat member has been defined, try to parse the  
             54        * supplied string with that.    If that parse fails, or if the recentDateFormat 
             55        * member has not been defined, attempt to parse with the defaultDateFormat 
             56        * member.    If that fails, throw a ParseException.  
             57        *  
             58        * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)   
             59        */ 
             60       public Calendar parseTimestamp(String timestampStr) throws ParseException 
             61         { 
             62           timestampStr = formatDate_Zh2En(timestampStr); 
             63           Calendar now = Calendar.getInstance(); 
             64           now.setTimeZone(this.getServerTimeZone()); 
             65  
             66           Calendar working = Calendar.getInstance(); 
             67           working.setTimeZone(this.getServerTimeZone()); 
             68           ParsePosition pp = new ParsePosition(0); 
             69  
             70           Date parsed = null; 
             71           if (this.recentDateFormat != null) 
             72             { 
             73               parsed = recentDateFormat.parse(timestampStr, pp); 
             74           } 
             75           if (parsed != null && pp.getIndex() == timestampStr.length()) 
             76             { 
             77               working.setTime(parsed); 
             78               working.set(Calendar.YEAR, now.get(Calendar.YEAR)); 
             79               if (working.after(now)) 
             80                 { 
             81                   working.add(Calendar.YEAR, -1); 
             82               } 
             83           } 
             84           else 
             85             { 
             86               pp = new ParsePosition(0); 
             87               parsed = defaultDateFormat.parse(timestampStr, pp); 
             88               // note, length checks are mandatory for us since 
             89               // SimpleDateFormat methods will succeed if less than 
             90               // full string is matched. They will also accept, 
             91               // despite "leniency" setting, a two-digit number as 
             92               // a valid year (e.g. 22:04 will parse as 22 A.D.) 
             93               // so could mistakenly confuse an hour with a year, 
             94               // if we don't insist on full length parsing. 
             95               if (parsed != null && pp.getIndex() == timestampStr.length()) 
             96                 { 
             97                   working.setTime(parsed); 
             98               } 
             99               else 
            100                 { 
            101                   throw new ParseException( 
            102                           "Timestamp could not be parsed with older or recent DateFormat", 
            103                           pp.getIndex()); 
            104               } 
            105           } 
            106           return working; 
            107       } 
            108 } 
            109  
            110  
            111  
             
             
            (2) 修改org.apache.commons.net.ftp.parser.UnixFTPEntryParser类的parseFTPEntry方法: 
                  
             1  public FTPFile parseFTPEntry(String entry) 
             2         { 
             3             .. 
             4           if (matches(entry)) 
             5             { 
             6               String typeStr = group(1); 
             7               String hardLinkCount = group(15); 
             8               String usr = group(16); 
             9               String grp = group(17); 
            10               String filesize = group(18); 
            11               String datestr = group(19) + " " + group(20); 
            12               String name = group(21); 
            13               String endtoken = group(22); 
            14  
            15               try 
            16                 { 
            17                   file.setTimestamp(super.parseTimestamp(datestr)); 
            18               } 
            19               catch (ParseException e) 
            20                 { 
            21                    /**//* ***mod by hzwei206 将中文时间格式转换 2007-8-15 begin*** */ 
            22                   //return null; // this is a parsing failure too. 
            23                   try 
            24                     { 
            25                       FTPTimestampParserImplExZH Zh2En = new FTPTimestampParserImplExZH(); 
            26                       file.setTimestamp(Zh2En.parseTimestamp(datestr)); 
            27                   } 
            28                   catch (ParseException e1) 
            29                     { 
            30                       return null; // this is a parsing failure too. 
            31                   } 
            32                    /**//* ***mod by hzwei206 将中文时间格式转换 2007-8-15 end*** */ 
            33               } 
            34  
            35                    .. 
            36       } 
            37  
            
             | 
        
    
	posted on 2008-08-21 21:30 
wodong 阅读(14980) 
评论(1)  编辑  收藏