KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ui > synchronize > RefreshSubscriberJob


1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.team.internal.ui.synchronize;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.List JavaDoc;
15
16 import org.eclipse.core.resources.IResource;
17 import org.eclipse.core.resources.ResourcesPlugin;
18 import org.eclipse.core.runtime.*;
19 import org.eclipse.core.runtime.jobs.*;
20 import org.eclipse.jface.action.IAction;
21 import org.eclipse.jface.dialogs.ErrorDialog;
22 import org.eclipse.jface.util.IPropertyChangeListener;
23 import org.eclipse.jface.util.PropertyChangeEvent;
24 import org.eclipse.osgi.util.NLS;
25 import org.eclipse.team.core.TeamException;
26 import org.eclipse.team.core.subscribers.Subscriber;
27 import org.eclipse.team.core.synchronize.SyncInfo;
28 import org.eclipse.team.core.synchronize.SyncInfoTree;
29 import org.eclipse.team.internal.core.Assert;
30 import org.eclipse.team.internal.core.subscribers.SubscriberSyncInfoCollector;
31 import org.eclipse.team.internal.ui.*;
32 import org.eclipse.team.ui.synchronize.ISynchronizeManager;
33 import org.eclipse.team.ui.synchronize.SubscriberParticipant;
34 import org.eclipse.ui.actions.ActionFactory;
35 import org.eclipse.ui.progress.IProgressConstants;
36 import org.eclipse.ui.progress.UIJob;
37
38 /**
39  * Job to refresh a {@link Subscriber} in the background. The job can be configured
40  * to be re-scheduled and run at a specified interval.
41  * <p>
42  * The job supports a basic workflow for modal/non-modal usage. If the job is
43  * run in the foreground (e.g. in a modal progress dialog) the refresh listeners
44  * action is invoked immediately after the refresh is completed. Otherwise the refresh
45  * listeners action is associated to the job as a <i>goto</i> action. This will
46  * allow the user to select the action in the progress view and run it when they
47  * choose.
48  * </p>
49  * @since 3.0
50  */

