Chinaunix首页 | 论坛 | 博客
  • 博客访问: 49033
  • 博文数量: 17
  • 博客积分: 74
  • 博客等级: 民兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-07 23:31
文章分类
文章存档

2012年(12)

2011年(5)

我的朋友
最近访客

分类:

2012-02-17 07:22:33

第六章 基础知识
    这一章根据手册中描述的编程实例所需的编译、运行的相关步骤讨论了JXTA开发的相关开发环境。
  • 1.开始
    • 系统要求
    • 访问在线文档
    • 下载JXTA项目的二进制文件
    • 编译JXTA技术的代码
    • 运行JXTA技术的应用
    • 配置JXTA技术的相关环境
  • 2.创建各种ID类型
  • 3.创建新的广告信息
  • 4.创建一个信息和信息元素

开始

系统要求
    当前的JXSE实现需要5.0版或更高的JRE和SDK。JRE和JDK当前对于Solaris系统、Moicrosoft Windows、Linux、Mac OS X和其它部分操作系统都是可用的。你可以为这些系统从甲骨文的网站下载所对应的JDK版本。

访问在线文档
    你可以访问JXTA JXSE API的在线文档。(没找到……)
    
下载二进制文件
    从官方站点下载JXSE的配套编程手册(不知道是不是SUN被收购的原因,我没找到JXSE的手册项目,听了吗?可以在这找些JXSE的资源)。这个压缩文档中包含了教程的源码和二进制发布。
    你可以从网站下载JXTA的当前版本。这个站点提供了JXTA的几个版本:
  • 发布版——JXTA JXSE的经过测试的稳定版。这个版本是JXTA初学者的最好的选择。
  • 每日更新版——JXTA还在进行中的最新版。这个版本用于开发者的测试且并不保证运行的正确性。
    通常下载的JXTA的源码同样允许你自己编译。你可以从下载源代码,用来编译你自己的JXTA二进制文件。

编译JXTA代码
    例子中的HelloWorld程序在编译时需要jxta.jar文件包。当你运行Java编译程序(javac)时,你需要使用-classpath参数来指定jxta.jar文件的位置。你将需要提供jxta.jar文件在你系统上的具体位置。
    编译命令的例子(Windows systems):
    C:>javac -classpath .\lib\jxta.jar IDTutorial.java

运行JXTA程序
    当你输入java命令运行程序的时候,你需要使用-classpath参数指定所需的jar文件的位置。你将需要提供所需的.jar文件的具体位置。
    程序运行命令例子(Windows systems):
    C:>java -classpath .\lib\jxta.jar;.\lib\bcprov-jdk14.jar;. IDTutorial
  •     注意——你会发现通过创建一个脚本或批处理文件来运行你的程序是非常简单的。这就消除了你每次都是用非常长的命令来运行你的程序的需要。
    JXSE包含一个简单的用户界面的配置。多数情况下这种默认的配种会被使用编程性的平台配置替代,例如作为NetworkConfigurator类或通过使用NetworkManager类,需要进行的预先的概要配置:
  1.     Ad-Hoc:这种节点在点对点网络中是典型的部署。这个节点不会使用基础设施节点(集合节点或中继节点)。
  2. 边缘节点——除了支持Ad-Hoc的行为,一个边缘节点还会依附于一个基础设施节点(集合节点、中继节点或这两者)。
  3. 集合节点——支持网络初期引导服务,例如发现,管道解析等。
  4. 中继节点——提供信息中继服务,使其能穿越防火墙。
  5. 代理节点——为J2ME代理服务提供JXME JXTA。
  6. 超级节点——提供集合节点、中继节点、代理节点的所有功能。
    强烈推荐JXTA节点通过NetworkManager或NetworkConfigurator类的静态静态方法进行节点配置,被期望使用的方法例如newEdgeConfiguration(storeHome)。一旦配置被创建,它可能会符合程序的要求。
    NetworkConfigurator可以使用一个给定的URI载入配置的能力也是值得注意的。它的一个特点是为提供节点的PeerID和节点证书提供了系统的方法。例如,load("");

“公共JXTA网络”配置参数
    在一些例子中可能会用到JXTA开发网络,以用来支持初始示例程序和教程。它们并不打算在生产网络中被使用。这个配置是用来对运行在本地多子网、防火墙、NATs环境中的JXTA程序进行测试。
 
JXTA和HTTP代理
    为了能让JXTA节点穿过防火墙进行连接,JXTA支持使用HTTP代理。它们是被web浏览器所使用的代理相同的代理。一些代理被配置为需要认证。大多数情况下,Java程序在安装的时候JXTA会使用系统HTTP代理配置参数或者是使用提供的配置。你也可以通过Java控制台调整HTTP代理设置。

我需要代理吗?
    如果你的web浏览器需要使用HTTP代理,那么你的JXTA很可能也需要代理。大多数服务器都有一个包含代理信息的首选项页面。在一些自动浏览器的代理机制中,确定是否使用了代理是很困难的。在这种情况下,要问一问本地的专家或IT部员工。
    
在Java代码行中配置HTTP代理
    如果你通过直接运行Java来启动你的程序,你可以把HTTP代理参数作为java命令行的一部分。HTTP代理通过Java系统性能定义。例如:
    % java -Dhttp.proxyHost=proxy.mycompany.com Dhttp.proxyPort=8080...
    这个设定将会配置Java使用proxy.mycompany.com为代理服务器,端口为8080。你需要使用你从web服务器模仿的值。

JXTA和代理人证
    当HTTP需要认证的时候,以下Java系统性质需要被设定:
    % java -Djxta.proxy.user=usr -Djxta.proxy.password=pwd...
    配置信息被保存在安全信息文件中(用户名和密码),存储位置./.cache/example_name/PlatfornConfig;(./.cache/example_name/cm)。程序下次运行的时候,这些信息被用来配置你的节点。
  • 注意——指定一个默认配置信息存储位置的代替位置(比使用默认的./.jxta子目录更好),使用:
    java -DJXTA_HOME="alternate dir" ...

