2015-05-05 3 views
16

Я пытаюсь адаптировать приведенный пример here для Smack 4.1.0. и немного запутался.GCM XMPP Server с использованием Smack 4.1.0

В частности, я пытаюсь понять, что теперь должно расширять расширение GcmPacketExtension, как должен работать конструктор и как обновлять Providermanager.addExtensionProvider для привязки к нему.

Я уверен, что кто-то должен был это сделать раньше, но я не могу найти никаких примеров , и я, кажется, обойдусь кругами, используя только документацию.

Любая помощь будет высоко оценена, я уверен, что ответ очень прост!

Текущий код (компилирует, но не работает):

static { 

    ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new ExtensionElementProvider<ExtensionElement>() { 
     @Override 
     public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException, 
     IOException { 
      String json = parser.nextText(); 
      return new GcmPacketExtension(json); 
     } 
    }); 
} 

и:

private static final class GcmPacketExtension extends DefaultExtensionElement { 

    private final String json; 

    public GcmPacketExtension(String json) { 
     super(GCM_ELEMENT_NAME, GCM_NAMESPACE); 
     this.json = json; 
    } 

    public String getJson() { 
     return json; 
    } 

    @Override 
    public String toXML() { 
     return String.format("<%s xmlns=\"%s\">%s</%s>", 
       GCM_ELEMENT_NAME, GCM_NAMESPACE, 
       StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); 
    } 

    public Stanza toPacket() { 
     Message message = new Message(); 
     message.addExtension(this); 
     return message; 
    } 
} 

Текущее исключение:

Exception in thread "main" java.lang.NoClassDefFoundError: de/measite/minidns/DNSCache 
at java.lang.Class.forName0(Native Method) 
at java.lang.Class.forName(Unknown Source) 
at org.jivesoftware.smack.SmackInitialization.loadSmackClass(SmackInitialization.java:213) 
at org.jivesoftware.smack.SmackInitialization.parseClassesToLoad(SmackInitialization.java:193) 
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:163) 
at org.jivesoftware.smack.SmackInitialization.processConfigFile(SmackInitialization.java:148) 
at org.jivesoftware.smack.SmackInitialization.<clinit>(SmackInitialization.java:116) 
at org.jivesoftware.smack.SmackConfiguration.getVersion(SmackConfiguration.java:96) 
at org.jivesoftware.smack.provider.ProviderManager.<clinit>(ProviderManager.java:121) 
at SmackCcsClient.<clinit>(SmackCcsClient.java:58) 
Caused by: java.lang.ClassNotFoundException: de.measite.minidns.DNSCache 
at java.net.URLClassLoader$1.run(Unknown Source) 
at java.net.URLClassLoader$1.run(Unknown Source) 
at java.security.AccessController.doPrivileged(Native Method) 
at java.net.URLClassLoader.findClass(Unknown Source) 
at java.lang.ClassLoader.loadClass(Unknown Source) 
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) 
at java.lang.ClassLoader.loadClass(Unknown Source) 
... 10 more 
+0

Мне удалось получить код для компиляции и запуска. Он успешно отправляет сообщения, но мне еще предстоит проверить получение. Я опубликую полный источник, как только все будет работать! –

ответ

27

ОК, так что мне удалось получить его работая после большого чтения и боли, поэтому здесь очень грубая реализация сервера, которая фактически работает. Очевидно, что не для производства и не стесняйтесь исправить что-то не так. Я не говорю, что это лучший способ сделать это, но он работает. Он отправит сообщение и получит сообщения, но отобразит их только в журнале.

import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; 
 
import org.jivesoftware.smack.ConnectionListener; 
 
import org.jivesoftware.smack.SmackException; 
 
import org.jivesoftware.smack.SmackException.NotConnectedException; 
 
import org.jivesoftware.smack.XMPPConnection; 
 
import org.jivesoftware.smack.XMPPException; 
 
