因为以前也没有做过相关的web service开发,对于Xfire也只是知道有这么一个框架。当然现在它已经变成apache基金会旗下的一个开源项目CXF。不过,现在依旧有很多公司还在用Xfire作web service 的开发,这说明它在业界一向是口碑不错的啊。

在学习一个框架时,我强烈建议将其自带的例子部置运行几次,这样对于理解以及搭建运行环境都有很大的帮助,也不容易出错。

在这里,我主要讲解一下用Xfire开发文件上传下载的web service应用。对于文件的传输Xfire主要有两种方式。一种是将文件编码为字符串的样式进行传输,但文件大小有一定的限制且效率较低;另一种是基于MTOM协议以附件字节流的形式进行传输,基本满足现有的文件传输大小并且效率较高。

首先,我们先把开发环境搭建好。在myeclipse新建一个web应用程序,将Xfire所需jar包复制到lib目录下,并在src目录下按照META-INF/xfire/services.xml些目录结构新建一个services.xml文件,如下图

接下来就是真正的环境配置了,打开WebRoot/WEB-INF/web.xml进行如下配置,如图

至此,Xfire框架已经整合到我们的web应用程序中了,这样我们就可以开发我们的web service 应用了。

对于第一种小文件传输方式开发如下:

1.首先我们先定义一个接口,因为我们暴露给客户端的就是这个接口,定义如下:

2.主要是定义文件上传与下载的两个方法,下面我们编写接口的实现类。

  1. package org.carrot.file1;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import org.codehaus.xfire.util.Base64;
  10. public class File1Impl implements File1
  11. {
  12. @Override
  13. public String downFile(String fileName)
  14. {
  15. File file = new File("F:/", fileName);
  16. System.out.println(file.length());
  17. StringBuffer buffer = new StringBuffer();
  18. InputStream in;
  19. try
  20. {
  21. in = new FileInputStream(file);
  22. ];
  23. ;
  24. )))
  25. {
  26. , len));
  27. }
  28. in.close();
  29. }
  30. catch (FileNotFoundException e1)
  31. {
  32. e1.printStackTrace();
  33. }
  34. catch (IOException e)
  35. {
  36. e.printStackTrace();
  37. }
  38. String fileString = buffer.toString();
  39. System.out.println(fileString.length());
  40. System.out.println("running");
  41. return fileString;
  42. }
  43. @Override
  44. public String uploadFile(String file, String fileName)
  45. {
  46. File loadFile = new File("F:/", fileName);
  47. InputStream in = new ByteArrayInputStream(Base64.decode(file));
  48. ];
  49. ;
  50. try
  51. {
  52. FileOutputStream out = new FileOutputStream(loadFile);
  53. , buffer.length)))
  54. {
  55. , len);
  56. }
  57. in.close();
  58. out.close();
  59. }
  60. catch (FileNotFoundException e)
  61. {
  62. e.printStackTrace();
  63. return "file not found";
  64. }
  65. catch (IOException e)
  66. {
  67. e.printStackTrace();
  68. return "error";
  69. }
  70. return "success";
  71. }
  72. }

3.接下来就是我们对外发布服务信息的配置了,打开services.xml文件进行如下配置

这里的name指发布的服务名,namespace指命名空间用于区别,serviceClass指发布的接口,implementationClass指实现类,具体还有很多元素可以当阅相关文档,这里只列出几个我们常用的。

4.最后我们只要将这个应用部署到服务器上发布,这里我用的是Tomcat6.x进行部署,如下

5.这时我们就可以启动Tomcat服务器进行一下访问测试了,在浏览器中输入网址:http://localhost:8080/MyFile/services

6.点击进入wsdl就可以看到我们发布的服务信息了

7.这样我们的服务就发布成功了,接下来就是编写客户端进行服务的调用看是否成功。在这里客户端既可以是java web应用,也可以是java一般应用,为了jar包好导入以下皆采用java web应用作为客户端调用。客户端调用我们的服务也有三种方式:动态方式(反射)、代理方式、客户端桩方式。