创建多种ID
    节点、节点组、管道和其它的JXTA资源需要被唯一的标识出来。一个JXTA ID唯一的标识了一个资源和服务。当前有六种JXTA实体定义了JXTA  ID:节点、节点组、管道、内容、模块类和模块规范。URNs被用来描述JXTA IDs。URNs是一种URI的格式“... are intended to serve as persistent, location-independent, resource identifiers”。就像是其它的URI结构,JXTA使用字符串来提供。
    一个JXTA节点ID的例子:
    urn:jxta:uuid-59616261646162614A78746150325033F3BC76FF13C2414CBC0AB663666DA53903
    一个JXTA管道的例子:
    urn:jxta:uuid-59616261646162614E504720503250338E3E786229EA460DADC1A176B69B731504
    每一个JXTA ID都有一个ID格式。这个格式描述了ID怎样被创建和怎样被程序操作。(后边这一段与前边重复了,怎么这么罗嗦……)
    下边的例子阐述了各种类型的JXTA表示是怎么构建的:
ID教程源码:
  1. package tutorial.id;
  2. import net.jxta.id.IDFactory;
  3. import net.jxta.peer.PeerID;
  4. import net.jxta.peergroup.PeerGroupID;
  5. import net.jxta.pipe.PipeID;
  6. import java.io.UnsupportedEncodingException;
  7. import java.security.MessageDigest;
  8. import java.security.NoSuchAlgorithmException;
  9. import java.text.MessageFormat;
  10. /**
  11.  * A simple and re-usable example of creating various JXTA IDs
  12.  *


  13.  * This is a two part tutorial :
  14.  *

    1.  *
    2. Illustrates the creation of predictable ID's based upon the hash of a
    3.  * provided string. This method provides an independent and deterministic
    4.  * method for generating IDs. However using this method does require care
    5.  * in the choice of the seed expression in order to prevent ID collisions
    6.  * (duplicate IDs).

    7.  *


    8.  *
    9. New random ID's encoded with a specific GroupID.

    10.  *

  15.  */
  16. public class IDTutorial {
  17.     private static final String SEED = "IDTuorial";
  18.     /**
  19.      * Returns a SHA1 hash of string.
  20.      *
  21.      * @param expression to hash
  22.      * @return a SHA1 hash of string or {@code null} if the expression could
  23.      * not be hashed.
  24.      */
  25.     private static byte[] hash(final String expression) {
  26.         byte[] result;
  27.         MessageDigest digest;
  28.         if (expression == null) {
  29.             throw new IllegalArgumentException("Invalid null expression");
  30.         }
  31.         try {
  32.             digest = MessageDigest.getInstance("SHA1");
  33.         } catch (NoSuchAlgorithmException failed) {
  34.             failed.printStackTrace(System.err);
  35.             RuntimeException failure = new IllegalStateException("Could not get
  36. SHA-1 Message");
  37.             failure.initCause(failed);
  38.             throw failure;
  39.         }
  40.         try {
  41.  byte[] expressionBytes = expression.getBytes("UTF-8");
  42.             result = digest.digest(expressionBytes);
  43.         } catch (UnsupportedEncodingException impossible) {
  44.             RuntimeException failure =
  45.                   new IllegalStateException("Could not encode expression as UTF8");
  46.             failure.initCause(impossible);
  47.             throw failure;
  48.         }
  49.         return result;
  50.     }
  51.     /**
  52.      * Given a pipe name, it returns a PipeID who's value is chosen based upon that name.
  53.      *
  54.      * @param pipeName instance name
  55.      * @param pgID the group ID encoding
  56.      * @return The pipeID value
  57.      */
  58.     public static PipeID createPipeID(PeerGroupID pgID, String pipeName) {
  59.         String seed = pipeName + SEED;
  60.         return IDFactory.newPipeID(pgID, hash(seed.toLowerCase()));
  61.     }
  62.     /**
  63.      * Creates group encoded random PipeID.
  64.      *
  65.      * @param pgID the group ID encoding
  66.      * @return The pipeID value
  67.      */
  68.     public static PipeID createNewPipeID(PeerGroupID pgID) {
  69.         return IDFactory.newPipeID(pgID);
  70.     }
  71.     /**
  72.      * Creates group encoded random PeerID.
  73.      *
  74.      * @param pgID the group ID encoding
  75.      * @return The PeerID value
  76.      */
  77.     public static PeerID createNewPeerID(PeerGroupID pgID) {
  78.         return IDFactory.newPeerID(pgID);
  79.     }
  80.     /**
  81.      * Given a peer name generates a Peer ID who's value is chosen based upon that name.
  82.      *
  83.      * @param peerName instance name
  84.      * @param pgID the group ID encoding
  85.      * @return The PeerID value
  86.      */
  87.     public static PeerID createPeerID(PeerGroupID pgID, String peerName) {
  88.         String seed = peerName + SEED;
  89.         return IDFactory.newPeerID(pgID, hash(seed.toLowerCase()));
  90.     }
  91.     /**
  92.      * Creates group encoded random PeerGroupID.
  93. *
  94.      * @param pgID the group ID encoding
  95.      * @return The PeerGroupID value
  96.      */
  97.     public static PeerGroupID createNewPeerGroupID(PeerGroupID pgID) {
  98.         return IDFactory.newPeerGroupID(pgID);
  99.     }
  100.     /**
  101.      * Given a group name generates a Peer Group ID who's value is chosen based
  102.      * upon that name.
  103.      *
  104.      * @param groupName group name encoding value
  105.      * @return The PeerGroupID value
  106.      */
  107.     public static PeerGroupID createPeerGroupID(final String groupName) {
  108.         //Use lower case to avoid any locale conversion inconsistencies
  109.         return IDFactory.newPeerGroupID(PeerGroupID.defaultNetPeerGroupID,
  110.                                              hash(SEED + groupName.toLowerCase()));
  111.     }
  112.     /**
  113.      * Contructs and returns an string encoded Infrastructure PeerGroupID.
  114.      *
  115.      * @param groupName the string encoding
  116.      * @return The infraPeerGroupID PeerGroupID
  117.      */
  118.     public static PeerGroupID createInfraPeerGroupID(String groupName) {
  119.         return createPeerGroupID(groupName);
  120.     }
  121.     /**
  122.      * Main method
  123.      *
  124.      * @param args command line arguments. None defined
  125.      */
  126.     public static void main(String args[]) {
  127.         PeerGroupID infra = createInfraPeerGroupID("infra");
  128.         PeerID peerID = createPeerID(infra, "peer");
  129.         PipeID pipeID = createPipeID(PeerGroupID.defaultNetPeerGroupID, "pipe");
  130.         System.out.println(MessageFormat.format(
  131.                        "\n\nAn infrastucture PeerGroupID: {0}", infra.toString()));
  132.         System.out.println(MessageFormat.format(
  133.                "PeerID with the above infra ID encoding: {0}", peerID.toString()));
  134.         System.out.println(MessageFormat.format("PipeID with the default
  135. defaultNetPeerGroupID encoding: {0}", pipeID.toString()));
  136.         peerID = createNewPeerID(PeerGroupID.defaultNetPeerGroupID);
  137.         pipeID = createNewPipeID(PeerGroupID.defaultNetPeerGroupID);
  138.         PeerGroupID pgid = createNewPeerGroupID(PeerGroupID.defaultNetPeerGroupID);
  139.         System.out.println(
  140.           MessageFormat.format("\n\nNew PeerID created : {0}", peerID.toString()));
  141.         System.out.println(
  142.               MessageFormat.format("New PipeID created : {0}", pipeID.toString()));
  143.         System.out.println(
  144. MessageFormat.format("New PeerGroupID created : {0}", pgid.toString()));
  145.     }
  146. }

广告
    广告是JXTA项目的核心内容,它用来广告节点、节点组、服务、管道和其它的JXTA资源。广告为JXTA资源提供了一种平***立地表示,使得两个不同的平台实现之间可以交换信息(Java、C等)。
    每一个广告都带有一个可以描述此广告的文档。广告通常被描述为XML文档。方法Advertisement.getDocument(MimeMediaType)用来生成广告的描述。不同的描述通过mime类型的选择后是有效的。典型的mime类型是“text/xml”或“text/plain”,它们能够生成广告的文本表示。使用AdvertisementFactory构造广告实例比使用Advertisement类构造广告实例更常用。所有的在net.jxta.protocol中定义的公共广告都是抽象类。对于每一个抽象公共广告,它们都有一个包含文档创建和解析方法的私有实现。私有的实现使用AdvertisementFactory注册。新的广告类型必须用下边阐述所示的方式进行注册或者是使用META-INF/services/net.jxta.document.Advertisement扩展。
    下边的实例阐述了新的广告类型的创建方法,还有通过广告工厂对其进行注册的方法:
     AdvertisementFactory.registerAdvertisementInstance(
      AdvertisementTutorial.getAdvertisementType(),new AdvertisementTutorial.Instantiator());

广告教程代码
  1. package tutorial.advertisement;
  2. import net.jxta.document.*;
  3. import net.jxta.id.ID;
  4. import net.jxta.id.IDFactory;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.Serializable;
  8. import java.net.InetAddress;
  9. import java.net.URI;
  10. import java.net.URISyntaxException;
  11. import java.net.UnknownHostException;
  12. import java.util.Enumeration;
  13. import java.util.logging.Logger;
  14. /**
  15.  * Simple Advertisement Tutorial creates a advertisment describing a system
  16.  *


  17.  *

  18.  * <?xml version="1.0"?>
  19.  * <!DOCTYPE jxta:System>
  20.  * <jxta:System xmlns:jxta="">
  21.  * <id>id</id>
  22.  * <name>Device Name</name>
  23.  * <ip>ip address</ip>
  24.  * <hwarch>x86</hwarch>
  25.  * <hwvendor>Sun MicroSystems</hwvendor>
  26.  * <OSName></OSName>
  27.  * <OSVer></OSVer>
  28.  * <osarch></osarch>
  29.  * <sw></sw>
  30.  * </jxta:System>
  31.  *
  32.  */
  33. public class AdvertisementTutorial extends Advertisement
  34.         implements Comparable, Cloneable, Serializable {
  35.     private String hwarch;
  36.     private String hwvendor;
  37.     private ID id = ID.nullID;
  38.     private String ip;
  39.     private String name;
  40.     private String osname;
  41.     private String osversion;
  42.     private String osarch;
  43.     private String inventory;
  44.     private final static Logger LOG =
  45.             Logger.getLogger(AdvertisementTutorial.class.getName());
  46.     private final static String OSNameTag = "OSName";
  47.     private final static String OSVersionTag = "OSVer";
  48.     private final static String OSarchTag = "osarch";
  49.     private final static String hwarchTag = "hwarch";
  50.     private final static String hwvendorTag = "hwvendor";
  51.     private final static String idTag = "ID";
  52.  private final static String ipTag = "ip";
  53.     private final static String nameTag = "name";
  54.     private final static String swTag = "sw";
  55.     /**
  56.      * Indexable fields. Advertisements must define the indexables, in order
  57.      * to properly index and retrieve these advertisements locally and on the
  58.      * network
  59.      */
  60.     private final static String[] fields = {idTag, nameTag, hwarchTag};
  61.     /**
  62.      * Default Constructor
  63.      */
  64.     public AdvertisementTutorial() {
  65.     }
  66.     /**
  67.      * Construct from a StructuredDocument
  68.      *
  69.      * @param root Root element
  70.      */
  71.     public AdvertisementTutorial(Element root) {
  72.         TextElement doc = (TextElement) root;
  73.         if (!getAdvertisementType().equals(doc.getName())) {
  74.             throw new IllegalArgumentException("Could not construct : " +
  75.                     getClass().getName() + "from doc containing a " +
  76. doc.getName());
  77.         }
  78.         initialize(doc);
  79.     }
  80.     /**
  81.      * Construct a doc from InputStream
  82.      *
  83.      * @param stream the underlying input stream.
  84.      * @throws IOException if an I/O error occurs.
  85.      */
  86.     public AdvertisementTutorial(InputStream stream) throws IOException {
  87.         StructuredTextDocument doc = (StructuredTextDocument)
  88.                 StructuredDocumentFactory
  89.                         .newStructuredDocument(MimeMediaType.XMLUTF8, stream);
  90.         initialize(doc);
  91.     }
  92.     /**
  93.      * Sets the hWArch attribute of the AdvertisementTutorial object
  94.      *
  95.      * @param hwarch The new hWArch value
  96.      */
  97.     public void setHWArch(String hwarch) {
  98.         this.hwarch = hwarch;
  99.     }
  100.     /**
  101.      * Sets the OSArch attribute of the AdvertisementTutorial object
  102. *
  103.      * @param osarch The new hWArch value
  104.      */
  105.     public void setOSArch(String osarch) {
  106.         this.osarch = osarch;
  107.     }
  108.     /**
  109.      * Sets the hWVendor attribute of the AdvertisementTutorial object
  110.      *
  111.      * @param hwvendor The new hWVendor value
  112.      */
  113.     public void setHWVendor(String hwvendor) {
  114.         this.hwvendor = hwvendor;
  115.     }
  116.     /**
  117.      * sets the unique id
  118.      *
  119.      * @param id The id
  120.      */
  121.     public void setID(ID id) {
  122.         this.id = (id == null ? null : id);
  123.     }
  124.     /**
  125.      * Sets the iP attribute of the AdvertisementTutorial object
  126.      *
  127.      * @param ip The new iP value
  128.      */
  129.     public void setIP(String ip) {
  130.         this.ip = ip;
  131.     }
  132.     /**
  133.      * Sets the name attribute of the AdvertisementTutorial object
  134.      *
  135.      * @param name The new name value
  136.      */
  137.     public void setName(String name) {
  138.         this.name = name;
  139.     }
  140.     /**
  141.      * Sets the oSName attribute of the AdvertisementTutorial object
  142.      *
  143.      * @param osname The new oSName value
  144.      */
  145.     public void setOSName(String osname) {
  146.         this.osname = osname;
  147.     }
  148.     /**
  149.      * Sets the oSVersion attribute of the AdvertisementTutorial object
  150.      *
  151.      * @param osversion The new oSVersion value
  152.      */
  153.  public void setOSVersion(String osversion) {
  154.         this.osversion = osversion;
  155.     }
  156.     /**
  157.      * Sets the SWInventory attribute of the AdvertisementTutorial object
  158.      *
  159.      * @param inventory the software inventory of the system
  160.      */
  161.     public void setSWInventory(String inventory) {
  162.         this.inventory = inventory;
  163.     }
  164.     /**
  165.      * {@inheritDoc}
  166.      *
  167.      * @param asMimeType Document encoding
  168.      * @return The document value
  169.      */
  170.     @Override
  171.     public Document getDocument(MimeMediaType asMimeType) {
  172.         StructuredDocument adv =
  173. StructuredDocumentFactory.newStructuredDocument(asMimeType,
  174.                 getAdvertisementType());
  175.         if (adv instanceof Attributable) {
  176.             ((Attributable) adv).addAttribute("xmlns:jxta", "");
  177.         }
  178.         Element e;
  179.         e = adv.createElement(idTag, getID().toString());
  180.         adv.appendChild(e);
  181.         e = adv.createElement(nameTag, getName().trim());
  182.         adv.appendChild(e);
  183.         e = adv.createElement(OSNameTag, getOSName().trim());
  184.         adv.appendChild(e);
  185.         e = adv.createElement(OSVersionTag, getOSVersion().trim());
  186.         adv.appendChild(e);
  187.         e = adv.createElement(OSarchTag, getOSArch().trim());
  188.         adv.appendChild(e);
  189.         e = adv.createElement(ipTag, getIP().trim());
  190.         adv.appendChild(e);
  191.         e = adv.createElement(hwarchTag, getHWArch().trim());
  192.         adv.appendChild(e);
  193.         e = adv.createElement(hwvendorTag, getHWVendor().trim());
  194.         adv.appendChild(e);
  195.         e = adv.createElement(swTag, getSWInventory().trim());
  196.         adv.appendChild(e);
  197.         return adv;
  198.     }
  199.     /**
  200.      * Gets the hWArch attribute of the AdvertisementTutorial object
  201.      *
  202.      * @return The hWArch value
  203.      */
  204.     public String getHWArch() {
  205.         return hwarch;
  206.     }
  207.  /**
  208.      * Gets the OSArch attribute of the AdvertisementTutorial object
  209.      *
  210.      * @return The OSArch value
  211.      */
  212.     public String getOSArch() {
  213.         return osarch;
  214.     }
  215.     /**
  216.      * Gets the hWVendor attribute of the AdvertisementTutorial object
  217.      *
  218.      * @return The hWVendor value
  219.      */
  220.     public String getHWVendor() {
  221.         return hwvendor;
  222.     }
  223.     /**
  224.      * returns the id of the device
  225.      *
  226.      * @return ID the device id
  227.      */
  228.     @Override
  229.     public ID getID() {
  230.         return (id == null ? null : id);
  231.     }
  232.     /**
  233.      * Gets the IP attribute of the AdvertisementTutorial object
  234.      *
  235.      * @return The IP value
  236.      */
  237.     public String getIP() {
  238.         return ip;
  239.     }
  240.     /**
  241.      * Gets the name attribute of the AdvertisementTutorial object
  242.      *
  243.      * @return The name value
  244.      */
  245.     public String getName() {
  246.         return name;
  247.     }
  248.     /**
  249.      * Gets the OSName attribute of the AdvertisementTutorial object
  250.      *
  251.      * @return The OSName value
  252.      */
  253.     public String getOSName() {
  254.         return osname;
  255.     }
  256.     /**
  257. * Gets the Software Inventory text element
  258.      *
  259.      * @return The Inventory value
  260.      */
  261.     public String getSWInventory() {
  262.         if (inventory == null) {
  263.             inventory = "";
  264.         }
  265.         return inventory;
  266.     }
  267.     /**
  268.      * Gets the OSVersion attribute of the AdvertisementTutorial object
  269.      *
  270.      * @return The OSVersion value
  271.      */
  272.     public String getOSVersion() {
  273.         return osversion;
  274.     }
  275.     /**
  276.      * Process an individual element from the document.
  277.      *
  278.      * @param elem the element to be processed.
  279.      * @return true if the element was recognized, otherwise false.
  280.      */
  281.     protected boolean handleElement(TextElement elem) {
  282.         if (elem.getName().equals(idTag)) {
  283.             try {
  284.                 URI id = new URI(elem.getTextValue());
  285.                 setID(IDFactory.fromURI(id));
  286.             } catch (URISyntaxException badID) {
  287.                 throw new IllegalArgumentException("unknown ID format in
  288. advertisement: " +
  289.                         elem.getTextValue());
  290.             }
  291.             catch (ClassCastException badID) {
  292.                 throw new IllegalArgumentException("Id is not a known id type: " +
  293.                         elem.getTextValue());
  294.             }
  295.             return true;
  296.         }
  297.         if (elem.getName().equals(nameTag)) {
  298.             setName(elem.getTextValue());
  299.             return true;
  300.         }
  301.         if (elem.getName().equals(OSNameTag)) {
  302.             setOSName(elem.getTextValue());
  303.             return true;
  304.         }
  305.         if (elem.getName().equals(OSVersionTag)) {
  306.             setOSVersion(elem.getTextValue());
  307.             return true;
  308.         }
  309.         if (elem.getName().equals(OSarchTag)) {
  310.             setOSArch(elem.getTextValue());
  311.             return true;
  312. }
  313.         if (elem.getName().equals(ipTag)) {
  314.             setIP(elem.getTextValue());
  315.             return true;
  316.         }
  317.         if (elem.getName().equals(hwarchTag)) {
  318.             setHWArch(elem.getTextValue());
  319.             return true;
  320.         }
  321.         if (elem.getName().equals(hwvendorTag)) {
  322.             setHWVendor(elem.getTextValue());
  323.             return true;
  324.         }
  325.         if (elem.getName().equals(swTag)) {
  326.             setSWInventory(elem.getTextValue());
  327.             return true;
  328.         }
  329.         // element was not handled
  330.         return false;
  331.     }
  332.     /**
  333.      * Intialize a System advertisement from a portion of a structured document.
  334.      *
  335.      * @param root document root
  336.      */
  337.     protected void initialize(Element root) {
  338.         if (!TextElement.class.isInstance(root)) {
  339.             throw new IllegalArgumentException(getClass().getName() +
  340.                     " only supports TextElement");
  341.         }
  342.         TextElement doc = (TextElement) root;
  343.         if (!doc.getName().equals(getAdvertisementType())) {
  344.             throw new IllegalArgumentException("Could not construct : "
  345.                     + getClass().getName() + "from doc containing a " +
  346.                     doc.getName());
  347.         }
  348.         Enumeration elements = doc.getChildren();
  349.         while (elements.hasMoreElements()) {
  350.             TextElement elem = (TextElement) elements.nextElement();
  351.             if (!handleElement(elem)) {
  352.                 LOG.warning("Unhandleded element \'" + elem.getName() + "\' in " +
  353.                         doc.getName());
  354.             }
  355.         }
  356.     }
  357.     /**
  358.      * {@inheritDoc}
  359.      */
  360.     @Override
  361.     public final String[] getIndexFields() {
  362.         return fields;
  363.     }
  364.     /**
  365.      * {@inheritDoc}
  366.  */
  367.     @Override
  368.     public boolean equals(Object obj) {
  369.         if (this == obj) {
  370.             return true;
  371.         }
  372.         if (obj instanceof AdvertisementTutorial) {
  373.             AdvertisementTutorial adv = (AdvertisementTutorial) obj;
  374.             return getID().equals(adv.getID());
  375.         }
  376.         return false;
  377.     }
  378.     /**
  379.      * {@inheritDoc}
  380.      */
  381.     public int compareTo(Object other) {
  382.         return getID().toString().compareTo(other.toString());
  383.     }
  384.     /**
  385.      * All messages have a type (in xml this is !doctype) which
  386.      * identifies the message
  387.      *
  388.      * @return String "jxta:AdvertisementTutorial"
  389.      */
  390.     public static String getAdvertisementType() {
  391.         return "jxta:AdvertisementTutorial";
  392.     }
  393.     /**
  394.      * Instantiator
  395.      */
  396.     public static class Instantiator implements AdvertisementFactory.Instantiator {
  397.         /**
  398.          * Returns the identifying type of this Advertisement.
  399.          *
  400.          * @return String the type of advertisement
  401.          */
  402.         public String getAdvertisementType() {
  403.             return AdvertisementTutorial.getAdvertisementType();
  404.         }
  405.         /**
  406.          * Constructs an instance of Advertisement matching the
  407.          * type specified by the advertisementType parameter.
  408.          *
  409.          * @return The instance of Advertisement or null if it
  410.          * could not be created.
  411.          */
  412.         public Advertisement newInstance() {
  413.             return new AdvertisementTutorial();
  414.         }
  415.         /**
  416.          * Constructs an instance of Advertisement matching the
  417. * type specified by the advertisementType parameter.
  418.          *
  419.          * @param root Specifies a portion of a StructuredDocument which will
  420.          * be converted into an Advertisement.
  421.          * @return The instance of Advertisement or null if it
  422.          * could not be created.
  423.          */
  424.         public Advertisement newInstance(net.jxta.document.Element root) {
  425.             return new AdvertisementTutorial(root);
  426.         }
  427.     }
  428.     /**
  429.      * Main method
  430.      *
  431.      * @param args command line arguments. None defined
  432.      */
  433.     public static void main(String args[]) {
  434.         // The following step is required and only need to be done once,
  435.         // without this step the AdvertisementFactory has no means of
  436.         // associating an advertisement name space with the proper obect
  437.         // in this cast the AdvertisementTutorial
  438.         AdvertisementFactory.registerAdvertisementInstance(
  439.                 AdvertisementTutorial.getAdvertisementType(),
  440.                 new AdvertisementTutorial.Instantiator());
  441.         AdvertisementTutorial advTutorial = new AdvertisementTutorial();
  442.         advTutorial.setID(ID.nullID);
  443.         advTutorial.setName("AdvertisementTutorial");
  444.         try {
  445.             advTutorial.setIP(InetAddress.getLocalHost().getHostAddress());
  446.         } catch (UnknownHostException ignored) {
  447.             //ignored
  448.         }
  449.         advTutorial.setOSName(System.getProperty("os.name"));
  450.         advTutorial.setOSVersion(System.getProperty("os.version"));
  451.         advTutorial.setOSArch(System.getProperty("os.arch"));
  452.         advTutorial.setHWArch(System.getProperty("HOSTTYPE",
  453.                 System.getProperty("os.arch")));
  454.         advTutorial.setHWVendor(System.getProperty("java.vm.vendor"));
  455.         System.out.println(advTutorial.toString());
  456.     }
  457. }

信息和信息元素
    一个JXTA信息是一个协议信息和内容的容器。每一个信息都由一系列的信息元素组成。信息元素包含信息的协议信息和内容。每一个元素都包含信息的一部分。元素的目标和内容是由信息协议定义所定义的。按照信息协议,信息元素可以包含所需的多种类型的数据。
    JXTA信息包含一个含有0个或多个信息元素的有序列表。相同的信息元素可能会随时出现在信息中。信息中的每一个信息元素都与一个名字空间相联系。名字空间允许在一个信息中使用多个协议层,而避免了它们信息元素的冲突。例如,所有的JXTA核心协议都把它们的信息存储到保留的名字空间‘jxta’中。默认的命名空间对程序和服务来说是保留的。应用总会为它们的信息元素定义自己的命名空间,但是要避免使用‘jxta’和在模块规则ID中定义的命名空间,除了它们自己的MSID。
    一个JXTA信息元素可以用来存储多种类型的数据。JXTA JSE API支持多种信息元素,而附加的信息类型可能被定义。所提供的元素类允许你从一个字符串、字节数组、文档、输入流等创建信息元素。为了将数据作为信息的一部分传输,则元素的数据在发送之前必须被分成原始的字节流。有多种信息元素的“调料”用来方便而有效地进行原始比特的转换。所有的元素类只是将元素数据转换为二进制数据的简单代理。
    在使用信息元素的开发中,你不用在创建之后考虑信息元素的原始类型。比起StringMessageElement elem = new StringMessageElement("foo","bar",null);我们更常使用MessageElement elem = new StringMessageElement("foo","bar",null);。通过使用MessageElement类加强每一个信息元素的通用性——所有的数据都以字节的形式发送和接受。每一个信息元素都分为四部分:
  1. 一个可选的名字可以是任何字符串。没有命名的元素假定有“”的名字(空字符串)。
  2. 一个可选的MIME媒体类型。如果没有指定类型,MIME媒体类型会被假设为“Applacation/Octet-Stream”,二进制数据的默认类型。
  3. 信息元素的内容,一个字节序列。为了能将信息元素的内容作为信息的一部分进行传输,信息元素必须在发送前被分成原始字节。信息元素可以从各种各样的数据类型转化而来,其中有字符串、字节数组、文档、二进制数据等。在各种情况下,不管数据的数据源,结果信息元素都是一个数组队列的容器。所有信息元素都会以原始比特的方式进行发送和接受。 有多种信息元素的“调料”用来方便而有效地进行原始比特的转换。
  4. 一个可选的签名:每个信息元素都可以与一个额外的信息元素相联系,这个元素中可能包含经过加密的这个元素的签名或哈希值。
为JXTA信息设计节点
  • 每一个信息元素你大约会加入50个字节的传输开销。如果可能,避免协议设计使用太多的小元素。
  • 命名空间只与将信息元素作为元素加入一个信息有联系。命名空间在信息中动态的与信息元素相关联,因为对于元素来说适当的命名空间往往是基于上下文发生改变的。
  • 有四种简单的方法让程序和服务使用信息命名空间:
    • 只是用默认的命名空间。因为这个命名空间是为要创建信息的程序和服务保留的,所以它是最容易,也是最通常的选择。
    • 使用一个习惯性的硬编码命名空间,如“myApplication”。这种方法在JXTA程序中是很常用的,它使得程序可以按照需求定义尽可能多的命名空间。尽管不太可能,当使用硬编码命名空间是有一个小问题,就是两个单独的编码之间可能会有冲突。如果方便的话,程序和服务需要使用它们的MSID代替它们的命名空间。
    • 它们的MSID是它们常用的命名空间。基于MSID的命名空间是为那些实现了某些模块规范的程序和服务而预定义的。例如,标准JXTA集合节点服务的MSID是urn:jxta:uuid-deadbeefdeafbabafeedbabe000000060106.则命名空间 urn:jxta:uuid-deadbeefdeafbabafeedbabe000000060106.可能只会被实现了相应规范的JXTA集合节点所使用。
    • 使用一个唯一的随即命名空间。MessageElement类为创建的基于UUIDs的唯一的随即名字提供了工具。这些随机的名字可以用于程序和服务的单一信息或序列信息。程序也可以产生它们自己的随即命名空间的名字,只要所选的名字是唯一的。通常这意味着要使用UUIDs或安全信息摘要(SHA1、MD5等)作为命名空间的名字。
  • 信息可以被重用或非常方便的克隆。在许多程序中,在信息发送前创建一个预定义的、可被克隆的信息模板比每次都直接创建一个新的信息要更方便。
  • 来自一个单一线程的信息并不是同步的,而且只能可靠的使用。这在信息发送的时候经常被套用。直到信息发送成功它都不会被其它程序使用。

为信息元素设计节点
  • 信息元素是不变的,同时它作为信息的一部分被重复使用。这个特点常常用在不变的值,例如地址数据,或者是缓存的信息,例如节点广告。
  • 包含在MessageElement中的数据有四种方法访问:
    • 作为一个 java.io.InputStream
    • 发数据发给一个 java.io.OutputStream
    • 作为一个 java.lang.String
    • 作为一个字节数组

信息和信息元素教程源码
  1. package tutorial.message;
  2. import net.jxta.document.AdvertisementFactory;
  3. import net.jxta.document.MimeMediaType;
  4. import net.jxta.document.StructuredDocumentFactory;
  5. import net.jxta.document.XMLDocument;
  6. import net.jxta.endpoint.ByteArrayMessageElement;
  7. import net.jxta.endpoint.Message;
  8. import net.jxta.endpoint.Message.ElementIterator;
  9. import net.jxta.endpoint.MessageElement;
  10. import net.jxta.endpoint.StringMessageElement;
  11. import net.jxta.endpoint.TextDocumentMessageElement;
  12. import net.jxta.endpoint.WireFormatMessage;
  13. import net.jxta.endpoint.WireFormatMessageFactory;
  14. import net.jxta.id.IDFactory;
  15. import net.jxta.peergroup.PeerGroupID;
  16. import net.jxta.pipe.PipeService;
  17. import net.jxta.protocol.PipeAdvertisement;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.ObjectInputStream;
  23. import java.io.ObjectOutputStream;
  24. import java.io.RandomAccessFile;
  25. import java.util.zip.GZIPInputStream;
  26. import java.util.zip.GZIPOutputStream;
  27. /**
  28. * A simple and re-usable example of manipulating JXATA Messages. Included in
  29. * this tutorial are:
  30. *

  31. *
    • *
    • Adding and reading {@code String}, {@code int} and {@code long} with Message elements
    • *
    • Adding and reading Java {@code Object} with Message Elements.
    • *
    • Adding and reading byte arrays with Message Elements.
    • *
    • Adding and reading JXTA Advertisements with Message Elements.
    • *
    • Compressing message element content with gzip.
    • *
  32. */
  33. public class MessageTutorial {
  34. private final static MimeMediaType GZIP_MEDIA_TYPE =
  35. new MimeMediaType("application/gzip").intern();
  36. /**
  37. * Adds a String to a Message as a StringMessageElement
  38. *
  39. * @param message The message to add to
  40. * @param nameSpace The namespace of the element to add. a null value assumes
  41. default namespace.
  42. * @param elemName Name of the Element.
  43. * @param string The string to add
  44. */
  45. public static void addStringToMessage(Message message,
  46. String nameSpace, String elemName, String string) {
  47. message.addMessageElement(nameSpace,
  48. new StringMessageElement(elemName,
  49. string,
  50. null));
  51. }
  52. /**
  53. * Adds a long to a message
  54. *
  55. * @param message The message to add to
  56. * @param nameSpace The namespace of the element to add. a null value assumes
  57. default namespace.
  58. * @param elemName Name of the Element.
  59. * @param data The feature to be added to the LongToMessage attribute
  60. */
  61. public static void addLongToMessage(Message message,
  62. String nameSpace, String elemName, long data) {
  63. message.addMessageElement(nameSpace,
  64. new StringMessageElement(elemName,
  65. Long.toString(data),
  66. null));
  67. }
  68. /**
  69. * Adds a int to a message
  70. *
  71. * @param message The message to add to
  72. * @param nameSpace The namespace of the element to add. a null value assumes
  73. default namespace.
  74. * @param elemName Name of the Element.
  75. * @param data The feature to be added to the IntegerToMessage attribute
  76. */
  77. public static void addIntegerToMessage(Message message, String nameSpace,
  78. String elemName, int data) {
  79. message.addMessageElement(nameSpace,
  80. new StringMessageElement(elemName,
  81. Integer.toString(data),
  82. null));
  83. }
  84. /**
  85. * Adds an byte array to a message
  86. *
  87. * @param message The message to add to
  88. * @param nameSpace The namespace of the element to add. a null value assumes
  89. default namespace.
  90. * @param elemName Name of the Element.
  91. * @param data the byte array
  92. * @param compress indicates whether to use GZIP compression
  93. * @throws IOException if an io error occurs
  94. */
  95. public static void addByteArrayToMessage(Message message, String nameSpace,
  96. String elemName, byte[] data, boolean compress) throws IOException {
  97. byte[] buffer = data;
  98. MimeMediaType mimeType = MimeMediaType.AOS;
  99. if (compress) {
  100. ByteArrayOutputStream outStream = new ByteArrayOutputStream();
  101. GZIPOutputStream gos = new GZIPOutputStream(outStream);
  102. gos.write(data, 0, data.length);
  103. gos.finish();
  104. gos.close();
  105. buffer = outStream.toByteArray();
  106. mimeType = GZIP_MEDIA_TYPE;
  107. }
  108. message.addMessageElement(nameSpace,
  109. new ByteArrayMessageElement(elemName,
  110. mimeType,
  111. buffer,
  112. null));
  113. }
  114. /**
  115. * Adds an Object to message within the specified name space and with the
  116. specified element name
  117. * @param message the message to add the object to
  118. * @param nameSpace the name space to add the object under
  119. * @param elemName the given element name
  120. * @param object the object
  121. * @throws IOException if an io error occurs
  122. */
  123. public static void addObjectToMessage(Message message, String nameSpace,
  124. String elemName, Object object) throws IOException {
  125. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  126. ObjectOutputStream oos = new ObjectOutputStream(bos);
  127. oos.writeObject(object);
  128. oos.close();
  129. bos.close();
  130. addByteArrayToMessage(message, nameSpace, elemName, bos.toByteArray(), false);
  131. }
  132. /**
  133. * Returns a String from a message
  134. *
  135. * @param message The message to retrieve from
  136. * @param nameSpace The namespace of the element to get.
  137. * @param elemName Name of the Element.
  138. * @return The string value or {@code null} if there was no element matching
  139. the specified name.
  140. */
  141. public static String getStringFromMessage(Message message,
  142. String nameSpace, String elemName) {
  143. MessageElement me = message.getMessageElement(nameSpace, elemName);
  144. if (null != me) {
  145. return me.toString();
  146. } else {
  147. return null;
  148. }
  149. }
  150. /**
  151. * Returns an long from a message
  152. *
  153. * @param message The message to retrieve from
  154. * @param nameSpace The namespace of the element to get.
  155. * @param elemName Name of the Element.
  156. * @return The long value
  157. * @throws NumberFormatException If the String does not contain a parsable int.
  158. */
  159. public static long getLongFromMessage(Message message,
  160. String nameSpace, String elemName) throws NumberFormatException {
  161. String longStr = getStringFromMessage(message, nameSpace, elemName);
  162. if (null != longStr) {
  163. return Long.parseLong(longStr);
  164. } else {
  165. throw new NumberFormatException("No such Message Element.");
  166. }
  167. }
  168. /**
  169. * Returns an int from a message
  170. *
  171. * @param message The message to retrieve from
  172. * @param nameSpace The namespace of the element to get.
  173. * @param elemName Name of the Element.
  174. * @return The int value
  175. * @throws NumberFormatException If the String does not contain a parsable long.
  176. */
  177. public static int getIntegerFromMessage(Message message, String nameSpace,
  178. String elemName) throws NumberFormatException {
  179. String intStr = getStringFromMessage(message, nameSpace, elemName);
  180. if (null != intStr) {
  181. return Integer.parseInt(intStr);
  182. } else {
  183. throw new NumberFormatException("No such Message Element.");
  184. }
  185. }
  186. /**
  187. * Returns an InputStream for a byte array
  188. *
  189. * @param message The message to retrieve from
  190. * @param nameSpace The namespace of the element to get.
  191. * @param elemName Name of the Element.
  192. * @return The {@code InputStream} or {@code null} if the message has no such
  193. element, String elemName) throws IOException {
  194. * @throws IOException if an io error occurs
  195. */
  196. public static InputStream getInputStreamFromMessage(Message message,
  197. String nameSpace, String elemName) throws IOException {
  198. InputStream result = null;
  199. MessageElement element = message.getMessageElement(nameSpace, elemName);
  200. if (null == element) {
  201. return null;
  202. }
  203. if (element.getMimeType().equals(GZIP_MEDIA_TYPE)) {
  204. result = new GZIPInputStream(element.getStream());
  205. } else if (element.getMimeType().equals(MimeMediaType.AOS)) {
  206. result = element.getStream();
  207. }
  208. return result;
  209. }
  210. /**
  211. * Reads a single Java Object from a Message.
  212. *
  213. * @param message The message containing the object.
  214. * @param nameSpace The name space of the element containing the object.
  215. * @param elemName The name of the element containing the object.
  216. * @return The Object or {@code null} if the Message contained no such element.
  217. * @throws IOException if an io error occurs
  218. * @throws ClassNotFoundException if an object could not constructed from the message
  219. element
  220. */
  221. public static Object getObjectFromMessage(Message message, String nameSpace,
  222. String elemName) throws IOException, ClassNotFoundException {
  223. InputStream is = getInputStreamFromMessage(message, nameSpace, elemName);
  224. if (null == is) {
  225. return null;
  226. }
  227. ObjectInputStream ois = new ObjectInputStream(is);
  228. return ois.readObject();
  229. }
  230. /**
  231. * Prints message element names and content and some stats
  232. *
  233. * @param msg message to print
  234. * @param verbose indicates whether to print elment content
  235. */
  236. public static void printMessageStats(Message msg, boolean verbose) {
  237. try {
  238. System.out.println("------------------Begin Message---------------------");
  239. WireFormatMessage serialed = WireFormatMessageFactory.toWire(
  240. msg,
  241. new MimeMediaType("application/x-jxta-msg"), null);
  242. System.out.println("Message Size :" + serialed.getByteLength());
  243. ElementIterator it = msg.getMessageElements();
  244. while (it.hasNext()) {
  245. MessageElement el = it.next();
  246. System.out.println("Element : " + it.getNamespace() + " :: " +
  247. el.getElementName());
  248. if (verbose) {
  249. System.out.println("[" + el + "]");
  250. }
  251. }
  252. System.out.println("-------------------End Message----------------------");
  253. } catch (Exception e) {
  254. e.printStackTrace();
  255. }
  256. }
  257. /**
  258. * Illustrates adding and retrieving a String to and from a Message
  259. */
  260. public static void stringExample() {
  261. Message message = new Message();
  262. addStringToMessage(message, "TutorialNameSpace", "String Test", "This is a test");
  263. printMessageStats(message, true);
  264. System.out.println("String Value :" +
  265. getStringFromMessage(message, "TutorialNameSpace", "String Test"));
  266. }
  267. /**
  268. * Illustrates adding and retrieving a long to and from a Message
  269. */
  270. public static void longExample() {
  271. Message message = new Message();
  272. addLongToMessage(message, "TutorialNameSpace", "long test", Long.MAX_VALUE);
  273. printMessageStats(message, true);
  274. System.out.println("long Value :" +
  275. getLongFromMessage(message, "TutorialNameSpace", "long test"));
  276. }
  277. /**
  278. * Illustrates adding and retrieving an integer to and from a Message
  279. */
  280. public static void intExample() {
  281. Message message = new Message();
  282. addIntegerToMessage(message, "TutorialNameSpace", "int test", Integer.MAX_VALUE);
  283. printMessageStats(message, true);
  284. System.out.println("int Value :" +
  285. getIntegerFromMessage(message, "TutorialNameSpace", "int test"));
  286. }
  287. /**
  288. * Illustrates adding and retrieving byte-array to and from a Message
  289. */
  290. public static void byteArrayExample() {
  291. Message message = new Message();
  292. try {
  293. File file = new File("message.tst");
  294. file.createNewFile();
  295. RandomAccessFile raf = new RandomAccessFile(file, "rw");
  296. raf.setLength(1024 * 4);
  297. int size = 4096;
  298. byte[] buf = new byte[size];
  299. raf.read(buf, 0, size);
  300. addByteArrayToMessage(message, "TutorialNameSpace", "byte test", buf, true);
  301. printMessageStats(message, true);
  302. InputStream is = getInputStreamFromMessage(message,
  303. "TutorialNameSpace", "byte test");
  304. int count = 0;
  305. while (is.read() != -1) {
  306. count++;
  307. }
  308. System.out.println("Read " + count + " byte back");
  309. } catch (IOException io) {
  310. io.printStackTrace();
  311. }
  312. }
  313. /**
  314. * Illustrates adding and retrieving advertisements to and from a Message
  315. */
  316. public static void xmlDocumentExample() {
  317. Message message = new Message();
  318. PipeAdvertisement pipeAdv = (PipeAdvertisement)
  319. AdvertisementFactory.newAdvertisement(
  320. PipeAdvertisement.getAdvertisementType());
  321. pipeAdv.setPipeID(IDFactory.newPipeID(PeerGroupID.defaultNetPeerGroupID));
  322. pipeAdv.setType(PipeService.UnicastType);
  323. message.addMessageElement("MESSAGETUT", new
  324. TextDocumentMessageElement("MESSAGETUT",
  325. (XMLDocument) pipeAdv.getDocument(MimeMediaType.XMLUTF8), null));
  326. MessageElement msgElement = message.getMessageElement("MESSAGETUT", "MESSAGETUT");
  327. try {
  328. XMLDocument asDoc = (XMLDocument)
  329. StructuredDocumentFactory.newStructuredDocument(msgElement.getMimeType(),
  330. msgElement.getStream());
  331. PipeAdvertisement newPipeAdv = (PipeAdvertisement)
  332. AdvertisementFactory.newAdvertisement(asDoc);
  333. System.out.println(newPipeAdv.toString());
  334. } catch (IOException e) {
  335. // This is thrown if the message element could not be read.
  336. e.printStackTrace();
  337. } catch (IllegalArgumentException e) {
  338. // This is thrown if the document or advertisement is invalid (illegal
  339. // values, missing tags, etc.)
  340. e.printStackTrace();
  341. }
  342. }
  343. /**
  344. * Main method
  345. *
  346. * @param args command line arguments. None defined
  347. */
  348. public static void main(String args[]) {
  349. stringExample();
  350. longExample();
  351. intExample();
  352. byteArrayExample();
  353. xmlDocumentExample();
  354. }
  355. }

阅读(1238) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~