import org.jivesoftware.smack.packet.DefaultExtensionElement; 
 
import org.jivesoftware.smack.packet.Message; 
 
import org.jivesoftware.smack.packet.Stanza; 
 
import org.jivesoftware.smack.filter.StanzaFilter; 
 
import org.jivesoftware.smack.provider.ProviderManager; 
 
import org.jivesoftware.smack.tcp.XMPPTCPConnection; 
 
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; 
 
import org.jivesoftware.smack.util.StringUtils; 
 
import org.json.simple.JSONValue; 
 
import org.json.simple.parser.ParseException; 
 
import org.xmlpull.v1.XmlPullParser; 
 
import org.jivesoftware.smack.*; 
 
import org.jivesoftware.smack.packet.ExtensionElement; 
 
import org.jivesoftware.smack.provider.ExtensionElementProvider; 
 
import org.jivesoftware.smack.roster.Roster; 
 

 
import java.io.IOException; 
 
import java.util.HashMap; 
 
import java.util.Map; 
 
import java.util.UUID; 
 
import java.util.logging.Level; 
 
import java.util.logging.Logger; 
 

 

 
import javax.net.ssl.SSLSocketFactory; 
 

 
/** 
 
* Sample Smack implementation of a client for GCM Cloud Connection Server. This 
 
* code can be run as a standalone CCS client. 
 
* 
 
* <p>For illustration purposes only. 
 
*/ 
 
public class SmackCcsClient { 
 

 
    private static final Logger logger = Logger.getLogger("SmackCcsClient"); 
 

 
    private static final String GCM_SERVER = "gcm.googleapis.com"; 
 
    private static final int GCM_PORT = 5235; 
 

 
    private static final String GCM_ELEMENT_NAME = "gcm"; 
 
    private static final String GCM_NAMESPACE = "google:mobile:data"; 
 

 
    private static final String YOUR_PROJECT_ID = "<your ID here>"; 
 
    private static final String YOUR_API_KEY = "<your API Key here>"; // your API Key 
 
    private static final String YOUR_PHONE_REG_ID = "<your test phone's registration id here>"; 
 
    
 
    
 
