GCM XMPP - 启动Web应用程序服务器时出错

时间:2015-10-04 22:30:02

标签: spring tomcat xmpp google-cloud-messaging

我的后端java的目标是使用GCM XMPP接收移动消息。我的Web应用程序有spring 4.1.4和smack 4.1.4。

Smack依赖项:

<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-core</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-tcp</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-extensions</artifactId>
    <version>4.1.4</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-java7</artifactId>
    <version>4.0.1</version>
</dependency>

Bean for XMPP Connection:

@Component("CcsClientImpl")
public class CcsClientImpl {

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

    private static XMPPTCPConnection connection;

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

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

    @Value("${sender.id}")
    private String mSenderId;

    @Value("${server.api.key}")
    private String mServerApiKey;

    @Value("${gcm.xmpp.host}")
    private String mHost;

    @Value("${gcm.xmpp.port}")
    private int mPort;

    @Value("${gcm.xmpp.debuggable}")
    private boolean mDebuggable;

    @Autowired
    private ProcessorFactory processorFactory;

    //@Autowired
    public CcsClientImpl() {

        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);
            }
        });

        try {
            connect(mSenderId, mServerApiKey);
        } catch (XMPPException ex) {
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
        } catch (SmackException ex) {
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
        } catch (IOException ex) {
            logger.error("ERRO AO CONECTAR COM GCM XMPP", ex);
        }
    }

    /**
     * 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);
    }

    /// new: customized version of the standard handleIncomingDateMessage method
    /**
     * Handles an upstream data message from a device application.
     */
    public void handleIncomingDataMessage(CcsMessage msg) {
        if (msg.getPayload().get("action") != null) {
            PayloadProcessor processor = processorFactory.getProcessor(msg.getPayload().get("action"));
            processor.handleMessage(msg);
        }   
    }    

    /**
     * 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.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.info("handleNackReceipt() from: " + from + ",messageId: " + messageId);
    }

    protected void handleControlMessage(Map<String, Object> jsonObject) {
        logger.info("handleControlMessage(): " + jsonObject);
        String controlType = (String) jsonObject.get("control_type");
        if ("CONNECTION_DRAINING".equals(controlType)) {
            connectionDraining = true;
        } else {
            logger.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 serverApiKey)
            throws XMPPException, IOException, SmackException {
        XMPPTCPConnectionConfiguration config =
                XMPPTCPConnectionConfiguration.builder()
                .setServiceName(mHost)
                 .setHost(mHost)
                 .setCompressionEnabled(false)
                 .setPort(mPort)
                 .setConnectTimeout(30000)
                 .setSecurityMode(SecurityMode.disabled)
                 .setSendPresence(false)
                                .setDebuggerEnabled(mDebuggable)
                 .setSocketFactory(SSLSocketFactory.getDefault())
                .build();

        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" , serverApiKey);

        logger.info("Logged in: " + mSenderId);

    }

    private CcsMessage getMessage(Map<String, Object> jsonObject) {
        String from = jsonObject.get("from").toString();

        // PackageName of the application that sent this message.
        String category = jsonObject.get("category").toString();

        // unique id of this message
        String messageId = jsonObject.get("message_id").toString();

        @SuppressWarnings("unchecked")
        Map<String, String> payload = (Map<String, String>) jsonObject.get("data");

        CcsMessage msg = new CcsMessage(from, category, messageId, payload);

        return msg;
    }

    private class MyStanzaFilter implements StanzaFilter {

        @Override
        public boolean accept(Stanza arg0) {
            // TODO Auto-generated method stub
            if (arg0.getClass() == Stanza.class) {
                return true;
            } else {
                if (arg0.getTo() != null) {
                    if (arg0.getTo().startsWith(mSenderId)) {
                        return true;
                    }
                }

            }

            return false;
        }
    }

    private class MyStanzaListener implements StanzaListener{

        @Override
        public void processPacket(Stanza packet) {
            logger.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
                    CcsMessage msg = getMessage(jsonObject);

                    handleIncomingDataMessage(msg);

                    // 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.warn("Unrecognized message type (%s)",
                              messageType.toString());
                }
            } catch (ParseException e) {
                logger.info("Error parsing JSON " + json, e);
            } catch (Exception e) {
                logger.info("Failed to process packet", e);
            }
        }

    }

    private class MyStanzaInterceptor implements StanzaListener
    {
        @Override
        public void processPacket(Stanza packet) {
            logger.info("Sent: {0}", packet.toXML());
        }

    }


//    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) {
            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.info("Reconnection failed.. ", e);
        }

        @Override
        public void reconnectingIn(int seconds) {
            logger.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.");
        }

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

        }
    }

    @PreDestroy
    public void cleanUp() throws Exception {
        logger.info("Bean do cliente XMPP está sendo destruído...");
        if (connection.isConnected()) {
            logger.info("Conexão GCM XMPP está aberta. Desconectando...");
            connection.disconnect();
        }    
    }    

}

启动tomcat时发生错误:

Caused by: java.lang.NoClassDefFoundError: org/jivesoftware/smack/initializer/SmackAndOsgiInitializer
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2476)
    at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:857)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1282)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    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 br.com.soma.service.gcm.xmpp.CcsClientImpl.<init>(CcsClientImpl.java:73)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
    ... 60 more
Caused by: java.lang.ClassNotFoundException: org.jivesoftware.smack.initializer.SmackAndOsgiInitializer
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1313)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1164)
    ... 82 more

有人能帮助我吗?谢谢!

1 个答案:

答案 0 :(得分:0)

现在工作正常。我只是按照你的建议为smack设置一个变量。谢谢!

<properties>
   <smack.version>4.1.4</smack.version>
</properties>


<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-core</artifactId>
    <version>${smack.version}</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-tcp</artifactId>
    <version>${smack.version}</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-extensions</artifactId>
    <version>${smack.version}</version>
</dependency>
<dependency>
    <groupId>org.igniterealtime.smack</groupId>
    <artifactId>smack-java7</artifactId>
    <version>${smack.version}</version>
</dependency>