Add session to cache with both hostname and address.
[cyberduck.git] / source / ch / cyberduck / core / AbstractController.java
blob76acbc8ab985df746d1a384fad3ea630a5ada676
1 package ch.cyberduck.core;
3 /*
4 * Copyright (c) 2009 David Kocher. All rights reserved.
5 * http://cyberduck.ch/
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to:
18 * dkocher@cyberduck.ch
21 import ch.cyberduck.core.exception.BackgroundException;
22 import ch.cyberduck.core.exception.ConnectionCanceledException;
23 import ch.cyberduck.core.threading.BackgroundAction;
24 import ch.cyberduck.core.threading.BackgroundActionRegistry;
25 import ch.cyberduck.core.threading.ControllerMainAction;
26 import ch.cyberduck.core.threading.LoggingUncaughtExceptionHandler;
27 import ch.cyberduck.core.threading.MainAction;
28 import ch.cyberduck.core.threading.ThreadPool;
30 import org.apache.commons.lang3.concurrent.ConcurrentUtils;
31 import org.apache.log4j.Logger;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.Future;
35 import java.util.concurrent.RejectedExecutionException;
37 /**
38 * @version $Id$
40 public abstract class AbstractController implements Controller {
41 private static final Logger log = Logger.getLogger(AbstractController.class);
43 private ThreadPool singleExecutor;
45 private ThreadPool concurrentExecutor;
47 protected AbstractController() {
48 this(new LoggingUncaughtExceptionHandler());
51 protected AbstractController(final Thread.UncaughtExceptionHandler handler) {
52 singleExecutor = new ThreadPool(1, handler);
53 concurrentExecutor = new ThreadPool(handler);
56 /**
57 * Does wait for main action to return before continuing the caller thread.
59 * @param runnable The action to execute
61 @Override
62 public void invoke(final MainAction runnable) {
63 this.invoke(runnable, false);
66 /**
67 * List of pending background tasks or this browser
69 private BackgroundActionRegistry registry
70 = new BackgroundActionRegistry();
72 /**
73 * Pending background actions
75 * @return List of tasks.
77 public BackgroundActionRegistry getActions() {
78 return registry;
81 /**
82 * @return true if there is any network activity running in the background
84 public boolean isActivityRunning() {
85 final BackgroundAction current = this.getActions().getCurrent();
86 return null != current;
89 /**
90 * Will queue up the <code>BackgroundAction</code> to be run in a background thread. Will be executed
91 * as soon as no other previous <code>BackgroundAction</code> is pending.
92 * Will return immediately but not run the runnable before the lock of the runnable is acquired.
94 * @param action The runnable to execute in a secondary Thread
95 * @see java.lang.Thread
96 * @see ch.cyberduck.core.threading.BackgroundAction#lock()
98 @Override
99 public <T> Future<T> background(final BackgroundAction<T> action) {
100 if(log.isDebugEnabled()) {
101 log.debug(String.format("Run action %s in background", action));
103 if(registry.contains(action)) {
104 log.warn(String.format("Skip duplicate background action %s found in registry", action));
105 return null;
107 registry.add(action);
108 action.init();
109 // Start background task
110 final Callable<T> command = new BackgroundCallable<T>(action);
111 try {
112 final Future<T> task;
113 if(null == action.lock()) {
114 task = concurrentExecutor.execute(command);
116 else {
117 if(log.isDebugEnabled()) {
118 log.debug(String.format("Synchronize on lock %s for action %s", action.lock(), action));
120 task = singleExecutor.execute(command);
122 if(log.isInfoEnabled()) {
123 log.info(String.format("Scheduled background runnable %s for execution", action));
125 return task;
127 catch(RejectedExecutionException e) {
128 log.error(String.format("Error scheduling background task %s for execution. %s", action, e.getMessage()));
129 action.cleanup();
130 return ConcurrentUtils.constantFuture(null);
134 protected void invalidate() {
135 if(log.isInfoEnabled()) {
136 log.info(String.format("Terminating single executor thread pool %s", singleExecutor));
138 singleExecutor.shutdown(false);
139 if(log.isInfoEnabled()) {
140 log.info(String.format("Terminating concurrent executor thread pool %s", concurrentExecutor));
142 concurrentExecutor.shutdown(false);
145 private final class BackgroundCallable<T> implements Callable<T> {
146 private final BackgroundAction<T> action;
149 * Keep client stacktrace
151 private final Exception client = new Exception();
153 public BackgroundCallable(final BackgroundAction<T> action) {
154 this.action = action;
157 @Override
158 public T call() {
159 if(log.isDebugEnabled()) {
160 log.debug(String.format("Acquired lock for background runnable %s", action));
162 if(action.isCanceled()) {
163 // Canceled action yields no result
164 return null;
166 try {
167 if(log.isDebugEnabled()) {
168 log.debug(String.format("Prepare background action %s", action));
170 action.prepare();
171 // Execute the action of the runnable
172 if(log.isDebugEnabled()) {
173 log.debug(String.format("Call background action %s", action));
175 return action.call();
177 catch(ConnectionCanceledException e) {
178 log.warn(String.format("Connection canceled for background task %s", action));
180 catch(Exception e) {
181 failure(client, e);
183 finally {
184 try {
185 action.finish();
187 finally {
188 registry.remove(action);
190 // If there was any failure, display the summary now
191 if(action.alert()) {
192 if(log.isDebugEnabled()) {
193 log.debug(String.format("Retry background action %s", action));
195 // Retry
196 this.call();
198 else {
199 if(log.isDebugEnabled()) {
200 log.debug(String.format("Invoke cleanup for background action %s", action));
202 // Invoke the cleanup on the main thread to let the action synchronize the user interface
203 invoke(new ControllerMainAction(AbstractController.this) {
204 @Override
205 public void run() {
206 try {
207 action.cleanup();
209 catch(Exception e) {
210 log.error(String.format("Exception running cleanup task %s", e.getMessage()), e);
214 if(log.isDebugEnabled()) {
215 log.debug(String.format("Releasing lock for background runnable %s", action));
219 // Canceled action yields no result
220 return null;
224 protected void failure(final Exception trace, final Exception failure) {
225 trace.initCause(failure);
226 log.error(String.format("Unhandled exception running background task %s", failure.getMessage()), trace);
229 @Override
230 public void start(final BackgroundAction action) {
231 if(log.isDebugEnabled()) {
232 log.debug(String.format("Start action %s", action));
236 @Override
237 public void cancel(final BackgroundAction action) {
238 if(log.isDebugEnabled()) {
239 log.debug(String.format("Cancel action %s", action));
243 @Override
244 public void stop(final BackgroundAction action) {
245 if(log.isDebugEnabled()) {
246 log.debug(String.format("Stop action %s", action));
250 @Override
251 public void message(final String message) {
252 log.info(message);
255 @Override
256 public void log(final boolean request, final String message) {
257 log.trace(message);
260 @Override
261 public boolean alert(final Host host, final BackgroundException failure, final StringBuilder transcript) {
262 log.warn(failure.getMessage(), failure);
263 return false;