    static { 
 

 
     ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new ExtensionElementProvider<ExtensionElement>() { 
 
      @Override 
 
      public DefaultExtensionElement parse(XmlPullParser parser,int initialDepth) throws org.xmlpull.v1.XmlPullParserException, 
 
      IOException { 
 
       String json = parser.nextText(); 
 
       return new GcmPacketExtension(json); 
 
      } 
 
     }); 
 
    } 
 

 
    private XMPPTCPConnection connection; 
 

 
    /** 
 
    * Indicates whether the connection is in draining state, which means that it 
 
    * will not accept any new downstream messages. 
 
    */ 
 
    protected volatile boolean connectionDraining = false; 
 

 
    /** 
 
    * Sends a downstream message to GCM. 
 
    * 
 
    * @return true if the message has been successfully sent. 
 
    */ 
 
    public boolean sendDownstreamMessage(String jsonRequest) throws 
 
      NotConnectedException { 
 
     if (!connectionDraining) { 
 
      send(jsonRequest); 
 
      return true; 
 
     } 
 
     logger.info("Dropping downstream message since the connection is draining"); 
 
     return false; 
 
    } 
 

 
    /** 
 
    * Returns a random message id to uniquely identify a message. 
 
    * 
 
    * <p>Note: This is generated by a pseudo random number generator for 
 
    * illustration purpose, and is not guaranteed to be unique. 
 
    */ 
 
    public String nextMessageId() { 
 
     return "m-" + UUID.randomUUID().toString(); 
 
    } 
 

 
    /** 
 
    * Sends a packet with contents provided. 
 
    */ 
 
    protected void send(String jsonRequest) throws NotConnectedException { 
 
     Stanza request = new GcmPacketExtension(jsonRequest).toPacket(); 
 
     connection.sendStanza(request); 
 
    } 
 

 
    /** 
 
    * Handles an upstream data message from a device application. 
 
    * 
 
    * <p>This sample echo server sends an echo message back to the device. 
 
    * Subclasses should override this method to properly process upstream messages. 
 
    */ 
 
    protected void handleUpstreamMessage(Map<String, Object> jsonObject) { 
 
     // PackageName of the application that sent this message. 
 
     String category = (String) jsonObject.get("category"); 
 
     String from = (String) jsonObject.get("from"); 
 
     @SuppressWarnings("unchecked") 
 
     Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); 
 
     payload.put("ECHO", "Application: " + category); 
 

 
     // Send an ECHO response back 
 
     String echo = createJsonMessage(from, nextMessageId(), payload, 
 
       "echo:CollapseKey", null, false); 
 

 
     try { 
 
      sendDownstreamMessage(echo); 
 
     } catch (NotConnectedException e) { 
 
      logger.log(Level.WARNING, "Not connected anymore, echo message is not sent", e); 
 
     } 
 
     
 
    } 
 

 
    /** 
 
    * Handles an ACK. 
 
    * 
 
    * <p>Logs a INFO message, but subclasses could override it to 
 
    * properly handle ACKs. 
 
    */ 
 
    protected void handleAckReceipt(Map<String, Object> jsonObject) { 
 
     String messageId = (String) jsonObject.get("message_id"); 
 
     String from = (String) jsonObject.get("from"); 
 
     logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",messageId: " + messageId); 
 
    } 
 

 
    /** 
 
    * Handles a NACK. 
 
    * 
 
    * <p>Logs a INFO message, but subclasses could override it to 
 
    * properly handle NACKs. 
 
    */ 
 
    protected void handleNackReceipt(Map<String, Object> jsonObject) { 
 
     String messageId = (String) jsonObject.get("message_id"); 
 
     String from = (String) jsonObject.get("from"); 
 
     logger.log(Level.INFO, "handleNackReceipt() from: " + from + ",messageId: " + messageId); 
 
    } 
 

 
    protected void handleControlMessage(Map<String, Object> jsonObject) { 
 
     logger.log(Level.INFO, "handleControlMessage(): " + jsonObject); 
 
     String controlType = (String) jsonObject.get("control_type"); 
 
     if ("CONNECTION_DRAINING".equals(controlType)) { 
 
      connectionDraining = true; 
 
     } else { 
 
      logger.log(Level.INFO, "Unrecognized control type: %s. This could happen if new features are " + "added to the CCS protocol.", 
 
        controlType); 
 
     } 
 
    } 
 

 
    /** 
 
    * Creates a JSON encoded GCM message. 
 
    * 
 
    * @param to RegistrationId of the target device (Required). 
 
    * @param messageId Unique messageId for which CCS sends an 
 
    *   "ack/nack" (Required). 
 
    * @param payload Message content intended for the application. (Optional). 
 
    * @param collapseKey GCM collapse_key parameter (Optional). 
 
    * @param timeToLive GCM time_to_live parameter (Optional). 
 
    * @param delayWhileIdle GCM delay_while_idle parameter (Optional). 
 
    * @return JSON encoded GCM message. 
 
    */ 
 
    public static String createJsonMessage(String to, String messageId, 
 
      Map<String, String> payload, String collapseKey, Long timeToLive, 
 
      Boolean delayWhileIdle) { 
 
     Map<String, Object> message = new HashMap<String, Object>(); 
 
     message.put("to", to); 
 
     if (collapseKey != null) { 
 
      message.put("collapse_key", collapseKey); 
 
     } 
 
     if (timeToLive != null) { 
 
      message.put("time_to_live", timeToLive); 
 
     } 
 
     if (delayWhileIdle != null && delayWhileIdle) { 
 
      message.put("delay_while_idle", true); 
 
     } 
 
     message.put("message_id", messageId); 
 
     message.put("data", payload); 
 
     return JSONValue.toJSONString(message); 
 
    } 
 

 
    /** 
 
    * Creates a JSON encoded ACK message for an upstream message received 
 
    * from an application. 
 
    * 
 
    * @param to RegistrationId of the device who sent the upstream message. 
 
    * @param messageId messageId of the upstream message to be acknowledged to CCS. 
 
    * @return JSON encoded ack. 
 
    */ 
 
     protected static String createJsonAck(String to, String messageId) { 
 
     Map<String, Object> message = new HashMap<String, Object>(); 
 
     message.put("message_type", "ack"); 
 
     message.put("to", to); 
 
     message.put("message_id", messageId); 
 
     return JSONValue.toJSONString(message); 
 
    } 
 

 
    /** 
 
    * Connects to GCM Cloud Connection Server using the supplied credentials. 
 
    * 
 
    * @param senderId Your GCM project number 
 
    * @param apiKey API Key of your project 
 
    */ 
 
    public void connect(String senderId, String apiKey) 
 
      throws XMPPException, IOException, SmackException { 
 
    \t XMPPTCPConnectionConfiguration config = 
 
    \t \t \t XMPPTCPConnectionConfiguration.builder() 
 
    \t \t \t .setServiceName(GCM_SERVER) 
 
    \t \t  .setHost(GCM_SERVER) 
 
    \t \t  .setCompressionEnabled(false) 
 
    \t \t  .setPort(GCM_PORT) 
 
    \t \t  .setConnectTimeout(30000) 
 
    \t \t  .setSecurityMode(SecurityMode.disabled) 
 
    \t \t  .setSendPresence(false) 
 
    \t \t  .setSocketFactory(SSLSocketFactory.getDefault()) 
 
    \t \t  .build(); 
 
    \t 
 
     connection = new XMPPTCPConnection(config); 
 
     
 
     //disable Roster as I don't think this is supported by GCM 
 
     Roster roster = Roster.getInstanceFor(connection); 
 
     roster.setRosterLoadedAtLogin(false); 
 

 
     logger.info("Connecting..."); 
 
     connection.connect(); 
 

 
     connection.addConnectionListener(new LoggingConnectionListener()); 
 

 
     // Handle incoming packets 
 
     connection.addAsyncStanzaListener(new MyStanzaListener() , new MyStanzaFilter()); 
 

 
     // Log all outgoing packets 
 
     connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter()); 
 

 
     connection.login(senderId + "@gcm.googleapis.com" , apiKey); 
 
     
 
    } 
 
    
 
    private class MyStanzaFilter implements StanzaFilter 
 
    { 
 
    
 
\t \t \t @Override 
 
\t \t \t public boolean accept(Stanza arg0) { 
 
\t \t \t \t // TODO Auto-generated method stub 
 
\t \t \t \t if(arg0.getClass() == Stanza.class) 
 
\t \t \t \t \t return true; 
 
\t \t \t \t else 
 
\t \t \t \t { 
 
\t \t \t \t \t if(arg0.getTo()!= null) 
 
\t \t \t \t \t \t if(arg0.getTo().startsWith(YOUR_PROJECT_ID)) 
 
\t \t \t \t \t \t \t return true; 
 
\t \t \t \t 
 
\t \t \t \t } 
 
\t \t \t \t 
 
\t \t \t \t return false; 
 
\t \t \t } 
 
    } 
 
    
 
    private class MyStanzaListener implements StanzaListener{ 
 
\t \t 
 
     @Override 
 
     public void processPacket(Stanza packet) { 
 
      logger.log(Level.INFO, "Received: " + packet.toXML()); 
 
      Message incomingMessage = (Message) packet; 
 
      GcmPacketExtension gcmPacket = 
 
        (GcmPacketExtension) incomingMessage. 
 
        getExtension(GCM_NAMESPACE); 
 
      String json = gcmPacket.getJson(); 
 
      try { 
 
       @SuppressWarnings("unchecked") 
 
       Map<String, Object> jsonObject = 
 
         (Map<String, Object>) JSONValue. 
 
         parseWithException(json); 
 

 
       // present for "ack"/"nack", null otherwise 
 
       Object messageType = jsonObject.get("message_type"); 
 

 
       if (messageType == null) { 
 
        // Normal upstream data message 
 
        handleUpstreamMessage(jsonObject); 
 

 
        // Send ACK to CCS 
 
        String messageId = (String) jsonObject.get("message_id"); 
 
        String from = (String) jsonObject.get("from"); 
 
        String ack = createJsonAck(from, messageId); 
 
        send(ack); 
 
       } else if ("ack".equals(messageType.toString())) { 
 
         // Process Ack 
 
         handleAckReceipt(jsonObject); 
 
       } else if ("nack".equals(messageType.toString())) { 
 
         // Process Nack 
 
         handleNackReceipt(jsonObject); 
 
       } else if ("control".equals(messageType.toString())) { 
 
         // Process control message 
 
         handleControlMessage(jsonObject); 
 
       } else { 
 
         logger.log(Level.WARNING, 
 
           "Unrecognized message type (%s)", 
 
           messageType.toString()); 
 
       } 
 
      } catch (ParseException e) { 
 
       logger.log(Level.SEVERE, "Error parsing JSON " + json, e); 
 
      } catch (Exception e) { 
 
       logger.log(Level.SEVERE, "Failed to process packet", e); 
 
      } 
 
     } 
 
    
 
    } 
 
    
 
    private class MyStanzaInterceptor implements StanzaListener 
 
    { 
 
    \t @Override 
 
     public void processPacket(Stanza packet) { 
 
    \t \t logger.log(Level.INFO, "Sent: {0}", packet.toXML()); 
 
    \t } 
 
    \t 
 
    } 
 
    
 

 
    public static void main(String[] args) throws Exception { 
 
     
 
     SmackCcsClient ccsClient = new SmackCcsClient(); 
 

 
     ccsClient.connect(YOUR_PROJECT_ID, YOUR_API_KEY); 
 
     
 
     // Send a sample hello downstream message to a device. 
 
     String messageId = ccsClient.nextMessageId(); 
 
     Map<String, String> payload = new HashMap<String, String>(); 
 
     payload.put("Message", "Ahha, it works!"); 
 
     payload.put("CCS", "Dummy Message"); 
 
     payload.put("EmbeddedMessageId", messageId); 
 
     String collapseKey = "sample"; 
 
     Long timeToLive = 10000L; 
 
     String message = createJsonMessage(YOUR_PHONE_REG_ID, messageId, payload, 
 
       collapseKey, timeToLive, true); 
 

 
     ccsClient.sendDownstreamMessage(message); 
 
     logger.info("Message sent."); 
 
     
 
     //crude loop to keep connection open for receiving messages 
 
     while(true) 
 
     {;} 
 
    } 
 

 
    /** 
 
    * XMPP Packet Extension for GCM Cloud Connection Server. 
 
    */ 
 
    private static final class GcmPacketExtension extends DefaultExtensionElement { 
 

 
     private final String json; 
 

 
     public GcmPacketExtension(String json) { 
 
     \t super(GCM_ELEMENT_NAME, GCM_NAMESPACE); 
 
      this.json = json; 
 
     } 
 

 
     public String getJson() { 
 
      return json; 
 
     } 
 

 
     @Override 
 
     public String toXML() { 
 
      return String.format("<%s xmlns=\"%s\">%s</%s>", 
 
        GCM_ELEMENT_NAME, GCM_NAMESPACE, 
 
        StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); 
 
     } 
 

 
     public Stanza toPacket() { 
 
      Message message = new Message(); 
 
      message.addExtension(this); 
 
      return message; 
 
     } 
 
    } 
 

 
    private static final class LoggingConnectionListener 
 
      implements ConnectionListener { 
 

 
     @Override 
 
     public void connected(XMPPConnection xmppConnection) { 
 
      logger.info("Connected."); 
 
     } 
 
     
 

 
     @Override 
 
     public void reconnectionSuccessful() { 
 
      logger.info("Reconnecting.."); 
 
     } 
 

 
     @Override 
 
     public void reconnectionFailed(Exception e) { 
 
      logger.log(Level.INFO, "Reconnection failed.. ", e); 
 
     } 
 

 
     @Override 
 
     public void reconnectingIn(int seconds) { 
 
      logger.log(Level.INFO, "Reconnecting in %d secs", seconds); 
 
     } 
 

 
     @Override 
 
     public void connectionClosedOnError(Exception e) { 
 
      logger.info("Connection closed on error."); 
 
     } 
 

 
     @Override 
 
     public void connectionClosed() { 
 
      logger.info("Connection closed."); 
 
     } 
 

 
\t \t @Override 
 
\t \t public void authenticated(XMPPConnection arg0, boolean arg1) { 
 
\t \t \t // TODO Auto-generated method stub 
 
\t \t \t 
 
\t \t } 
 
    } 
 
}

