Tigase组件第四节 – 服务发现

Tigase组件第四节 – 服务发现

发表评论作者  储天行 on  2010/11/17

本文翻译自 – http://www.tigase.org/content/component-implementation-lesson-4-service-discovery

新组件在服务发现列表当中仍然显示“未定义的描述”。它也没有提供任何有趣的特性和子节点。

接下来,我们将用简单的方式修改组件的基本信息,并添加一些服务发现特性。除此之外,文档还提供一些如何在运行时添加/删除服务发现节点,如何更新已有元素的指导。

组件的描述和类别/类型可以通过覆写下面两个方法来实现:

1
2
3
4
5
6
7
8
9
@Override
publicString getDiscoDescription() {
  return"Spam filtering";
}
 
@Override
publicString getDiscoCategoryType() {
  return"spam";
}

请注意,在服务发现标识注册表当中,并没有定义“Spam”类别/类型。这仅仅是用来演示。在真实应用的时候请参照服务发现标识注册表当中的类别/类型来挑选一个最适合。添加完上面两个方法并重新启动服务器之后,再查看服务发现列表窗口,可能会出现下图所示的效果。

Tigase组件第四节 – 服务发现

更新描述和分类之后的服务发现列表

这很简单,但事实上除了显示效果上有一些变化之外,本质上组件并没有发生任何改变。下面我们修改它的“本质”。

通过覆写方法来修改组件描述和类别/类型的一个限制就是:你不能在运行时动态得改变组件的信息。那两个方法只在setProperties(…)方法执行的时候被调用一次,之后组件的服务发现信息就被创建了。但有时在运行时动态改变服务发现信息是很有意义的,别人可以从服务发现信息中获得有用信息。

以咱们的垃圾信息过滤组件为例,看看到底能从组件发现信息当中获得多少有用信息。如果在每一次接收到一条消息的时候都调用:

1
2
3
updateServiceDiscoveryItem(getName(),null,
  getDiscoDescription() +": ["+
  (++messagesCounter) +"]",true);

关于服务性能的小贴士:在有些情况下调用“updateServiceDiscoveryItem(…)方法”会产生很大的性能开销,所以一个比较好的推荐是每100条消息才调用这个方法一次,而不是每条调用一次。

updateServiceDiscoveryItem(…)方法的第一个入口参数是组件的名称,它会显示在服务发现列表的JID列里。之所以不使用JID是因为:Tigase服务器可能为多个虚拟域名提供服务,域名部分会在低层方法当中被添加,所以我们在这里只使用组件名称。第二个入口参数是服务发现节点,顶级的disco条目项应该传递为null。第三个参数是它的描述(在disco规范中实际上称之为“name”)。最后一个参数是它是否仅对管理员可见。

使用上面的方法我们还可以为组件元素添加子节点。虽然XMPP的服务发现信息本来不是用来显示计数器的,但是这个例子很好的展示了Tigase API的功能,所以接下来我们使用服务发现信息来显示计数器。这一次,第二个参数不再传null,我们传递一些有意义的文字看看会产生什么效果:

1
2
3
4
5
6
// 当组件接收一条消息之后调用下面的语句
updateServiceDiscoveryItem(getName(),"messages",
  "Messages processed: ["+ (++messagesCounter) +"]",true);
// 当组件确定消息是垃圾信息的时候调用下面的语句
updateServiceDiscoveryItem(getName(),"spam","Spam caught: ["+
  (++totalSpamCounter) +"]",true);

看看最下面的完整代码样例。之后我们向组件发送几条消息,其中一些是垃圾信息(包含垃圾信息关键字)。再打开服务发现列表窗口,就会出现下面的截图:

Tigase组件第四节 – 服务发现

正常和垃圾消息计数器

依据我们的实现方式,在服务器没有接收到任何消息之前,组件的服务发现信息是不会有子节点的;只有当组件接收到消息才会调用“updateServiceDiscoveryItem(…)”方法。如果希望在服务启动之后就包含子节点,那么可以在“setProperties(…)”方法当中调用“updateServiceDiscoveryItem(…)”。

请注意,“updateServiceDiscoveryItem(…)”方法可以添加或修改服务发现信息项,如果是删除操作还有一个单独的方法:

1
2
voidremoveServiceDiscoveryItem(String jid,
  String node, String description)

实际上只有前两个参数比较重要:“jid”和“node”必须对应已经存在的服务发现条目。

update方法还提供两个附加的变量可以用来更好得控制服务发现条目项。可以为条目项设置不同的类型/类别,也可以让它展示一些额外的特性。其中一个比较容易理解的变量可以更新服务发现条目规范。XMPP有一个专门的规范文档来描述那些已存在并注册的特性,我们创建的垃圾信息过滤组件使用了一个未经定义的特性“垃圾过滤”。下面的文字将说明如何创建两个特性标识字符串,并把他们设置到我们的新组件当中。我们可以这样调用update方法:

1
2
updateServiceDiscoveryItem(getName(),null, getDiscoDescription(),
  true,"tigase:x:spam-filter","tigase:x:spam-reporting");

最好在setProperties(…)方法当中调用上面这个方法,这样组件的服务发现信息可以在一开始就被设定好。我们为组件的disco信息设置了两个特性:“tigase:x:spam-filter”和“tigase:x:spam-reporting”。update方法可以接受任意多个入口参数,所以我们可以为它设置任意多个需要的特性(或者依据java规范,传递特性字符串数组)。

更新好代码之后重新启动服务器,看看服务发现信息发生了什么改变。

Tigase组件第四节 – 服务发现

新增的两个特性

