Java tutorial
/* * Numenta Platform for Intelligent Computing (NuPIC) * Copyright (C) 2015, Numenta, Inc. Unless you have purchased from * Numenta, Inc. a separate commercial license for this software code, the * following terms and conditions apply: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero Public License for more details. * * You should have received a copy of the GNU Affero Public License * along with this program. If not, see http://www.gnu.org/licenses. * * http://numenta.org/licenses/ * */ package com.numenta.core.service; import com.numenta.core.app.HTMApplication; import com.numenta.core.utils.BackgroundThreadFactory; import com.numenta.core.utils.Log; import android.app.IntentService; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.WakefulBroadcastReceiver; import java.io.IOException; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * <code>DataService</code> is a background {@link Service} responsible for * fetching data from the server and updating its local data cache. */ public class DataService extends IntentService { private static final String TAG = "DataService"; // Upload logs each hour. private static final int LOG_UPLOAD_INTERVAL = 60; // Run clean up task each hour. private static final int CLEANUP_TASK_INTERVAL = 60; // Max HTTP Connections to cache (http.maxConnections) @see // http://developer.android.com/reference/java/net/HttpURLConnection.html private static final int HTTP_CONNECTION_POOL_SIZE = 100; // I/O thread pool size private static final int IOTHREAD_POOL_SIZE = 10; // I/O thread pool private ExecutorService _ioThreadPool; // Generic worker thread pool size //private static final int WORKER_POOL_SIZE = 10; // Generic worker thread pool private ExecutorService _workerPool; // Single thread pool used to schedule periodic tasks private ScheduledExecutorService _timer; // This task will periodically upload the application logs to the server private ScheduledFuture<?> _updateLogsTask; // This task will periodically run clean up tasks private ScheduledFuture<?> _cleanupTask; // Data synchronization service private DataSyncService _dataSyncService; // Notification Services private NotificationService _notificationService; // Background worker thread factory private static final ThreadFactory WORKER_THREAD_FACTORY = new BackgroundThreadFactory("Worker"); // Background IO thread factory private static final ThreadFactory IO_THREAD_FACTORY = new BackgroundThreadFactory("IOThread"); // Background timer thread factory private static final ThreadFactory TIMER_THREAD_FACTORY = new BackgroundThreadFactory("Timer"); private final DataBinder _binder = new DataBinder(); /** * This Event is fired whenever the client fails to authenticate with the * server. */ public static final String AUTHENTICATION_FAILED_EVENT = "com.numenta.core.data.AuthenticationFailedEvent"; /** * Force client to refresh the data by downloading new data from the server */ public void forceRefresh() { _dataSyncService.forceRefresh(); } /** * Delete annotation from the server * @param annotationId The annotation ID to delete */ public boolean deleteAnnotation(String annotationId) { return _dataSyncService.deleteAnnotation(annotationId); } /** * Add new annotation associating it to the given server and the given timestamp. * The current device will also be associated with the annotation. * * @param timestamp The date and time to be annotated * @param server Instance Id associated with this annotation * @param message Annotation message * @param user User name * * @return {@code true} if the annotation was successfully added to the server */ public boolean addAnnotation(Date timestamp, String server, String message, String user) { return _dataSyncService.addAnnotation(timestamp, server, message, user); } /** * Force client to synchronize notifications with the server * * @throws IOException * @throws HTMException */ public void synchronizeNotifications() throws HTMException, IOException { _notificationService.synchronizeNotifications(); } /** * Validate user credentials and server connection */ public void checkConnection() { getIOThreadPool().execute(new Runnable() { @Override public void run() { // Try to connect to server try { HTMClient client = connectToServer(); if (client != null) { client.login(); } else { throw new HTMException("Unable to connect to server"); } } catch (AuthenticationException e) { fireAuthenticationFailedEvent(); } catch (HTMException e) { Log.e(TAG, "Unable to connect to server", e); } catch (IOException e) { Log.e(TAG, "Unable to connect to server", e); } } }); } /** * Returns {@code true} if the data service is refreshing the data */ public boolean isRefreshing() { return _dataSyncService.isRefreshing(); } @Override public IBinder onBind(Intent intent) { return _binder; } /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class DataBinder extends Binder { public DataService getService() { // Return this instance of LocalService so clients can call public // methods return DataService.this; } } /** * Creates and executes a periodic action at the given delay, and * subsequently with the given delay between the termination of one * execution and the commencement of the next. If any execution of the task * encounters an exception, subsequent executions are suppressed. Otherwise, * the task will only terminate via cancellation or termination of the * executor. * * @param task the task to execute * @param rate the delay between the termination of one execution and the * commencement of the next * @param unit the time unit of the initialDelay and delay parameters * @return a ScheduledFuture representing pending completion of the task, * and whose <tt>get()</tt> method will throw an exception upon * cancellation * @throws RejectedExecutionException if the task cannot be scheduled for * execution * @throws NullPointerException if command is null * @throws IllegalArgumentException if delay less than or equal to zero */ public ScheduledFuture<?> scheduleTask(Runnable task, long rate, TimeUnit unit) { return _timer.scheduleWithFixedDelay(task, 0, rate, unit); } /** * Returns the service {@link ScheduledExecutorService} used to schedule * background task at specific time or repeating periodic time * * @return {@link ScheduledExecutorService} */ public ScheduledExecutorService getTimerThread() { return _timer; } /** * Returns a pre-configured thread pool to be used for I/O Tasks * * @return {@link ExecutorService} for the I/O thread pool */ public ExecutorService getIOThreadPool() { return _ioThreadPool; } /** * Returns a pre-configured thread pool to be used for generic background * Tasks * * @return {@link ExecutorService} for the generic thread pool */ public ExecutorService getWorkerThreadPool() { return _workerPool; } public DataService() { super("DataService"); } public void cancelScheduledTasks() { if (_timer != null) { if (_cleanupTask != null) { _cleanupTask.cancel(false); } if (_updateLogsTask != null) { _updateLogsTask.cancel(false); } } } public void startScheduledTasks() { // Schedule clean up tasks _cleanupTask = _timer.scheduleWithFixedDelay(new Runnable() { @Override public void run() { HTMApplication.getDatabase().deleteOldRecords(); } }, CLEANUP_TASK_INTERVAL / 10, CLEANUP_TASK_INTERVAL, TimeUnit.MINUTES); // Schedule Log uploads if (HTMApplication.shouldUploadLog()) { _updateLogsTask = _timer.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { HTMApplication.getInstance().uploadLogs(); } catch (Exception e) { Log.e(TAG, "Error uploading Logs", e); } } }, LOG_UPLOAD_INTERVAL / 6, LOG_UPLOAD_INTERVAL, TimeUnit.MINUTES); } } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "Service started"); AlarmReceiver alarm = new AlarmReceiver(); alarm.startAlarm(getApplicationContext()); // Initialize thread pools _timer = Executors.newSingleThreadScheduledExecutor(TIMER_THREAD_FACTORY); _ioThreadPool = Executors.newFixedThreadPool(IOTHREAD_POOL_SIZE, IO_THREAD_FACTORY); _workerPool = Executors.newCachedThreadPool(WORKER_THREAD_FACTORY);// Executors.newFixedThreadPool(WORKER_POOL_SIZE); // Optimize HTTP connection by keeping the HTTP connections alive and // reusing them System.getProperties().setProperty("sun.net.http.errorstream.enableBuffering", "true"); System.getProperties().setProperty("http.maxConnections", String.valueOf(HTTP_CONNECTION_POOL_SIZE)); startScheduledTasks(); // Start Metric Data Sync Service _dataSyncService = HTMApplication.getInstance().createDataSyncService(this); _dataSyncService.start(); // TODO: Start Notification Service, taking into account prefs _notificationService = HTMApplication.getInstance().createNotificationService(this); _notificationService.start(); } /** * Fire {@link DataService#AUTHENTICATION_FAILED_EVENT} */ public void fireAuthenticationFailedEvent() { Intent intent = new Intent(DataService.AUTHENTICATION_FAILED_EVENT); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } /** * Attempts to stop all actively executing I/O tasks and halts the * processing of waiting I/O tasks. * <p> * This method does not wait for actively executing tasks to terminate * beyond best-effort attempts to stop processing actively executing tasks. * * @see ExecutorService#shutdownNow() */ public void cancelPendingIOTasks() { if (_ioThreadPool != null) { // Shutdown pending task. Interrupt if necessary _ioThreadPool.shutdownNow(); // Don't wait for old tasks to terminate before restarting the pool _ioThreadPool = Executors.newFixedThreadPool(IOTHREAD_POOL_SIZE, IO_THREAD_FACTORY); } } /** * Establish a connection with server. * * @return {@link HTMClient} object used to get data from the server * @throws IOException * @throws HTMException */ public HTMClient connectToServer() throws HTMException, IOException { HTMClient connection; try { connection = HTMApplication.getInstance().connectToServer(); if (connection != null) { connection.login(); HTMApplication.getInstance().setServerVersion(connection.getServerVersion()); Log.i(TAG, "Service connected to " + connection.getServerUrl() + " - Version : " + HTMApplication.getInstance().getServerVersion()); } else { Log.e(TAG, "Unable to connect to server."); } } catch (AuthenticationException e) { Log.w(TAG, "Authentication failure"); throw e; } catch (HTMException e) { Log.e(TAG, "Unable to connect to server.", e); throw e; } catch (IOException e) { Log.e(TAG, "Unable to connect to server.", e); throw e; } //if we're connecting to a new server, make sure to cancel existing scheduled tasks //and restart them so that they use the new connection cancelScheduledTasks(); startScheduledTasks(); return connection; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "Service stopped"); // Stop child Services if (_dataSyncService != null) { _dataSyncService.stop(); } if (_notificationService != null) { _notificationService.stop(); } // Stop periodic tasks if (_updateLogsTask != null) { _updateLogsTask.cancel(true); } if (_cleanupTask != null) { _cleanupTask.cancel(true); } // Shutdown thread pools if (_ioThreadPool != null) { _ioThreadPool.shutdown(); } if (_workerPool != null) { _workerPool.shutdown(); } if (_timer != null) { _timer.shutdown(); } } @Override protected void onHandleIntent(Intent intent) { Log.i(TAG, "onHandleIntent:"); // Force data synchronization try { _dataSyncService.synchronizeWithServer(); } catch (IOException e) { Log.e(TAG, "Unable to connect", e); } // Handle the case when the intent was sent from the AlarmManger if (intent != null) { WakefulBroadcastReceiver.completeWakefulIntent(intent); } } }