使用Vysper,TomEE和PrimeFaces将XMPP服务器嵌入JSF Web应用程序内部
我有一个需要在完成某些工作时通知用户的应用程序。 它使用JSF和Primefaces,因此可以使用大气 (也称为Push)来实现这种通知。
但是另一个有趣的方法是使用嵌入在Java Web应用程序中的XMPP服务器。 好的,好的,您不必嵌入它,只需运行一个工业级XMPP服务器的实例,例如Openfire和Tigase即可 。 但是,嘿,我们只是玩了一点,所以我将向您展示如何使用Vysper进行操作, Vysper是使用Apache Mina开发的概念证明,只需几分钟即可轻松完成。
在展示如何做之前,很高兴记住线程和JEE应用程序通常不会混合使用 ,因此我们可以玩,但是我们必须知道我们在做什么。
首先,您将需要这些JAR,其中大多数来自Vysper。 只是几个?
- aopalliance-1.0.jar
- commons-codec-1.4.jar
- commons-collections-3.1.jar
- commons-io-1.4.jar
- commons-lang-2.5.jar
- commons-logging-1.1.jar
- 并发1.3.4.jar
- derby-10.2.1.6.jar
- dnsjava-2.0.8.jar
- ehcache-core-2.2.0.jar
- fontbox-0.1.0.jar
- jackrabbit-api-1.5.0.jar
- jackrabbit-core-1.5.3.jar
- jackrabbit-jcr-commons-1.5.3.jar
- jackrabbit-spi-1.5.0.jar
- jackrabbit-spi-commons-1.5.0.jar
- jackrabbit-text-extractors-1.5.0.jar
- jcl-over-slf4j-1.5.3.jar
- jcr-1.0.jar
- jempbox-0.2.0.jar
- jetty-continuation-7.2.1.v20101111.jar
- jetty-http-7.2.1.v20101111.jar
- jetty-io-7.2.1.v20101111.jar
- jetty-security-7.2.1.v20101111.jar
- jetty-server-7.2.1.v20101111.jar
- jetty-servlet-7.2.1.v20101111.jar
- jetty-util-7.2.1.v20101111.jar
- jetty-websocket-7.2.1.v20101111.jar
- log4j-1.2.14.jar
- lucene-core-2.3.2.jar
- mina-core-2.0.2.jar
- nbxml-0.7.jar
- nekohtml-1.9.7.jar
- pdfbox-0.7.3.jar
- poi-3.0.2-FINAL.jar
- poi-scratchpad-3.0.2-FINAL.jar
- primefaces-4.0.jar
- servlet-api-2.5.jar
- slf4j-api-1.5.3.jar
- slf4j-log4j12-1.5.3.jar
- smack-3.1.0.jar
- smackx-3.1.0.jar
- spec-compliance-0.7.jar
- 弹簧-aop-3.0.5.RELEASE.jar
- 弹簧-asm-3.0.5.RELEASE.jar
- Spring Bean3.0.5.RELEASE.jar
- spring-context-3.0.5.RELEASE.jar
- 弹簧核心-3.0.5.RELEASE.jar
- spring-expression-3.0.5.RELEASE.jar
- vysper-core-0.7.jar
- vysper-websockets-0.7.jar
- xep0045-muc-0.7.jar
- xep0060-pubsub-0.7.jar
- xep0124-xep0206-bosh-0.7.jar
- xercesImpl-2.8.1.jar
- xml-apis-1.3.03.jar
现在,从Vysper复制虚假证书,以便您的XMPP服务器可以在“安全”通道下“工作”。 它称为bogus_mina_tls.cert。
我的xhtml看起来像这样:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Messaging Prototype</title> <link rel="icon" type="image/png" href="favicon.ico"></link> </h:head> <h:body> <h:outputStylesheet library="css" name="style.css" /> <p:ajaxStatus style="width:16px;height:16px;" id="ajaxStatusPanel"> <f:facet name="start"> <h:graphicImage value="./ajaxloading.gif" /> </f:facet> <f:facet name="complete"> <h:outputText value="" /> </f:facet> </p:ajaxStatus> <h:form> <p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" /> <p:spacer height="10" /> <p:panel> <h:panelGrid columns="2"> <p:commandButton value="Enter" action="#{messagingMB.sendMessage}" /> </h:panelGrid> </p:panel> <p:spacer height="10" /> </h:form> </h:body> </html>
很简单吧? 托管Bean也很容易。
import java.io.Serializable; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import org.jivesoftware.smack.XMPPException; @ManagedBean @ViewScoped public class MessagingMB implements Serializable { private static final long serialVersionUID = -9092497421080796430L; @EJB private JSFUtilEJB jsfUtilEJB; @PostConstruct public void init() { } public void sendMessage() { try { new BasicClient().test(); } catch (XMPPException e) { jsfUtilEJB.addErrorMessage(e,"Could not send"); } } }
当然,EJB:
import javax.ejb.Stateless; import javax.faces.application.FacesMessage; import javax.faces.application.FacesMessage.Severity; import javax.faces.context.FacesContext; @Stateless public class JSFUtilEJB { @SuppressWarnings("unchecked") public <T> T findBean(String beanName) { FacesContext context = FacesContext.getCurrentInstance(); return (T) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class); } public long addErrorMessage(String msg) { return addMessage(null,FacesMessage.SEVERITY_ERROR,msg); } public long addErrorMessage(Exception e,String summary){ return addMessage(e,FacesMessage.SEVERITY_ERROR,summary); } public long addFatalErrorMessage(Exception e,String summary){ return addMessage(e,FacesMessage.SEVERITY_FATAL,summary); } public long addInfoMessage(String summary){ return addMessage(null,FacesMessage.SEVERITY_INFO,summary); } public long addWarnMessage(Exception e,String summary){ return addMessage(e,FacesMessage.SEVERITY_WARN,summary); } public long addErrorMessage(Exception e) { return addMessage(e,FacesMessage.SEVERITY_ERROR,e.getMessage(),e.getClass().getSimpleName()); } private long addMessage(Exception e,Severity severity, String summary) { FacesContext context = FacesContext.getCurrentInstance(); String clientId = null; long id = -1; if (e != null){ id = printStackTrace(e); FacesMessage facesMessage = null; if (e.getCause() instanceof org.apache.openjpa.persistence.EntityExistsException){ facesMessage = new FacesMessage(severity,"[Error: #"+id+"] "+summary,"You are trying are to add a new object that already exists or your're trying to violate a unique constraint)" ); }else{ facesMessage = new FacesMessage(severity,"[Error: #"+id+"] "+summary,e.getMessage() ); } context.addMessage(clientId , facesMessage ); }else{ FacesMessage facesMessage = new FacesMessage(severity,summary," "); context.addMessage(clientId , facesMessage ); } return id; } private long addMessage(Exception e,Severity severity, String summary, String detail) { FacesContext context = FacesContext.getCurrentInstance(); String clientId = null; long id = -1; if (e != null){ id = printStackTrace(e); FacesMessage facesMessage = new FacesMessage(severity,"["+id+"] "+summary,detail ); context.addMessage(clientId , facesMessage ); }else{ FacesMessage facesMessage = new FacesMessage(severity,summary,detail ); context.addMessage(clientId , facesMessage ); } return id; } public long printStackTrace(Exception e){ long uniqueId = System.currentTimeMillis(); return uniqueId; } }
和
import java.io.File; import java.io.Serializable; import javax.annotation.PostConstruct; import javax.ejb.Singleton; import javax.ejb.Startup; import org.apache.vysper.mina.TCPEndpoint; import org.apache.vysper.storage.StorageProviderRegistry; import org.apache.vysper.storage.inmemory.MemoryStorageProviderRegistry; import org.apache.vysper.xmpp.addressing.EntityImpl; import org.apache.vysper.xmpp.authorization.AccountManagement; import org.apache.vysper.xmpp.server.XMPPServer; @Startup @Singleton public class XmppEJB implements Serializable { /** * <br>06/09/2014 */ private static final long serialVersionUID = 1L; private boolean started; @PostConstruct public void init() { try { // choose the storage you want to use // StorageProviderRegistry providerRegistry = new JcrStorageProviderRegistry(); StorageProviderRegistry providerRegistry = new MemoryStorageProviderRegistry(); final AccountManagement accountManagement = (AccountManagement) providerRegistry.retrieve(AccountManagement.class); if(!accountManagement.verifyAccountExists(EntityImpl.parse("[email protected]"))) { accountManagement.addUser(EntityImpl.parse("[email protected]"), "password"); } if(!accountManagement.verifyAccountExists(EntityImpl.parse("[email protected]"))) { accountManagement.addUser(EntityImpl.parse("[email protected]"), "password"); } XMPPServer server = new XMPPServer("vysper.org"); server.addEndpoint(new TCPEndpoint()); server.setStorageProviderRegistry(providerRegistry); server.setTLSCertificateInfo(new File("/path/to/bogus_mina_tls.cert"), "boguspw"); server.start(); System.out.println("server is running..."); } catch (Exception e) { e.printStackTrace(); } started = true; } public boolean isStarted() { return this.started; } }
和基本客户, 来自Vysper 。
import java.util.Date; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.packet.Time; import org.jivesoftware.smackx.packet.Version; public class BasicClient { static class IQListener implements PacketListener { public void processPacket(Packet packet) { IQ iq = (IQ) packet; String iqString = iq.toString(); System.out.println("T" + System.currentTimeMillis() + " IQ: " + iqString + ": " + iq.toXML()); } } static class PresenceListener implements PacketListener { public void processPacket(Packet packet) { Presence presence = (Presence) packet; String iqString = presence.toString(); final PacketExtension extension = presence .getExtension("http://jabber.org/protocol/caps"); if (extension != null) System.out.println("T" + System.currentTimeMillis() + " Pres: " + iqString + ": " + presence.toXML()); } } public void test() throws XMPPException { String me = "[email protected]"; String to = "[email protected]"; try { ConnectionConfiguration connectionConfiguration = new ConnectionConfiguration( "localhost"); connectionConfiguration.setCompressionEnabled(false); connectionConfiguration.setSelfSignedCertificateEnabled(true); connectionConfiguration.setExpiredCertificatesCheckEnabled(false); // connectionConfiguration.setDebuggerEnabled(true); connectionConfiguration .setSecurityMode(ConnectionConfiguration.SecurityMode.required); // XMPPConnection.DEBUG_ENABLED = true; XMPPConnection connection = new XMPPConnection( connectionConfiguration); connection.connect(); SASLAuthentication saslAuthentication = connection .getSASLAuthentication(); saslAuthentication.authenticate(me, "password", "test"); connection.login(me, "pqssword"); connection.getRoster().setSubscriptionMode( Roster.SubscriptionMode.accept_all); connection.addPacketListener(new IQListener(), new PacketFilter() { public boolean accept(Packet packet) { return packet instanceof IQ; } }); connection.addPacketListener(new PresenceListener(), new PacketFilter() { public boolean accept(Packet packet) { return packet instanceof Presence; } }); Chat chat = null; if (to != null) { Presence presence = new Presence(Presence.Type.subscribe); presence.setFrom(connection.getUser()); String toEntity = to; presence.setTo(toEntity); connection.sendPacket(presence); chat = connection.getChatManager().createChat(toEntity, new MessageListener() { public void processMessage(Chat inchat, Message message) { System.out.println("log received message: " + message.getBody()); } }); } connection.sendPacket(new Presence(Presence.Type.available, "pommes", 1, Presence.Mode.available)); Thread.sleep(1000); // query server version sendIQGetWithTimestamp(connection, new Version()); // query server time sendIQGetWithTimestamp(connection, new Time()); chat.sendMessage("Hello " + to + " at " + new Date()); connection.disconnect(); } catch (Throwable e) { e.printStackTrace(); // To change body of catch statement use File | // Settings | File Templates. } System.out.println("bye"); } private static void sendIQGetWithTimestamp(XMPPConnection connection, IQ iq) { iq.setType(IQ.Type.GET); connection.sendPacket(iq); System.out.println("T" + System.currentTimeMillis() + " IQ request sent"); } }
我们快要准备好了。 当然,现在我们需要一个XMPP客户端,例如Pidgin 。
首先,让我对这个博客表示感谢 ,因为我不知道为什么,Vysper站点几乎没有关于如何配置Pidgin的信息,因此这篇博客非常有用。
让我向您展示我的pidgin用户的样子:
我知道,是葡萄牙语。
就是这样。 我们都准备好了。 启动您的JSF Web应用程序并播放。
请注意,通信是双向的,因此您可以仅使用XMPP客户端将命令发送到服务器。 为此,您只需要更改此侦听器即可:
chat = connection.getChatManager().createChat(toEntity, new MessageListener() { public void processMessage(Chat inchat, Message message) { System.out.println("log received message: " + message.getBody()); } });
我想知道我们是否可以创建一个DSL来处理一些命令,是否可以找到一些自动完成的pidgin插件来使用该DSL编写命令。 欢迎提出建议!
ps。 EJB不能正常关闭服务器。 但是我敢打赌,如果服务器关闭,有一些EJB注释可以做到这一点。