javamanager/javainstaller/installer/javasrc/com/nokia/mj/impl/installer/downloader/NotificationPoster.java
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 14 May 2010 15:47:24 +0300
changeset 23 98ccebc37403
parent 21 2a9601315dfc
permissions -rw-r--r--
Revision: v2.1.24 Kit: 201019

/*
* Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/


package com.nokia.mj.impl.installer.downloader;

import com.nokia.mj.impl.installer.storagehandler.OtaStatusHandler;
import com.nokia.mj.impl.installer.storagehandler.OtaStatusNotification;
import com.nokia.mj.impl.installer.utils.InstallerException;
import com.nokia.mj.impl.installer.utils.Log;
import com.nokia.mj.impl.rt.support.Jvm;

import java.io.IOException;

/**
 * NotificationPoster takes care of posting OTA status notifcations.
 * It uses a separate thread for posting pending notifications.
 * HTTP authentication is not supported for the OTA status notifications.
 */
abstract public class NotificationPoster implements Runnable
{
    // Character set used in notification body:
    // use MIDP default character set UTF-8.
    protected static final String CHARSET = "utf-8";
    // ContentType used in notification.
    protected static final String CONTENT_TYPE =
        "text/plain; charset=" + CHARSET;

    // Maximum number of HTTP redirects to be followed.
    protected static final int MAX_REDIRECT_COUNT = 5;

    // Internal states.
    protected static final int STATE_NOT_STARTED = 0;
    protected static final int STATE_POSTING = 1;
    protected static final int STATE_STOPPED = 2;

    // Internal state.
    protected int iState = STATE_NOT_STARTED;
    // Internet access point id.
    protected String iIap = null;
    // Service network access point id.
    protected String iSnap = null;
    // HTTP redirect count.
    protected int iRedirectCount = 0;

    // OtaStatusHandler instance.
    protected OtaStatusHandler iOtaStatusHandler = new OtaStatusHandler();

    /** Constructor. */
    protected NotificationPoster()
    {
    }

    /** Constructor. */
    protected NotificationPoster(String aIap, String aSnap)
    {
        iIap = aIap;
        iSnap = aSnap;
    }

    /**
     * Send OTA status notification. If notification cannot be sent,
     * use OtaStatusHandler to save it to storage for future sending.
     * This is a synchronous call and returns only after notification
     * sending has been completed.
     */
    synchronized public void notifyStatus(OtaStatusNotification aOtaStatusNotification)
    {
        if (iState == STATE_POSTING)
        {
            InstallerException.internalError
            ("NotificationPoster.notifyStatus: already posting");
        }
        if (aOtaStatusNotification == null)
        {
            InstallerException.internalError
            ("NotificationPoster.notifyStatus: null parameter");
        }
        if (!isNotificationUrl(aOtaStatusNotification.getUrl()))
        {
            // Not a valid notification URL, let's ignore it
            // but write a log warning message.
            Log.logWarning("NotificationPoster.notifyStatus: not a " +
                           "valid notification URL\n" +
                           aOtaStatusNotification);
            return;
        }
        try
        {
            iState = STATE_POSTING;
            doPost(aOtaStatusNotification, null);
        }
        catch (Throwable t)
        {
            Log.logWarning("NotificationPoster.notifyStatus: posting " +
                           "notification failed", t);
            // Posting failed, save notification to storage for
            // further retry attempts.
            aOtaStatusNotification.setLatestRetryTime
            (System.currentTimeMillis());
            iOtaStatusHandler.addNotification(aOtaStatusNotification);
        }
        finally
        {
            // Notify that waitForCompletion() can proceed.
            iState = STATE_STOPPED;
            this.notify();
        }
    }

    /**
     * Send pending OTA status notifications. Use OtaStatusHandler to
     * retrieve pending notifications and try to send them. If sending
     * fails, use OtaStatusHandler to save the remaining notifications
     * to storage for future sending attempts.
     * This is an asynchronous call and starts a new thread for
     * notification sending. Thread can be stopped with stop() method,
     * or its completion can be waited with waitForCompletion() method.
     */
    synchronized public void notifyPendingStatuses()
    {
        if (iState == STATE_POSTING)
        {
            InstallerException.internalError
            ("NotificationPoster.notifyPendingStatuses: already posting");
        }
        iState = STATE_POSTING;
        Thread thread = new Thread(this, "NotificationPosterThread");
        Jvm.setThreadAsDaemon(thread, true);
        thread.start();
    }