注意:这里的三种方式是在java的环境下笔者所知的开发方式,在其它语言环境下开发并不是很了解,只知道C#也是采用客户端桩方式。

动态方式:客户端采用动态方式要加入Xfire相关jar包就能调用服务的方法,类似java反射

为了简单,调用服务我都写在一个main方法中,如下

  1. package org.carrot.client2;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.net.URL;
  9. import org.codehaus.xfire.client.Client;
  10. import org.codehaus.xfire.transport.http.HttpTransport;
  11. import org.codehaus.xfire.util.Base64;
  12. public class Client2
  13. {
  14. public static void main(String[] args)
  15. {
  16. try
  17. {
  18. URL myURL = new URL(
  19. "http://localhost:8080/MyFile/services/File1?wsdl");
  20. Client client = new Client(myURL);
  21. // 文件下载
  22. Object[] result = client.invoke("downFile", new Object[]
  23. { "java6webservice.doc" });
  24. String fileName = "java6webservice.doc";
  25. File file = new File(fileName);
  26. ]);
  27. System.out.println(bytes.length);
  28. file.createNewFile();
  29. FileOutputStream out = new FileOutputStream(file);
  30. out.write(bytes);
  31. out.flush();
  32. out.close();
  33. // 文件上传
  34. //          String fileName2 = "java6webservice.doc";
  35. //          File file2 = new File("E:/", fileName2);
  36. //          System.out.println(file2.length());
  37. //          StringBuffer sb = new StringBuffer();
  38. //          InputStream is = new FileInputStream(file2);
  39. //          byte[] buff = new byte[1024 * 1024];
  40. //          int len = -1;
  41. //          while (-1 != (len = is.read(buff, 0, buff.length)))
  42. //          {
  43. //              sb.append(Base64.encode(buff, 0, len));
  44. //          }
  45. //          is.close();
  46. //          String fileString = sb.toString();
  47. //          System.out.println(fileString.length());
  48. //          Object[] result = client.invoke("uploadFile", new Object[]
  49. //          { fileString, fileName2});
  50. //          System.out.println(result[0]);
  51. }
  52. catch (FileNotFoundException e)
  53. {
  54. e.printStackTrace();
  55. }
  56. catch (IOException e)
  57. {
  58. e.printStackTrace();
  59. }
  60. catch (Exception e)
  61. {
  62. e.printStackTrace();
  63. }
  64. }
  65. }

代理方式:需要加入Xfire相关jar包,并且客户端必须提供与服务端一样的接口,即我们的服务暴露给客户端的接口,包名也最好与服务端的一样。服务调用如下

  1. package org.carrot.client3;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import java.net.MalformedURLException;
  10. import org.codehaus.xfire.client.Client;
  11. import org.codehaus.xfire.client.XFireProxyFactory;
  12. import org.codehaus.xfire.service.Service;
  13. import org.codehaus.xfire.service.binding.ObjectServiceFactory;
  14. import org.codehaus.xfire.util.Base64;
  15. public class Client3
  16. {
  17. public static void main(String[] args)
  18. {
  19. Service serviceModel = new ObjectServiceFactory().create(File1.class,
  20. "File1", "http://file1.carrot.org/File1", null);
  21. File1 service = null;
  22. try
  23. {
  24. service = (File1) new XFireProxyFactory().create(serviceModel,
  25. "http://localhost:8080/MyFile/services/File1");
  26. }
  27. catch (MalformedURLException e)
  28. {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. }
  32. //文件下载
  33. //      String fileName1 = "java6webservice.doc";
  34. //      File file1 = new File("E:/", fileName1);
  35. //      InputStream in = new ByteArrayInputStream(Base64
  36. //              .decode(service
  37. //                      .downFile(fileName1)));
  38. //      byte[] buffer =  new byte[1024*1024];
  39. //      try
  40. //      {
  41. //          int len1 = -1;
  42. //          file1.createNewFile();
  43. //          FileOutputStream out = new FileOutputStream(file1);
  44. //          while(-1 != (len1 = in.read(buffer, 0, buffer.length)))
  45. //          {
  46. //              out.write(buffer, 0 , len1);
  47. //          }
  48. //          out.flush();
  49. //          out.close();
  50. //      }
  51. //      catch (FileNotFoundException e)
  52. //      {
  53. //          e.printStackTrace();
  54. //      }
  55. //      catch (IOException e)
  56. //      {
  57. //          e.printStackTrace();
  58. //      }
  59. //      System.out.println(file1.length());
  60. //文件 上传
  61. String fileName2 = "java6webservice.doc";
  62. File file2 = new File("E:/", fileName2);
  63. System.out.println(file2.length());
  64. StringBuffer sb = new StringBuffer();
  65. InputStream is;
  66. try
  67. {
  68. is = new FileInputStream(file2);
  69. ];
  70. ;
  71. , buff.length)))
  72. {
  73. , len));
  74. }
  75. is.close();
  76. }
  77. catch (FileNotFoundException e1)
  78. {
  79. e1.printStackTrace();
  80. }
  81. catch (IOException e)
  82. {
  83. e.printStackTrace();
  84. }
  85. String fileString = sb.toString();
  86. System.out.println(fileString.length());
  87. service.uploadFile(fileString, fileName2);
  88. }
  89. }

