001: // The contents of this file are subject to the Mozilla Public License Version
002: // 1.1
003: //(the "License"); you may not use this file except in compliance with the
004: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
005: //
006: //Software distributed under the License is distributed on an "AS IS" basis,
007: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
008: //for the specific language governing rights and
009: //limitations under the License.
010: //
011: //The Original Code is "The Columba Project"
012: //
013: //The Initial Developers of the Original Code are Frederik Dietz and Timo
014: // Stich.
015: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
016: //
017: //All Rights Reserved.
018: package org.columba.core.shutdown;
019:
020: import java.awt.Component;
021: import java.util.Iterator;
022: import java.util.LinkedList;
023: import java.util.List;
024: import java.util.logging.Logger;
025:
026: import javax.swing.JFrame;
027: import javax.swing.JOptionPane;
028:
029: import org.columba.api.shutdown.IShutdownManager;
030: import org.columba.core.backgroundtask.BackgroundTaskManager;
031: import org.columba.core.command.Command;
032: import org.columba.core.command.CommandProcessor;
033: import org.columba.core.command.TaskManager;
034: import org.columba.core.logging.Logging;
035: import org.columba.core.main.ColumbaServer;
036: import org.columba.core.resourceloader.GlobalResourceLoader;
037:
038: /**
039: * Manages all tasks which are responsible for doing clean-up work when shutting
040: * down Columba.
041: * <p>
042: * This includes saving the xml configuration, saving folder data, etc.
043: * <p>
044: * Tasks use <code>register</code> to the managers shutdown queue.
045: * <p>
046: * When shutting down Columba, the tasks will be running in the opposite order
047: * the have registered at. <br>
048: * Currently this is the following: <br>
049: * <ul>
050: * <li>addressbook folders header cache</li>
051: * <li>POP3 header cache</li>
052: * <li>email folders header cache</li>
053: * <li>core tasks (no core tasks used currently)!</li>
054: * <ul>
055: * <p>
056: * Note, that I used the opposite ordering to make sure that core tasks are
057: * executed first. But, currently there are no core tasks available which would
058: * demand this behaviour.
059: * <p>
060: * Saving email folder header cache is running as a {@link Command}. Its
061: * therefore a background thread, where we don't know when its finished. This is
062: * the reason why we use
063: * <code>MainInterface.processor.getTaskManager().count()</code> to check if
064: * no more commands are running.
065: * <p>
066: * Finally, note that the {@link ColumbaServer}is stopped first, then the
067: * background manager, afterwards all registered shutdown tasks and finally the
068: * xml configuration is saved. Note, that the xml configuration has to be saved
069: * <b>after </b> the email folders where saved.
070: *
071: * @author fdietz
072: */
073: public class ShutdownManager implements IShutdownManager {
074:
075: private static final Logger LOG = Logger
076: .getLogger("org.columba.core.shutdown");
077:
078: protected static final String RESOURCE_PATH = "org.columba.core.i18n.dialog";
079:
080: /**
081: * The singleton instance of this class.
082: */
083: private static IShutdownManager instance;
084:
085: /**
086: * Indicates whether this ShutdownManager instance is registered as a system
087: * shutdown hook.
088: */
089: private boolean shutdownHook = false;
090:
091: /**
092: * The thread performing the actual shutdown procedure.
093: */
094: protected final Thread shutdownThread;
095:
096: /**
097: * The list of runnable plugins that should be executed on shutdown.
098: */
099: protected List list = new LinkedList();
100:
101: private boolean shuttingDown;
102:
103: /**
104: * This constructor is only to be accessed by getInstance() and by
105: * subclasses.
106: */
107: protected ShutdownManager() {
108: shutdownThread = new Thread(new Runnable() {
109:
110: public void run() {
111: // stop background-manager so it doesn't interfere with
112: // shutdown manager
113: BackgroundTaskManager.getInstance().stop();
114:
115: CommandProcessor.getInstance().stop();
116:
117: while (!isShutdownHook()
118: && (TaskManager.getInstance().count() > 0)) {
119: // ask user to kill pending running commands or wait
120: Object[] options = {
121: GlobalResourceLoader.getString(
122: RESOURCE_PATH, "session",
123: "tasks_wait"),
124: GlobalResourceLoader.getString(
125: RESOURCE_PATH, "session",
126: "tasks_exit") };
127: int n = JOptionPane.showOptionDialog(null,
128: GlobalResourceLoader.getString(
129: RESOURCE_PATH, "session",
130: "tasks_msg"), GlobalResourceLoader
131: .getString(RESOURCE_PATH,
132: "session", "tasks_title"),
133: JOptionPane.YES_NO_OPTION,
134: JOptionPane.QUESTION_MESSAGE, null,
135: options, options[0]);
136:
137: if (n == 0) {
138: // wait 10 seconds and check for pending commands again
139: // this is useful if a command causes a deadlock
140: for (int i = 0; i < 10; i++) {
141: try {
142: Thread.sleep(1000);
143: } catch (InterruptedException ie) {
144: }
145: }
146: } else {
147: // don't wait, just continue shutdown procedure,
148: // commands will be killed
149: break;
150: }
151: }
152:
153: ShutdownDialog dialog = (ShutdownDialog) openShutdownDialog();
154:
155: Iterator iterator = list.iterator();
156: Runnable plugin;
157:
158: while (iterator.hasNext()) {
159: plugin = (Runnable) iterator.next();
160:
161: try {
162: plugin.run();
163: } catch (Exception e) {
164: LOG.severe(e.getMessage());
165:
166: // TODO (@author javaprog): better exception handling
167: }
168: }
169:
170: // we don't need to check for running commands here because
171: // there aren't any, shutdown plugins only use this thread
172: if (dialog != null)
173: dialog.close();
174: }
175: }, "ShutdownManager");
176: setShutdownHook(true);
177:
178: shuttingDown = false;
179: }
180:
181: /*
182: * (non-Javadoc)
183: *
184: * @see org.columba.core.shutdown.IShutdownManager#register(java.lang.Runnable)
185: */
186: public void register(Runnable plugin) {
187: list.add(0, plugin);
188: }
189:
190: /**
191: * Returns whether this ShutdownManager instance runs inside a system
192: * shutdown hook.
193: */
194: public synchronized boolean isShutdownHook() {
195: return shutdownHook;
196: }
197:
198: /**
199: * Registers or unregisters this ShutdownManager instance as a system
200: * shutdown hook.
201: */
202: protected synchronized void setShutdownHook(boolean b) {
203: if (shutdownHook == b) {
204: return;
205: }
206:
207: if (b) {
208: Runtime.getRuntime().addShutdownHook(shutdownThread);
209: } else {
210: Runtime.getRuntime().removeShutdownHook(shutdownThread);
211: }
212:
213: shutdownHook = b;
214: }
215:
216: /*
217: * (non-Javadoc)
218: *
219: * @see org.columba.core.shutdown.IShutdownManager#shutdown(int)
220: */
221: public synchronized void shutdown(final int status) {
222: if (!shuttingDown) {
223: setShutdownHook(false);
224: new Thread(new Runnable() {
225:
226: public void run() {
227: shutdownThread.run();
228: System.exit(status);
229: }
230: }, "ShutdownManager").start();
231:
232: shuttingDown = true;
233: }
234: }
235:
236: /**
237: * Returns a component notifying the user of the shutdown procedure.
238: */
239: protected Component openShutdownDialog() {
240: JFrame dialog = null;
241: try {
242: dialog = new ShutdownDialog();
243: } catch (Exception e) {
244: if (Logging.DEBUG)
245: e.printStackTrace();
246: }
247: return dialog;
248: }
249:
250: /**
251: * Returns the singleton instance of this class.
252: */
253: public static synchronized IShutdownManager getInstance() {
254: if (instance == null) {
255: instance = new ShutdownManager();
256: }
257:
258: return instance;
259: }
260: }
|