最后一个功能可能对于咱们的垃圾过滤组件用处不大,但是对于类似MUC/PubSub这种设置了正确类别和类型的服务发现条目项而言还是非常有用的。下面我们为垃圾过滤组件设置“自动”类别和“垃圾过滤”类型:

1
2
3
updateServiceDiscoveryItem(getName(),null, getDiscoDescription(),
  "automation","spam-filtering",true,
  "tigase:x:spam-filter","tigase:x:spam-reporting");

当然所有的这些设置可以应用到任何一个服务发现条目当中,即使是条目子节点。下面是完整的代码样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
importjava.util.Arrays;
importjava.util.Map;
importjava.util.logging.Logger;
importtigase.server.AbstractMessageReceiver;
importtigase.server.Packet;
importtigase.util.JIDUtils;
importtigase.xmpp.StanzaType;
 
publicclassTestComponentextendsAbstractMessageReceiver {
 
  privatestaticfinalLogger log =
    Logger.getLogger(TestComponent.class.getName());
 
  privatestaticfinalString BAD_WORDS_KEY ="bad-words";
  privatestaticfinalString WHITELIST_KEY ="white-list";
  privatestaticfinalString PREPEND_TEXT_KEY ="log-prepend";
  privatestaticfinalString SECURE_LOGGING_KEY ="secure-logging";
  privatestaticfinalString ABUSE_ADDRESS_KEY ="abuse-address";
  privatestaticfinalString NOTIFICATION_FREQ_KEY ="notification-freq";
 
  privateString[] badWords = {"word1","word2","word3"};
  privateString[] whiteList = {"admin@localhost"};
  privateString prependText ="Spam detected: ";
  privateString abuseAddress ="abuse@locahost";
  privateintnotificationFrequency =10;
  privateintdelayCounter =0;
  privatebooleansecureLogging =false;
  privatelongspamCounter =0;
  privatelongtotalSpamCounter =0;
  privatelongmessagesCounter =0;
 
  @Override
  publicvoidprocessPacket(Packet packet) {
    // 这是一个message packet吗?
    if("message"== packet.getElemName()) {
      updateServiceDiscoveryItem(getName(),"messages",
        "Messages processed: ["+ (++messagesCounter) +"]",true);
      String from = JIDUtils.getNodeID(packet.getElemFrom());
      // 消息的发送者在白名单内吗?
      if(Arrays.binarySearch(whiteList, from) <0) {
        // 如果ta不在白名单里面,那么检查消息的内容
        String body = packet.getElemCData("/message/body");
        if(body !=null&& !body.isEmpty()) {
          body = body.toLowerCase();
          for(String word : badWords) {
            if(body.contains(word)) {
              log.finest(prependText + packet.toString(secureLogging));
              ++spamCounter;
              updateServiceDiscoveryItem(getName(),"spam","Spam caught: ["+
                (++totalSpamCounter) +"]",true);
              return;
            }
          }
        }
      }
    }
    // 不是垃圾信息,返回以便做下一步处理
    Packet result = packet.swapElemFromTo();
    addOutPacket(result);
  }
 
  @Override
  publicintprocessingThreads() {
    returnRuntime.getRuntime().availableProcessors();
  }
 
  @Override
  publicinthashCodeForPacket(Packet packet) {
    if(packet.getElemTo() !=null) {
      returnpacket.getElemTo().hashCode();
    }
    // 程序不应该运行到这里,所有的packet都必须具有一个目的地地址,但是也许垃圾过滤器也许会过滤一些奇怪的地址
    if(packet.getElemFrom() !=null) {
      returnpacket.getElemFrom().hashCode();
    }
    // 如果程序真的运行到这一部,就应该好好检查一下到达组件的packet是否正常,然后找到一个更好的计算hashCode方法。
    return1;
  }
 
  @Override
  publicMap<String, Object> getDefaults(Map<String, Object> params) {
    Map<String, Object> defs =super.getDefaults(params);
    defs.put(BAD_WORDS_KEY, badWords);
    defs.put(WHITELIST_KEY, whiteList);
    defs.put(PREPEND_TEXT_KEY, prependText);
    defs.put(SECURE_LOGGING_KEY, secureLogging);
    defs.put(ABUSE_ADDRESS_KEY, abuseAddress);
    defs.put(NOTIFICATION_FREQ_KEY, notificationFrequency);
    returndefs;
  }
 
  @Override
  publicvoidsetProperties(Map<String, Object> props) {
    super.setProperties(props);
    badWords = (String[])props.get(BAD_WORDS_KEY);
    whiteList = (String[])props.get(WHITELIST_KEY);
    Arrays.sort(whiteList);
    prependText = (String)props.get(PREPEND_TEXT_KEY);
    secureLogging = (Boolean)props.get(SECURE_LOGGING_KEY);
    abuseAddress = (String)props.get(ABUSE_ADDRESS_KEY);
    notificationFrequency = (Integer)props.get(NOTIFICATION_FREQ_KEY);
    updateServiceDiscoveryItem(getName(),null, getDiscoDescription(),
      "automation","spam-filtering",true,
      "tigase:x:spam-filter","tigase:x:spam-reporting");
  }
 
  @Override
  publicsynchronizedvoideveryMinute() {
    super.everyMinute();
    if((++delayCounter) >= notificationFrequency) {
      addOutPacket(Packet.getMessage(abuseAddress, getComponentId(),
        StanzaType.chat,"Detected spam messages: "+ spamCounter,
        "Spam counter",null, newPacketId("spam-")));
      delayCounter =0;
      spamCounter =0;
    }
  }
 
  @Override
  publicString getDiscoDescription() {
    return"Spam filtering";
  }
 
  @Override
  publicString getDiscoCategoryType() {
    return"spam";
  }
 
}

你可能感兴趣的:(Tigase组件第四节 – 服务发现)