Я также импортировал следующие внешние баночки: (! Они не могут все быть необходимы, но большинство из них)

JSon-простого-1.1.1.jar

jxmpp-core-0.4.1.jar

jxmpp-util-cache-0.5.0-alpha2.jar

minidns-0.1.3.jar

Обще-каротаж 1.2.jar

HttpClient-4.3.4.jar

xpp3_xpath-1.1.4c.jar

xpp3-1.1 .4c.jar

Для Клиента я использовал образец образца GCM here. (Прокрутите вниз страницу для ссылки источника)

Надеюсь, это поможет кому-то!

[23-Oct-2015] Я редактирую этот ответ для тех, кто использует Gradle ... ниже приведены все зависимости, которые мне нужны для его компиляции (добавьте в конец файла build.gradle).

dependencies { 
    compile 'com.googlecode.json-simple:json-simple:1.1.1' 
    compile 'org.igniterealtime.smack:smack-java7:4.1.4' 
    compile 'org.igniterealtime.smack:smack-tcp:4.1.4' 
    compile 'org.igniterealtime.smack:smack-im:4.1.4' 
    compile 'org.jxmpp:jxmpp-core:0.5.0-alpha6' 
    compile 'org.jxmpp:jxmpp-util-cache:0.5.0-alpha6' 
} 
+0

Я искал то же самое. Благодаря тонну. Сэкономил много работы для меня –

+1

Вам также нужна зависимость в smack-java7. Вот зависимости для проекта maven. com.googlecode.json-простой: JSON-простой: 1.1.1, org.igniterealtime.smack: шлепок-java7: 4.1.0, org.igniterealtime.smack: шлепок-ТКФ: 4.1.0, орг .igniterealtime.smack: шлепок-им: 4.1.0. –

+0

Спасибо за это. Какова лицензия на этот код? Могу ли я использовать его в своем проекте? – user152468

1

Существует две справочные реализации, предоставляемые Google для сервера облачных соединений GCM (конечная точка XMPP).

И здесь:

https://github.com/googlesamples/friendlyping/tree/master/server

Сервер Java использует библиотеку Смак XMPP.

Сервер Go использует собственный Go-GCM библиотеки от Google - https://github.com/google/go-gcm

Сервер Go также используется в примере GCM площадка - https://github.com/googlesamples/gcm-playground - так похоже на сервер Go может быть предпочитаемой Google. Будучи Go, он может быть развернут без каких-либо зависимостей, что является преимуществом по сравнению с Java-сервером.

Смежные вопросы