51 public final class RefreshSubscriberJob extends Job {
52     
53     private final static boolean TEST_PROGRESS_VIEW = false;
54     /**
55      * Uniquely identifies this type of job. This is used for cancellation.
56      */

57     private final static Object JavaDoc FAMILY_ID = new Object JavaDoc();
58     
59     /**
60      * If true this job will be restarted when it completes
61      */

62     private boolean reschedule = false;
63     
64     /**
65      * If true a rescheduled refresh job should be retarted when cancelled
66      */

67     private boolean restartOnCancel = true;
68     
69     /**
70      * The schedule delay used when rescheduling a completed job
71      */

72     private static long scheduleDelay;
73     
74     /**
75      * The subscribers and resources to refresh.
76      */

77     private IResource[] resources;
78
79     /**
80      * The participant that is being refreshed.
81      */

82     private SubscriberParticipant participant;
83     
84     /**
85      * The task name for this refresh. This is usually more descriptive than the
86      * job name.
87      */

88     private String JavaDoc taskName;
89     
90     /**
91      * Refresh started/completed listener for every refresh
92      */

93     private static List JavaDoc listeners = new ArrayList JavaDoc(1);
94     private static final int STARTED = 1;
95     private static final int DONE = 2;
96     
97     /*
98      * Lock used to sequence refresh jobs
99      */

100     private static final ILock lock = Platform.getJobManager().newLock();
101     
102     /*
103      * Constant used for postponement
104      */

105     private static final IStatus POSTPONED = new Status(IStatus.CANCEL, TeamUIPlugin.ID, 0, "Scheduled refresh postponed due to conflicting operation", null); //$NON-NLS-1$
106

107     /*
108      * Action wrapper which allows the goto action
109      * to be set later. It also handles errors
110      * that have occurred during the refresh
111      */

112     private final class GotoActionWrapper extends WorkbenchAction {
113         private ActionFactory.IWorkbenchAction gotoAction;
114         private IStatus status;
115         public void run() {
116             if (status != null && !status.isOK()) {
117                 ErrorDialog.openError(Utils.getShell(null), null, TeamUIMessages.RefreshSubscriberJob_3, status); //$NON-NLS-1$
118
} else if(gotoAction != null) {
119                 gotoAction.run();
120             }
121         }
122         public boolean isEnabled() {
123             if(gotoAction != null) {
124                 return gotoAction.isEnabled();
125             }
126             return true;
127         }
128         public String JavaDoc getText() {
129             if(gotoAction != null) {
130                 return gotoAction.getText();
131             }
132             return null;
133         }
134         public String JavaDoc getToolTipText() {
135             if (status != null && !status.isOK()) {
136                 return status.getMessage();
137             }
138             if(gotoAction != null) {
139                 return gotoAction.getToolTipText();
140             }
141             return Utils.shortenText(SynchronizeView.MAX_NAME_LENGTH, RefreshSubscriberJob.this.getName());
142         }
143         public void dispose() {
144             super.dispose();
145             if(gotoAction != null) {
146                 gotoAction.dispose();
147             }
148         }
149         public void setGotoAction(ActionFactory.IWorkbenchAction gotoAction) {
150             this.gotoAction = gotoAction;
151             setEnabled(isEnabled());
152             setToolTipText(getToolTipText());
153             gotoAction.addPropertyChangeListener(new IPropertyChangeListener() {
154                 public void propertyChange(PropertyChangeEvent event) {
155                     if(event.getProperty().equals(IAction.ENABLED)) {
156                         Boolean JavaDoc bool = (Boolean JavaDoc) event.getNewValue();
157                         GotoActionWrapper.this.setEnabled(bool.booleanValue());
158                     }
159                 }
160             });
161         }
162         public void setStatus(IStatus status) {
163             this.status = status;
164         }
165     }
166
167     /**
168      * Notification for safely notifying listeners of refresh lifecycle.
169      */

170     private abstract class Notification implements ISafeRunnable {
171         private IRefreshSubscriberListener listener;
172         public void handleException(Throwable JavaDoc exception) {
173             // don't log the exception....it is already being logged in Platform#run
174
}
175         public void run(IRefreshSubscriberListener listener) {
176             this.listener = listener;
177             Platform.run(this);
178         }
179         public void run() throws Exception JavaDoc {
180             notify(listener);
181         }
182         /**
183          * Subsclasses overide this method to send an event safely to a lsistener
184          * @param listener
185          */

186         protected abstract void notify(IRefreshSubscriberListener listener);
187     }
188     
189     /**
190      * Monitor wrapper that will indicate that the job is cancelled
191      * if the job is blocking another.
192      */

193     private class NonblockingProgressMonitor extends ProgressMonitorWrapper {
194         private final RefreshSubscriberJob job;
195         private long blockTime;
196         private static final int THRESHOLD = 250;
197         private boolean wasBlocking = false;
198         protected NonblockingProgressMonitor(IProgressMonitor monitor, RefreshSubscriberJob job) {
199             super(monitor);
200             this.job = job;
201         }
202         public boolean isCanceled() {
203             if (super.isCanceled()) {
204                 return true;
205             }
206             if (job.shouldReschedule() && job.isBlocking()) {
207                 if (blockTime == 0) {
208                     blockTime = System.currentTimeMillis();
209                 } else if (System.currentTimeMillis() - blockTime > THRESHOLD) {
210                     // We've been blocking for too long
211
wasBlocking = true;
212                     return true;
213                 }
214             } else {
215                 blockTime = 0;
216             }
217             wasBlocking = false;
218             return false;
219         }
220         public boolean wasBlocking() {
221             return wasBlocking;
222         }
223     }
224     
225     /**
226      * Create a job to refresh the specified resources with the subscriber.
227      *
228      * @param participant the subscriber participant
229      * @param name
230      * @param resources
231      * @param subscriber
232      */

233     public RefreshSubscriberJob(SubscriberParticipant participant, String JavaDoc jobName, String JavaDoc taskName, IResource[] resources, IRefreshSubscriberListener listener) {
234         super(taskName);
235         Assert.isNotNull(resources);
236         Assert.isNotNull(participant);
237         Assert.isNotNull(resources);
238         this.resources = resources;
239         this.participant = participant;
240         this.taskName = jobName;
241         setPriority(Job.DECORATE);
242         setRefreshInterval(3600 /* 1 hour */);
243         
244         // Handle restarting of job if it is configured as a scheduled refresh job.
245
addJobChangeListener(new JobChangeAdapter() {
246             public void done(IJobChangeEvent event) {
247                 if(shouldReschedule()) {
248                     IStatus result = event.getResult();
249                     if(result.getSeverity() == IStatus.CANCEL && ! restartOnCancel) {
250                         return;
251                     }
252                     long delay = scheduleDelay;
253                     if (result == POSTPONED) {
254                         // Restart in 5 seconds
255
delay = 5000;
256                     }
257                     RefreshSubscriberJob.this.schedule(delay);
258                     restartOnCancel = true;
259                 }
260             }
261         });
262         if(listener != null)
263             initialize(listener);
264     }
265     
266     /**
267      * If a collector is available then run the refresh and the background event processing
268      * within the same progess group.
269      */

270     public boolean shouldRun() {
271         // Ensure that any progress shown as a result of this refresh occurs hidden in a progress group.
272
return getSubscriber() != null;
273     }
274
275     public boolean belongsTo(Object JavaDoc family) {
276         if(family instanceof RefreshSubscriberJob) {
277             return ((RefreshSubscriberJob)family).getSubscriber() == getSubscriber();
278         } else if (family instanceof SubscriberParticipant) {
279             return family == participant;
280         } else {
281             return (family == getFamily() || family == ISynchronizeManager.FAMILY_SYNCHRONIZE_OPERATION);
282         }
283     }
284     
285     public static Object JavaDoc getFamily() {
286         return FAMILY_ID;
287     }
288     
289     /**
290      * This is run by the job scheduler. A list of subscribers will be refreshed, errors will not stop the job
291      * and it will continue to refresh the other subscribers.
292      */

293     public IStatus run(IProgressMonitor monitor) {
294         // Perform a pre-check for auto-build or manual build jobs
295
// when auto-refreshing
296
if (shouldReschedule() &&
297                 (isJobInFamilyRunning(ResourcesPlugin.FAMILY_AUTO_BUILD)
298                 || isJobInFamilyRunning(ResourcesPlugin.FAMILY_MANUAL_BUILD))) {
299             return POSTPONED;
300         }
301         // Only allow one refresh job at a time
302
// NOTE: It would be cleaner if this was done by a scheduling
303
// rule but at the time of writting, it is not possible due to
304
// the scheduling rule containment rules.
305
// Acquiring lock to ensure only one refresh job is running at a particular time
306
boolean acquired = false;
307         try {
308             while (!acquired) {
309                 try {
310                     acquired = lock.acquire(1000);
311                 } catch (InterruptedException JavaDoc e1) {
312                     acquired = false;
313                 }
314                 Policy.checkCanceled(monitor);
315             }
316             Subscriber subscriber = getSubscriber();
317             IResource[] roots = getResources();
318             
319             // if there are no resources to refresh, just return
320
if(subscriber == null || roots == null) {
321                 return Status.OK_STATUS;
322             }
323             SubscriberSyncInfoCollector collector = getCollector();
324             RefreshEvent event = new RefreshEvent(reschedule ? IRefreshEvent.SCHEDULED_REFRESH : IRefreshEvent.USER_REFRESH, roots, collector.getSubscriber());
325             RefreshChangeListener changeListener = new RefreshChangeListener(collector);
326             IStatus status = null;
327             NonblockingProgressMonitor wrappedMonitor = null;
328             try {
329                 event.setStartTime(System.currentTimeMillis());
330                 if(monitor.isCanceled()) {
331                     return Status.CANCEL_STATUS;
332                 }
333                 // Set-up change listener so that we can determine the changes found
334
// during this refresh.
335
subscriber.addListener(changeListener);
336                 // Pre-Notify
337
notifyListeners(STARTED, event);
338                 // Perform the refresh
339
monitor.setTaskName(getName());
340                 wrappedMonitor = new NonblockingProgressMonitor(monitor, this);
341                 subscriber.refresh(roots, IResource.DEPTH_INFINITE, wrappedMonitor);
342                 // Prepare the results
343
setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(! isJobModal()));
344             } catch(OperationCanceledException e2) {
345                 if (monitor.isCanceled()) {
346                     // The refresh was cancelled by the user
347
status = Status.CANCEL_STATUS;
348                 } else {
349                     // The refresh was cancelled due to a blockage or a cancelled authentication
350
if (wrappedMonitor != null && wrappedMonitor.wasBlocking()) {
351                         status = POSTPONED;
352                     } else {
353                         status = Status.CANCEL_STATUS;
354                     }
355                 }
356             } catch(TeamException e) {
357                 // Determine the status to be returned and the GOTO action
358
status = e.getStatus();
359                 if (!isUser()) {
360                     if (!TEST_PROGRESS_VIEW) {
361                         // Use the GOTO action to show the error and return OK
362
Object JavaDoc prop = getProperty(IProgressConstants.ACTION_PROPERTY);
363                         if (prop instanceof GotoActionWrapper) {
364                             GotoActionWrapper wrapper = (GotoActionWrapper)prop;
365                             wrapper.setStatus(e.getStatus());
366                             status = new Status(IStatus.OK, TeamUIPlugin.ID, IStatus.OK, e.getStatus().getMessage(), e);
367                         }
368                     }
369                 }
370 // TODO: Code that can be added when new error handling gets released (see bug 76726)
371
// if (!isUser() && status.getSeverity() == IStatus.ERROR) {
372
// // Never prompt for errors on non-user jobs
373
// setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
374
// }
375
} finally {
376                 event.setStopTime(System.currentTimeMillis());
377                 subscriber.removeListener(changeListener);
378             }
379             
380             // Post-Notify
381
event.setChanges(changeListener.getChanges(monitor));
382             if (status == null) {
383                 status = calculateStatus(event);
384             }
385             event.setStatus(status);
386             notifyListeners(DONE, event);
387             return event.getStatus();
388         } finally {
389             if (acquired) lock.release();
390             monitor.done();
391         }
392     }
393     
394     private boolean isJobInFamilyRunning(Object JavaDoc family) {
395         Job[] jobs = Platform.getJobManager().find(family);
396         if (jobs != null && jobs.length > 0) {
397             for (int i = 0; i < jobs.length; i++) {
398                 Job job = jobs[i];
399                 if (job.getState() != Job.NONE) {
400                     return true;
401                 }
402             }
403         }
404         return false;
405     }
406
407     private IStatus calculateStatus(IRefreshEvent event) {
408         StringBuffer JavaDoc text = new StringBuffer JavaDoc();
409         int code = IStatus.OK;
410         SyncInfo[] changes = event.getChanges();
411         SubscriberSyncInfoCollector collector = getCollector();
412         if (collector != null) {
413             int numChanges = refreshedResourcesContainChanges(event);
414             if (numChanges > 0) {
415                 code = IRefreshEvent.STATUS_CHANGES;
416                 if (changes.length > 0) {
417                 // New changes found
418
String JavaDoc numNewChanges = Integer.toString(event.getChanges().length);
419                     if (event.getChanges().length == 1) {
420                             text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_newChangesSingular, (new Object JavaDoc[]{getName(), numNewChanges}))); //$NON-NLS-1$
421
} else {
422                             text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_newChangesPlural, (new Object JavaDoc[]{getName(), numNewChanges}))); //$NON-NLS-1$
423
}
424                 } else {
425                     // Refreshed resources contain changes
426
if (numChanges == 1) {
427                         text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_changesSingular, (new Object JavaDoc[]{getName(), new Integer JavaDoc(numChanges)}))); //$NON-NLS-1$
428
} else {
429                         text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_changesPlural, (new Object JavaDoc[]{getName(), new Integer JavaDoc(numChanges)}))); //$NON-NLS-1$
430
}
431                 }
432             } else {
433                 // No changes found
434
code = IRefreshEvent.STATUS_NO_CHANGES;
435                 text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_6, new String JavaDoc[] { getName() })); //$NON-NLS-1$
436
}
437             return new Status(IStatus.OK, TeamUIPlugin.ID, code, text.toString(), null);
438         }
439         return Status.OK_STATUS;
440     }
441     
442     private int refreshedResourcesContainChanges(IRefreshEvent event) {
443         int numChanges = 0;
444         SubscriberSyncInfoCollector collector = getCollector();
445         if (collector != null) {
446             SyncInfoTree set = collector.getSyncInfoSet();
447             IResource[] resources = event.getResources();
448             for (int i = 0; i < resources.length; i++) {
449                 IResource resource = resources[i];
450                 SyncInfo[] infos = set.getSyncInfos(resource, IResource.DEPTH_INFINITE);
451                 if(infos != null && infos.length > 0) {
452                     numChanges += infos.length;
453                 }
454             }
455         }
456         return numChanges;
457     }
458     
459     private void initialize(final IRefreshSubscriberListener listener) {
460         final GotoActionWrapper actionWrapper = new GotoActionWrapper();
461         
462         IProgressMonitor group = Platform.getJobManager().createProgressGroup();
463         group.beginTask(taskName, 100);
464         setProgressGroup(group, 80);
465         getCollector().setProgressGroup(group, 20);
466         setProperty(IProgressConstants.ICON_PROPERTY, participant.getImageDescriptor());
467         setProperty(IProgressConstants.ACTION_PROPERTY, actionWrapper);
468         setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(! isJobModal()));
469         // Listener delagate
470
IRefreshSubscriberListener autoListener = new IRefreshSubscriberListener() {
471             public void refreshStarted(IRefreshEvent event) {
472                 if(listener != null) {
473                     listener.refreshStarted(event);
474                 }
475             }
476             public ActionFactory.IWorkbenchAction refreshDone(IRefreshEvent event) {
477                 if(listener != null) {
478                     boolean isModal = isJobModal();
479                     final ActionFactory.IWorkbenchAction runnable = listener.refreshDone(event);
480                     if(runnable != null) {
481                         // If the job is being run modally then simply prompt the user immediatly
482
if(isModal) {
483                             if(runnable != null) {
484                                 Job update = new UIJob("") { //$NON-NLS-1$
485
public IStatus runInUIThread(IProgressMonitor monitor) {
486                                         runnable.run();
487                                         return Status.OK_STATUS;
488                                     }
489                                 };
490                                 update.setSystem(true);
491                                 update.schedule();
492                             }
493                         } else {
494                             // If the job is being run in the background, don't interrupt the user and simply update the goto action
495
// to perform the results.
496
actionWrapper.setGotoAction(runnable);
497                         }
498                     }
499                     RefreshSubscriberJob.removeRefreshListener(this);
500                 }
501                 return null;
502             }
503         };
504         
505         if (listener != null) {
506             RefreshSubscriberJob.addRefreshListener(autoListener);
507         }
508     }
509     
510     protected IResource[] getResources() {
511         return resources;
512     }
513     
514     protected Subscriber getSubscriber() {
515         return participant.getSubscriber();
516     }
517     
518     protected SubscriberSyncInfoCollector getCollector() {
519         return participant.getSubscriberSyncInfoCollector();
520     }
521     
522     public long getScheduleDelay() {
523         return scheduleDelay;
524     }
525     
526     protected void start() {
527         if(getState() == Job.NONE) {
528             if(shouldReschedule()) {
529                 schedule(getScheduleDelay());
530             }
531         }
532     }
533     
534     /**
535      * Specify the interval in seconds at which this job is scheduled.
536      * @param seconds delay specified in seconds
537      */

