Java tutorial
/* * Copyright 2012 - 2014 Weald Technology Trading Limited * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.wealdtech.gcm; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; import com.wealdtech.ServerError; import com.wealdtech.jackson.WealdMapper; import org.jivesoftware.smack.*; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.DefaultPacketExtension; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.PacketExtensionProvider; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlpull.v1.XmlPullParser; import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * A CCS client for Google Cloud Messaging, with the ability to send and receive messages. */ public class GCMClient { private static final Logger LOGGER = LoggerFactory.getLogger(GCMClient.class); public static final String GCM_SERVER = "gcm.googleapis.com"; public static final int GCM_PORT = 5235; public static final String GCM_ELEMENT_NAME = "gcm"; public static final String GCM_NAMESPACE = "google:mobile:data"; static Random random = new Random(); XMPPConnection connection; ConnectionConfiguration config; /** * XMPP Packet Extension for GCM Cloud Connection Server. */ class GcmPacketExtension extends DefaultPacketExtension { 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, json, GCM_ELEMENT_NAME); } @SuppressWarnings("unused") public Packet toPacket() { return new Message() { // // Must override toXML() because it includes a <body> // @Override // public String toXML() // { // // StringBuilder buf = new StringBuilder(); // buf.append("<message"); // if (getXmlns() != null) // { // buf.append(" xmlns=\"").append(getXmlns()).append("\""); // } // if (getLanguage() != null) // { // buf.append(" xml:lang=\"").append(getLanguage()).append("\""); // } // if (getPacketID() != null) // { // buf.append(" id=\"").append(getPacketID()).append("\""); // } // if (getTo() != null) // { // buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); // } // if (getFrom() != null) // { // buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); // } // buf.append(">"); // buf.append(GcmPacketExtension.this.toXML()); // buf.append("</message>"); // return buf.toString(); // } }; } } public GCMClient() { // Add GcmPacketExtension ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { @Override public PacketExtension parseExtension(XmlPullParser parser) throws Exception { final String json = parser.nextText(); final GcmPacketExtension packet = new GcmPacketExtension(json); return packet; } }); } /** * Returns a random message id to uniquely identify a message. * <p/> * <p>Note: This is generated by a pseudo random number generator for illustration purpose, and is not guaranteed to be unique. */ public String getRandomMessageId() { return "m-" + Long.toHexString(random.nextLong()); } /** * Sends a downstream GCM message. */ public void send(String jsonRequest) { final Packet request = new GcmPacketExtension(jsonRequest).toPacket(); try { connection.sendPacket(request); } catch (final SmackException.NotConnectedException e) { LOGGER.warn("Attempt to send a packet when not connected"); // TODO what to do with the packet? throw new ServerError("Attempt to send a packet when not connected"); } } /** * Handles an upstream data message from a device application. * <p/> * <p>This sample echo server sends an echo message back to the device. Subclasses should override this method to process an * upstream message. */ public void handleIncomingDataMessage(final Map<String, Object> jsonObject) { final String from = jsonObject.get("from").toString(); // PackageName of the application that sent this message. final String category = jsonObject.get("category").toString(); // Use the packageName as the collapseKey in the echo packet final String collapseKey = "echo:CollapseKey"; @SuppressWarnings("unchecked") final Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); payload.put("ECHO", "Application: " + category); // Send an ECHO response back final String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); send(echo); } /** * Handles an ACK. * <p/> * <p>By default, it only logs a INFO message, but subclasses could override it to properly handle ACKS. */ public void handleAckReceipt(Map<String, Object> jsonObject) { final String messageId = jsonObject.get("message_id").toString(); final String from = jsonObject.get("from").toString(); LOGGER.info("handleAckReceipt() from: {}, messageId: {}", from, messageId); } /** * Handles a NACK. * <p/> * <p>By default, it only logs a INFO message, but subclasses could override it to properly handle NACKS. */ public void handleNackReceipt(Map<String, Object> jsonObject) { final String messageId = jsonObject.get("message_id").toString(); final String from = jsonObject.get("from").toString(); LOGGER.info("handleNackReceipt() from: {}, messageId: {}", from, messageId); } /** * Creates a JSON encoded GCM message. * * @param to RegistrationId of the target device (Required). * @param messageId Unique messageId for which CCS will send 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(final String to, final String messageId, final Map<String, String> payload, final String collapseKey, final Long timeToLive, final Boolean delayWhileIdle) { final Map<String, Object> message = new HashMap<>(); 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 encodeMessageAsJson(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. */ public static String createJsonAck(String to, String messageId) { final Map<String, Object> message = new HashMap<>(); message.put("message_type", "ack"); message.put("to", to); message.put("message_id", messageId); return encodeMessageAsJson(message); } /** * Encode a message as JSON, capturing errors * @param message the message * @return a JSON string */ private static String encodeMessageAsJson(final Map<String, Object> message) { try { return WealdMapper.getMapper().writeValueAsString(message); } catch (JsonProcessingException e) { throw new ServerError( "Failed to create JSON message from \"" + message.toString() + "\": " + e.getMessage()); } } /** * Connects to GCM Cloud Connection Server using the supplied credentials. * * @param username GCM_SENDER_ID@gcm.googleapis.com * @param password API Key * * @throws XMPPException */ public void connect(String username, String password) throws XMPPException { config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled); config.setReconnectionAllowed(true); config.setRosterLoadedAtLogin(false); config.setSendPresence(false); config.setSocketFactory(SSLSocketFactory.getDefault()); config.setDebuggerEnabled(true); // NOTE: Set to true to launch a window with information about packets sent and received config.setDebuggerEnabled(true); connection = new XMPPTCPConnection(config); try { connection.connect(); } catch (final SmackException e) { throw new ServerError("Smack exception", e); } catch (final IOException e) { throw new ServerError("IO exception", e); } connection.addConnectionListener(new ConnectionListener() { @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 {} secs", seconds); } @Override public void connectionClosedOnError(Exception e) { LOGGER.info("Connection closed on error."); } @Override public void connected(final XMPPConnection connection) { LOGGER.info("Connected."); } @Override public void authenticated(final XMPPConnection connection) { LOGGER.info("Authenticated."); } @Override public void connectionClosed() { LOGGER.info("Connection closed."); } }); // Handle incoming packets connection.addPacketListener(new PacketListener() { @Override public void processPacket(Packet packet) { LOGGER.info("Received: {}", packet.toXML()); final Message incomingMessage = (Message) packet; final GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage .getExtension(GCM_NAMESPACE); final String json = gcmPacket.getJson(); try { final Map<String, Object> jsonObject = WealdMapper.getMapper().readValue(json, new TypeReference<Map<String, Object>>() { }); // present for "ack"/"nack", null otherwise final Object messageType = jsonObject.get("message_type"); if (messageType == null) { // Normal upstream data message handleIncomingDataMessage(jsonObject); // Send ACK to CCS String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); 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 { LOGGER.warn("Unrecognized message type {}", messageType.toString()); } } catch (JsonMappingException e) { LOGGER.error("Error mapping JSON {}: {}", json, e.getMessage()); } catch (JsonParseException e) { LOGGER.error("Error parsing JSON {}: {}", json, e.getMessage()); } catch (IOException e) { LOGGER.error("General IO error", e); } } }, new PacketTypeFilter(Message.class)); // Log all outgoing packets connection.addPacketInterceptor(new PacketInterceptor() { @Override public void interceptPacket(Packet packet) { LOGGER.info("Sent: {}", packet.toXML()); } }, new PacketTypeFilter(Message.class)); try { connection.login(username, password); } catch (SmackException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { final String userName = "your_sender_id" + "@gcm.googleapis.com"; final String password = "your_api_key"; final GCMClient ccsClient = new GCMClient(); try { ccsClient.connect(userName, password); } catch (XMPPException e) { e.printStackTrace(); } // Send a sample hello downstream message to a device. final String toRegId = "RegistrationIdOfTheTargetDevice"; final String messageId = ccsClient.getRandomMessageId(); final Map<String, String> payload = new HashMap<>(); payload.put("Hello", "World"); payload.put("CCS", "Dummy Message"); payload.put("EmbeddedMessageId", messageId); final String collapseKey = "sample"; final Long timeToLive = 10000L; final Boolean delayWhileIdle = true; ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey, timeToLive, delayWhileIdle)); } }