客户端桩方式:也是最容易理解的一种方式,它通过工具自动生成客户端代码,在调用服务时就好像在本地方法一样,同样需要加入Xfire相关jar包的支持。

自动生成工具有很多,myeclipse自带就有。这里我采用的是官方给出的例子,用Ant工具来进行生成。我们要在客户端根路径下新建一个build.xml文件,如图

打开build.xml文件,配置我们要生成的信息,如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project default="genfiles" basedir=".">
  3. <property name="lib" value="WebRoot/WEB-INF/lib" />
  4. <path id="myclasspath">
  5. <fileset dir="${lib}">
  6. <include name="*.jar" />
  7. </fileset>
  8. <pathelement location="${genfiles}" />
  9. </path>
  10. <property name="code_path" value="src" />
  11. <propertynamepropertyname="wsdl_path" value="http://localhost:8080/MyFile/services/File1?wsdl" />
  12. <property name="code_package" value="org.carrot.client1" />
  13. <target name="genfiles" description="Generate the files">
  14. <taskdef name="wsgen" classname="org.codehaus.xfire.gen.WsGenTask" classpathref="myclasspath" />
  15. <wsgen outputDirectory="${code_path}" wsdl="${wsdl_path}" package="${code_package}" binding="xmlbeans" overwrite="true" />
  16. </target>
  17. </project>

我们运行此文件就会在src目录下生成一堆称之为桩的文件,如下图

大家可以看到在包org.carrot.client1下生成了三个文件File1Client.java、File1Impl.java、File1PortType.java。这里的Client1.java是我接下来要讲的我写的调用服务的测试类。有了这几个文件我们与服务端打交道就轻松多了,不信你看

  1. package org.carrot.client1;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import org.codehaus.xfire.util.Base64;
  10. public class Client1
  11. {
  12. public static void main(String[] args)
  13. {
  14. File1Client client = new File1Client();
  15. File1PortType service = client.getFile1HttpPort();
  16. //文件下载
  17. //      String fileName1 = "java6webservice.doc";
  18. //      File file1 = new File("E:/", fileName1);
  19. //      InputStream in = new ByteArrayInputStream(Base64
  20. //              .decode(service
  21. //                      .downFile(fileName1)));
  22. //      byte[] buffer =  new byte[1024*1024];
  23. //      try
  24. //      {
  25. //          int len1 = -1;
  26. //          file1.createNewFile();
  27. //          FileOutputStream out = new FileOutputStream(file1);
  28. //          while(-1 != (len1 = in.read(buffer, 0, buffer.length)))
  29. //          {
  30. //              out.write(buffer, 0 , len1);
  31. //          }
  32. //          out.flush();
  33. //          out.close();
  34. //      }
  35. //      catch (FileNotFoundException e)
  36. //      {
  37. //          e.printStackTrace();
  38. //      }
  39. //      catch (IOException e)
  40. //      {
  41. //          e.printStackTrace();
  42. //      }
  43. //      System.out.println(file1.length());
  44. //文件 上传
  45. String fileName2 = "java6webservice.doc";
  46. File file2 = new File("E:/", fileName2);
  47. System.out.println(file2.length());
  48. StringBuffer sb = new StringBuffer();
  49. InputStream is;
  50. try
  51. {
  52. is = new FileInputStream(file2);
  53. ];
  54. ;
  55. , buff.length)))
  56. {
  57. , len));
  58. }
  59. is.close();
  60. }
  61. catch (FileNotFoundException e1)
  62. {
  63. e1.printStackTrace();
  64. }
  65. catch (IOException e)
  66. {
  67. e.printStackTrace();
  68. }
  69. String fileString = sb.toString();
  70. System.out.println(fileString.length());
  71. service.uploadFile(fileString, fileName2);
  72. }
  73. }