538     public void setRefreshInterval(long seconds) {
539         boolean restart = false;
540         if(getState() == Job.SLEEPING) {
541             restart = true;
542             cancel();
543         }
544         scheduleDelay = seconds * 1000;
545         if(restart) {
546             start();
547         }
548     }
549     
550     public void setRestartOnCancel(boolean restartOnCancel) {
551         this.restartOnCancel = restartOnCancel;
552     }
553     
554     public void setReschedule(boolean reschedule) {
555         this.reschedule = reschedule;
556     }
557     
558     public boolean shouldReschedule() {
559         return reschedule;
560     }
561     
562     public static void addRefreshListener(IRefreshSubscriberListener listener) {
563         synchronized(listeners) {
564             if(! listeners.contains(listener)) {
565                 listeners.add(listener);
566             }
567         }
568     }
569     
570     public static void removeRefreshListener(IRefreshSubscriberListener listener) {
571         synchronized(listeners) {
572             listeners.remove(listener);
573         }
574     }
575     
576     protected void notifyListeners(final int state, final IRefreshEvent event) {
577         // Get a snapshot of the listeners so the list doesn't change while we're firing
578
IRefreshSubscriberListener[] listenerArray;
579         synchronized (listeners) {
580             listenerArray = (IRefreshSubscriberListener[]) listeners.toArray(new IRefreshSubscriberListener[listeners.size()]);
581         }
582         // Notify each listener in a safe manner (i.e. so their exceptions don't kill us)
583
for (int i = 0; i < listenerArray.length; i++) {
584             IRefreshSubscriberListener listener = listenerArray[i];
585             Notification notification = new Notification() {
586                 protected void notify(IRefreshSubscriberListener listener) {
587                     switch (state) {
588                         case STARTED:
589                             listener.refreshStarted(event);
590                             break;
591                         case DONE:
592                             listener.refreshDone(event);
593                             break;
594                         default:
595                             break;
596                     }
597                 }
598             };
599             notification.run(listener);
600         }
601     }
602     
603     private boolean isJobModal() {
604         Boolean JavaDoc isModal = (Boolean JavaDoc)getProperty(IProgressConstants.PROPERTY_IN_DIALOG);
605         if(isModal == null) return false;
606         return isModal.booleanValue();
607     }
608 }
609
Popular Tags