/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * 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 org.onebusaway.api.impl.apns; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.onebusaway.api.services.apns.ApplePushNotificationService; import org.onebusaway.utility.IOLibrary; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import com.notnoop.apns.APNS; import com.notnoop.apns.ApnsService; import com.notnoop.apns.ApnsServiceBuilder; class ApplePushNotificationServiceImpl implements ApplePushNotificationService { private static Logger _log = LoggerFactory.getLogger(ApplePushNotificationServiceImpl.class); private Resource _keystoreResource; private String _keystorePassword; private boolean _production = false; private ApnsService _service; private ScheduledExecutorService _executor; private File _inactiveDevicesPath; private ConcurrentMap<String, Date> _inactiveDevices = new ConcurrentHashMap<String, Date>(); public void setKeystoreResource(Resource keystoreResource) { _keystoreResource = keystoreResource; } public void setKeystorePassword(String keystorePassword) { _keystorePassword = keystorePassword; } public void setProduction(boolean production) { _production = production; } public void setInactiveDevicesPath(File inactiveDevicesPath) { _inactiveDevicesPath = inactiveDevicesPath; } @PostConstruct public void start() throws Exception { ApnsServiceBuilder builder = APNS.newService(); InputStream in = _keystoreResource.getInputStream(); builder.withCert(in, _keystorePassword); if (_production) builder.withProductionDestination(); else builder.withSandboxDestination(); _service = builder.build(); _service.start(); loadInactiveDevices(); _executor = Executors.newSingleThreadScheduledExecutor(); _executor.scheduleAtFixedRate(new InactiveDevicesTask(), 0, 12, TimeUnit.HOURS); } @PreDestroy public void stop() throws IOException { saveInactiveDevices(); if (_executor != null) _executor.shutdownNow(); if (_service != null) _service.stop(); } /**** * {@link ApplePushNotificationService} Interface ****/ @Override public boolean isProduction() { return _production; } @Override public void pushNotification(String deviceToken, String payload) { if (_inactiveDevices.containsKey(deviceToken)) { _log.info("attempt to send push notification to inactive device: " + deviceToken); return; } _service.push(deviceToken, payload); } /**** * Private Methods ****/ private void loadInactiveDevices() { if (_inactiveDevicesPath == null || !_inactiveDevicesPath.exists()) return; try { BufferedReader reader = IOLibrary.getFileAsBufferedReader(_inactiveDevicesPath); String line = null; Date now = new Date(); while ((line = reader.readLine()) != null) { _inactiveDevices.put(line, now); } reader.close(); } catch (IOException ex) { _log.warn("error reading inactive devices from " + _inactiveDevicesPath, ex); } } private void saveInactiveDevices() { if (_inactiveDevicesPath == null) return; try { PrintWriter writer = new PrintWriter( IOLibrary.getFileAsWriter(_inactiveDevicesPath)); for (String deviceToken : _inactiveDevices.keySet()) { writer.println(deviceToken); } writer.close(); } catch (IOException ex) { _log.warn("error reading inactive devices from " + _inactiveDevicesPath, ex); } } private class InactiveDevicesTask implements Runnable { @Override public void run() { Map<String, Date> inactiveDevices = _service.getInactiveDevices(); _inactiveDevices.putAll(inactiveDevices); saveInactiveDevices(); } } }