上面的测试类调用服务,我们只写了两行代码就搞定了是不是比前面两种方式简单多了。经本人测试这三种方式都能够成功的运行。但是我们从代码上可以分析出,这三种方式的文件传输本质上都是一样的。就是将文件编码为字符串的形式再传出去,再深入一点也就是将文件整个读入内存中然后一并发出去。如果文件太大就会报内存溢出的问题,并且对于字符串编码的转换也是非常耗时的。所以这种方式只能传一些小文件,大文件就不行了。下面我就要讲的是Xfire支持的一种优化的支持大数据二进制流方式传输文件的技术MTOM,在Xfire自带的例子中已有很详细的介绍了。

对于第二种大文件传输方式开发如下:

注意:用MTOM时建议大家用java ee6的库,因数java ee5库中有个mail包冲突错误。

1.第二种方式,主要针对大数据以二进制流进行文件的传输,主要支持byte[]、DataSource、DataHandler三种数据类型。这里我选用DataHandler进行讲解,同样我们要定义一个接口以及实现类,如下:

接口:

实现类:

  1. package org.file.service;
  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import javax.activation.DataHandler;
  7. import javax.activation.FileDataSource;
  8. public class MyFileImpl implements MyFile
  9. {
  10. @Override
  11. public String uploadFile(DataHandler dh, String fileName)
  12. {
  13. long startTime = System.currentTimeMillis();
  14. File file = new File("E://", fileName);
  15. try
  16. {
  17. file.createNewFile();
  18. InputStream in = dh.getInputStream();
  19. FileOutputStream out = new FileOutputStream(file);
  20. ];
  21. ;
  22. , buff.length)))
  23. {
  24. , len);
  25. }
  26. in.close();
  27. out.flush();
  28. out.close();
  29. }
  30. catch (IOException e)
  31. {
  32. e.printStackTrace();
  33. return "error";
  34. }
  35. long endTime = System.currentTimeMillis();
  36. + " s");
  37. return "success";
  38. }
  39. @Override
  40. public DataHandler downFile(String fileName)
  41. {
  42. File file = new File("F://", fileName);
  43. System.out.println(file.length());
  44. DataHandler dh = new DataHandler(new FileDataSource(file));
  45. return dh;
  46. }
  47. }

2.基本的配置方式与第一种方式差不多,但是要用到MTOM技术,所以我们要在services.xml中增加一个元素,如下

3.接下来在Tomcat上部署发布都与方式一是一样的,这里就不多说了。

4.好了这里重点讲的是如何开发客户端调用基于MTOM的应用。也许你会问是否也有三种方式,我想说官方的例子只给出了用代理方式,本人也经过大量测式另外两种方式都会出现一些莫名的问题。比如MTOM支持的数据类型,生成的客户端代码都会变成DataHandler类型。

