Java tutorial
/* * Copyright (c) 2014. * * BaasBox - info-at-baasbox.com * * 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.baasbox.service.push.providers; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import com.baasbox.configuration.IosCertificateHandler; import com.baasbox.exception.BaasBoxPushException; import com.baasbox.service.logging.BaasBoxLogger; import com.baasbox.service.logging.PushLogger; import com.baasbox.service.push.PushNotInitializedException; import com.baasbox.service.push.providers.Factory.ConfigurationKeys; import com.baasbox.util.ConfigurationFileContainer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.notnoop.apns.APNS; import com.notnoop.apns.ApnsDelegate; import com.notnoop.apns.ApnsNotification; import com.notnoop.apns.ApnsService; import com.notnoop.apns.DeliveryError; import com.notnoop.apns.PayloadBuilder; import com.notnoop.exceptions.NetworkIOException; public class APNServer extends PushProviderAbstract { private String certificate; private String password; private boolean sandbox; private int timeout; private boolean isInit = false; private ApnsDelegate apnDelegate = new ApnsDelegate() { PushLogger pushLogger = PushLogger.getInstance(); @Override public void cacheLengthExceeded(int arg0) { pushLogger.addMessage("The resend cache needed a bigger size: %d", arg0); } @Override public void connectionClosed(DeliveryError err, int msgId) { pushLogger.addMessage( "The connection was closed and/or an error packet was received. Message id: %d error: %s error code: %d", msgId, err.name(), err.code()); } @Override public void messageSendFailed(ApnsNotification notification, Throwable e) { pushLogger.addMessage("***** Error sending the message:"); if (notification != null) { pushLogger.addMessage("** message : ", notification); } else { pushLogger.addMessage("** unfortunately there is no info to log but the error.."); } pushLogger.addMessage("** error: %s", ExceptionUtils.getMessage(e)); } @Override public void messageSent(ApnsNotification notification, boolean resent) { if (resent) pushLogger.addMessage("+++ Message %s was sent after an error", notification); else pushLogger.addMessage("+++ Message %s was sent", notification); } @Override public void notificationsResent(int arg0) { pushLogger.addMessage( "..%d message(s) has/ve queued for resending due to an error-response from server", arg0); } }; APNServer() { } @Override public boolean send(String message, List<String> deviceid, JsonNode bodyJson) throws Exception { PushLogger pushLogger = PushLogger.getInstance(); pushLogger.addMessage("............ APN Push Message: -%s- to the device(s) %s", message, deviceid); ApnsService service = null; try { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("APN Push message: " + message + " to the device " + deviceid); if (!isInit) { pushLogger.addMessage("............ APNS is not initialized!"); return true; } String payload = null; try { service = getService(); } catch (com.notnoop.exceptions.InvalidSSLConfig e) { pushLogger.addMessage("Error sending push notification ..."); pushLogger.addMessage(" Exception is: %s ", ExceptionUtils.getStackTrace(e)); BaasBoxLogger.error("Error sending push notification"); throw new PushNotInitializedException( "Error decrypting certificate.Verify your password for given certificate"); //icallbackPush.onError(ExceptionUtils.getMessage(e)); } JsonNode contentAvailableNode = bodyJson.findValue("content-available"); Integer contentAvailable = null; if (!(contentAvailableNode == null)) { if (!(contentAvailableNode.isInt())) throw new PushContentAvailableFormatException( "Content-available MUST be an Integer (1 for silent notification)"); contentAvailable = contentAvailableNode.asInt(); } JsonNode categoryNode = bodyJson.findValue("category"); String category = null; if (!(categoryNode == null)) { if (!(categoryNode.isTextual())) throw new PushCategoryFormatException("Category MUST be a String"); category = categoryNode.asText(); } JsonNode soundNode = bodyJson.findValue("sound"); String sound = null; if (!(soundNode == null)) { if (!(soundNode.isTextual())) throw new PushSoundKeyFormatException("Sound value MUST be a String"); sound = soundNode.asText(); } JsonNode actionLocKeyNode = bodyJson.findValue("actionLocalizedKey"); String actionLocKey = null; if (!(actionLocKeyNode == null)) { if (!(actionLocKeyNode.isTextual())) throw new PushActionLocalizedKeyFormatException("ActionLocalizedKey MUST be a String"); actionLocKey = actionLocKeyNode.asText(); } JsonNode locKeyNode = bodyJson.findValue("localizedKey"); String locKey = null; if (!(locKeyNode == null)) { if (!(locKeyNode.isTextual())) throw new PushLocalizedKeyFormatException("LocalizedKey MUST be a String"); locKey = locKeyNode.asText(); } JsonNode locArgsNode = bodyJson.get("localizedArguments"); List<String> locArgs = new ArrayList<String>(); if (!(locArgsNode == null)) { if (!(locArgsNode.isArray())) throw new PushLocalizedArgumentsFormatException( "LocalizedArguments MUST be an Array of String"); for (JsonNode locArgNode : locArgsNode) { if (locArgNode.isNumber()) throw new PushLocalizedArgumentsFormatException( "LocalizedArguments MUST be an Array of String"); locArgs.add(locArgNode.toString()); } } JsonNode customDataNodes = bodyJson.get("custom"); Map<String, JsonNode> customData = new HashMap<String, JsonNode>(); if (!(customDataNodes == null)) { customData.put("custom", customDataNodes); } JsonNode badgeNode = bodyJson.findValue("badge"); int badge = 0; if (!(badgeNode == null)) { if (!(badgeNode.isNumber())) throw new PushBadgeFormatException("Badge value MUST be a number"); else badge = badgeNode.asInt(); } if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("APN Push message: " + message + " to the device " + deviceid + " with sound: " + sound + " with badge: " + badge + " with Action-Localized-Key: " + actionLocKey + " with Localized-Key: " + locKey); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Localized arguments: " + locArgs.toString()); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Custom Data: " + customData.toString()); pushLogger.addMessage("APN Push message: " + message + " to the device " + deviceid + " with sound: " + sound + " with badge: " + badge + " with Action-Localized-Key: " + actionLocKey + " with Localized-Key: " + locKey); pushLogger.addMessage("Localized arguments: " + locArgs.toString()); pushLogger.addMessage("Custom Data: " + customData.toString()); pushLogger.addMessage("Timeout: " + timeout); PayloadBuilder payloadBuilder = APNS.newPayload().alertBody(message).sound(sound) .actionKey(actionLocKey).localizedKey(locKey).localizedArguments(locArgs).badge(badge) .customFields(customData).category(category); if (contentAvailable != null && contentAvailable.intValue() == 1) { payloadBuilder.instantDeliveryOrSilentNotification(); } payload = payloadBuilder.build(); Collection<? extends ApnsNotification> result = null; if (timeout <= 0) { try { result = service.push(deviceid, payload); } catch (NetworkIOException e) { pushLogger.addMessage("Error sending push notification ..."); pushLogger.addMessage(" Exception is: %s ", ExceptionUtils.getStackTrace(e)); BaasBoxLogger.error("Error sending push notification"); BaasBoxLogger.error(ExceptionUtils.getStackTrace(e)); throw new PushNotInitializedException("Error processing certificate, maybe it's revoked"); //icallbackPush.onError(ExceptionUtils.getMessage(e)); } } else { try { Date expiry = new Date(Long.MAX_VALUE); pushLogger.addMessage("Timeout is > 0 (%d), expiration date is set to %s", timeout, expiry.toString()); result = service.push(deviceid, payload, expiry); } catch (NetworkIOException e) { pushLogger.addMessage("Error sending push notification ..."); pushLogger.addMessage(" Exception is: %s ", ExceptionUtils.getStackTrace(e)); BaasBoxLogger.error("Error sending enhanced push notification"); BaasBoxLogger.error(ExceptionUtils.getStackTrace(e)); throw new PushNotInitializedException("Error processing certificate, maybe it's revoked"); //icallbackPush.onError(ExceptionUtils.getMessage(e)); } } if (result != null) { Iterator<? extends ApnsNotification> it = result.iterator(); while (it.hasNext()) { ApnsNotification item = it.next(); //item. } } //icallbackPush.onSuccess(); return false; } catch (Exception e) { pushLogger.addMessage("Error sending push notification (APNS)..."); pushLogger.addMessage(ExceptionUtils.getMessage(e)); throw e; } finally { if (service != null) service.stop(); } } public static boolean validatePushPayload(JsonNode bodyJson) throws BaasBoxPushException { JsonNode soundNode = bodyJson.findValue("sound"); JsonNode contentAvailableNode = bodyJson.findValue("content-available"); Integer contentAvailable = null; if (!(contentAvailableNode == null)) { if (!(contentAvailableNode.isInt())) throw new PushContentAvailableFormatException( "Content-available MUST be an Integer (1 for silent notification)"); contentAvailable = contentAvailableNode.asInt(); } if (contentAvailable != null && contentAvailable != 1) { JsonNode categoryNode = bodyJson.findValue("category"); String category = null; if (!(categoryNode == null)) { if (!(categoryNode.isTextual())) throw new PushCategoryFormatException("Category MUST be a String"); category = categoryNode.asText(); } String sound = null; if (!(soundNode == null)) { if (!(soundNode.isTextual())) throw new PushSoundKeyFormatException("Sound value MUST be a String"); sound = soundNode.asText(); } JsonNode actionLocKeyNode = bodyJson.findValue("actionLocalizedKey"); String actionLocKey = null; if (!(actionLocKeyNode == null)) { if (!(actionLocKeyNode.isTextual())) throw new PushActionLocalizedKeyFormatException("ActionLocalizedKey MUST be a String"); actionLocKey = actionLocKeyNode.asText(); } JsonNode locKeyNode = bodyJson.findValue("localizedKey"); String locKey = null; if (!(locKeyNode == null)) { if (!(locKeyNode.isTextual())) throw new PushLocalizedKeyFormatException("LocalizedKey MUST be a String"); locKey = locKeyNode.asText(); } JsonNode locArgsNode = bodyJson.get("localizedArguments"); List<String> locArgs = new ArrayList<String>(); if (!(locArgsNode == null)) { if (!(locArgsNode.isArray())) throw new PushLocalizedArgumentsFormatException( "LocalizedArguments MUST be an Array of String"); for (JsonNode locArgNode : locArgsNode) { if (!locArgNode.isTextual()) throw new PushLocalizedArgumentsFormatException( "LocalizedArguments MUST be an Array of String"); locArgs.add(locArgNode.toString()); } } JsonNode customDataNodes = bodyJson.get("custom"); Map<String, JsonNode> customData = new HashMap<String, JsonNode>(); if (!(customDataNodes == null)) { if (customDataNodes.isTextual()) { customData.put("custom", customDataNodes); } else { for (JsonNode customDataNode : customDataNodes) { customData.put("custom", customDataNodes); } } } JsonNode badgeNode = bodyJson.findValue("badge"); int badge = 0; if (!(badgeNode == null)) { if (!(badgeNode.isNumber())) throw new PushBadgeFormatException("Badge value MUST be a number"); else badge = badgeNode.asInt(); } } return true; } private ApnsService getService() { ApnsService service; PushLogger pushLogger = PushLogger.getInstance(); if (!sandbox) { service = APNS.newService().withCert(certificate, password).withProductionDestination() .withDelegate(apnDelegate).build(); pushLogger.addMessage("............ APNS production mode"); } else { service = APNS.newService().withCert(certificate, password).withSandboxDestination() .withDelegate(apnDelegate).build(); pushLogger.addMessage("............ APNS sandbox mode"); } return service; } @Override public void setConfiguration(ImmutableMap<ConfigurationKeys, String> configuration) { String json = configuration.get(ConfigurationKeys.IOS_CERTIFICATE); String name = null; ObjectMapper mp = new ObjectMapper(); try { ConfigurationFileContainer cfc = mp.readValue(json, ConfigurationFileContainer.class); if (cfc == null) { isInit = false; return; } name = cfc.getName(); } catch (Exception e) { BaasBoxLogger.error(ExceptionUtils.getMessage(e)); throw new RuntimeException(e); } if (name != null && !name.equals("null")) { File f = IosCertificateHandler.getCertificate(name); this.certificate = f.getAbsolutePath(); } password = configuration.get(ConfigurationKeys.IOS_CERTIFICATE_PASSWORD); sandbox = configuration.get(ConfigurationKeys.IOS_SANDBOX).equalsIgnoreCase("true"); timeout = Integer.parseInt(configuration.get(ConfigurationKeys.APPLE_TIMEOUT)); isInit = StringUtils.isNotEmpty(this.certificate) && StringUtils.isNotEmpty(password); } }