From 42a30fa166472791f5de1027e357e15016d7f54b Mon Sep 17 00:00:00 2001
From: Andrei <andrei@andrein.itcnetworks.ro>
Date: Tue, 15 Jul 2008 19:52:20 +0300
Subject: [PATCH] XCF-1098 - first proposal

This is the patch that implements XCF-1098
It introduces a new TimerTask in RestartManagerImpl.java which triggers a restart at a specified time.
It also introduces a new column to all the devices (phones, gateways, sbcDevices): information. This column
displays the restart date.
I've named this column information, because the restart date ends up in the database only after a profile
is generated, so the user might have been confused if he scheduled a restart and the scheduled restart date
didn't appear immediately. That's why I introduced a new class: DeviceStatus which keeps track whether or not a device
is generating its profile. Also, now, when a profile is generated, a device cannot be deleted (this was a bug,
I believe). This behavior can be extended so a device can report different statuses. Right now it only reports if it
is generating its profile or not (updating).
---
 .../neoconf/etc/database/add-scheduled-restart.sql |    8 +
 sipXconfig/neoconf/etc/database/database.xml       |    5 +
 .../admin/dialplan/sbc/SbcDeviceDeletedEvent.java  |   25 +++
 .../admin/dialplan/sbc/SbcDeviceManager.java       |    3 +-
 .../admin/dialplan/sbc/SbcDeviceManagerImpl.java   |   57 +++++-
 .../admin/dialplan/sbc/SbcDeviceSource.java        |   15 ++
 .../sipxconfig/admin/dialplan/sbc/sbc.beans.xml    |    4 +
 .../sipxconfig/admin/dialplan/sbc/sbc.hbm.xml      |    1 +
 .../org/sipfoundry/sipxconfig/device/Device.java   |   12 +
 .../sipfoundry/sipxconfig/device/DeviceSource.java |    9 +
 .../sipxconfig/device/DevicesStatus.java           |   54 +++++
 .../sipxconfig/device/LazyProfileManagerImpl.java  |   80 ++++++-
 .../sipxconfig/device/LazyRestartManagerImpl.java  |    9 +
 .../sipxconfig/device/ProfileManager.java          |    5 +
 .../sipxconfig/device/ProfileManagerImpl.java      |   15 ++
 .../sipxconfig/device/RestartManager.java          |    5 +
 .../sipxconfig/device/RestartManagerImpl.java      |  225 +++++++++++++++++++-
 .../sipxconfig/device/ScheduleRestart.java         |   24 ++
 .../sipxconfig/gateway/GatewayContext.java         |    4 +-
 .../sipxconfig/gateway/GatewayContextImpl.java     |   60 +++++-
 .../sipxconfig/gateway/GatewayDeletedEvent.java    |   25 +++
 .../sipxconfig/gateway/GatewaySource.java          |   15 ++
 .../sipxconfig/gateway/gateway.beans.xml           |    5 +
 .../sipfoundry/sipxconfig/gateway/gateway.hbm.xml  |    1 +
 .../sipfoundry/sipxconfig/phone/PhoneContext.java  |    3 +-
 .../sipxconfig/phone/PhoneContextImpl.java         |   65 ++++++-
 .../sipxconfig/phone/PhoneDeletedEvent.java        |   25 +++
 .../sipfoundry/sipxconfig/phone/PhoneSource.java   |   15 ++
 .../sipfoundry/sipxconfig/phone/phone.beans.xml    |    4 +
 .../org/sipfoundry/sipxconfig/phone/phone.hbm.xml  |    1 +
 .../sipxconfig/device/ProfileManagerImplTest.java  |  105 +++++++++
 .../sipxconfig/device/RestartManagerImplTest.java  |  126 +++++++++++
 .../sipxconfig/phone/PhoneSubclassSaveExpected.xml |   17 +-
 .../sipxconfig/phone/SaveEndpointExpected.xml      |   17 +-
 .../WEB-INF/device/ConfirmProfileGeneration.html   |   18 ++
 .../device/ConfirmProfileGeneration.properties     |    6 +
 .../web/context/WEB-INF/gateway/GatewayTable.html  |    3 +
 .../web/context/WEB-INF/gateway/GatewayTable.jwc   |    3 +-
 .../WEB-INF/gateway/GatewayTable.properties        |    3 +
 .../web/context/WEB-INF/gateway/GatewaysPanel.jwc  |    1 +
 .../web/context/WEB-INF/gateway/ListGateways.html  |   10 +-
 .../WEB-INF/gateway/ListGateways.properties        |    7 +
 .../context/WEB-INF/gateway/SelectGateways.page    |    1 +
 .../web/context/WEB-INF/phone/EditPhone.html       |    5 +-
 .../web/context/WEB-INF/phone/ManagePhones.html    |   15 +-
 .../web/context/WEB-INF/phone/PhoneTable.html      |    5 +-
 .../context/WEB-INF/phone/PhoneTable.properties    |    3 +
 .../WEB-INF/phone/PhoneTableActions.properties     |    6 +-
 .../web/context/WEB-INF/sbc/ListSbcDevices.html    |   11 +-
 .../context/WEB-INF/sbc/ListSbcDevices.properties  |    8 +
 .../web/context/WEB-INF/user/AddExistingPhone.html |    2 +-
 .../web/context/WEB-INF/user/UserPhones.html       |   15 +-
 .../sipxconfig/components/GatewayTable.java        |   15 ++
 .../site/device/ConfirmProfileGeneration.java      |   51 ++++-
 .../sipxconfig/site/gateway/ListGateways.java      |  100 ++++++++--
 .../sipxconfig/site/phone/EditPhone.java           |   15 ++
 .../sipxconfig/site/phone/ManagePhones.java        |   14 ++
 .../sipxconfig/site/phone/PhoneTable.java          |   17 ++
 .../sipxconfig/site/phone/PhoneTableActions.java   |   83 ++++++--
 .../sipxconfig/site/sbc/ListSbcDevices.java        |  104 ++++++++-
 .../sipxconfig/site/user/UserPhones.java           |   18 ++
 .../sipxconfig/site/gateway/GatewaysTestUi.java    |   12 +-
 .../sipxconfig/site/phone/ManagePhonesTestUi.java  |   14 ++
 .../sipxconfig/site/sbc/EditSbcDeviceTestUi.java   |    4 +-
 64 files changed, 1504 insertions(+), 109 deletions(-)
 create mode 100644 sipXconfig/neoconf/etc/database/add-scheduled-restart.sql
 create mode 100644 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceDeletedEvent.java
 create mode 100644 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DevicesStatus.java
 create mode 100644 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ScheduleRestart.java
 create mode 100644 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayDeletedEvent.java
 create mode 100644 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneDeletedEvent.java

diff --git a/sipXconfig/neoconf/etc/database/add-scheduled-restart.sql b/sipXconfig/neoconf/etc/database/add-scheduled-restart.sql
new file mode 100644
index 0000000..7c93148
--- /dev/null
+++ b/sipXconfig/neoconf/etc/database/add-scheduled-restart.sql
@@ -0,0 +1,8 @@
+ALTER TABLE phone
+    ADD COLUMN scheduled_restart_date timestamp with time zone;
+
+ALTER TABLE gateway
+    ADD COLUMN scheduled_restart_date timestamp with time zone;
+    
+ALTER TABLE sbc_device
+    ADD COLUMN scheduled_restart_date timestamp with time zone;
diff --git a/sipXconfig/neoconf/etc/database/database.xml b/sipXconfig/neoconf/etc/database/database.xml
index 2fc73ff..6e3e750 100644
--- a/sipXconfig/neoconf/etc/database/database.xml
+++ b/sipXconfig/neoconf/etc/database/database.xml
@@ -272,6 +272,7 @@
 	<antcall target="discovered-devices"/>
   	<antcall target="add-park-service"/>
   	<antcall target="add-presence-service"/>
+    <antcall target="add-scheduled-restart" />
     <!-- When freezing schema uncomment this line -->
     <!-- sipx-version version="8" -->
   </target>
@@ -680,6 +681,10 @@
     <db-patch patch="add-presence-service"/>
   </target>
 
+  <target name="add-scheduled-restart" unless="add-scheduled-restart">
+    <db-patch patch="add-scheduled-restart"/>
+  </target>
+
   <!-- 
     Migration Targets Legend
     
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceDeletedEvent.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceDeletedEvent.java
new file mode 100644
index 0000000..c5b6901
--- /dev/null
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceDeletedEvent.java
@@ -0,0 +1,25 @@
+/*
+ * 
+ * 
+ * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
+ * Contributors retain copyright to elements licensed under a Contributor Agreement.
+ * Licensed to the User under the LGPL license.
+ * 
+ * $
+ */
+package org.sipfoundry.sipxconfig.admin.dialplan.sbc;
+
+import org.springframework.context.ApplicationEvent;
+
+public class SbcDeviceDeletedEvent extends ApplicationEvent {
+    private Integer m_id;
+    
+    public SbcDeviceDeletedEvent(SbcDevice src) {
+        super(src);
+        m_id = src.getId();
+    }
+    
+    public Integer getId() {
+        return m_id;
+    }
+}
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManager.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManager.java
index ffeab06..0cbf9ed 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManager.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManager.java
@@ -13,8 +13,9 @@ import java.util.Collection;
 import java.util.List;
 
 import org.sipfoundry.sipxconfig.admin.dialplan.sbc.bridge.BridgeSbc;
+import org.sipfoundry.sipxconfig.device.ScheduleRestart;
 