所以,我推荐大家还是用代理方式进行客户端的开发,客户端必须提供与服务端一样的接口(即我们的服务暴露给客户端的接口),包名也最好与服务端的一样,不然会出现问题。这里的客户端写法也与第一种方式有些差别,代码如下

  1. package org.file.service;
  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.net.MalformedURLException;
  7. import javax.activation.DataHandler;
  8. import javax.activation.FileDataSource;
  9. import org.codehaus.xfire.client.Client;
  10. import org.codehaus.xfire.client.XFireProxyFactory;
  11. import org.codehaus.xfire.service.Service;
  12. import org.codehaus.xfire.service.binding.ObjectServiceFactory;
  13. import org.codehaus.xfire.transport.http.HttpTransport;
  14. public class MyTest
  15. {
  16. public static void main(String[] args) throws MalformedURLException
  17. {
  18. Service serviceModel = new ObjectServiceFactory().create(MyFile.class,
  19. "FileService", "http://service.file.org/FileService", null);
  20. MyFile service = (MyFile) new XFireProxyFactory().create(serviceModel,
  21. "http://localhost:8080/MyFileService/services/FileService");
  22. // 增加的代码,表示采用MTOM方式处理
  23. Client client = Client.getInstance(service);
  24. client.setProperty("mtom-enabled", "true");
  25. client.setProperty(HttpTransport.CHUNKING_ENABLED, "true");
  26. long startTime = System.currentTimeMillis();
  27. //文件上传
  28. String fileName = "ZGC_CD_2011V4.1.iso";
  29. File file = new File("F:/", fileName);
  30. DataHandler dh = new DataHandler(new FileDataSource(file));
  31. System.out.println(service.uploadFile(dh, fileName));
  32. //文件下载
  33. //      File file1 = new File("E://", fileName);
  34. //
  35. //      DataHandler dh = service.downFile(fileName);
  36. //
  37. //      try
  38. //      {
  39. //          file1.createNewFile();
  40. //          InputStream in = dh.getInputStream();
  41. //          FileOutputStream out = new FileOutputStream(file1);
  42. //          byte[] buff = new byte[1024 * 1024];
  43. //          int len = -1;
  44. //          while (-1 != (len = in.read(buff, 0, buff.length)))
  45. //          {
  46. //              out.write(buff, 0, len);
  47. //          }
  48. //          in.close();
  49. //          out.flush();
  50. //          out.close();
  51. //          System.out.println("down over");
  52. //      }
  53. //      catch (IOException e)
  54. //      {
  55. //          e.printStackTrace();
  56. //      }
  57. long endTime = System.currentTimeMillis();
  58. + " s");
  59. }
  60. }

上面的代码与第一种方式写法大同小异,只是多了三行代码表示客户端采用MTOM方式处理数据类型。

  1. Client client = Client.getInstance(service);
  2. client.setProperty("mtom-enabled", "true");
  3. client.setProperty(HttpTransport.CHUNKING_ENABLED, "true");

至此,客户端也就写完了,经测试1G的文件没有问题。

希望对大家有帮助。

