本文翻译自 – 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”类别/类型。这仅仅是用来演示。在真实应用的时候请参照服务发现标识注册表当中的类别/类型来挑选一个最适合。添加完上面两个方法并重新启动服务器之后,再查看服务发现列表窗口,可能会出现下图所示的效果。
这很简单,但事实上除了显示效果上有一些变化之外,本质上组件并没有发生任何改变。下面我们修改它的“本质”。
通过覆写方法来修改组件描述和类别/类型的一个限制就是:你不能在运行时动态得改变组件的信息。那两个方法只在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);
|
看看最下面的完整代码样例。之后我们向组件发送几条消息,其中一些是垃圾信息(包含垃圾信息关键字)。再打开服务发现列表窗口,就会出现下面的截图:
依据我们的实现方式,在服务器没有接收到任何消息之前,组件的服务发现信息是不会有子节点的;只有当组件接收到消息才会调用“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规范,传递特性字符串数组)。
更新好代码之后重新启动服务器,看看服务发现信息发生了什么改变。
最后一个功能可能对于咱们的垃圾过滤组件用处不大,但是对于类似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";
}
}
|