Java tutorial
/** * Copyright 2015 Google Inc. All Rights Reserved. * Modifications copyright 2016 Thomas Elsensohn. All Rights Reserved. * 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 itcc.li.vereinsaustausch.fcmServerComponent; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.util.StringUtils; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.Query; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; public class VereinsaustauschFcmServer extends AbstractGcmServer { // Keys private static final String ACTION_KEY = "action"; private static final String CLIENT_KEY = "client"; private static final String CLIENTS_KEY = "clients"; private static final String DATA_KEY = "data"; // Actions private static final String REGISTER_NEW_CLIENT = "register_new_client"; private static final String SEND_CLIENT_LIST = "send_client_list"; private static final String CLICK_ACTION = "ping_received"; private static final String BROADCAST_NEW_CLIENT = "broadcast_new_client"; private static final String NEW_CLIENT_TOPIC = "/topics/newclient"; private static final String PING_CLIENT = "ping_client"; private static final String PING_ICON = "mipmap/ic_launcher"; private static final String PING_TITLE = "Friendly Ping!"; private static final String NEW_MESSAGE = "new_message"; private static final String SENDER_ID = "596165323328"; private static final String SENDER_KEY = "AIzaSyB5FL58bVV3pZ5RiLe3pHyZtrWPl4eJG2U"; public static final String SERVICE_NAME = "Vereinsaustausch Fcm-Server"; // Store of clients registered with VereinsaustauschFcmServer. private Map<String, ClientModel> clientMap; static final Logger logger = Logger.getLogger("VereinsaustauschFcmServer"); // Gson helper to assist with going to and from JSON and Client. private Gson gson; private Query dbLastChatEntry; private ChildEventListener messageListener; private boolean firstInitial = true; public VereinsaustauschFcmServer(String apiKey, String senderId) { super(apiKey, senderId, SERVICE_NAME); clientMap = new ConcurrentHashMap<String, ClientModel>(); ClientModel serverClient = createServerClient(); clientMap.put(serverClient.registrationToken, serverClient); gson = new GsonBuilder().create(); dbLastChatEntry = FirebaseDatabase.getInstance().getReference().child("Vereine").child("itcc") .child("ChatLog").limitToLast(1); messageListener = new ChildEventListener() { @Override public void onCancelled(DatabaseError arg0) { // TODO Auto-generated method stub } @Override public void onChildAdded(DataSnapshot arg0, String arg1) { if (!firstInitial) { sendNotificationToClients( arg0.getValue(itcc.li.vereinsaustausch.fcmServerComponent.ChatEntry.class).message); } else { firstInitial = false; } } @Override public void onChildChanged(DataSnapshot arg0, String arg1) { // TODO Auto-generated method stub } @Override public void onChildMoved(DataSnapshot arg0, String arg1) { // TODO Auto-generated method stub } @Override public void onChildRemoved(DataSnapshot arg0) { // TODO Auto-generated method stub } }; dbLastChatEntry.addChildEventListener(messageListener); } /** * Add given client to Map of Clients. * * @param client * Client to be added. */ private void addClient(ClientModel client) { clientMap.put(client.registrationToken, client); } /** * Broadcast the newly registered client to clients that have already been * registered. The broadcast is sent via the PubSub topic "/topics/newuser" * all registered clients should be subscribed to this topic. * * @param client * Newly registered client. */ private void broadcastNewClient(ClientModel client) { JsonObject jBroadcast = new JsonObject(); JsonObject jData = new JsonObject(); jData.addProperty(ACTION_KEY, BROADCAST_NEW_CLIENT); JsonObject jClient = gson.toJsonTree(client).getAsJsonObject(); jData.add(CLIENT_KEY, jClient); jBroadcast.add(DATA_KEY, jData); this.send(NEW_CLIENT_TOPIC, jBroadcast); } /** * Create a Client object to be used in responses to pings to the server. * * @return Server Client. */ private ClientModel createServerClient() { ClientModel client = new ClientModel(); client.name = "ThomasServer"; client.registrationToken = SENDER_ID + "@gcm.googleapis.com"; client.profilePictureUrl = ""; return client; } @Override public void onMessage(String from, JsonObject jData) { if (jData.has("action")) { String action = jData.get("action").getAsString(); if (action.equals(REGISTER_NEW_CLIENT)) { registerNewClient(jData); } else if (action.equals("ping_client")) { String toToken = jData.get("to").getAsString(); String senderToken = jData.get("sender").getAsString(); if (StringUtils.isNotEmpty(toToken) && StringUtils.isNotEmpty(senderToken)) { pingClient(toToken, senderToken); } else { logger.info("Unable to ping unless to and sender tokens are available."); } } } else { logger.info("No action found. Message received missing action."); } } /** * Send message to Client with matching toToken. The validity of to and * sender tokens should be check before this method is called. * * @param toToken * Token of recipient of ping. * @param senderToken * Token of sender of ping. */ void pingClient(String toToken, String senderToken) { ClientModel senderClient; // If the server is the recipient of the ping, send ping to sender, // otherwise send ping to // toToken. if (toToken.equals(SENDER_ID + "@" + AbstractGcmServer.GCM_HOST)) { senderClient = clientMap.get(toToken); toToken = senderToken; } else { senderClient = clientMap.get(senderToken); } JsonObject jPing = new JsonObject(); JsonObject jData = new JsonObject(); jData.addProperty(ACTION_KEY, PING_CLIENT); jData.addProperty(SENDER_KEY, senderClient.registrationToken); // Create notification that is handled appropriately on the receiving // platform. JsonObject jNotification = new JsonObject(); jNotification.addProperty("body", senderClient.name + " is pinging you."); jNotification.addProperty("title", PING_TITLE); jNotification.addProperty("icon", PING_ICON); jNotification.addProperty("sound", "default"); jNotification.addProperty("click_action", CLICK_ACTION); jPing.add(DATA_KEY, jData); jPing.add("notification", jNotification); this.send(toToken, jPing); } /** * Create Client from given JSON data, add client to client list, broadcast * newly registered client to all previously registered clients and send * client list to new client. * * @param jData * JSON data containing properties of new Client. */ void registerNewClient(JsonObject jData) { ClientModel newClient = gson.fromJson(jData, ClientModel.class); if (newClient.isValid()) { addClient(newClient); broadcastNewClient(newClient); sendClientList(newClient); logger.log(Level.INFO, "Register new client"); } else { logger.log(Level.WARNING, "Could not unpack received data into a Client."); } } /** * Send client list to newly registered client. When a new client is * registered, that client must be informed about the other registered * clients. * * @param client * Newly registered client. */ private void sendClientList(ClientModel client) { ArrayList<ClientModel> clientList = new ArrayList<ClientModel>(); for (Entry<String, ClientModel> clientEntry : clientMap.entrySet()) { ClientModel currentClient = clientEntry.getValue(); if (currentClient.registrationToken != client.registrationToken) { clientList.add(currentClient); } } JsonElement clientElements = gson.toJsonTree(clientList, new TypeToken<Collection<ClientModel>>() { }.getType()); if (clientElements.isJsonArray()) { JsonObject jSendClientList = new JsonObject(); JsonObject jData = new JsonObject(); jData.addProperty(ACTION_KEY, SEND_CLIENT_LIST); jData.add(CLIENTS_KEY, clientElements); jSendClientList.add(DATA_KEY, jData); this.send(client.registrationToken, jSendClientList); } } private void sendNotificationToClients(String message) { JsonObject jPing = new JsonObject(); JsonObject jData = new JsonObject(); jData.addProperty(ACTION_KEY, NEW_MESSAGE); jData.addProperty(SENDER_KEY, SENDER_ID); // Create notification that is handled appropriately on the receiving // platform. JsonObject jNotification = new JsonObject(); jNotification.addProperty("body", message); jNotification.addProperty("title", "Neue Nachricht"); jNotification.addProperty("icon", PING_ICON); jNotification.addProperty("sound", "default"); jNotification.addProperty("click_action", CLICK_ACTION); jPing.add(DATA_KEY, jData); jPing.add("notification", jNotification); clientMap.forEach((k, v) -> this.send(k, jPing)); } }