    /**
     * Returns true if notification posting has been started.
     * Note that this method returns true also if notification posting
     * has been started and stopped, so it does not tell if posting is
     * currently going on.
     */
    synchronized public boolean isStarted()
    {
        if (iState == STATE_NOT_STARTED)
        {
            return false;
        }
        return true;
    }

    /**
     * Stop notification posting.
     */
    synchronized public void stop()
    {
        if (iState == STATE_POSTING)
        {
            iState = STATE_STOPPED;
            // Notify waitForCompletion() that notification posting has been stopped.
            this.notify();
        }
    }

    /**
     * This method blocks until notification posting has been completed.
     * If posting is not going on, this method returns immediately.
     */
    synchronized public void waitForCompletion()
    {
        if (iState != STATE_POSTING)
        {
            return;
        }
        try
        {
            this.wait();
        }
        catch (InterruptedException ie)
        {
            // Ignore
        }
    }

    /**
     * This method posts pending notifications in a separate Thread.
     */
    public void run()
    {
        Log.log("NotificationPoster: thread starts");

        try
        {
            // Get pending notifications from storage and try to send them.
            OtaStatusNotification[] notifications =
                iOtaStatusHandler.getNotifications();
            if (notifications != null)
            {
                Log.log("NotificationPoster: found " + notifications.length +
                        " pending notifications");
                for (int i = 0;
                        i < notifications.length && iState == STATE_POSTING;
                        i++)
                {
                    try
                    {
                        Log.log("NotificationPoster: posting notifications[" +
                                i + "]");
                        doPost(notifications[i], null);
                        // Posting succeeded, remove notification
                        // from storage.
                        iOtaStatusHandler.removeNotification
                        (notifications[i]);
                    }
                    catch (Throwable t)
                    {
                        Log.logWarning("NotificationPoster: posting " +
                                       "notification failed", t);
                        // Posting failed, update counter and save
                        // notification to storage for further retry
                        // attempts.
                        if (notifications[i].increaseRetryCount())
                        {
                            iOtaStatusHandler.updateNotification
                            (notifications[i]);
                        }
                        else
                        {
                            // Maxmimum number of retries has been exceeded,
                            // posting this notification should not be retried
                            // anymore, remove it from storage.
                            iOtaStatusHandler.removeNotification
                            (notifications[i]);
                        }
                    }
                }
            }
        }
        catch (Throwable t)
        {
            Log.logWarning("NotificationPoster: unexpected exception", t);
        }

        Log.log("NotificationPoster: thread exits");

        // Notify that waitForCompletion() can proceed.
        synchronized (this)
        {
            iState = STATE_STOPPED;
            this.notify();
        }
    }

    /**
     * Returns true if given URL is valid for notifications, false otherwise.
     */
    public static boolean isNotificationUrl(String aUrl)
    {
        if (aUrl == null || aUrl.length() == 0)
        {
            return false;
        }
        String lcUrl = aUrl.toLowerCase();
        return (lcUrl.startsWith("http://") || lcUrl.startsWith("https://"));
    }


    /**
     * Posts given OTA status notification with HTTP POST request.
     *
     * @param aOtaStatusNotification notification to be posted
     * @param aLocation redirect url for this notification, can be null
     */
    abstract protected void doPost
    (OtaStatusNotification aOtaStatusNotification,
     String aLocation) throws IOException;

    /**
     * Checks if HTTP redirect is needed. If redirect is needed,
     * this method increases redirect counter.
     *
     * @param aHttpStatus HTTP response status code
     * @param aLocation Location header from HTTP response
     * @return true if redirect is needed, false otherwise
     */
    protected boolean redirectNeeded(int aHttpStatus, String aLocation)
    {
        boolean result = false;
        if (iRedirectCount >= MAX_REDIRECT_COUNT)
        {
            // Maximum number of redirections has been exceeded.
            return result;
        }
        if (aHttpStatus >= 301 && aHttpStatus <= 307 &&
                isNotificationUrl(aLocation))
        {
            // Redirect is needed.
            iRedirectCount++;
            result = true;
        }
        return result;
    }
}