快速浏览了RFC3920,XEP-0147和RFC3921,大致明白了XMPP的数据格式和通讯原理,接着开始尝试基于Vysper(我用的版本是0.7)做点小定制,尤其是增加“联系人”相关的数据。
先从改造demo开始吧,把org.apache.vysper.xmpp.authorization.SimpleUserAuthorization、org.apache.vysper.xmpp.modules.roster.persistence.MemoryRosterManager和org.apache.vysper.storage.inmemory.MemoryStorageProviderRegistry的源码拿到手,根据自己的喜好做修改:
1、AccountManagementImpl:帐户管理相关的代码实现
package z.vysper.impl; import org.apache.log4j.Logger; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.addressing.EntityImpl; import org.apache.vysper.xmpp.authorization.AccountCreationException; import org.apache.vysper.xmpp.authorization.AccountManagement; import org.apache.vysper.xmpp.authorization.UserAuthorization; import z.vysper.test.TestData; /** * @author auzll * @blog http://auzll.iteye.com */ public class AccountManagementImpl implements AccountManagement, UserAuthorization { private static final Logger LOG = Logger.getLogger(AccountManagementImpl.class); private static final boolean INFO = LOG.isInfoEnabled(); public void addUser(Entity username, String password) throws AccountCreationException{ if (INFO) { LOG.info("method:addUser,username:" + username + ",passwordExist:" + (null != password)); } throw new AccountCreationException("无法添加用户"); } public void changePassword(Entity username, String password) throws AccountCreationException { if (INFO) { LOG.info("method:changePassword,username:" + username + ",passwordExist:" + (null != password)); } throw new AccountCreationException("无法修改用户密码"); } public boolean verifyCredentials(Entity jid, String passwordCleartext, Object credentials) { if (INFO) { LOG.info("method:verifyCredentials,jid:" + jid + ",passwordExist:" + (null != passwordCleartext) + ",credentials:" + (null != credentials ? credentials.getClass().getSimpleName() : null)); } return verify(jid.getBareJID(), passwordCleartext); } public boolean verifyCredentials(String username, String passwordCleartext, Object credentials) { if (INFO) { LOG.info("method:verifyCredentials,username:" + username + ",passwordExist:" + (null != passwordCleartext) + ",credentials:" + (null != credentials ? credentials.getClass().getSimpleName() : null)); } return verify(EntityImpl.parseUnchecked(username).getBareJID(), passwordCleartext); } public boolean verifyAccountExists(Entity jid) { if (INFO) { LOG.info("method:verifyAccountExists,jid:" + jid); } return TestData.USER_NAME_PASSWORD.get(jid.getBareJID()) != null; } private boolean verify(Entity username, String passwordCleartext) { return passwordCleartext.equals(TestData.USER_NAME_PASSWORD.get(username)); } }
2、RosterManagerImpl:通讯录相关的代码实现
package z.vysper.impl; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.log4j.Logger; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.modules.ServerRuntimeContextService; import org.apache.vysper.xmpp.modules.roster.MutableRoster; import org.apache.vysper.xmpp.modules.roster.Roster; import org.apache.vysper.xmpp.modules.roster.RosterException; import org.apache.vysper.xmpp.modules.roster.RosterItem; import org.apache.vysper.xmpp.modules.roster.persistence.RosterManager; import z.vysper.test.TestData; /** * @author auzll * @blog http://auzll.iteye.com */ public class RosterManagerImpl implements RosterManager, ServerRuntimeContextService { private static final Logger LOG = Logger.getLogger(RosterManagerImpl.class); private static final boolean INFO = LOG.isInfoEnabled(); // private static final boolean DEBUG = LOG.isDebugEnabled(); // TODO private Map<Entity, Roster> rosterMap = new HashMap<Entity, Roster>(); protected Roster addNewRosterInternal(Entity jid) { if (INFO) { LOG.info("method:addNewRosterInternal,jid:" + jid); } MutableRoster mutableRoster = new MutableRoster(); rosterMap.put(jid.getBareJID(), (MutableRoster) mutableRoster); return mutableRoster; } protected Roster retrieveRosterInternal(Entity bareJid) { if (INFO) { LOG.info("method:retrieveRosterInternal,bareJid:" + bareJid); } if (!rosterMap.containsKey(bareJid)) { // TODO MutableRoster roster = new MutableRoster(); if (bareJid.getDomain().equals(TestData.SERVER_DOMAIN)) { Iterator<RosterItem> it = TestData.SHARE_ROSTER.iterator(); while (it.hasNext()) { RosterItem item = it.next(); if (!item.getJid().equals(bareJid)) { // 剔除当前账户 roster.addItem(item); } } } rosterMap.put(bareJid, roster); } return rosterMap.get(bareJid); } public Roster retrieve(Entity jid) { jid = jid.getBareJID(); return retrieveRosterInternal(jid); } public void addContact(Entity jid, RosterItem rosterItem) throws RosterException { throw new RosterException("无法添加联系人"); } public RosterItem getContact(Entity jidUser, Entity jidContact) throws RosterException { if (jidUser == null) throw new RosterException("jid not provided"); Roster roster = retrieve(jidUser); if (roster == null) throw new RosterException("roster not available for jid = " + jidUser.getFullQualifiedName()); return roster.getEntry(jidContact); } public void removeContact(Entity jidUser, Entity jidContact) throws RosterException { throw new RosterException("无法删除联系人"); } public String getServiceName() { return RosterManager.SERVER_SERVICE_ROSTERMANAGER; } }
3、StorageProviderRegistryImpl:用于辅助注册上面两个实现类的
package z.vysper.impl; import org.apache.vysper.storage.OpenStorageProviderRegistry; /** * @author auzll * @blog http://auzll.iteye.com */ public class StorageProviderRegistryImpl extends OpenStorageProviderRegistry { public StorageProviderRegistryImpl() { add(new AccountManagementImpl()); add(new RosterManagerImpl()); // provider from external modules, low coupling, fail when modules are not present add("org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.storageprovider.LeafNodeInMemoryStorageProvider"); add("org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.storageprovider.CollectionNodeInMemoryStorageProvider"); //add("org.apache.vysper.xmpp.modules.extension.xep0160_offline_storage.MemoryOfflineStorageProvider"); } }
4、TestData:自己设定的一些测试数据
/** * */ package z.vysper.test; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.addressing.EntityImpl; import org.apache.vysper.xmpp.modules.roster.AskSubscriptionType; import org.apache.vysper.xmpp.modules.roster.MutableRoster; import org.apache.vysper.xmpp.modules.roster.Roster; import org.apache.vysper.xmpp.modules.roster.RosterItem; import org.apache.vysper.xmpp.modules.roster.SubscriptionType; /** * 测试数据 * @author auzll * @blog http://auzll.iteye.com */ public final class TestData { public static final Map<Entity, String> USER_NAME_PASSWORD; public static final Roster SHARE_ROSTER; public static final String SERVER_DOMAIN = "helloworld.com"; static { try { // 添加测试数据 Map<Entity, String> map = new HashMap<Entity, String>(); MutableRoster roster = new MutableRoster(); String password = "testing"; String entityFormat = "contact%d@%s"; int total = 10; for (int i = 1; i <= total; i++) { Entity entity = EntityImpl.parse(String.format(entityFormat, i, SERVER_DOMAIN)); map.put(entity, password); roster.addItem(new RosterItem(entity, "姓名" + i, SubscriptionType.BOTH, AskSubscriptionType.NOT_SET)); } USER_NAME_PASSWORD = Collections.unmodifiableMap(map); SHARE_ROSTER = roster; } catch (Exception e) { throw new ExceptionInInitializerError(e.getMessage()); } } }
5、spring配置文件:好久没用spring了,不过用起来还是蛮简单的,只是没深入看过spring的源码。(在此推荐另一款容器,就是google出品的guice啦,我经常用的,非常精简,代码也很有学习价值)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <!-- 使用自己配置的Registry --> <bean id="zStorageRegistry" class="z.vysper.impl.StorageProviderRegistryImpl" /> <!-- choose one of the storage registries --> <alias name="zStorageRegistry" alias="storageRegistry" /> <bean id="tcpEndpoint" class="org.apache.vysper.mina.TCPEndpoint"> </bean> <bean id="server" class="org.apache.vysper.spring.SpringCompatibleXMPPServer" init-method="init" destroy-method="destroy"> <!-- TODO change domain name to your domain --> <constructor-arg value="helloworld.com"/> <property name="endpoints"> <list> <ref bean="tcpEndpoint"/> <!--<ref bean="boshEndpoint"/>--> </list> </property> <property name="storageProviderRegistry" ref="storageRegistry" /> <property name="certificateFile" value="classpath:bogus_mina_tls.cert" /> <property name="certificatePassword" value="boguspw" /> <property name="modules" > <list> <bean class="org.apache.vysper.xmpp.modules.extension.xep0092_software_version.SoftwareVersionModule"/> <bean class="org.apache.vysper.xmpp.modules.extension.xep0202_entity_time.EntityTimeModule" /> <bean class="org.apache.vysper.xmpp.modules.extension.xep0054_vcardtemp.VcardTempModule" /> <bean class="org.apache.vysper.xmpp.modules.extension.xep0119_xmppping.XmppPingModule" /> <bean class="org.apache.vysper.xmpp.modules.extension.xep0049_privatedata.PrivateDataModule" /> <bean class="org.apache.vysper.xmpp.modules.extension.xep0050_adhoc_commands.AdhocCommandsModule" /> <bean class="org.apache.vysper.xmpp.modules.extension.xep0133_service_administration.ServiceAdministrationModule" > <property name="addAdmins"> <list><value>[email protected]</value></list> </property> </bean> <!-- below some more modules which are available as separately-built jars. make sure they are on the classpath when enabling them or remove the beans below, depending on the features your server should expose --> <!--<bean class="org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.PublishSubscribeModule" />--> <bean class="org.apache.vysper.xmpp.modules.extension.xep0045_muc.MUCModule" /> </list> </property> </bean> </beans>
6、改好代码和配置文件之后,程序就可以跑起来咯,截两个包含联系人的客户端界面:
好了,至此已完成帐户验证逻辑和通讯录相关逻辑的整合,下一步再找找企业邮箱的通讯录整合一下就可以正式完成我这个小项目的demo了!!
距离这款“基于企业邮箱通讯录打造的XMPP聊天系统”的诞生又进一步了!感兴趣的朋友可以关注 qiyeliao.com 哦!