-public interface SbcDeviceManager {
+public interface SbcDeviceManager extends ScheduleRestart {
     public static final String CONTEXT_BEAN_NAME = "sbcDeviceManager";
     
     List<SbcDevice> getSbcDevices();
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManagerImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManagerImpl.java
index 74b4117..dddaff7 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManagerImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceManagerImpl.java
@@ -11,7 +11,10 @@ package org.sipfoundry.sipxconfig.admin.dialplan.sbc;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.hibernate.Criteria;
 import org.hibernate.Session;
@@ -21,23 +24,34 @@ import org.sipfoundry.sipxconfig.common.SipxHibernateDaoSupport;
 import org.sipfoundry.sipxconfig.common.UserException;
 import org.sipfoundry.sipxconfig.common.event.DaoEventPublisher;
 import org.sipfoundry.sipxconfig.common.event.SbcDeviceDeleteListener;
+import org.sipfoundry.sipxconfig.device.DevicesStatus;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.dao.support.DataAccessUtils;
 import org.springframework.orm.hibernate3.HibernateCallback;
 
 public class SbcDeviceManagerImpl extends SipxHibernateDaoSupport<SbcDevice> implements
-        SbcDeviceManager, BeanFactoryAware {
+        SbcDeviceManager, BeanFactoryAware, ApplicationContextAware {
 
     private static final String SBC_ID = "sbcId";
 
     private static final String SBC_NAME = "sbcName";
+    
+    private ApplicationContext m_applicationContext;
 
     private BeanFactory m_beanFactory;
 
     private DaoEventPublisher m_daoEventPublisher;
 
     private String m_localIpAddress;
+    
+    private DevicesStatus m_devicesStatus;
+    
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        m_applicationContext = applicationContext;
+    }
 
     public void setDaoEventPublisher(DaoEventPublisher daoEventPublisher) {
         m_daoEventPublisher = daoEventPublisher;
@@ -60,6 +74,7 @@ public class SbcDeviceManagerImpl extends SipxHibernateDaoSupport<SbcDevice> imp
         for (Integer id : ids) {
             SbcDevice sbcDevice = getSbcDevice(id);
             m_daoEventPublisher.publishDelete(sbcDevice);
+            m_applicationContext.publishEvent(new SbcDeviceDeletedEvent(sbcDevice));
         }
         removeAll(SbcDevice.class, ids);
     }
@@ -196,4 +211,44 @@ public class SbcDeviceManagerImpl extends SipxHibernateDaoSupport<SbcDevice> imp
     public String getLocalIpAddress() {
         return m_localIpAddress;
     }
+    
+    public void addScheduledRestart(Integer sbcDeviceId, Date scheduledDate) {
+        SbcDevice sbcDevice = getSbcDevice(sbcDeviceId);
+        if (scheduledDate == sbcDevice.getScheduledRestartDate()) {
+            // no need to store the phone ... nothing changed
+            m_devicesStatus.removeDeviceFromUpdatingDevicesList(sbcDeviceId);
+            return;
+        }
+        sbcDevice.setScheduledRestartDate(scheduledDate);
+        storeSbcDevice(sbcDevice);
+        m_devicesStatus.removeDeviceFromUpdatingDevicesList(sbcDeviceId);
+    }
+    
+    public void removeScheduledRestart(Integer sbcDeviceId) {
+        SbcDevice sbcDevice = getSbcDevice(sbcDeviceId);
+        if (sbcDevice.getScheduledRestartDate() == null) {
+            return;
+        }
+        sbcDevice.setScheduledRestartDate(null);
+        storeSbcDevice(sbcDevice);
+    }
+    
+    public Map<Integer, Date> getScheduledRestartDates() {
+        Map<Integer, Date> toReturn = new HashMap<Integer, Date>();
+        List<SbcDevice> sbcDevicesList = getSbcDevices();
+        for (SbcDevice sbc : sbcDevicesList) {
+            if (sbc.getScheduledRestartDate() != null) {
+                toReturn.put(sbc.getId(), sbc.getScheduledRestartDate());
+            }
+        }
+        return toReturn;
+    }
+
+    public DevicesStatus getDevicesStatus() {
+        return m_devicesStatus;
+    }
+
+    public void setDevicesStatus(DevicesStatus devicesStatus) {
+        m_devicesStatus = devicesStatus;
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceSource.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceSource.java
index 157623c..4d209e6 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceSource.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/SbcDeviceSource.java
@@ -9,6 +9,9 @@
  */
 package org.sipfoundry.sipxconfig.admin.dialplan.sbc;
 
+import java.util.Date;
+import java.util.Map;
+
 import org.sipfoundry.sipxconfig.device.DeviceSource;
 import org.springframework.beans.factory.annotation.Required;
 
@@ -24,4 +27,16 @@ public class SbcDeviceSource implements DeviceSource<SbcDevice> {
     public SbcDevice loadDevice(Integer id) {
         return m_sbcDeviceManager.getSbcDevice(id);
     }
+    
+    public void scheduleRestart(Integer id, Date scheduledDate) {
+        m_sbcDeviceManager.addScheduledRestart(id, scheduledDate);
+    }
+    
+    public void removeScheduledRestart(Integer id) {
+        m_sbcDeviceManager.removeScheduledRestart(id);
+    }
+    
+    public Map<Integer, Date> getScheduledRestarts() {
+        return m_sbcDeviceManager.getScheduledRestartDates();
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.beans.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.beans.xml
index f2368cb..7bf3a81 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.beans.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.beans.xml
@@ -31,6 +31,9 @@
   <bean id="sbcDeviceManagerImpl" class="org.sipfoundry.sipxconfig.admin.dialplan.sbc.SbcDeviceManagerImpl">
     <property name="sessionFactory" ref="sessionFactory" />
     <property name="daoEventPublisher" ref="daoEventPublisher" />
+    <property name="devicesStatus">
+      <ref local="sbcDevicesStatus" />
+    </property>
   </bean>
 
   <bean id="sbcDeviceManagerSbcDeviceDelete" factory-bean="sbcDeviceManagerImpl"
@@ -138,4 +141,5 @@
       </list>
     </property>
   </bean>
+  <bean id="sbcDevicesStatus" class="org.sipfoundry.sipxconfig.device.DevicesStatus" />
 </beans>
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.hbm.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.hbm.xml
index adb8fde..213ef0b 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.hbm.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/admin/dialplan/sbc/sbc.hbm.xml
@@ -47,6 +47,7 @@
       type="org.sipfoundry.sipxconfig.device.DeviceVersion$Type" />
     <many-to-one cascade="all" name="valueStorage" column="value_storage_id"
       class="org.sipfoundry.sipxconfig.setting.ValueStorage" />
+    <property name="scheduledRestartDate" column="scheduled_restart_date"/>
   </class>
 
   <query name="sbcIds">
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/Device.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/Device.java
index a4e195e..96c8154 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/Device.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/Device.java
@@ -10,6 +10,7 @@
 package org.sipfoundry.sipxconfig.device;
 
 import java.io.File;
+import java.util.Date;
 import java.util.Set;
 
 import org.sipfoundry.sipxconfig.common.NamedObject;
@@ -32,6 +33,8 @@ public abstract class Device extends BeanWithGroups {
     private String m_serialNumber;
 
     private DeviceVersion m_version;
+    
+    private Date m_scheduledRestartDate;
 
     protected ProfileContext createContext() {
         return new ProfileContext(this, getModel().getProfileTemplate());
@@ -215,4 +218,13 @@ public abstract class Device extends BeanWithGroups {
         }
         return jn.toString();
     }
+    
+    public Date getScheduledRestartDate() {
+        return m_scheduledRestartDate;
+    }
+
+    public void setScheduledRestartDate(Date scheduledRestartDate) {
+        m_scheduledRestartDate = scheduledRestartDate;
+    }
+
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DeviceSource.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DeviceSource.java
index 116d9a3..9b35810 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DeviceSource.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DeviceSource.java
@@ -9,6 +9,15 @@
  */
 package org.sipfoundry.sipxconfig.device;
 
+import java.util.Date;
+import java.util.Map;
+
 public interface DeviceSource<T extends Device> {
     T loadDevice(Integer id);
+    
+    void scheduleRestart(Integer id, Date scheduledDate);
+    
+    void removeScheduledRestart(Integer id);
+    
+    Map<Integer, Date> getScheduledRestarts();
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DevicesStatus.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DevicesStatus.java
new file mode 100644
index 0000000..781e605
--- /dev/null
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/DevicesStatus.java
@@ -0,0 +1,54 @@
+/*
+ * 
+ * 
+ * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
+ * Contributors retain copyright to elements licensed under a Contributor Agreement.
+ * Licensed to the User under the LGPL license.
+ * 
+ * $
+ */
+package org.sipfoundry.sipxconfig.device;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DevicesStatus {
+    
+    private List<Integer> m_updatingDevicesList = new ArrayList<Integer>();
+    
+    public synchronized void addDeviceToUpdatingDevicesList(Integer id) {
+        m_updatingDevicesList.add(id);
+    }
+
+    public synchronized void addDevicesToUpdatingDevicesList(Collection<Integer> ids) {
+        m_updatingDevicesList.addAll(ids);
+    }
+
+    public synchronized void removeDeviceFromUpdatingDevicesList(Integer id) {
+        m_updatingDevicesList.remove(id);
+    }
+
+    public synchronized void removeDevicesFromUpdatingDevicesList(Collection<Integer> ids) {
+        m_updatingDevicesList.removeAll(ids);
+    }
+
+    public synchronized boolean isDeviceUpdating(Integer id) {
+        if (m_updatingDevicesList.contains(id)) {
+            return true;
+        }
+        return false;
+    }
+
+    public synchronized boolean isAnyDeviceUpdating() {
+        if (m_updatingDevicesList.size() != 0) {
+            return true;
+        }
+        return false;
+    }
+    
+    // unit test only
+    public void clear() {
+        m_updatingDevicesList.clear();
+    }
+}
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyProfileManagerImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyProfileManagerImpl.java
index 70425d4..889ae98 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyProfileManagerImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyProfileManagerImpl.java
@@ -11,13 +11,14 @@ package org.sipfoundry.sipxconfig.device;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.sipfoundry.sipxconfig.common.LazyDaemon;
 
-public class LazyProfileManagerImpl implements ProfileManager {
-    private Map<Integer, Boolean> m_ids = new HashMap<Integer, Boolean>();
+public class LazyProfileManagerImpl implements ProfileManager {    
+    private Map<Integer, RestartAndDate> m_ids = new HashMap<Integer, RestartAndDate>();
 
     private ProfileManager m_target;
 
@@ -46,13 +47,33 @@ public class LazyProfileManagerImpl implements ProfileManager {
         m_worker.workScheduled();
         notify();
     }
+    
+    public synchronized void generateProfiles(Collection<Integer> deviceIds, Date scheduledRestartDate) {
+        for (Integer id : deviceIds) {
+            updateRestart(id, scheduledRestartDate);
+        }
+        m_worker.workScheduled();
+        notify();
+    }
+    
+    public synchronized void generateProfile(Integer deviceId, Date scheduledRestartDate) {
+        updateRestart(deviceId, scheduledRestartDate);
+        m_worker.workScheduled();
+        notify();
+    }
 
     private void updateRestart(Integer id, boolean restart) {
-        boolean newRestart = restart;
+        RestartAndDate rad = new RestartAndDate(restart);
         if (m_ids.containsKey(id)) {
-            newRestart = restart || m_ids.get(id);
+            rad = m_ids.get(id);
+            rad.setRestart(restart || rad.getRestart());
         }
-        m_ids.put(id, newRestart);
+        m_ids.put(id, rad);
+    }
+    
+    private void updateRestart(Integer id, Date scheduledDate) {
+        RestartAndDate rad = new RestartAndDate(scheduledDate);
+        m_ids.put(id, rad);
     }
 
     private synchronized void waitForWork() throws InterruptedException {
@@ -66,12 +87,12 @@ public class LazyProfileManagerImpl implements ProfileManager {
         m_worker.start();
     }
 
-    private synchronized Map<Integer, Boolean> getTasks() {
+    private synchronized Map<Integer, RestartAndDate> getTasks() {
         if (m_ids.isEmpty()) {
             return Collections.emptyMap();
         }
-        Map<Integer, Boolean> oldTasks = m_ids;
-        m_ids = new HashMap<Integer, Boolean>();
+        Map<Integer, RestartAndDate> oldTasks = m_ids;
+        m_ids = new HashMap<Integer, RestartAndDate>();
         return oldTasks;
     }
 
@@ -85,11 +106,48 @@ public class LazyProfileManagerImpl implements ProfileManager {
         }
 
         protected boolean work() {
-            Map<Integer, Boolean> tasks = getTasks();
-            for (Map.Entry<Integer, Boolean> entry : tasks.entrySet()) {
-                m_target.generateProfile(entry.getKey(), entry.getValue());
+            Map<Integer, RestartAndDate> tasks = getTasks();
+            for (Map.Entry<Integer, RestartAndDate> entry : tasks.entrySet()) {
+                if (entry.getValue().getDate() != null) {
+                    m_target.generateProfile(entry.getKey(), entry.getValue().getDate());
+                } else {
+                    m_target.generateProfile(entry.getKey(), entry.getValue().getRestart());
+                }
             }
             return true;
         }
     }
+    
+    private class RestartAndDate {
+        private boolean m_restart;
+        private Date m_date;
+        
+        public RestartAndDate(boolean restart) {
+            this.m_restart = restart;
+            this.m_date = null;
+        }
+        
+        public RestartAndDate(Date date) {
+            this.m_date = date;
+            this.m_restart = false;
+        }
+        
+        public Date getDate() {
+            return m_date;
+        }
+        
+        public boolean getRestart() {
+            return m_restart;
+        }
+        
+        public void setDate(Date date) {
+            this.m_restart = false;
+            this.m_date = date;
+        }
+        
+        public void setRestart(boolean restart) {
+            this.m_restart = restart;
+            this.m_date = null;
+        }
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyRestartManagerImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyRestartManagerImpl.java
index dc3b1b8..94f2656 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyRestartManagerImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/LazyRestartManagerImpl.java
@@ -11,6 +11,7 @@ package org.sipfoundry.sipxconfig.device;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -44,6 +45,14 @@ public class LazyRestartManagerImpl implements RestartManager {
         m_worker.workScheduled();
         notify();
     }
+    
+    public void scheduleRestart(Collection<Integer> deviceIds, Date scheduledDate) {
+        m_target.scheduleRestart(deviceIds, scheduledDate);
+    }
+    
+    public void scheduleRestart(Integer deviceId, Date scheduledDate) {
+        m_target.scheduleRestart(deviceId, scheduledDate);
+    }
 
     private synchronized void waitForWork() throws InterruptedException {
         if (m_ids.isEmpty()) {
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManager.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManager.java
index 58d4fde..94a9f23 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManager.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManager.java
@@ -10,6 +10,7 @@
 package org.sipfoundry.sipxconfig.device;
 
 import java.util.Collection;
+import java.util.Date;
 
 public interface ProfileManager {
     /**
@@ -21,4 +22,8 @@ public interface ProfileManager {
     public void generateProfiles(Collection<Integer> deviceIds, boolean restart);
 
     public void generateProfile(Integer deviceId, boolean restart);
+    
+    public void generateProfiles(Collection<Integer> deviceIds, Date scheduledRestartDate);
+
+    public void generateProfile(Integer deviceId, Date scheduledRestartDate);
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManagerImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManagerImpl.java
index 16822bb..b81295e 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManagerImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ProfileManagerImpl.java
@@ -11,6 +11,7 @@ package org.sipfoundry.sipxconfig.device;
 
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Date;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -35,6 +36,7 @@ public class ProfileManagerImpl implements ProfileManager {
     public final void generateProfile(Integer id, boolean restart) {
         Device d = m_deviceSource.loadDevice(id);
         generate(d);
+        m_deviceSource.scheduleRestart(id, null);
         if (restart) {
             m_restartManager.restart(id);
         }
@@ -70,4 +72,17 @@ public class ProfileManagerImpl implements ProfileManager {
     public void setDeviceSource(DeviceSource deviceSource) {
         m_deviceSource = deviceSource;
     }
+    
+    public void generateProfiles(Collection<Integer> deviceIds, Date scheduledRestartDate) {
+        for (Integer id : deviceIds) {
+            generateProfile(id, scheduledRestartDate);
+        }
+    }
+
+    public void generateProfile(Integer deviceId, Date scheduledRestartDate) {
+        Device d = m_deviceSource.loadDevice(deviceId);
+        generate(d);
+        m_deviceSource.scheduleRestart(deviceId, scheduledRestartDate);
+        m_restartManager.scheduleRestart(deviceId, scheduledRestartDate);
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManager.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManager.java
index 74d4970..94a4f51 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManager.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManager.java
@@ -10,6 +10,7 @@
 package org.sipfoundry.sipxconfig.device;
 
 import java.util.Collection;
+import java.util.Date;
 
 public interface RestartManager {
 
@@ -21,4 +22,8 @@ public interface RestartManager {
     public void restart(Collection<Integer> deviceIds);
 
     public void restart(Integer deviceId);
+    
+    public void scheduleRestart(Integer deviceId, Date scheduledDate);
+
+    public void scheduleRestart(Collection<Integer> deviceIds, Date scheduledDate);
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManagerImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManagerImpl.java
index 6fd8623..e704722 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManagerImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/RestartManagerImpl.java
@@ -10,14 +10,29 @@
 package org.sipfoundry.sipxconfig.device;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.sipfoundry.sipxconfig.admin.dialplan.sbc.SbcDeviceDeletedEvent;
+import org.sipfoundry.sipxconfig.admin.dialplan.sbc.SbcDeviceSource;
+import org.sipfoundry.sipxconfig.common.ApplicationInitializedEvent;
+import org.sipfoundry.sipxconfig.common.DSTChangeEvent;
+import org.sipfoundry.sipxconfig.gateway.GatewayDeletedEvent;
+import org.sipfoundry.sipxconfig.gateway.GatewaySource;
 import org.sipfoundry.sipxconfig.job.JobContext;
+import org.sipfoundry.sipxconfig.phone.PhoneDeletedEvent;
+import org.sipfoundry.sipxconfig.phone.PhoneSource;
 import org.springframework.beans.factory.annotation.Required;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
 
-public class RestartManagerImpl implements RestartManager {
+public class RestartManagerImpl implements RestartManager, ApplicationListener {
     private static final Log LOG = LogFactory.getLog(RestartManagerImpl.class);
 
     private static final int DEFAULT_THROTTLE_INTERVAL = 1000;
@@ -35,12 +50,81 @@ public class RestartManagerImpl implements RestartManager {
      */
     private int m_throttleInterval = DEFAULT_THROTTLE_INTERVAL;
 
-    public void restart(Collection<Integer> deviceIds) {
+    private Collection<ScheduleRestartTask> m_scheduledRestarts = new ArrayList<ScheduleRestartTask>();
+    
+    private Timer m_timer;
+    
+    public synchronized void restart(Collection<Integer> deviceIds) {
         for (Integer id : deviceIds) {
             restart(id);
             throttle();
         }
     }
+    
+    public void scheduleRestart(Integer deviceId, Date scheduledDate) {
+        addScheduledRestart(deviceId, scheduledDate);
+    }
+
+    public void scheduleRestart(Collection<Integer> deviceIds, Date scheduledDate) {
+        for (Integer deviceId : deviceIds) {
+            addScheduledRestart(deviceId, scheduledDate);
+        }
+    }
+    
+    public void onApplicationEvent(ApplicationEvent event) {
+        if (event instanceof ApplicationInitializedEvent || event instanceof DSTChangeEvent) {
+            resetTimer();
+            rescheduleTasks();
+        } else if (event instanceof PhoneDeletedEvent && m_deviceSource instanceof PhoneSource) { 
+            // We're dealing with phones
+            // A phone was deleted => cancel its schedule!
+            Integer deviceId = ((PhoneDeletedEvent) event).getId();
+            ScheduleRestartTask srt = null;
+            for (ScheduleRestartTask scheduledTask : getScheduledRestartTasks()) {
+                if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                    srt = scheduledTask;
+                }
+            }
+
+            if (srt != null) {
+                srt.cancel();
+                m_scheduledRestarts.remove(srt);
+                m_timer.purge(); // so it can be garbage collected
+            }
+        } else if (event instanceof GatewayDeletedEvent && m_deviceSource instanceof GatewaySource) { 
+            // We're dealing with gateways
+            // A gateway was deleted => cancel its schedule!
+            Integer deviceId = ((GatewayDeletedEvent) event).getId();
+            ScheduleRestartTask srt = null;
+            for (ScheduleRestartTask scheduledTask : getScheduledRestartTasks()) {
+                if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                    srt = scheduledTask;
+                }
+            }
+
+            if (srt != null) {
+                srt.cancel();
+                m_scheduledRestarts.remove(srt);
+                m_timer.purge(); // so it can be garbage collected
+            }
+        } else if (event instanceof SbcDeviceDeletedEvent && m_deviceSource instanceof SbcDeviceSource) { 
+            // We're dealing with sbc devices
+            // A sbc device was deleted => cancel its schedule!
+            Integer deviceId = ((SbcDeviceDeletedEvent) event).getId();
+            ScheduleRestartTask srt = null;
+            for (ScheduleRestartTask scheduledTask : getScheduledRestartTasks()) {
+                if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                    srt = scheduledTask;
+                }
+            }
+
+            if (srt != null) {
+                srt.cancel();
+                m_scheduledRestarts.remove(srt);
+                m_timer.purge(); // so it can be garbage collected
+            }
+        } 
+    }
 
     private void throttle() {
         if (m_throttleInterval <= 0) {
@@ -54,6 +138,19 @@ public class RestartManagerImpl implements RestartManager {
     }
 
     public void restart(Integer deviceId) {
+        ScheduleRestartTask srt = null;
+        for (ScheduleRestartTask scheduledTask : m_scheduledRestarts) {
+            if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                srt = scheduledTask;
+                break; // can't be more than 1
+            }
+        }
+        if (srt != null) {
+            srt.cancel();
+            m_scheduledRestarts.remove(srt);
+            m_timer.purge();
+        }
+        m_deviceSource.removeScheduledRestart(deviceId);
         Device device = m_deviceSource.loadDevice(deviceId);
         restart(device);
     }
@@ -88,4 +185,128 @@ public class RestartManagerImpl implements RestartManager {
     public void setThrottleInterval(int throttleInterval) {
         m_throttleInterval = throttleInterval;
     }
+    
+    public void resetTimer() {
+        if (m_timer != null) {
+            m_timer.cancel();
+        }
+        m_timer = new Timer(false); // daemon, dies with main thread
+    }
+
+    public Timer getTimer() {
+        return m_timer;
+    }
+    
+    public synchronized Collection<ScheduleRestartTask> getScheduledRestartTasks() {
+        return m_scheduledRestarts;
+    }
+
+    public synchronized void setScheduledRestarts(Collection<ScheduleRestartTask> scheduledRestarts) {
+        m_scheduledRestarts = scheduledRestarts;
+    }
+
+    public synchronized void addScheduledRestart(Integer deviceId, Date scheduledDate) {
+        ScheduleRestartTask srt = null;
+        for (ScheduleRestartTask scheduledTask : getScheduledRestartTasks()) {
+            if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                // this means we'll have to reschedule
+                srt = scheduledTask;
+            }
+        }
+        if (srt != null) {
+            srt.cancel();
+            m_scheduledRestarts.remove(srt);
+            m_timer.purge(); // so it can be garbage collected
+        }
+        srt = new ScheduleRestartTask(deviceId, scheduledDate);
+        m_scheduledRestarts.add(srt);
+        m_timer.schedule(srt, srt.getScheduledRestartDate());
+    }
+
+    public synchronized void removeScheduledRestart(Integer deviceId) {
+        ScheduleRestartTask srt = null;
+        for (ScheduleRestartTask scheduledTask : getScheduledRestartTasks()) {
+            if (scheduledTask.getScheduledRestartDeviceId().equals(deviceId)) {
+                srt = scheduledTask;
+            }
+        }
+
+        if (srt != null) {
+            srt.cancel();
+            m_scheduledRestarts.remove(srt);
+            m_timer.purge(); // so it can be garbage collected
+            m_deviceSource.removeScheduledRestart(deviceId);
+        }
+    }
+
+    public synchronized void removeScheduledRestart(ScheduleRestartTask srt) {
+        srt.cancel();
+        if (m_scheduledRestarts.remove(srt)) {
+            m_timer.purge();
+            m_deviceSource.removeScheduledRestart(srt.getScheduledRestartDeviceId());
+        }
+    }
+
+    public Map<Integer, Date> getScheduledRestarts() {
+        return m_deviceSource.getScheduledRestarts();
+    }
+
+    public void rescheduleTasks() {
+        for (ScheduleRestartTask srt : getScheduledRestartTasks()) {
+            srt.cancel();
+        }
+        m_scheduledRestarts.clear();
+        m_timer.purge();
+        Map<Integer, Date> map = getScheduledRestarts();
+        for (Map.Entry<Integer, Date> entry : map.entrySet()) {
+            addScheduledRestart(entry.getKey(), entry.getValue());
+        }
+    }
+    
+    protected class ScheduleRestartTask extends TimerTask {
+        private Integer m_deviceId;
+        private Date m_scheduledDate;
+
+        public ScheduleRestartTask(Integer deviceId, Date scheduledDate) {
+            this.m_deviceId = deviceId;
+            this.m_scheduledDate = scheduledDate;
+        }
+
+        public void setScheduledRestart(Integer deviceId, Date scheduledDate) {
+            this.m_deviceId = deviceId;
+            this.m_scheduledDate = scheduledDate;
+        }
+
+        public void setScheduledRestart(Date scheduledDate) {
+            m_scheduledDate = scheduledDate;
+        }
+
+        public Date getScheduledRestartDate() {
+            return m_scheduledDate;
+        }
+
+        public Integer getScheduledRestartDeviceId() {
+            return m_deviceId;
+        }
+
+        public boolean equals(ScheduleRestartTask srt) {
+            if (this.getScheduledRestartDeviceId().equals(srt.getScheduledRestartDeviceId())) {
+                return true;
+            }
+            return false;
+        }
+
+        public int hashCode() {
+            return m_deviceId.hashCode();
+        }
+        
+        // Debug only
+        public String toString() {
+            return " (" + m_deviceId + ", " + m_scheduledDate + ") ";
+        }
+
+        public void run() {
+            RestartManagerImpl.this.restart(getScheduledRestartDeviceId());
+        }
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ScheduleRestart.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ScheduleRestart.java
new file mode 100644
index 0000000..bbf94ae
--- /dev/null
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/device/ScheduleRestart.java
@@ -0,0 +1,24 @@
+/*
+ * 
+ * 
+ * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
+ * Contributors retain copyright to elements licensed under a Contributor Agreement.
+ * Licensed to the User under the LGPL license.
+ * 
+ * $
+ */
+package org.sipfoundry.sipxconfig.device;
+
+import java.util.Date;
+import java.util.Map;
+
+public interface ScheduleRestart {
+    
+    void addScheduledRestart(Integer phoneId, Date scheduledDate);
+    
+    void removeScheduledRestart(Integer phoneId);
+    
+    Map<Integer, Date> getScheduledRestartDates();
+    
+    DevicesStatus getDevicesStatus();
+}
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContext.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContext.java
index fec0220..e8bbf24 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContext.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContext.java
@@ -12,7 +12,9 @@ package org.sipfoundry.sipxconfig.gateway;
 import java.util.Collection;
 import java.util.List;
 
-public interface GatewayContext {
+import org.sipfoundry.sipxconfig.device.ScheduleRestart;
+
+public interface GatewayContext extends ScheduleRestart {
     public static final String CONTEXT_BEAN_NAME = "gatewayContext";
 
     List<Gateway> getGateways();
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContextImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContextImpl.java
index fbb69ba..4f9cca9 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContextImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayContextImpl.java
@@ -12,7 +12,10 @@ package org.sipfoundry.sipxconfig.gateway;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.hibernate.Criteria;
 import org.hibernate.Session;
@@ -24,15 +27,18 @@ import org.sipfoundry.sipxconfig.admin.dialplan.sbc.SbcDevice;
 import org.sipfoundry.sipxconfig.common.DaoUtils;
 import org.sipfoundry.sipxconfig.common.UserException;
 import org.sipfoundry.sipxconfig.common.event.SbcDeviceDeleteListener;
+import org.sipfoundry.sipxconfig.device.DevicesStatus;
 import org.sipfoundry.sipxconfig.device.ProfileLocation;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.orm.hibernate3.HibernateCallback;
 import org.springframework.orm.hibernate3.HibernateTemplate;
 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
 
 public class GatewayContextImpl extends HibernateDaoSupport implements GatewayContext,
-        BeanFactoryAware {
+        BeanFactoryAware, ApplicationContextAware {
 
     private static final String QUERY_GATEWAY_ID_BY_SERIAL_NUMBER = "gatewayIdsWithSerialNumber";
 
@@ -51,16 +57,24 @@ public class GatewayContextImpl extends HibernateDaoSupport implements GatewayCo
             super(ERROR, name);
         }
     }
+    
+    private ApplicationContext m_applicationContext;
 
     private DialPlanContext m_dialPlanContext;
 
     private BeanFactory m_beanFactory;
 
     private SipxReplicationContext m_replicationContext;
+    
+    private DevicesStatus m_devicesStatus;
 
     public GatewayContextImpl() {
         super();
     }
+    
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        m_applicationContext = applicationContext;
+    }
 
     public List<Gateway> getGateways() {
         return getHibernateTemplate().loadAll(Gateway.class);
@@ -102,6 +116,10 @@ public class GatewayContextImpl extends HibernateDaoSupport implements GatewayCo
         ProfileLocation location = g.getModel().getDefaultProfileLocation();
         g.removeProfiles(location);
         getHibernateTemplate().delete(g);
+        if (g.getScheduledRestartDate() != null) {
+            // no need rasing this if it's not scheduled for a restart ... (at least for the moment)
+            m_applicationContext.publishEvent(new GatewayDeletedEvent(g));
+        }
         return true;
     }
 
@@ -225,4 +243,44 @@ public class GatewayContextImpl extends HibernateDaoSupport implements GatewayCo
             }
         }
     }
+    
+    public void addScheduledRestart(Integer gatewayId, Date scheduledDate) {
+        Gateway gateway = getGateway(gatewayId);
+        if (scheduledDate == gateway.getScheduledRestartDate()) {
+            // no need to store the phone ... nothing changed
+            m_devicesStatus.removeDeviceFromUpdatingDevicesList(gatewayId);
+            return;
+        }
+        gateway.setScheduledRestartDate(scheduledDate);
+        storeGateway(gateway);
+        m_devicesStatus.removeDeviceFromUpdatingDevicesList(gatewayId);
+    }
+    
+    public void removeScheduledRestart(Integer gatewayId) {
+        Gateway gateway = getGateway(gatewayId);
+        if (gateway.getScheduledRestartDate() == null) {
+            return;
+        }
+        gateway.setScheduledRestartDate(null);
+        storeGateway(gateway);
+    }
+
+    public Map<Integer, Date> getScheduledRestartDates() {
+        Map<Integer, Date> toReturn = new HashMap<Integer, Date>();
+        List<Gateway> gatewayList = getGateways();
+        for (Gateway gateway : gatewayList) {
+            if (gateway.getScheduledRestartDate() != null) {
+                toReturn.put(gateway.getId(), gateway.getScheduledRestartDate());
+            }
+        }
+        return toReturn;
+    }
+
+    public DevicesStatus getDevicesStatus() {
+        return m_devicesStatus;
+    }
+
+    public void setDevicesStatus(DevicesStatus devicesStatus) {
+        m_devicesStatus = devicesStatus;
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayDeletedEvent.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayDeletedEvent.java
new file mode 100644
index 0000000..afe7f8e
--- /dev/null
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewayDeletedEvent.java
@@ -0,0 +1,25 @@
+/*
+ * 
+ * 
+ * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
+ * Contributors retain copyright to elements licensed under a Contributor Agreement.
+ * Licensed to the User under the LGPL license.
+ * 
+ * $
+ */
+package org.sipfoundry.sipxconfig.gateway;
+
+import org.springframework.context.ApplicationEvent;
+
+public class GatewayDeletedEvent extends ApplicationEvent {
+    private Integer m_id;
+    
+    public GatewayDeletedEvent(Gateway src) {
+        super(src);
+        m_id = src.getId();
+    }
+    
+    public Integer getId() {
+        return m_id;
+    }
+}
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewaySource.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewaySource.java
index 5eef576..aa2b2f3 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewaySource.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/GatewaySource.java
@@ -9,6 +9,9 @@
  */
 package org.sipfoundry.sipxconfig.gateway;
 
+import java.util.Date;
+import java.util.Map;
+
 import org.sipfoundry.sipxconfig.device.DeviceSource;
 
 public class GatewaySource implements DeviceSource<Gateway> {
@@ -22,4 +25,16 @@ public class GatewaySource implements DeviceSource<Gateway> {
     public Gateway loadDevice(Integer id) {
         return m_gatewayContext.getGateway(id);
     }
+    
+    public void scheduleRestart(Integer id, Date scheduledDate) {
+        m_gatewayContext.addScheduledRestart(id, scheduledDate);
+    }
+    
+    public void removeScheduledRestart(Integer id) {
+        m_gatewayContext.removeScheduledRestart(id);
+    }
+    
+    public Map<Integer, Date> getScheduledRestarts() {
+        return m_gatewayContext.getScheduledRestartDates();
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.beans.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.beans.xml
index 5bc5f5f..fcaf5c0 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.beans.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.beans.xml
@@ -42,6 +42,9 @@
     <property name="dialPlanContext" ref="dialPlanContext" />
     <property name="sessionFactory" ref="sessionFactory" />
     <property name="replicationContext" ref="sipxReplicationContext" />
+    <property name="devicesStatus">
+      <ref local="gatewaysStatus" />
+    </property>
   </bean>
 
   <bean id="gatewayContextSbcDeviceDelete" factory-bean="gatewayContextImpl"
@@ -121,4 +124,6 @@
       </list>
     </property>
   </bean>
+  
+  <bean id="gatewaysStatus" class="org.sipfoundry.sipxconfig.device.DevicesStatus" />
 </beans>
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.hbm.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.hbm.xml
index 1bc49f6..949a2cf 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.hbm.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/gateway/gateway.hbm.xml
@@ -43,6 +43,7 @@
       <list-index column="position" />
       <one-to-many class="FxoPort" />
     </list>
+    <property name="scheduledRestartDate" column="scheduled_restart_date"/>
   </class>
 
   <!-- L I N E -->
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContext.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContext.java
index ea52247..37bfe02 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContext.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContext.java
@@ -16,6 +16,7 @@ import java.util.List;
 import org.sipfoundry.sipxconfig.admin.intercom.Intercom;
 import org.sipfoundry.sipxconfig.common.DataObjectSource;
 import org.sipfoundry.sipxconfig.device.DeviceDefaults;
+import org.sipfoundry.sipxconfig.device.ScheduleRestart;
 import org.sipfoundry.sipxconfig.phonebook.PhonebookEntry;
 import org.sipfoundry.sipxconfig.setting.Group;
 import org.sipfoundry.sipxconfig.speeddial.SpeedDial;
@@ -23,7 +24,7 @@ import org.sipfoundry.sipxconfig.speeddial.SpeedDial;
 /**
  * Context for entire sipXconfig framework. Holder for service layer bean factories.
  */
-public interface PhoneContext extends DataObjectSource {
+public interface PhoneContext extends DataObjectSource, ScheduleRestart {
 
     String GROUP_RESOURCE_ID = "phone";
     String CONTEXT_BEAN_NAME = "phoneContext";
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContextImpl.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContextImpl.java
index b579d84..32d37e5 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContextImpl.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneContextImpl.java
@@ -12,7 +12,10 @@ package org.sipfoundry.sipxconfig.phone;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.sipfoundry.sipxconfig.admin.intercom.Intercom;
 import org.sipfoundry.sipxconfig.admin.intercom.IntercomManager;
@@ -24,6 +27,7 @@ import org.sipfoundry.sipxconfig.common.User;
 import org.sipfoundry.sipxconfig.common.UserException;
 import org.sipfoundry.sipxconfig.common.event.DaoEventListener;
 import org.sipfoundry.sipxconfig.device.DeviceDefaults;
+import org.sipfoundry.sipxconfig.device.DevicesStatus;
 import org.sipfoundry.sipxconfig.device.ProfileLocation;
 import org.sipfoundry.sipxconfig.phonebook.Phonebook;
 import org.sipfoundry.sipxconfig.phonebook.PhonebookEntry;
@@ -34,6 +38,8 @@ import org.sipfoundry.sipxconfig.speeddial.SpeedDial;
 import org.sipfoundry.sipxconfig.speeddial.SpeedDialManager;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationListener;
 import org.springframework.orm.hibernate3.HibernateTemplate;
@@ -41,10 +47,11 @@ import org.springframework.orm.hibernate3.HibernateTemplate;
 /**
  * Context for entire sipXconfig framework. Holder for service layer bean factories.
  */
-public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFactoryAware,
-        PhoneContext, ApplicationListener, DaoEventListener {
-
+public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFactoryAware, 
+            PhoneContext, ApplicationListener, DaoEventListener, ApplicationContextAware {
     private static final String QUERY_PHONE_ID_BY_SERIAL_NUMBER = "phoneIdsWithSerialNumber";
+    
+    private ApplicationContext m_applicationContext;
 
     private CoreContext m_coreContext;
 
@@ -62,6 +69,12 @@ public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFac
 
     private SpeedDialManager m_speedDialManager;
     
+    private DevicesStatus m_devicesStatus;
+    
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        m_applicationContext = applicationContext;
+    }
+    
     public PhonebookManager getPhonebookManager() {
         return m_phonebookManager;
     }
@@ -114,6 +127,11 @@ public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFac
             line.setValueStorage(clearUnsavedValueStorage(line.getValueStorage()));
         }
         getHibernateTemplate().delete(phone);
+
+        if (phone.getScheduledRestartDate() != null) { 
+            // no need rasing this if it's not scheduled for a restart ... (at least for the moment)
+            m_applicationContext.publishEvent(new PhoneDeletedEvent(phone));
+        }
     }
 
     public void storeLine(Line line) {
@@ -187,6 +205,7 @@ public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFac
         // not leave hanging references. DB will reject otherwise
         deleteAll("from Phone");
         deleteAll("from Group where resource = 'phone'");
+        m_devicesStatus.clear();
     }
 
     private void deleteAll(String query) {
@@ -308,4 +327,44 @@ public class PhoneContextImpl extends SipxHibernateDaoSupport implements BeanFac
         }
         return null;
     }
+    
+    public void addScheduledRestart(Integer phoneId, Date scheduledDate) {
+        Phone phone = loadPhone(phoneId);
+        if (scheduledDate == phone.getScheduledRestartDate()) {
+            // no need to store the phone ... nothing changed
+            m_devicesStatus.removeDeviceFromUpdatingDevicesList(phoneId);
+            return;
+        }
+        phone.setScheduledRestartDate(scheduledDate);
+        storePhone(phone);
+        m_devicesStatus.removeDeviceFromUpdatingDevicesList(phoneId);
+    }
+    
+    public void removeScheduledRestart(Integer phoneId) {
+        Phone phone = loadPhone(phoneId);
+        if (phone.getScheduledRestartDate() == null) {
+            return;
+        }
+        phone.setScheduledRestartDate(null);
+        storePhone(phone);
+    }
+
+    public Map<Integer, Date> getScheduledRestartDates() {
+        Map<Integer, Date> toReturn = new HashMap<Integer, Date>();
+        List<Phone> phoneList = loadPhones();
+        for (Phone phone : phoneList) {
+            if (phone.getScheduledRestartDate() != null) {
+                toReturn.put(phone.getId(), phone.getScheduledRestartDate());
+            }
+        }
+        return toReturn;
+    }
+
+    public DevicesStatus getDevicesStatus() {
+        return m_devicesStatus;
+    }
+
+    public void setDevicesStatus(DevicesStatus devicesStatus) {
+        m_devicesStatus = devicesStatus;
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneDeletedEvent.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneDeletedEvent.java
new file mode 100644
index 0000000..da814c9
--- /dev/null
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneDeletedEvent.java
@@ -0,0 +1,25 @@
+/*
+ * 
+ * 
+ * Copyright (C) 2007 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
+ * Contributors retain copyright to elements licensed under a Contributor Agreement.
+ * Licensed to the User under the LGPL license.
+ * 
+ * $
+ */
+package org.sipfoundry.sipxconfig.phone;
+
+import org.springframework.context.ApplicationEvent;
+
+public class PhoneDeletedEvent extends ApplicationEvent {
+    private Integer m_id;
+    
+    public PhoneDeletedEvent(Phone src) {
+        super(src);
+        m_id = src.getId();
+    }
+    
+    public Integer getId() {
+        return m_id;
+    }
+}
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneSource.java b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneSource.java
index e50ef1c..7c37442 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneSource.java
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/PhoneSource.java
@@ -9,6 +9,9 @@
  */
 package org.sipfoundry.sipxconfig.phone;
 
+import java.util.Date;
+import java.util.Map;
+
 import org.sipfoundry.sipxconfig.device.DeviceSource;
 
 public class PhoneSource implements DeviceSource<Phone> {
@@ -21,4 +24,16 @@ public class PhoneSource implements DeviceSource<Phone> {
     public Phone loadDevice(Integer id) {
         return m_phoneContext.loadPhone(id);
     }
+    
+    public void scheduleRestart(Integer phoneId, Date scheduledDate) {
+        m_phoneContext.addScheduledRestart(phoneId, scheduledDate);
+    }
+    
+    public void removeScheduledRestart(Integer phoneId) {
+        m_phoneContext.removeScheduledRestart(phoneId);
+    }
+    
+    public Map<Integer, Date> getScheduledRestarts() {
+        return m_phoneContext.getScheduledRestartDates();
+    }
 }
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.beans.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.beans.xml
index 4b28e32..f95f39e 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.beans.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.beans.xml
@@ -32,6 +32,9 @@
     <property name="intercomManager" ref="intercomManager" />
     <property name="phonebookManager" ref="phonebookManager" />
     <property name="speedDialManager" ref="speedDialManager" />
+    <property name="devicesStatus">
+      <ref local="phonesStatus" />
+    </property>
   </bean>
 
   <bean id="phoneContext" class="org.springframework.aop.framework.ProxyFactoryBean" parent="abstractDao">
@@ -95,4 +98,5 @@
   </bean>
 
   <bean id="line" class="org.sipfoundry.sipxconfig.phone.Line" singleton="false" parent="beanWithSettings" />
+  <bean id="phonesStatus" class="org.sipfoundry.sipxconfig.device.DevicesStatus" />
 </beans>
diff --git a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.hbm.xml b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.hbm.xml
index 106a528..bad98f9 100644
--- a/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.hbm.xml
+++ b/sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/phone/phone.hbm.xml
@@ -26,6 +26,7 @@
       <index column="position"/>
       <one-to-many class="Line"/>
     </list>
+    <property name="scheduledRestartDate" column="scheduled_restart_date"/> <!--  type="date" ? -->
   </class>  
   <!-- L I N E -->
   <class name="Line" table="line">
diff --git a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/ProfileManagerImplTest.java b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/ProfileManagerImplTest.java
index 3fc6d6d..7825313 100644
--- a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/ProfileManagerImplTest.java
+++ b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/ProfileManagerImplTest.java
@@ -10,6 +10,7 @@
 package org.sipfoundry.sipxconfig.device;
 
 import java.util.Arrays;
+import java.util.Date;
 
 import junit.framework.TestCase;
 
@@ -66,7 +67,9 @@ public class ProfileManagerImplTest extends TestCase {
 
         DeviceSource source = createMock(DeviceSource.class);
         expect(source.loadDevice(ids[0])).andReturn(phone);
+        source.scheduleRestart(ids[0], null);
         expect(source.loadDevice(ids[1])).andReturn(phone);
+        source.scheduleRestart(ids[1], null);
 
         RestartManager restartManager = createMock(RestartManager.class);
         restartManager.restart(ids[0]);
@@ -83,6 +86,64 @@ public class ProfileManagerImplTest extends TestCase {
 
         verify(jobContext, phone, source, restartManager);
     }
+    
+    public void testGenerateProfilesAndScheduleRestart() {
+        Integer jobId = new Integer(4);
+
+        Integer[] ids = {
+            new Integer(1000), new Integer(2000)
+        };
+        
+        Date date = new Date();
+
+        JobContext jobContext = createStrictMock(JobContext.class);
+        jobContext.schedule("Projection for: 110000000000");
+        expectLastCall().andReturn(jobId);
+        jobContext.start(jobId);
+        jobContext.success(jobId);
+        jobContext.schedule("Projection for: 120000000000");
+        expectLastCall().andReturn(jobId);
+        jobContext.start(jobId);
+        jobContext.success(jobId);
+
+        MemoryProfileLocation location = new MemoryProfileLocation();
+        DeviceDescriptor model = new DeviceDescriptor() {
+        };
+        model.setDefaultProfileLocation(location);
+
+        Device phone = createStrictMock(Device.class);
+        phone.getNiceName();
+        expectLastCall().andReturn("110000000000");
+        phone.getModel();
+        expectLastCall().andReturn(model);
+        phone.generateProfiles(same(location));
+        phone.getNiceName();
+        expectLastCall().andReturn("120000000000");
+        phone.getModel();
+        expectLastCall().andReturn(model);
+        phone.generateProfiles(same(location));
+
+        DeviceSource source = createMock(DeviceSource.class);
+        expect(source.loadDevice(ids[0])).andReturn(phone);
+        source.scheduleRestart(ids[0], date);
+        expect(source.loadDevice(ids[1])).andReturn(phone);
+        source.scheduleRestart(ids[1], date);
+
+        RestartManager restartManager = createMock(RestartManager.class);
+        restartManager.scheduleRestart(ids[0], date);
+        restartManager.scheduleRestart(ids[1], date);
+
+        replay(jobContext, phone, source, restartManager);
+
+        ProfileManagerImpl pm = new ProfileManagerImpl();
+        pm.setJobContext(jobContext);
+        pm.setDeviceSource(source);
+        pm.setRestartManager(restartManager);
+
+        pm.generateProfiles(Arrays.asList(ids), date);
+
+        verify(jobContext, phone, source, restartManager);
+    }
 
     public void testGenerateProfileAndRestart() {
         Integer jobId = new Integer(4);
@@ -108,6 +169,7 @@ public class ProfileManagerImplTest extends TestCase {
 
         DeviceSource source = createMock(DeviceSource.class);
         expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.scheduleRestart(phoneId, null);
 
         RestartManager restartManager = createMock(RestartManager.class);
         restartManager.restart(phoneId);
@@ -123,6 +185,48 @@ public class ProfileManagerImplTest extends TestCase {
 
         verify(jobContext, phone, source, restartManager);
     }
+    
+    public void testGenerateProfileAndScheduleRestart() {
+        Integer jobId = new Integer(4);
+        Integer phoneId = new Integer(1000);
+        Date date = new Date();
+
+        JobContext jobContext = createStrictMock(JobContext.class);
+        jobContext.schedule("Projection for: 110000000000");
+        expectLastCall().andReturn(jobId);
+        jobContext.start(jobId);
+        jobContext.success(jobId);
+
+        MemoryProfileLocation location = new MemoryProfileLocation();
+        DeviceDescriptor model = new DeviceDescriptor() {
+        };
+        model.setDefaultProfileLocation(location);
+
+        Device phone = createStrictMock(Device.class);
+        phone.getNiceName();
+        expectLastCall().andReturn("110000000000");
+        phone.getModel();
+        expectLastCall().andReturn(model);
+        phone.generateProfiles(same(location));
+
+        DeviceSource source = createMock(DeviceSource.class);
+        expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.scheduleRestart(phoneId, date);
+
+        RestartManager restartManager = createMock(RestartManager.class);
+        restartManager.scheduleRestart(phoneId, date);
+
+        replay(jobContext, phone, source, restartManager);
+
+        ProfileManagerImpl pm = new ProfileManagerImpl();
+        pm.setJobContext(jobContext);
+        pm.setDeviceSource(source);
+        pm.setRestartManager(restartManager);
+
+        pm.generateProfile(phoneId, date);
+
+        verify(jobContext, phone, source, restartManager);
+    }
 
     public void testGenerateProfile() {
         Integer jobId = new Integer(4);
@@ -148,6 +252,7 @@ public class ProfileManagerImplTest extends TestCase {
 
         DeviceSource source = createMock(DeviceSource.class);
         expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.scheduleRestart(phoneId, null);
 
         RestartManager restartManager = createMock(RestartManager.class);
 
diff --git a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/RestartManagerImplTest.java b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/RestartManagerImplTest.java
index 18b6ae8..6a15bc5 100644
--- a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/RestartManagerImplTest.java
+++ b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/device/RestartManagerImplTest.java
@@ -9,10 +9,17 @@
  */
 package org.sipfoundry.sipxconfig.device;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
 
 import junit.framework.TestCase;
 
+import org.sipfoundry.sipxconfig.device.RestartManagerImpl.ScheduleRestartTask;
 import org.sipfoundry.sipxconfig.job.JobContext;
 
 import static org.easymock.EasyMock.expect;
@@ -41,6 +48,7 @@ public class RestartManagerImplTest extends TestCase {
 
         DeviceSource source = createMock(DeviceSource.class);
         expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.removeScheduledRestart(phoneId);
 
         replay(jobContext, phone, source);
 
@@ -73,6 +81,7 @@ public class RestartManagerImplTest extends TestCase {
 
         DeviceSource source = createMock(DeviceSource.class);
         expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.removeScheduledRestart(phoneId);
 
         replay(jobContext, phone, source);
 
@@ -118,8 +127,10 @@ public class RestartManagerImplTest extends TestCase {
         for (int i = 0; i < 2; i++) {
             phoneSource.loadDevice(ids[0]);
             expectLastCall().andReturn(phone);
+            phoneSource.removeScheduledRestart(ids[0]);
             phoneSource.loadDevice(ids[1]);
             expectLastCall().andReturn(phone);
+            phoneSource.removeScheduledRestart(ids[1]);
         }
 
         replay(jobContext, phone, phoneSource);
@@ -150,4 +161,119 @@ public class RestartManagerImplTest extends TestCase {
 
         verify(jobContext, phone, phoneSource);
     }
+
+    public void testGenerateProfilesAndScheduleRestart() throws Exception {
+        Integer jobId = new Integer(4);
+        Integer phoneId = new Integer(1000);
+        Date date = new Date(); // this should restart the phone almost immediately
+
+        JobContext jobContext = createStrictMock(JobContext.class);
+        jobContext.schedule("Restarting: 000000000000");
+        expectLastCall().andReturn(jobId);
+        jobContext.start(jobId);
+        jobContext.success(jobId);
+
+        Device phone = createStrictMock(Device.class);
+        phone.getNiceName();
+        expectLastCall().andReturn("000000000000");
+        phone.restart();
+
+        DeviceSource source = createMock(DeviceSource.class);
+        expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.removeScheduledRestart(phoneId);
+
+        replay(jobContext, phone, source);
+
+        RestartManagerImpl rm = new RestartManagerImpl();
+        rm.setJobContext(jobContext);
+        rm.setDeviceSource(source);
+        rm.resetTimer();
+
+        rm.addScheduledRestart(phoneId, date);
+        assertEquals(1, rm.getScheduledRestartTasks().size());
+        Thread.sleep(100); // to be sure that the phone is restarted ...
+
+        verify(jobContext, phone, source);
+    }
+
+    public void testRescheduleRestart() throws Exception {
+        Integer phoneId = new Integer(1000);
+        GregorianCalendar gc = new GregorianCalendar();
+        gc.roll(Calendar.DAY_OF_YEAR, true);
+
+        RestartManagerImpl rm = new RestartManagerImpl();
+        rm.resetTimer();
+
+        rm.addScheduledRestart(phoneId, gc.getTime());
+        assertEquals(1, rm.getScheduledRestartTasks().size());
+        assertEquals(gc.getTime(), ((ArrayList<ScheduleRestartTask>)rm.getScheduledRestartTasks()).get(0).getScheduledRestartDate());
+        gc.roll(Calendar.DAY_OF_YEAR, true);
+        rm.addScheduledRestart(phoneId, gc.getTime());
+        assertEquals(1, rm.getScheduledRestartTasks().size());
+        assertEquals(gc.getTime(), ((ArrayList<ScheduleRestartTask>)rm.getScheduledRestartTasks()).get(0).getScheduledRestartDate());
+        rm.getScheduledRestartTasks().clear();
+    }
+
+    public void testGenerateProfilesAndRestartAfterScheduleRestart() throws Exception {
+        Integer jobId = new Integer(4);
+        Integer phoneId = new Integer(1000);
+        GregorianCalendar gc = new GregorianCalendar();
+        gc.roll(Calendar.DAY_OF_YEAR, true);
+
+        JobContext jobContext = createStrictMock(JobContext.class);
+        jobContext.schedule("Restarting: 000000000000");
+        expectLastCall().andReturn(jobId);
+        jobContext.start(jobId);
+        jobContext.success(jobId);
+
+        Device phone = createStrictMock(Device.class);
+        phone.getNiceName();
+        expectLastCall().andReturn("000000000000");
+        phone.restart();
+
+        DeviceSource source = createMock(DeviceSource.class);
+        expect(source.loadDevice(phoneId)).andReturn(phone);
+        source.removeScheduledRestart(phoneId);
+
+        replay(jobContext, phone, source);
+
+        RestartManagerImpl rm = new RestartManagerImpl();
+        rm.setJobContext(jobContext);
+        rm.setDeviceSource(source);
+        rm.resetTimer();
+
+        rm.addScheduledRestart(phoneId, gc.getTime());
+        assertEquals(1, rm.getScheduledRestartTasks().size());
+        rm.restart(phoneId);
+        assertEquals(0, rm.getScheduledRestartTasks().size());
+        verify(jobContext, phone, source);
+    }
+
+    public void testRescheduleTasks() throws Exception {
+        Integer[] ids = {
+                new Integer(1000), new Integer(2000)
+                };
+        GregorianCalendar gc = new GregorianCalendar();
+        gc.roll(Calendar.DAY_OF_YEAR, true);
+        
+        Map<Integer, Date> map = new HashMap<Integer, Date>();
+        
+        map.put(ids[0], gc.getTime());
+        map.put(ids[1], gc.getTime());        
+
+        DeviceSource source = createMock(DeviceSource.class);
+        source.getScheduledRestarts();
+        expectLastCall().andReturn(map);
+        
+        replay(source);
+
+        RestartManagerImpl rm = new RestartManagerImpl();
+        rm.setDeviceSource(source);
+        rm.resetTimer();
+        
+        rm.rescheduleTasks();
+        verify(source);
+        
+        assertEquals(2, rm.getScheduledRestartTasks().size());
+    }
 }
diff --git a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/PhoneSubclassSaveExpected.xml b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/PhoneSubclassSaveExpected.xml
index 2490fd9..b332f8a 100755
--- a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/PhoneSubclassSaveExpected.xml
+++ b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/PhoneSubclassSaveExpected.xml
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- DOCTYPE dataset SYSTEM "sipxconfig-db.dtd" -->
 <dataset>
-    <phone 
-  		serial_number="000000000000" 		
-	  	phone_id="[phone_id]"
-      bean_id="polycom"
-      model_id="polycom300"
-      description="[null]"
-      device_version_id="polycom2.0"
-		  value_storage_id="[null]"/> 
+  <phone 
+    serial_number="000000000000" 		
+    phone_id="[phone_id]"
+    bean_id="polycom"
+    model_id="polycom300"
+    description="[null]"
+    device_version_id="polycom2.0"
+    value_storage_id="[null]"
+    scheduled_restart_date="[null]" />
 </dataset>
diff --git a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/SaveEndpointExpected.xml b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/SaveEndpointExpected.xml
index 841fea3..264ca43 100644
--- a/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/SaveEndpointExpected.xml
+++ b/sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/phone/SaveEndpointExpected.xml
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- DOCTYPE dataset SYSTEM "sipxconfig-db.dtd" -->
 <dataset>
-    <phone 
-		serial_number="999123456" 		
-		phone_id="[phone_id_1]"
-        description="unittest-sample phone1"
-		value_storage_id="[null]"
-      device_version_id="[null]"
-      bean_id="testPhone"
-      model_id="testPhoneModel"/> 
+  <phone 
+    serial_number="999123456" 		
+    phone_id="[phone_id_1]"
+    description="unittest-sample phone1"
+    value_storage_id="[null]"
+    device_version_id="[null]"
+    bean_id="testPhone"
+    model_id="testPhoneModel"
+    scheduled_restart_date="[null]" /> 
 </dataset>
diff --git a/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.html b/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.html
index 3695aab..17e0124 100644
--- a/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.html
+++ b/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.html
@@ -9,6 +9,24 @@
       <span key="restart.label">Restart affected devices after generating profiles.</span>
     </label>
   </p>
+  <br>
+  <p>
+    <span key="scheduledRestart.prompt">Schedule restart</span>
+  </p>
+  <p>
+    <input jwcid="scheduledRestart@Checkbox" id="scheduledRestart:checkbox" value="ognl:scheduledRestart"/>
+    <!--
+    <span jwcid="scheduledRestartDay@PropertySelection"
+      model="ognl:scheduledRestartDayModel"
+      value="prop:scheduledRestartDay"/>
+    <input jwcid="scheduledRestartTime@TextField"
+        value="ognl:scheduledRestartTimeOfDay"
+        validators="validators:required"
+        translator="hivemind:spring:validTime"/>
+        -->
+    <span jwcid="scheduledRestartDate@common/DateTimeEditor" datetime="ognl:restartDate" label=""/>
+    <!--label=""-->
+  </p>
 </fieldset>
   <ul class="form-actions">
     <li>
diff --git a/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.properties b/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.properties
index fea4adb..5dc8f9c 100644
--- a/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.properties
+++ b/sipXconfig/web/context/WEB-INF/device/ConfirmProfileGeneration.properties
@@ -4,3 +4,9 @@ msg.success.profiles = {0} profile(s) will be generated. Go to Job Status page t
 prompt = You are about to generate new configuration profiles for {0,choice,1#1 device|1<{0} devices}. Press OK to proceed.
 
 restart.label = Automatically restart affected devices after profiles are ready.
+
+scheduledRestart.label=Schedule a restart for the selected devices
+
+scheduledRestart.prompt=Schedule restart
+
+msg.selection.failure=You cannot select both restart and schedule restart
diff --git a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.html b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.html
index 52c9281..237e901 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.html
+++ b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.html
@@ -5,4 +5,7 @@
     <span jwcid="modelColumnValue@Block">
         <span jwcid="@Insert" value="ognl:currentRow.model.label"/>
     </span>
+    <span jwcid="informationColumnValue@Block">
+        <span jwcid="@Insert" value="ognl:information"/>
+    </span>
 </table>
diff --git a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.jwc b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.jwc
index b68b087..c8928a9 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.jwc
+++ b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.jwc
@@ -4,6 +4,7 @@
   allow-informal-parameters="no">
   <!-- P A R A M E T E R S -->
   <parameter name="gatewayCollection" required="yes" />
+  <parameter name="devicesStatus" required="yes"/>
   <parameter name="ruleId" required="no" default-value="null" />
   
   <!-- P R O P E R T I E S -->
@@ -11,7 +12,7 @@
   <property name="selections" />
   <!-- C O M P O N E N T S -->
   <component id="table" type="common/Table">
-    <binding name="columns" value="literal:* name,address,model,description" />
+    <binding name="columns" value="literal:* name,address,model,description,information" />
     <binding name="source" value="gatewayCollection" />
     <binding name="row" value="currentRow" />
     <binding name="selections" value="selections" />
diff --git a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.properties b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.properties
index 886159a..0063ca2 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.properties
+++ b/sipXconfig/web/context/WEB-INF/gateway/GatewayTable.properties
@@ -1,3 +1,6 @@
 # 'name' and 'description' translations are in application bundle (sipXconfig-web.properties) 
 address=Address
 model=Model
+information=Information
+generatingProfile=Generating profile
+restartOn=Restart on  
diff --git a/sipXconfig/web/context/WEB-INF/gateway/GatewaysPanel.jwc b/sipXconfig/web/context/WEB-INF/gateway/GatewaysPanel.jwc
index aa2b2fc..c118519 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/GatewaysPanel.jwc
+++ b/sipXconfig/web/context/WEB-INF/gateway/GatewaysPanel.jwc
@@ -31,6 +31,7 @@
   <!-- C O M P O N E N T S -->
   <component id="gatewayTable" type="gateway/GatewayTable">
     <binding name="gatewayCollection" value="rule.gateways" />
+    <binding name="devicesStatus" value="gatewayContext.devicesStatus" />
     <binding name="ruleId" value="rule.id" />
   </component>
   <component id="gatewayActions" type="phone/GroupActions">
diff --git a/sipXconfig/web/context/WEB-INF/gateway/ListGateways.html b/sipXconfig/web/context/WEB-INF/gateway/ListGateways.html
index d2cd48c..cd02e61 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/ListGateways.html
+++ b/sipXconfig/web/context/WEB-INF/gateway/ListGateways.html
@@ -21,10 +21,11 @@
   </div>
   <div jwcid="@common/QuickHelp" value="message:help.gatewayConfig">gateway config help text here...</div>
   <span jwcid="@common/ErrorMsg"/>
-  <div jwcid="@If" condition="ognl:generateProfileIds != null" renderTag="ognl:false">
+  <div jwcid="@If" condition="ognl:generateProfiles" renderTag="ognl:false">
     <span jwcid="@device/ConfirmProfileGeneration"
         profileManager="ognl:profileManager"
-        deviceIds="ognl:generateProfileIds"/>
+        deviceIds="ognl:generateProfileIds"
+        generateProfiles="ognl:generateProfiles"/>
   </div>
   <div jwcid="@Else" renderTag="ognl:false">
     <form jwcid="gatewaysListForm@Form" id="list:gateway:form"
@@ -38,7 +39,9 @@
             onchange="javascript:this.form.submit();"/>
         </li>
       </ul>
-      <fieldset jwcid="gatewayTable" gatewayCollection="ognl:gatewayContext.gateways" />
+    </form>
+    <form jwcid="gatewayManagement@common/AutoRefreshForm">
+      <fieldset jwcid="gatewayTable" gatewayCollection="ognl:gatewayContext.gateways" devicesStatus="ognl:gatewayContext.devicesStatus"/>
       <ul class="component-submit">
         <li>
           <input jwcid="propagateButton@Submit" id="list:gateway:propagate"
@@ -62,6 +65,7 @@
           <span jwcid="@common/Confirm" component="component:deleteButton" prompt="message:confirm.delete"/>
         </li>
       </ul>
+      <ul jwcid="actions@Block"></ul>
     </form>
   </div>
 </div>
diff --git a/sipXconfig/web/context/WEB-INF/gateway/ListGateways.properties b/sipXconfig/web/context/WEB-INF/gateway/ListGateways.properties
index fb47ab9..1c5579a 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/ListGateways.properties
+++ b/sipXconfig/web/context/WEB-INF/gateway/ListGateways.properties
@@ -14,3 +14,10 @@ help.gatewayConfig=Configure "unmanaged gateways", "SIP trunks" and PSTN gateway
   up, or the configuration file can be manually downloaded and transferred to the gateway. An "unmanaged gateway" needs to \
   be created for all manually configured gateways so that they can be inserted into the dialplan.
 msg.success.restart={0} gateway(s) will be restarted. Go to Job Status page to monitor operation progress.
+msg.partial.failure.restart={0} gateway(s) will be restarted. {1} are still generating profiles. Go to Job Status page to monitor operation progress.
+msg.failure.restart=The gateway(s) are still generating the profiles
+msg.success.delete={0} gateway(s) successfully deleted.
+msg.partial.failure.delete={0} gateway(s) successfully deleted. {1} are still generating profiles
+msg.failure.delete=The gateway(s) are still generating the profiles.
+
+
diff --git a/sipXconfig/web/context/WEB-INF/gateway/SelectGateways.page b/sipXconfig/web/context/WEB-INF/gateway/SelectGateways.page
index 892e2d2..5b672ba 100644
--- a/sipXconfig/web/context/WEB-INF/gateway/SelectGateways.page
+++ b/sipXconfig/web/context/WEB-INF/gateway/SelectGateways.page
@@ -22,6 +22,7 @@
   </component>
   <component id="gatewayTable" type="gateway/GatewayTable">
     <binding name="gatewayCollection" value="gateways"/>
+    <binding name="devicesStatus" value="gatewayContext.devicesStatus"/>
     <binding name="editPageName" value="literal:EditGateway"/>
   </component>
   <component id="selectButton" type="Submit">
diff --git a/sipXconfig/web/context/WEB-INF/phone/EditPhone.html b/sipXconfig/web/context/WEB-INF/phone/EditPhone.html
index 3d57f79..d0fb998 100644
--- a/sipXconfig/web/context/WEB-INF/phone/EditPhone.html
+++ b/sipXconfig/web/context/WEB-INF/phone/EditPhone.html
@@ -18,10 +18,11 @@
         </li>
       </ul>
     </div>
-    <div jwcid="@If" condition="ognl:generateProfileIds != null">
+    <div jwcid="@If" condition="ognl:generateProfiles">
       <span jwcid="@device/ConfirmProfileGeneration"
         profileManager="ognl:phoneProfileManager"
-        deviceIds="ognl:generateProfileIds"/>
+        deviceIds="ognl:generateProfileIds"
+        generateProfiles="ognl:generateProfiles"/>
     </div>
     <div jwcid="@Else">
       <span jwcid="@setting/GroupCloud" groups="ognl:phoneContext.groups"/>
diff --git a/sipXconfig/web/context/WEB-INF/phone/ManagePhones.html b/sipXconfig/web/context/WEB-INF/phone/ManagePhones.html
index 1809a06..49a539e 100644
--- a/sipXconfig/web/context/WEB-INF/phone/ManagePhones.html
+++ b/sipXconfig/web/context/WEB-INF/phone/ManagePhones.html
@@ -11,18 +11,21 @@
   </div>
   <div jwcid="@common/QuickHelp" value="message:help.restart">restart help text...</div>
   <span jwcid="@common/ErrorMsg"/>
-  <div jwcid="@If" condition="ognl:generateProfileIds != null">
+  <div jwcid="@If" condition="ognl:generateProfiles">
   	<span jwcid="@device/ConfirmProfileGeneration"
         profileManager="ognl:phoneProfileManager"
-        deviceIds="ognl:generateProfileIds"/>
+        deviceIds="ognl:generateProfileIds"
+        generateProfiles="ognl:generateProfiles"/>
   </div>
   <div jwcid="@Else">
-  <form jwcid="phoneManagement@Form" delegate="bean:validator">
+  <form jwcid="addNewPhone@Form">
     <ul class="component-links">
       <li>
         <span jwcid="@phone/ModelSelector"/>
       </li>
     </ul>
+  </form>
+  <form jwcid="phoneManagement@common/AutoRefreshForm">
     <ul class="component-submit">
       <li>
         <span jwcid="@setting/GroupFilter"
@@ -32,10 +35,14 @@
           searchMode="ognl:searchMode" />
       </li>
     </ul>
-    <table jwcid="@phone/PhoneTable" selections="bean:selections" source="ognl:tableModel"/>
+    <table jwcid="@phone/PhoneTable" 
+      selections="bean:selections" 
+      source="ognl:tableModel" 
+      devicesStatus="ognl:phoneContext.devicesStatus"/>
     <ul jwcid="@phone/PhoneTableActions"
       selections="bean:selections"
       generateProfileIds="ognl:generateProfileIds"
+      generateProfiles="ognl:generateProfiles"
       actionModel="ognl:actionModel"/>
   </form>
   </div>
diff --git a/sipXconfig/web/context/WEB-INF/phone/PhoneTable.html b/sipXconfig/web/context/WEB-INF/phone/PhoneTable.html
index 4c38f9c..4457c5b 100644
--- a/sipXconfig/web/context/WEB-INF/phone/PhoneTable.html
+++ b/sipXconfig/web/context/WEB-INF/phone/PhoneTable.html
@@ -1,6 +1,6 @@
 <table  id="phone:list" jwcid="newPhoneTable@common/Table"
   source="ognl:source" 
-  columns="* serialNumber,!lines,modelId:model.label + version,description"
+  columns="* serialNumber,!lines,modelId:model.label + version,description,information"
   usePager="ognl:true" 
   row="ognl:phone"
   selections="ognl:selections">
@@ -26,4 +26,7 @@
     <span jwcid="@Insert" value="ognl:phone.model.label"/>
     <span jwcid="@Insert" value="ognl:version"/>
   </span>
+  <span jwcid="informationColumnValue@Block">
+    <span jwcid="@Insert" value="ognl:information"/>
+  </span>
 </table>
diff --git a/sipXconfig/web/context/WEB-INF/phone/PhoneTable.properties b/sipXconfig/web/context/WEB-INF/phone/PhoneTable.properties
index 164183e..0597619 100644
--- a/sipXconfig/web/context/WEB-INF/phone/PhoneTable.properties
+++ b/sipXconfig/web/context/WEB-INF/phone/PhoneTable.properties
@@ -2,3 +2,6 @@ serialNumber=Phone
 lines=Lines
 modelId=Model
 description=Description
+information=Information
+generatingProfile=Generating profile
+restartOn=Restart on  
diff --git a/sipXconfig/web/context/WEB-INF/phone/PhoneTableActions.properties b/sipXconfig/web/context/WEB-INF/phone/PhoneTableActions.properties
index ab7a450..9668397 100644
--- a/sipXconfig/web/context/WEB-INF/phone/PhoneTableActions.properties
+++ b/sipXconfig/web/context/WEB-INF/phone/PhoneTableActions.properties
@@ -4,6 +4,10 @@ button.restart=Restart
 msg.success.addToPhoneGroupAction={0} phone(s) added to group {1}.
 msg.success.removeFromPhoneGroupAction={0} phone(s) removed from group {1}.
 msg.success.restart={0} phone(s) will be restarted. Go to Job Status page to monitor operation progress.
+msg.partial.failure.restart={0} phone(s) will be restarted. {1} are still generating profiles. Go to Job Status page to monitor operation progress.
+msg.failure.restart=The phone(s) are still generating the profiles
 msg.success.delete={0} phone(s) successfully deleted.
+msg.partial.failure.delete={0} phone(s) successfully deleted. {1} are still generating profiles
+msg.failure.delete=The phone(s) are still generating the prfiles.
 confirm.delete=Are you sure you want to delete selected phones?
-confirm.sendAllProfiles=Are you sure you want to send new configuration to all phones?
\ No newline at end of file
+confirm.sendAllProfiles=Are you sure you want to send new configuration to all phones?
diff --git a/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.html b/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.html
index 860ad7e..7f2242f 100644
--- a/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.html
+++ b/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.html
@@ -25,7 +25,8 @@
   <div jwcid="@If" condition="ognl:generateProfileIds != null">
     <span jwcid="@device/ConfirmProfileGeneration"
       profileManager="ognl:profileManager"
-      deviceIds="ognl:generateProfileIds"/>
+      deviceIds="ognl:generateProfileIds"
+      generateProfiles="ognl:generateProfiles"/>
   </div>
   <div jwcid="@Else">
     <form jwcid="@Form" delegate="bean:validator" listener="listener:formSubmit" id="list:sbc:form">
@@ -37,9 +38,11 @@
             onchange="literal:javascript:this.form.submit();"/>
         </li>
       </ul>
+    </form>
+    <form jwcid="gatewayManagement@common/AutoRefreshForm">
       <table jwcid="@common/Table" id="list:sbc"
         source="ognl:sbcDeviceManager.sbcDevices"
-        columns="* name,address,model,description"
+        columns="* name,address,model,description,information"
         selections="bean:selections"
         row="ognl:sbc">
         <span jwcid="nameColumnValue@Block">
@@ -52,6 +55,9 @@
         <span jwcid="modelColumnValue@Block">
           <span jwcid="@Insert" value="ognl:sbc.model.label"/>
         </span>
+        <span jwcid="informationColumnValue@Block">
+          <span jwcid="@Insert" value="ognl:information"/>
+        </span>
       </table>
       <ul class="component-submit">
         <li>
@@ -76,6 +82,7 @@
           <span jwcid="@common/Confirm" component="component:delete" prompt="message:confirm.delete"/>
         </li>
       </ul>
+      <ul jwcid="actions@Block"></ul>
     </form>
   </div>
 </div>
diff --git a/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.properties b/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.properties
index 4f5f362..375d26f 100644
--- a/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.properties
+++ b/sipXconfig/web/context/WEB-INF/sbc/ListSbcDevices.properties
@@ -1,6 +1,7 @@
 address=Address
 model=Model
 title=SBCs
+information=Infromation
 confirm.delete=Are you sure you want to remove selected SBCs?
 help.sbcConfig=<p>Session Border Controllers (SBC) allow SIP calls to securely traverse network boundaries (firewalls). SBCs defined here can \
   be used to configure "Internet Calling" rules. An "unmanaged" SBC needs to be manually configured using the configuration tools \
@@ -13,7 +14,14 @@ link.internetCalling=Internet Calling
 link.gateways=Gateways
 link.jobStatus=Job Status
 msg.success.restart={0} SBC(s) will be restarted. Go to Job Status page to monitor operation progress.
+msg.partial.failure.restart={0} SBC(s) will be restarted. {1} are still generating profiles. Go to Job Status page to monitor operation progress.
+msg.failure.restart=The SBC(s) are still generating the profiles
 button.propagate=Send Profiles
 button.propagateAll=Send All Profiles
 button.restart=Restart
 sbc.creation.error=You are not allowed to create more than {0} "{1}"(s)
+generatingProfile=Generating profile
+restartOn=Restart on  
+msg.success.delete={0} SBC(s) successfully deleted.
+msg.partial.failure.delete={0} SBC(s) successfully deleted. {1} are still generating profiles
+msg.failure.delete=The SBC(s) are still generating the profiles.
diff --git a/sipXconfig/web/context/WEB-INF/user/AddExistingPhone.html b/sipXconfig/web/context/WEB-INF/user/AddExistingPhone.html
index 62552ad..b04b2d6 100644
--- a/sipXconfig/web/context/WEB-INF/user/AddExistingPhone.html
+++ b/sipXconfig/web/context/WEB-INF/user/AddExistingPhone.html
@@ -18,7 +18,7 @@
           searchMode="ognl:searchMode" />
       </li>
     </ul>
-    <table jwcid="@phone/PhoneTable" selections="bean:selections" source="ognl:tableModel"/>
+    <table jwcid="@phone/PhoneTable" selections="bean:selections" source="ognl:tableModel" devicesStatus="ognl:phoneContext.devicesStatus"/>
     <span jwcid="select@Submit" value="message:button.select"
       listener="listener:select"/>
     <span jwcid="cancel@Submit" value="message:button.cancel" listener="listener:cancel"/>
diff --git a/sipXconfig/web/context/WEB-INF/user/UserPhones.html b/sipXconfig/web/context/WEB-INF/user/UserPhones.html
index 02a8552..f6e0a87 100644
--- a/sipXconfig/web/context/WEB-INF/user/UserPhones.html
+++ b/sipXconfig/web/context/WEB-INF/user/UserPhones.html
@@ -11,13 +11,14 @@
       </ul>
     </div>
     <span jwcid="@common/QuickHelp" value="message:quick.help" />
-    <div jwcid="@If" condition="ognl:generateProfileIds != null">
+    <div jwcid="@If" condition="ognl:generateProfiles">
       <span jwcid="@device/ConfirmProfileGeneration"
         profileManager="ognl:phoneProfileManager"
-        deviceIds="ognl:generateProfileIds"/>
+        deviceIds="ognl:generateProfileIds"
+        generateProfiles="ognl:generateProfiles"/>
     </div>
     <div jwcid="@Else">
-      <form jwcid="@Form" delegate="bean:validator">
+      <form jwcid="@common/AutoRefreshForm" actionBlock="component:actions">
         <ul class="component-links">
           <li>
             <span jwcid="@phone/ModelSelector" userId="ognl:userId"/>
@@ -26,11 +27,13 @@
             </span>
           </li>    
         </ul>   
-        <table jwcid="@phone/PhoneTable" selections="bean:selections" source="ognl:phones"/>
+        <table jwcid="@phone/PhoneTable" selections="bean:selections" source="ognl:phones" devicesStatus="ognl:phoneContext.devicesStatus"/>
         <ul jwcid="@phone/PhoneTableActions" 
           selections="bean:selections" 
-          generateProfileIds="ognl:generateProfileIds"          
-          userOnly="ognl:true"/>      
+          generateProfileIds="ognl:generateProfileIds"
+          generateProfiles="ognl:generateProfiles"
+          userOnly="ognl:true"/>
+        <ul jwcid="actions@Block"></ul>
       </form>
     </div>
   </div>
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/components/GatewayTable.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/components/GatewayTable.java
index 6fbfa01..ac08dd6 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/components/GatewayTable.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/components/GatewayTable.java
@@ -9,11 +9,14 @@
  */
 package org.sipfoundry.sipxconfig.components;
 
+import java.text.DateFormat;
+
 import org.apache.tapestry.BaseComponent;
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.IRequestCycle;
 import org.apache.tapestry.event.PageBeginRenderListener;
 import org.apache.tapestry.event.PageEvent;
+import org.sipfoundry.sipxconfig.device.DevicesStatus;
 import org.sipfoundry.sipxconfig.gateway.Gateway;
 import org.sipfoundry.sipxconfig.site.gateway.EditGateway;
 
@@ -24,6 +27,8 @@ public abstract class GatewayTable extends BaseComponent implements PageBeginRen
     public abstract Gateway getCurrentRow();
 
     public abstract SelectMap getSelections();
+    
+    public abstract DevicesStatus getDevicesStatus();
 
     public abstract void setSelections(SelectMap selected);
 
@@ -40,4 +45,14 @@ public abstract class GatewayTable extends BaseComponent implements PageBeginRen
     public IPage edit(IRequestCycle cycle, Integer id, Integer ruleId) {
         return EditGateway.getEditPage(cycle, id, getPage(), ruleId);
     }
+    
+    public String getInformation() {
+        if (getDevicesStatus().isDeviceUpdating(getCurrentRow().getId())) {
+            String msg = getMessages().getMessage("generatingProfile");
+            return msg;
+        }
+        DateFormat dateFormat = TapestryUtils.getDateFormat(getPage().getLocale());
+        return getCurrentRow().getScheduledRestartDate() == null ? "" : (getMessages().getMessage(
+                "restartOn") + dateFormat.format(getCurrentRow().getScheduledRestartDate()));
+    }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/device/ConfirmProfileGeneration.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/device/ConfirmProfileGeneration.java
index 6095ea3..41b521a 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/device/ConfirmProfileGeneration.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/device/ConfirmProfileGeneration.java
@@ -9,40 +9,83 @@
  */
 package org.sipfoundry.sipxconfig.site.device;
 
+import java.util.Calendar;
 import java.util.Collection;
+import java.util.Date;
 
+import org.apache.commons.lang.time.DateUtils;
 import org.apache.tapestry.BaseComponent;
 import org.apache.tapestry.annotations.InitialValue;
 import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.event.PageBeginRenderListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.valid.ValidatorException;
 import org.sipfoundry.sipxconfig.components.TapestryUtils;
 import org.sipfoundry.sipxconfig.device.ProfileManager;
 
-public abstract class ConfirmProfileGeneration extends BaseComponent {
+public abstract class ConfirmProfileGeneration extends BaseComponent implements PageBeginRenderListener {
+    public static final String SUCCESS_MSG = "msg.success.profiles";
+    
     @Parameter(required = true)
     public abstract ProfileManager getProfileManager();
 
     @Parameter(required = true)
     public abstract Collection<Integer> getDeviceIds();
+    
+    @Parameter(required = false)
+    public abstract boolean getGenerateProfiles();
+    
+    public abstract void setGenerateProfiles(boolean bool);
 
     public abstract void setDeviceIds(Collection<Integer> ids);
 
     @InitialValue(value = "true")
     public abstract boolean getRestart();
+    
+    @InitialValue(value = "false")
+    public abstract boolean getScheduledRestart();
+    
+    public abstract void setRestartDate(Date restartDate);
+    
+    public abstract Date getRestartDate();
 
     public void generate() {
         Collection<Integer> deviceIds = getDeviceIds();
-        getProfileManager().generateProfiles(deviceIds, getRestart());
-        String msg = getMessages().format("msg.success.profiles", deviceIds.size());
+        if (getRestart() && getScheduledRestart()) {
+            TapestryUtils.getValidator(getPage()).record(
+                    new ValidatorException(getMessages().getMessage("msg.selection.failure")));
+            return;
+        } else if (getScheduledRestart()) {
+            getProfileManager().generateProfiles(deviceIds, getRestartDate());
+            setGenerateProfiles(false);
+        } else if (getRestart()) {
+            getProfileManager().generateProfiles(deviceIds, true);
+            setGenerateProfiles(false);
+        } else {
+            getProfileManager().generateProfiles(deviceIds, false);
+            setGenerateProfiles(false);
+        }
+        String msg = getMessages().format(SUCCESS_MSG, deviceIds.size());
         TapestryUtils.recordSuccess(this, msg);
-        setDeviceIds(null);
     }
 
     public void cancel() {
         setDeviceIds(null);
+        setGenerateProfiles(false);
     }
 
     public String getPrompt() {
         Collection<Integer> deviceIds = getDeviceIds();
         return getMessages().format("prompt", deviceIds.size());
     }
+    
+    public void pageBeginRender(PageEvent event_) {
+        if (getRestartDate() == null) {
+            // Default restart time: Tomorrow, midnight
+            Calendar now = Calendar.getInstance();
+            now.add(Calendar.DAY_OF_MONTH, 1);
+            Calendar tomorrow = DateUtils.truncate(now, Calendar.DAY_OF_MONTH);
+            setRestartDate(tomorrow.getTime());
+        }
+    }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/gateway/ListGateways.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/gateway/ListGateways.java
index 593c5d3..6dd4bb1 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/gateway/ListGateways.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/gateway/ListGateways.java
@@ -9,16 +9,21 @@
  */
 package org.sipfoundry.sipxconfig.site.gateway;
 
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.IRequestCycle;
 import org.apache.tapestry.annotations.Bean;
 import org.apache.tapestry.annotations.Component;
+import org.apache.tapestry.annotations.InitialValue;
 import org.apache.tapestry.annotations.InjectObject;
 import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.event.PageBeginRenderListener;
+import org.apache.tapestry.event.PageEvent;
 import org.apache.tapestry.form.IPropertySelectionModel;
 import org.apache.tapestry.html.BasePage;
+import org.apache.tapestry.valid.ValidatorException;
 import org.sipfoundry.sipxconfig.components.GatewayTable;
 import org.sipfoundry.sipxconfig.components.SipxValidationDelegate;
 import org.sipfoundry.sipxconfig.components.TapestryUtils;
@@ -32,7 +37,7 @@ import org.sipfoundry.sipxconfig.site.sbc.ListSbcDevices.DeviceDescriptorSelecti
 /**
  * List all the gateways, allow adding and deleting gateways
  */
-public abstract class ListGateways extends BasePage {
+public abstract class ListGateways extends BasePage implements PageBeginRenderListener {
     public static final String PAGE = "gateway/ListGateways";
 
     @InjectObject(value = "spring:gatewayContext")
@@ -53,7 +58,15 @@ public abstract class ListGateways extends BasePage {
     public abstract GatewayTable getGatewayTable();
 
     @Persist
-    public abstract void setGenerateProfileIds(Collection<Integer> ids);
+    public abstract Collection<Integer> getGenerateProfileIds();
+    
+    public abstract void setGenerateProfileIds(Collection<Integer> profileIds);
+    
+    @InitialValue("false")
+    @Persist
+    public abstract boolean getGenerateProfiles();
+    
+    public abstract void setGenerateProfiles(boolean bool);
 
     @Bean
     public abstract SipxValidationDelegate getValidator();
@@ -70,31 +83,81 @@ public abstract class ListGateways extends BasePage {
     }
 
     public void propagate() {
-        Collection<Integer> ids = getGatewayTable().getSelections().getAllSelected();
-        if (!ids.isEmpty()) {
-            setGenerateProfileIds(ids);
+        Collection<Integer> gatewayIds = getGatewayTable().getSelections().getAllSelected();
+        if (!gatewayIds.isEmpty()) {
+            setGenerateProfileIds(gatewayIds);
+            setGenerateProfiles(true);
         }
     }
 
     public void delete() {
+        GatewayContext context = getGatewayContext();
+        Collection<String> notDeleted = new ArrayList<String>();
         Collection<Integer> ids = getGatewayTable().getSelections().getAllSelected();
-        if (!ids.isEmpty()) {
-            getGatewayContext().deleteGateways(ids);
+        if (ids.isEmpty()) {
+            return;
+        }
+        for (Integer gatewayId : ids) {
+            if (!context.getDevicesStatus().isDeviceUpdating(gatewayId)) {
+                context.deleteGateway(gatewayId);
+            } else {
+                notDeleted.add(context.getGateway(gatewayId).getSerialNumber());
+            }
+        }
+        
+        if (notDeleted.size() == 0) {
+            // all gateways deleted
+            String msg = getMessages().format("msg.success.delete", Integer.toString(ids.size()));
+            TapestryUtils.recordSuccess(this, msg);
+        } else if (notDeleted.size() == ids.size()) {
+            // no gateways deleted
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                    getMessages().getMessage("msg.failure.delete")));
+        } else {
+            // some gateways deleted
+            String msg = getMessages().format("msg.partial.success.delete", Integer.toString(ids.size() 
+                    - notDeleted.size()), notDeleted.toString());
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
         }
     }
 
     public void restart() {
-        Collection<Integer> ids = getGatewayTable().getSelections().getAllSelected();
-        if (!ids.isEmpty()) {
-            getRestartManager().restart(ids);
-            String msg = getMessages().format("msg.success.restart", Integer.toString(ids.size()));
-            TapestryUtils.recordSuccess(this, msg);
+        Collection<Integer> gatewayIds = getGatewayTable().getSelections().getAllSelected();
+        if (!gatewayIds.isEmpty()) {
+            Collection<Integer> gatewaysToRestart = new ArrayList<Integer>();
+            Collection<String> gatewaysUpdating = new ArrayList<String>();
+            for (Integer gatewayId : gatewayIds) {
+                if (getGatewayContext().getDevicesStatus().isDeviceUpdating(gatewayId)) {
+                    gatewaysUpdating.add(getGatewayContext().getGateway(gatewayId).getSerialNumber());
+                } else {
+                    gatewaysToRestart.add(gatewayId);
+                }
+            }
+            if (gatewaysToRestart.size() != 0) {
+                getRestartManager().restart(gatewaysToRestart);
+                if (gatewaysUpdating.size() == 0) {
+                    // all phones are set to be restarted ...
+                    String msg = getMessages().format("msg.success.restart",
+                            Integer.toString(gatewayIds.size()));
+                    TapestryUtils.recordSuccess(this, msg);
+                } else {
+                    // some phones have not been restarted
+                    String msg = getMessages().format("msg.partial.failure.restart", 
+                            gatewaysToRestart.size(), gatewaysUpdating.toString());
+                    TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
+                }
+            } else {
+                // no phones will be restarted
+                TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                        getMessages().getMessage("msg.failure.restart")));
+            }
         }
     }
 
-    public void propagateAllGateways() {
-        Collection gatewayIds = getGatewayContext().getAllGatewayIds();
+    public void propagateAll() {
+        Collection<Integer> gatewayIds = getGatewayContext().getAllGatewayIds();
         setGenerateProfileIds(gatewayIds);
+        setGenerateProfiles(true);
     }
 
     public IPropertySelectionModel getGatewaySelectionModel() {
@@ -104,4 +167,13 @@ public abstract class ListGateways extends BasePage {
         model.setExtraOption(null);
         return model;
     }
+    
+    public void pageBeginRender(PageEvent event_) {
+        if (!getGenerateProfiles()) {
+            if (getGenerateProfileIds() != null) {
+                getGatewayContext().getDevicesStatus().addDevicesToUpdatingDevicesList(getGenerateProfileIds());
+            }
+            setGenerateProfileIds(null);
+        }
+    }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/EditPhone.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/EditPhone.java
index d598f1d..84a38e2 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/EditPhone.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/EditPhone.java
@@ -15,6 +15,7 @@ import java.util.Collections;
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.IRequestCycle;
 import org.apache.tapestry.annotations.Bean;
+import org.apache.tapestry.annotations.InitialValue;
 import org.apache.tapestry.annotations.InjectObject;
 import org.apache.tapestry.annotations.Persist;
 import org.apache.tapestry.event.PageBeginRenderListener;
@@ -58,6 +59,12 @@ public abstract class EditPhone extends PageWithCallback implements PageBeginRen
 
     @Bean
     public abstract SipxValidationDelegate getValidator();
+    
+    @InitialValue("false")
+    @Persist
+    public abstract boolean getGenerateProfiles();
+
+    public abstract void setGenerateProfiles(boolean bool);
 
     @Persist
     public abstract Collection<Integer> getGenerateProfileIds();
@@ -90,9 +97,17 @@ public abstract class EditPhone extends PageWithCallback implements PageBeginRen
     public void generateProfile() {
         Collection<Integer> phoneIds = Collections.singleton(getPhone().getId());
         setGenerateProfileIds(phoneIds);
+        setGenerateProfiles(true);
     }
 
     public void pageBeginRender(PageEvent event_) {
+        if (!getGenerateProfiles()) {
+            if (getGenerateProfileIds() != null) {
+                getPhoneContext().getDevicesStatus().addDevicesToUpdatingDevicesList(getGenerateProfileIds());
+            }
+            setGenerateProfileIds(null);
+        }
+        
         if (getPhone() != null) {
             return;
         }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/ManagePhones.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/ManagePhones.java
index 0517c2a..adaa5a8 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/ManagePhones.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/ManagePhones.java
@@ -45,6 +45,8 @@ public abstract class ManagePhones extends BasePage implements PageBeginRenderLi
 
     @InjectObject(value = "spring:phoneContext")
     public abstract PhoneContext getPhoneContext();
+    
+//    public abstract void setPhoneContext
 
     @InjectObject(value = "spring:searchManager")
     public abstract SearchManager getSearchManager();
@@ -70,6 +72,12 @@ public abstract class ManagePhones extends BasePage implements PageBeginRenderLi
 
     @Persist
     public abstract Collection<Integer> getGenerateProfileIds();
+    
+    public abstract void setGenerateProfileIds(Collection<Integer> profileIds);
+    
+    @InitialValue("false")
+    @Persist
+    public abstract boolean getGenerateProfiles();
 
     public IBasicTableModel getTableModel() {
         String queryText = getQueryText();
@@ -84,6 +92,12 @@ public abstract class ManagePhones extends BasePage implements PageBeginRenderLi
      */
     public void pageBeginRender(PageEvent event_) {
         initActionsModel();
+        if (!getGenerateProfiles()) {
+            if (getGenerateProfileIds() != null) {
+                getPhoneContext().getDevicesStatus().addDevicesToUpdatingDevicesList(getGenerateProfileIds());
+            }
+            setGenerateProfileIds(null);
+        }
     }
 
     private void initActionsModel() {
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTable.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTable.java
index 0dbd5af..6e88ed7 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTable.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTable.java
@@ -9,13 +9,17 @@
  */
 package org.sipfoundry.sipxconfig.site.phone;
 
+import java.text.DateFormat;
+
 import org.apache.tapestry.BaseComponent;
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.annotations.ComponentClass;
 import org.apache.tapestry.annotations.InjectPage;
 import org.apache.tapestry.annotations.Parameter;
 import org.sipfoundry.sipxconfig.components.SelectMap;
+import org.sipfoundry.sipxconfig.components.TapestryUtils;
 import org.sipfoundry.sipxconfig.device.DeviceVersion;
+import org.sipfoundry.sipxconfig.device.DevicesStatus;
 import org.sipfoundry.sipxconfig.phone.Line;
 import org.sipfoundry.sipxconfig.phone.Phone;
 import org.sipfoundry.sipxconfig.site.line.EditLine;
@@ -27,6 +31,9 @@ public abstract class PhoneTable extends BaseComponent {
 
     @Parameter(required = true)
     public abstract Object getSource();
+    
+    @Parameter(required = true)
+    public abstract DevicesStatus getDevicesStatus();
 
     @InjectPage(value = EditPhone.PAGE)
     public abstract EditPhone getEditPhonePage();
@@ -65,4 +72,14 @@ public abstract class PhoneTable extends BaseComponent {
 
         return "v" + ver.getVersionId();
     }
+    
+    public String getInformation() {
+        if (getDevicesStatus().isDeviceUpdating(getPhone().getId())) {
+            String msg = getMessages().getMessage("generatingProfile");
+            return msg;
+        }
+        DateFormat dateFormat = TapestryUtils.getDateFormat(getPage().getLocale());
+        return getPhone().getScheduledRestartDate() == null ? "" : (getMessages().getMessage(
+                "restartOn") + dateFormat.format(getPhone().getScheduledRestartDate()));
+    }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTableActions.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTableActions.java
index 98070e7..c9fd2b2 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTableActions.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/phone/PhoneTableActions.java
@@ -9,14 +9,15 @@
  */
 package org.sipfoundry.sipxconfig.site.phone;
 
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Iterator;
 
 import org.apache.tapestry.BaseComponent;
 import org.apache.tapestry.annotations.ComponentClass;
 import org.apache.tapestry.annotations.InjectObject;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.form.IPropertySelectionModel;
+import org.apache.tapestry.valid.ValidatorException;
 import org.sipfoundry.sipxconfig.components.SelectMap;
 import org.sipfoundry.sipxconfig.components.TapestryUtils;
 import org.sipfoundry.sipxconfig.device.RestartManager;
@@ -41,6 +42,11 @@ public abstract class PhoneTableActions extends BaseComponent {
     public abstract Collection<Integer> getGenerateProfileIds();
 
     public abstract void setGenerateProfileIds(Collection<Integer> ids);
+    
+    @Parameter(required = true)
+    public abstract boolean getGenerateProfiles();
+
+    public abstract void setGenerateProfiles(boolean bool);
 
     @InjectObject(value = "spring:phoneContext")
     public abstract PhoneContext getPhoneContext();
@@ -50,37 +56,84 @@ public abstract class PhoneTableActions extends BaseComponent {
 
     public void generateProfiles() {
         Collection phoneIds = getSelections().getAllSelected();
-        setGenerateProfileIds(phoneIds);
+        if (phoneIds.size() != 0) {
+            setGenerateProfileIds(phoneIds);
+            setGenerateProfiles(true);
+        }
     }
 
     public void generateAllProfiles() {
         Collection phoneIds = getPhoneContext().getAllPhoneIds();
-        setGenerateProfileIds(phoneIds);
+        if (phoneIds.size() != 0) {
+            setGenerateProfileIds(phoneIds);
+            setGenerateProfiles(true);
+        }
     }
 
     public void restart() {
-        Collection phoneIds = getSelections().getAllSelected();
-        getRestartManager().restart(phoneIds);
-        String msg = getMessages().format("msg.success.restart",
-                Integer.toString(phoneIds.size()));
-        TapestryUtils.recordSuccess(this, msg);
+        Collection<Integer> phoneIds = getSelections().getAllSelected();
+        if (phoneIds.size() != 0) {
+            Collection<Integer> phonesToRestart = new ArrayList<Integer>();
+            Collection<String> phonesUpdating = new ArrayList<String>(); 
+            for (Integer phoneId : phoneIds) {
+                if (getPhoneContext().getDevicesStatus().isDeviceUpdating(phoneId)) {
+                    phonesUpdating.add(getPhoneContext().loadPhone(phoneId).getSerialNumber());
+                } else {
+                    phonesToRestart.add(phoneId);
+                }
+            }
+            if (phonesToRestart.size() != 0) {
+                getRestartManager().restart(phonesToRestart);
+                if (phonesUpdating.size() == 0) {
+                    // all phones are set to be restarted ...
+                    String msg = getMessages().format("msg.success.restart",
+                            Integer.toString(phoneIds.size()));
+                    TapestryUtils.recordSuccess(this, msg);
+                } else {
+                    // some phones have not been restarted
+                    String msg = getMessages().format("msg.partial.failure.restart", 
+                            phonesToRestart.size(), phonesUpdating.toString());
+                    TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
+                }
+            } else {
+                // no phones will be restarted
+                TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                        getMessages().getMessage("msg.failure.restart")));
+            }
+        }
     }
 
     public void deletePhone() {
         PhoneContext context = getPhoneContext();
 
-        Collection ids = getSelections().getAllSelected();
+        Collection<String> notDeleted = new ArrayList<String>();
+        Collection<Integer> ids = getSelections().getAllSelected();
         if (ids.isEmpty()) {
             return;
         }
 
-        for (Iterator i = ids.iterator(); i.hasNext();) {
-            Integer phoneId = (Integer) i.next();
-            Phone phone = context.loadPhone(phoneId);
-            context.deletePhone(phone);
+        for (Integer phoneId : ids) {
+            if (!context.getDevicesStatus().isDeviceUpdating(phoneId)) {
+                Phone phone = context.loadPhone(phoneId);
+                context.deletePhone(phone);
+            } else {
+                notDeleted.add(context.loadPhone(phoneId).getSerialNumber());
+            }
         }
 
-        String msg = getMessages().format("msg.success.delete", Integer.toString(ids.size()));
-        TapestryUtils.recordSuccess(this, msg);
+        if (notDeleted.size() == 0) {
+            // all phones deleted
+            String msg = getMessages().format("msg.success.delete", Integer.toString(ids.size()));
+            TapestryUtils.recordSuccess(this, msg);
+        } else if (notDeleted.size() == ids.size()) {
+            // no phones deleted
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                    getMessages().getMessage("msg.failure.delete")));
+        } else {
+            // some phones deleted
+            String msg = getMessages().format("msg.partial.success.delete", Integer.toString(ids.size() 
+                    - notDeleted.size()), notDeleted.toString());
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
+        }
     }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/sbc/ListSbcDevices.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/sbc/ListSbcDevices.java
index 9e1cada..39b9834 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/sbc/ListSbcDevices.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/sbc/ListSbcDevices.java
@@ -8,13 +8,18 @@
  */
 package org.sipfoundry.sipxconfig.site.sbc;
 
+import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.IRequestCycle;
 import org.apache.tapestry.annotations.Bean;
+import org.apache.tapestry.annotations.InitialValue;
 import org.apache.tapestry.annotations.InjectObject;
 import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.event.PageBeginRenderListener;
+import org.apache.tapestry.event.PageEvent;
 import org.apache.tapestry.form.IPropertySelectionModel;
 import org.apache.tapestry.html.BasePage;
 import org.apache.tapestry.valid.IValidationDelegate;
@@ -35,7 +40,7 @@ import org.sipfoundry.sipxconfig.device.RestartManager;
 /**
  * List all the gateways, allow adding and deleting gateways
  */
-public abstract class ListSbcDevices extends BasePage {
+public abstract class ListSbcDevices extends BasePage implements PageBeginRenderListener {
     public static final String PAGE = "sbc/ListSbcDevices";
 
     @InjectObject(value = "spring:sbcDeviceManager")
@@ -57,7 +62,15 @@ public abstract class ListSbcDevices extends BasePage {
     public abstract SipxValidationDelegate getValidator();
 
     @Persist
-    public abstract void setGenerateProfileIds(Collection<Integer> ids);
+    public abstract Collection<Integer> getGenerateProfileIds();
+    
+    public abstract void setGenerateProfileIds(Collection<Integer> profileIds);
+    
+    @InitialValue("false")
+    @Persist
+    public abstract boolean getGenerateProfiles();
+    
+    public abstract void setGenerateProfiles(boolean bool);
 
     public abstract SbcDescriptor getSbcDescriptor();
 
@@ -93,9 +106,34 @@ public abstract class ListSbcDevices extends BasePage {
     }
 
     public void delete() {
+        SbcDeviceManager context = getSbcDeviceManager();
+        Collection<String> notDeleted = new ArrayList<String>();
         Collection<Integer> ids = getSelections().getAllSelected();
-        if (!ids.isEmpty()) {
-            getSbcDeviceManager().deleteSbcDevices(ids);
+        if (ids.isEmpty()) {
+            return;
+        }
+        
+        for (Integer id : ids) {
+            if (!context.getDevicesStatus().isDeviceUpdating(id)) {
+                context.deleteSbcDevice(id);
+            } else {
+                notDeleted.add(context.getSbcDevice(id).getSerialNumber());
+            }
+        }
+
+        if (notDeleted.size() == 0) {
+            // all phones deleted
+            String msg = getMessages().format("msg.success.delete", Integer.toString(ids.size()));
+            TapestryUtils.recordSuccess(this, msg);
+        } else if (notDeleted.size() == ids.size()) {
+            // no phones deleted
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                    getMessages().getMessage("msg.failure.delete")));
+        } else {
+            // some phones deleted
+            String msg = getMessages().format("msg.partial.success.delete", Integer.toString(ids.size() 
+                    - notDeleted.size()), notDeleted.toString());
+            TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
         }
     }
 
@@ -103,22 +141,47 @@ public abstract class ListSbcDevices extends BasePage {
         Collection<Integer> ids = getSelections().getAllSelected();
         if (!ids.isEmpty()) {
             setGenerateProfileIds(ids);
+            setGenerateProfiles(true);
         }
     }
 
     public void propagateAll() {
-        Collection gatewayIds = getSbcDeviceManager().getAllSbcDeviceIds();
-        setGenerateProfileIds(gatewayIds);
+        Collection<Integer> sbcDevicesIds = getSbcDeviceManager().getAllSbcDeviceIds();
+        setGenerateProfileIds(sbcDevicesIds);
+        setGenerateProfiles(true);
     }
 
     public void restart() {
         Collection<Integer> ids = getSelections().getAllSelected();
-        if (ids.isEmpty()) {
-            return;
+        if (ids.size() != 0) {
+            Collection<Integer> sbcDebicesToRestart = new ArrayList<Integer>();
+            Collection<String> sbcDebicesUpdating = new ArrayList<String>(); 
+            for (Integer id : ids) {
+                if (getSbcDeviceManager().getDevicesStatus().isDeviceUpdating(id)) {
+                    sbcDebicesUpdating.add(getSbcDeviceManager().getSbcDevice(id).getSerialNumber());
+                } else {
+                    sbcDebicesToRestart.add(id);
+                }
+            }
+            if (sbcDebicesToRestart.size() != 0) {
+                getRestartManager().restart(sbcDebicesToRestart);
+                if (sbcDebicesUpdating.size() == 0) {
+                    // all phones are set to be restarted ...
+                    String msg = getMessages().format("msg.success.restart",
+                            Integer.toString(ids.size()));
+                    TapestryUtils.recordSuccess(this, msg);
+                } else {
+                    // some phones have not been restarted
+                    String msg = getMessages().format("msg.partial.failure.restart", 
+                            sbcDebicesToRestart.size(), sbcDebicesUpdating.toString());
+                    TapestryUtils.getValidator(getPage()).record(new ValidatorException(msg));
+                }
+            } else {
+                // no phones will be restarted
+                TapestryUtils.getValidator(getPage()).record(new ValidatorException(
+                        getMessages().getMessage("msg.failure.restart")));
+            }
         }
-        getRestartManager().restart(ids);
-        String msg = getMessages().format("msg.success.restart", Integer.toString(ids.size()));
-        TapestryUtils.recordSuccess(this, msg);
     }
 
     public IPage editSbc(IRequestCycle cycle, Integer sbcId) {
@@ -141,4 +204,23 @@ public abstract class ListSbcDevices extends BasePage {
             setModel(model);
         }
     }
+    
+    public String getInformation() {
+        if (getSbcDeviceManager().getDevicesStatus().isDeviceUpdating(getSbc().getId())) {
+            String msg = getMessages().getMessage("generatingProfile");
+            return msg;
+        }
+        DateFormat dateFormat = TapestryUtils.getDateFormat(getPage().getLocale());
+        return getSbc().getScheduledRestartDate() == null ? "" : (getMessages().getMessage(
+                "restartOn") + dateFormat.format(getSbc().getScheduledRestartDate()));
+    }
+    
+    public void pageBeginRender(PageEvent event_) {
+        if (!getGenerateProfiles()) {
+            if (getGenerateProfileIds() != null) {
+                getSbcDeviceManager().getDevicesStatus().addDevicesToUpdatingDevicesList(getGenerateProfileIds());
+            }
+            setGenerateProfileIds(null);
+        }
+    }
 }
diff --git a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/user/UserPhones.java b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/user/UserPhones.java
index 183ecc5..6d3397d 100644
--- a/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/user/UserPhones.java
+++ b/sipXconfig/web/src/org/sipfoundry/sipxconfig/site/user/UserPhones.java
@@ -13,9 +13,11 @@ import java.util.Collection;
 
 import org.apache.tapestry.IPage;
 import org.apache.tapestry.annotations.Bean;
+import org.apache.tapestry.annotations.InitialValue;
 import org.apache.tapestry.annotations.InjectObject;
 import org.apache.tapestry.annotations.InjectPage;
 import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.event.PageEvent;
 import org.sipfoundry.sipxconfig.components.SelectMap;
 import org.sipfoundry.sipxconfig.components.SipxValidationDelegate;
 import org.sipfoundry.sipxconfig.device.ProfileManager;
@@ -40,9 +42,15 @@ public abstract class UserPhones extends UserBasePage {
 
     @Bean
     public abstract SipxValidationDelegate getValidator();
+    
+    @InitialValue("false")
+    @Persist
+    public abstract boolean getGenerateProfiles();
 
     @Persist
     public abstract Collection<Integer> getGenerateProfileIds();
+    
+    public abstract void setGenerateProfileIds(Collection<Integer> ids);
 
     public Collection<Phone> getPhones() {
         return getPhoneContext().getPhonesByUserId(getUserId());
@@ -54,4 +62,14 @@ public abstract class UserPhones extends UserBasePage {
         page.setReturnPage(this);
         return page;
     }
+    
+    public void pageBeginRender(PageEvent event_) {
+        super.pageBeginRender(event_);
+        if (!getGenerateProfiles()) {
+            if (getGenerateProfileIds() != null) {
+                getPhoneContext().getDevicesStatus().addDevicesToUpdatingDevicesList(getGenerateProfileIds());
+            }
+            setGenerateProfileIds(null);
+        }
+    }
 }
diff --git a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/gateway/GatewaysTestUi.java b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/gateway/GatewaysTestUi.java
index 4683c00..3cb648b 100644
--- a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/gateway/GatewaysTestUi.java
+++ b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/gateway/GatewaysTestUi.java
@@ -60,8 +60,8 @@ public class GatewaysTestUi extends WebTestCase {
         Table gatewaysTable = getTable("list:gateway");
         // make sure it's sorted by name
         clickLinkWithText("Name");
-        int lastColumn = getColumnCount(gatewaysTable) - 1;
-        assertEquals(4, lastColumn);
+        int descriptionColumn = getColumnCount(gatewaysTable) - 2;
+        assertEquals(4, descriptionColumn);
 
         SiteTestHelper.selectOption(tester, "selectGatewayModel", "Unmanaged gateway");
 
@@ -74,7 +74,7 @@ public class GatewaysTestUi extends WebTestCase {
         gatewaysTable = getTable("list:gateway");
         // we should have 2 gateway now
         assertEquals(2, gatewaysTable.getRowCount());
-        assertEquals("bongoDescription", getCellAsText(gatewaysTable, 1, lastColumn));
+        assertEquals("bongoDescription", getCellAsText(gatewaysTable, 1, descriptionColumn));
 
         SiteTestHelper.selectOption(tester, "selectGatewayModel", "Unmanaged gateway");
 
@@ -83,7 +83,7 @@ public class GatewaysTestUi extends WebTestCase {
         gatewaysTable = getTable("list:gateway");
         // we should have 2 gateway now
         assertEquals(3, gatewaysTable.getRowCount());
-        assertEquals("kukuDescription", getCellAsText(gatewaysTable, 2, lastColumn));
+        assertEquals("kukuDescription", getCellAsText(gatewaysTable, 2, descriptionColumn));
     }
 
     public void testEditGatewaySettings() throws Exception {
@@ -153,6 +153,7 @@ public class GatewaysTestUi extends WebTestCase {
         Table gatewaysTable = getTable("list:gateway");
         assertEquals(11, gatewaysTable.getRowCount());
 
+        setWorkingForm("refreshForm");
         selectRow(tester, 0, true);
         selectRow(tester, 1, true);
         clickButton("list:gateway:delete");
@@ -162,6 +163,7 @@ public class GatewaysTestUi extends WebTestCase {
         gatewaysTable = getTable("list:gateway");
         assertEquals(9, gatewaysTable.getRowCount());
 
+        setWorkingForm("refreshForm");
         for (int i = 0; i < 8; i++) {
             selectRow(tester, i, true);
         }
@@ -216,7 +218,7 @@ public class GatewaysTestUi extends WebTestCase {
      */
     public static String[] addGateway(WebTester tester, String name) {
         String[] row = new String[] {
-            "unchecked", name + "Name", name + "Address", "Unmanaged gateway", name + "Description"
+            "unchecked", name + "Name", name + "Address", "Unmanaged gateway", name + "Description", ""
         };
 
         if (null != name) {
diff --git a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/phone/ManagePhonesTestUi.java b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/phone/ManagePhonesTestUi.java
index d682e2f..58a1ca6 100644
--- a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/phone/ManagePhonesTestUi.java
+++ b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/phone/ManagePhonesTestUi.java
@@ -28,11 +28,23 @@ public class ManagePhonesTestUi extends WebTestCase {
         m_helper = new PhoneTestHelper(tester);
         m_helper.reset();
     }
+    
+    public void testRefresh() {
+        clickLink("ManagePhones");
+        SiteTestHelper.assertNoException(tester);
+        setWorkingForm("refreshForm");
+        assertButtonPresent("refresh");
+        clickButton("refresh");
+        SiteTestHelper.assertNoException(tester);
+        setWorkingForm("refreshForm");
+        assertButtonPresent("refresh");
+    }
 
     public void testGenerateProfiles() {
         m_helper.seedPhone(1);
 
         clickLink("ManagePhones");
+        setWorkingForm("refreshForm");
         SiteTestHelper.enableCheckbox(tester, "checkbox", 0, true);
         checkCheckbox("checkbox");
         clickButton("phone:sendProfiles");
@@ -44,6 +56,7 @@ public class ManagePhonesTestUi extends WebTestCase {
         m_helper.seedPhone(1);
 
         clickLink("ManagePhones");
+        setWorkingForm("refreshForm");
         SiteTestHelper.enableCheckbox(tester, "checkbox", 0, true);
         clickButton("phone:restart");
         SiteTestHelper.assertNoException(tester);
@@ -54,6 +67,7 @@ public class ManagePhonesTestUi extends WebTestCase {
         m_helper.seedPhone(1);
 
         clickLink("ManagePhones");
+        setWorkingForm("refreshForm");
         SiteTestHelper.enableCheckbox(tester, "checkbox", 0, true);
         clickButton("phone:delete");
         // 2 = 1 thead (columns) + 1 tfoot (pager)
diff --git a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/sbc/EditSbcDeviceTestUi.java b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/sbc/EditSbcDeviceTestUi.java
index ff84801..29c309b 100644
--- a/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/sbc/EditSbcDeviceTestUi.java
+++ b/sipXconfig/web/test/org/sipfoundry/sipxconfig/site/sbc/EditSbcDeviceTestUi.java
@@ -67,7 +67,7 @@ public class EditSbcDeviceTestUi extends WebTestCase {
         SiteTestHelper.assertUserError(getTester());
 
         //delete one sbc
-        setWorkingForm("Form");
+        setWorkingForm("refreshForm");
         checkCheckbox("checkbox");
         clickButton("list:sbc:delete");
         SiteTestHelper.assertNoUserError(tester);
@@ -80,7 +80,7 @@ public class EditSbcDeviceTestUi extends WebTestCase {
     }
 
     private void deleteAllSbcs() {
-        setWorkingForm("Form");
+        setWorkingForm("refreshForm");
         int rowCount = SiteTestHelper.getRowCount(getTester(), "list:sbc");
         if (rowCount <= 1) {
             return;
-- 
1.5.2.4