采用web service传输超大数据的更多相关文章

  1. 使用Fiddler解析WCF RIA Service传输的数据

    原文 http://www.cnblogs.com/wintersun/archive/2011/01/05/1926386.html 使用Fiddler 2 解析WCF RIA Service传输的 ...

  2. iOS开发网络篇之Web Service和XML数据解析

    郝萌主倾心贡献,尊重作者的劳动成果,请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主.捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 游戏官方下 ...

  3. 通过Places API Web Service获取兴趣点数据

    实验将爬取新加坡地区的银行POI数据 数据库采用mongodb,请自行安装,同时申请google的key 直接上代码 #coding=utf-8 import urllib import json i ...

  4. asp.net项目下的web service返回json数据问题

    App_Code目录下放置WebService.cs文件,文件内容如: using System; using System.Collections.Generic; using System.Dat ...

  5. 构建安全的Xml Web Service系列之SSL篇

    原文:构建安全的Xml Web Service系列之SSL篇 首先介绍一下SSL, SSL 的英文全称是 "Secure Sockets Layer" ,中文名为 "安全 ...

  6. 几种通讯协议的比较RMI > Httpinvoker >= Hessian >> Burlap >> web service

    一.综述本文比较了RMI,Hessian,Burlap,Httpinvoker,web service等5种通讯协议的在不同的数据结构和不同数据量时的传输性能.RMI是java语言本身提供的远程通讯协 ...

  7. 转-Web Service中三种发送接受协议SOAP、http get、http post

    原文链接:web服务中三种发送接受协议SOAP/HTTP GET/HTTP POST 一.web服务中三种发送接受协议SOAP/HTTP GET/HTTP POST 在web服务中,有三种可供选择的发 ...

  8. 项目中使用WCF替换asmx Web service总结

    以前项目解决方案中,用http协议的asmx Web service作服务器数据访问入口,在SoapHeader中写入用户名和加盐密码进行身份认证. http asmx服务是明文传输,传输过程中数据很 ...

  9. 几种通讯协议的比较RMI > Httpinvoker >= Hessian >> Burlap >> web service (转)

    一.综述 本文比较了RMI,Hessian,Burlap,Httpinvoker,web service等5种通讯协议的在不同的数据结构和不同数据量时的传输性能.RMI是java语言本身提供的通讯协议 ...

随机推荐

  1. ServiceWorker pwa缓存

    index.js if ( navigator.serviceWorker ) { console.log("cache index") window.addEventListen ...

  2. pyqt5最简单操作数据库

    要先安一个包才能使用QtSql通过新立得安装 import PyQt5.QtSql as sql db=sql.QSqlDatabase.addDatabase('QMYSQL') db.setDat ...

  3. JQuery经典总结

    1.jQuery介绍 jQuery是一个js框架(其实就是一个.js文件),它的特点是使用选择器查找要操作的节点,并且将这些节点封装成一个jQuery对象.封装的目的是为了更好地兼容不同的浏览器之间的 ...

  4. Java多线程编程——生产者-消费者模式(1)

    生产者-消费者模式在生活中非常常见.就拿我们去餐馆吃饭为例.我们会遇到以下两种情况: 1.厨师-客人 如下图所示,生产者.消费者直接进行交互. 生产者生产出产品后,通知消费者:消费者消费后,通知生产者 ...

  5. 【数学】At Coder 091 D题

    [深夜题解] 题目链接:https://arc091.contest.atcoder.jp/tasks/arc091_b 题目大意:给出两个正整数N.K,找出所有的不大于N的正整数对(a,b)使b%a ...

  6. ZXing.Net.Mobile无法识别较大的条码

    ZXing.Net.Mobile无法识别较大的条码 在Xamarin项目中,使用ZXing.Net.Mobile实现条码扫描时,可能会出现无法顺利识别较大的条码,而可以正常识别较小的条码.这是由于ZX ...

  7. zstuoj 4245 KI的斐波那契

    KI的斐波那契 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 550  Solved: 208 Description KI十分喜欢美丽而优雅的斐波那 ...

  8. 【BZOJ 1019】 1019: [SHOI2008]汉诺塔 (DP?)

    1019: [SHOI2008]汉诺塔 Description 汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成.一开始n个盘子都摞在柱子A上,大的在下面,小的在上面,形成了一 ...

  9. ZOJ 3707 Calculate Prime S 数论

    思路:容易得到s[n]=s[n-1]+s[n-2],也就是fib数. 求第k小的fib质数的也就是第k个质数数-2,当k>2时. 在就是s[n]/x%m=s[n]%(x*m)/x. 代码如下: ...

  10. DP练习 巡逻

     国庆这天五一大道上人头攒动,这是因为大家都准备从五一广场上那个大屏幕观看新中国60周年的国庆阅兵式!这虽然是一件很喜庆的事情,可却让CS市的警察局长伤透了脑筋的,因为人潮拥挤是很容易发生安全事故的. ...