KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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  * Eugene Kuleshov (eu@md.pp.ru) - Bug 138152 Improve sync job status reporting
11  *******************************************************************************/

12 package org.eclipse.team.internal.ui.synchronize;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16
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.subscribers.Subscriber;
26 import org.eclipse.team.internal.ui.*;
27 import org.eclipse.team.ui.synchronize.*;
28 import org.eclipse.ui.actions.ActionFactory;
29 import org.eclipse.ui.progress.IProgressConstants;
30 import org.eclipse.ui.progress.UIJob;
31
32 /**
33  * Job to refresh a {@link Subscriber} in the background. The job can be configured
34  * to be re-scheduled and run at a specified interval.
35  * <p>
36  * The job supports a basic work flow for modal/non-modal usage. If the job is
37  * run in the foreground (e.g. in a modal progress dialog) the refresh listeners
38  * action is invoked immediately after the refresh is completed. Otherwise the refresh
39  * listeners action is associated to the job as a <i>goto</i> action. This will
40  * allow the user to select the action in the progress view and run it when they
41  * choose.
42  * </p>
43  * @since 3.0
44  */

45 public abstract class RefreshParticipantJob extends Job {
46     
47     /**
48      * Uniquely identifies this type of job. This is used for cancellation.
49      */

50     private final static Object JavaDoc FAMILY_ID = new Object JavaDoc();
51     
52     /**
53      * If true this job will be restarted when it completes
54      */

55     private boolean reschedule = false;
56     
57     /**
58      * If true a rescheduled refresh job should be restarted when canceled
59      */

60     private boolean restartOnCancel = true;
61     
62     /**
63      * The schedule delay used when rescheduling a completed job
64      */

65     private static long scheduleDelay;
66
67     /**
68      * The participant that is being refreshed.
69      */

70     private ISynchronizeParticipant participant;
71     
72     /**
73      * The task name for this refresh. This is usually more descriptive than the
74      * job name.
75      */

76     private String JavaDoc taskName;
77     
78     /**
79      * Refresh started/completed listener for every refresh
80      */

81     private static List JavaDoc listeners = new ArrayList JavaDoc(1);
82     private static final int STARTED = 1;
83     private static final int DONE = 2;
84     
85     /*
86      * Lock used to sequence refresh jobs
87      */

88     private static final ILock lock = Platform.getJobManager().newLock();
89     
90     /*
91      * Constant used for postponement
92      */

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

95     /*
96      * Action wrapper which allows the goto action
97      * to be set later. It also handles errors
98      * that have occurred during the refresh
99      */

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

158     private abstract class Notification implements ISafeRunnable {
159         private IRefreshSubscriberListener listener;
160         public void handleException(Throwable JavaDoc exception) {
161             // don't log the exception....it is already being logged in Platform#run
162
}
163         public void run(IRefreshSubscriberListener listener) {
164             this.listener = listener;
165             SafeRunner.run(this);
166         }
167         public void run() throws Exception JavaDoc {
168             notify(listener);
169         }
170         /**
171          * Subclasses override this method to send an event safely to a listener
172          * @param listener
173          */

174         protected abstract void notify(IRefreshSubscriberListener listener);
175     }
176     
177     /**
178      * Monitor wrapper that will indicate that the job is canceled
179      * if the job is blocking another.
180      */

181     private class NonblockingProgressMonitor extends ProgressMonitorWrapper {
182         private final RefreshParticipantJob job;
183         private long blockTime;
184         private static final int THRESHOLD = 250;
185         private boolean wasBlocking = false;
186         protected NonblockingProgressMonitor(IProgressMonitor monitor, RefreshParticipantJob job) {
187             super(monitor);
188             this.job = job;
189         }
190         public boolean isCanceled() {
191             if (super.isCanceled()) {
192                 return true;
193             }
194             if (job.shouldReschedule() && job.isBlocking()) {
195                 if (blockTime == 0) {
196                     blockTime = System.currentTimeMillis();
197                 } else if (System.currentTimeMillis() - blockTime > THRESHOLD) {
198                     // We've been blocking for too long
199
wasBlocking = true;
200                     return true;
201                 }
202             } else {
203                 blockTime = 0;
204             }
205             wasBlocking = false;
206             return false;
207         }
208         public boolean wasBlocking() {
209             return wasBlocking;
210         }
211     }
212     
213     public static interface IChangeDescription {
214         int getChangeCount();
215     }
216     
217     /**
218      * Create a job to refresh the specified resources with the subscriber.
219      *
220      * @param participant the subscriber participant
221      * @param jobName
222      * @param taskName
223      * @param listener
224      */

225     public RefreshParticipantJob(ISynchronizeParticipant participant, String JavaDoc jobName, String JavaDoc taskName, IRefreshSubscriberListener listener) {
226         super(jobName);
227         Assert.isNotNull(participant);
228         this.participant = participant;
229         this.taskName = taskName;
230         setPriority(Job.DECORATE);
231         setRefreshInterval(3600 /* 1 hour */);
232         
233         // Handle restarting of job if it is configured as a scheduled refresh job.
234
addJobChangeListener(new JobChangeAdapter() {
235             public void done(IJobChangeEvent event) {
236                 if(shouldReschedule()) {
237                     IStatus result = event.getResult();
238                     if(result.getSeverity() == IStatus.CANCEL && ! restartOnCancel) {
239                         return;
240                     }
241                     long delay = scheduleDelay;
242                     if (result == POSTPONED) {
243                         // Restart in 5 seconds
244
delay = 5000;
245                     }
246                     RefreshParticipantJob.this.schedule(delay);
247                     restartOnCancel = true;
248                 }
249             }
250         });
251         if(listener != null)
252             initialize(listener);
253     }
254
255     public boolean belongsTo(Object JavaDoc family) {
256         if (family instanceof SubscriberParticipant) {
257             return family == participant;
258         } else {
259             return (family == getFamily() || family == ISynchronizeManager.FAMILY_SYNCHRONIZE_OPERATION);
260         }
261     }
262     
263     public static Object JavaDoc getFamily() {
264         return FAMILY_ID;
265     }
266     
267     /**
268      * This is run by the job scheduler. A list of subscribers will be refreshed, errors will not stop the job
269      * and it will continue to refresh the other subscribers.
270      */

271     public IStatus run(IProgressMonitor monitor) {
272         // Perform a pre-check for auto-build or manual build jobs
273
// when auto-refreshing
274
if (shouldReschedule() &&
275                 (isJobInFamilyRunning(ResourcesPlugin.FAMILY_AUTO_BUILD)
276                 || isJobInFamilyRunning(ResourcesPlugin.FAMILY_MANUAL_BUILD))) {
277             return POSTPONED;
278         }
279         // Only allow one refresh job at a time
280
// NOTE: It would be cleaner if this was done by a scheduling
281
// rule but at the time of writing, it is not possible due to
282
// the scheduling rule containment rules.
283
// Acquiring lock to ensure only one refresh job is running at a particular time
284
boolean acquired = false;
285         try {
286             while (!acquired) {
287                 try {
288                     acquired = lock.acquire(1000);
289                 } catch (InterruptedException JavaDoc e1) {
290                     acquired = false;
291                 }
292                 Policy.checkCanceled(monitor);
293             }
294             
295             IChangeDescription changeDescription = createChangeDescription();
296             RefreshEvent event = new RefreshEvent(reschedule ? IRefreshEvent.SCHEDULED_REFRESH : IRefreshEvent.USER_REFRESH, participant, changeDescription);
297             IStatus status = null;
298             NonblockingProgressMonitor wrappedMonitor = null;
299             try {
300                 event.setStartTime(System.currentTimeMillis());
301                 if(monitor.isCanceled()) {
302                     return Status.CANCEL_STATUS;
303                 }
304                 // Pre-Notify
305
notifyListeners(STARTED, event);
306                 // Perform the refresh
307
monitor.setTaskName(getName());
308                 wrappedMonitor = new NonblockingProgressMonitor(monitor, this);
309                 doRefresh(changeDescription, wrappedMonitor);
310                 // Prepare the results
311
setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(! isJobModal()));
312             } catch(OperationCanceledException e2) {
313                 if (monitor.isCanceled()) {
314                     // The refresh was canceled by the user
315
status = Status.CANCEL_STATUS;
316                 } else {
317                     // The refresh was canceled due to a blockage or a canceled authentication
318
if (wrappedMonitor != null && wrappedMonitor.wasBlocking()) {
319                         status = POSTPONED;
320                     } else {
321                         status = Status.CANCEL_STATUS;
322                     }
323                 }
324             } catch(CoreException e) {
325                 // Determine the status to be returned and the GOTO action
326
status = e.getStatus();
327                 if (!isUser()) {
328                     // Use the GOTO action to show the error and return OK
329
Object JavaDoc prop = getProperty(IProgressConstants.ACTION_PROPERTY);
330                     if (prop instanceof GotoActionWrapper) {
331                         GotoActionWrapper wrapper = (GotoActionWrapper)prop;
332                         wrapper.setStatus(e.getStatus());
333                         status = new Status(IStatus.OK, TeamUIPlugin.ID, IStatus.OK, e.getStatus().getMessage(), e);
334                     }
335                 }
336                 if (!isUser() && status.getSeverity() == IStatus.ERROR) {
337                     // Never prompt for errors on non-user jobs
338
setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
339                 }
340             } finally {
341                 event.setStopTime(System.currentTimeMillis());
342             }
343             
344             // Post-Notify
345
if (status == null) {
346                 status = calculateStatus(event);
347             }
348             event.setStatus(status);
349             notifyListeners(DONE, event);
350             if (event.getChangeDescription().getChangeCount() > 0) {
351                 if (participant instanceof AbstractSynchronizeParticipant) {
352                     AbstractSynchronizeParticipant asp = (AbstractSynchronizeParticipant) participant;
353                     asp.firePropertyChange(participant, ISynchronizeParticipant.P_CONTENT, null, event.getChangeDescription());
354                 }
355             }
356             return event.getStatus();
357         } finally {
358             if (acquired) lock.release();
359             monitor.done();
360         }
361     }
362
363     protected abstract void doRefresh(IChangeDescription changeListener, IProgressMonitor monitor) throws CoreException;
364     
365     /**
366      * Return the total number of changes covered by the resources
367      * of this job.
368      * @return the total number of changes covered by the resources
369      * of this job
370      */

371     protected abstract int getChangeCount();
372
373     protected abstract int getIncomingChangeCount();
374     protected abstract int getOutgoingChangeCount();
375     
376     private boolean isJobInFamilyRunning(Object JavaDoc family) {
377         Job[] jobs = Platform.getJobManager().find(family);
378         if (jobs != null && jobs.length > 0) {
379             for (int i = 0; i < jobs.length; i++) {
380                 Job job = jobs[i];
381                 if (job.getState() != Job.NONE) {
382                     return true;
383                 }
384             }
385         }
386         return false;
387     }
388
389     private IStatus calculateStatus(IRefreshEvent event) {
390         StringBuffer JavaDoc text = new StringBuffer JavaDoc();
391         int code = IStatus.OK;
392         int changeCount = event.getChangeDescription().getChangeCount();
393         int numChanges = getChangeCount();
394         if (numChanges > 0) {
395             code = IRefreshEvent.STATUS_CHANGES;
396
397             int incomingChanges = getIncomingChangeCount();
398              String JavaDoc numIncomingChanges = incomingChanges==0 ? "" //$NON-NLS-1$
399
: NLS.bind(TeamUIMessages.RefreshCompleteDialog_incomingChanges, Integer.toString(incomingChanges));
400              
401             int outgoingChanges = getOutgoingChangeCount();
402             String JavaDoc numOutgoingChanges = outgoingChanges==0 ? "" //$NON-NLS-1$
403
: NLS.bind(TeamUIMessages.RefreshCompleteDialog_outgoingChanges, Integer.toString(outgoingChanges));
404             
405             String JavaDoc sep = incomingChanges>0 && outgoingChanges>0 ? "; " : ""; //$NON-NLS-1$ //$NON-NLS-2$
406

407             if (changeCount > 0) {
408             // New changes found
409
code = IRefreshEvent.STATUS_NEW_CHANGES;
410                 String JavaDoc numNewChanges = Integer.toString(changeCount);
411                 if (changeCount == 1) {
412                     text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_newChangesSingular, (new Object JavaDoc[]{getName(), numNewChanges, numIncomingChanges, sep, numOutgoingChanges})));
413                 } else {
414                     text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_newChangesPlural, (new Object JavaDoc[]{getName(), numNewChanges, numIncomingChanges, sep, numOutgoingChanges})));
415                 }
416             } else {
417                 // Refreshed resources contain changes
418
if (numChanges == 1) {
419                     text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_changesSingular, (new Object JavaDoc[]{getName(), new Integer JavaDoc(numChanges), numIncomingChanges, sep, numOutgoingChanges})));
420                 } else {
421                     text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_changesPlural, (new Object JavaDoc[]{getName(), new Integer JavaDoc(numChanges), numIncomingChanges, sep, numOutgoingChanges})));
422                 }
423             }
424         } else {
425             // No changes found
426
code = IRefreshEvent.STATUS_NO_CHANGES;
427             text.append(NLS.bind(TeamUIMessages.RefreshCompleteDialog_6, new String JavaDoc[] { getName() }));
428         }
429         return new Status(IStatus.OK, TeamUIPlugin.ID, code, text.toString(), null);
430     }
431     
432     private void initialize(final IRefreshSubscriberListener listener) {
433         final GotoActionWrapper actionWrapper = new GotoActionWrapper();
434         
435         IProgressMonitor group = Platform.getJobManager().createProgressGroup();
436         group.beginTask(taskName, 100);
437         setProgressGroup(group, 80);
438         handleProgressGroupSet(group, 20);
439         setProperty(IProgressConstants.ICON_PROPERTY, participant.getImageDescriptor());
440         setProperty(IProgressConstants.ACTION_PROPERTY, actionWrapper);
441         setProperty(IProgressConstants.KEEPONE_PROPERTY, Boolean.valueOf(! isJobModal()));
442         // Listener delegate
443
IRefreshSubscriberListener autoListener = new IRefreshSubscriberListener() {
444             public void refreshStarted(IRefreshEvent event) {
445                 if(listener != null) {
446                     listener.refreshStarted(event);
447                 }
448             }
449             public ActionFactory.IWorkbenchAction refreshDone(IRefreshEvent event) {
450                 if(listener != null) {
451                     boolean isModal = isJobModal();
452                     event.setIsLink(!isModal);
453                     final ActionFactory.IWorkbenchAction runnable = listener.refreshDone(event);
454                     if(runnable != null) {
455                         // If the job is being run modally then simply prompt the user immediately
456
if(isModal) {
457                             if(runnable != null) {
458                                 Job update = new UIJob("") { //$NON-NLS-1$
459
public IStatus runInUIThread(IProgressMonitor monitor) {
460                                         runnable.run();
461                                         return Status.OK_STATUS;
462                                     }
463                                 };
464                                 update.setSystem(true);
465                                 update.schedule();
466                             }
467                         } else {
468                             // If the job is being run in the background, don't interrupt the user and simply update the goto action
469
// to perform the results.
470
actionWrapper.setGotoAction(runnable);
471                         }
472                     }
473                     RefreshParticipantJob.removeRefreshListener(this);
474                 }
475                 return null;
476             }
477         };
478         
479         if (listener != null) {
480             RefreshParticipantJob.addRefreshListener(autoListener);
481         }
482     }
483
484     /**
485      * The progress group of this job has been set. Any subclasses should
486      * assign this group to any additional jobs they use to collect
487      * changes from the refresh.
488      * @param group a progress group
489      * @param ticks the ticks for the change collection job
490      */

491     protected abstract void handleProgressGroupSet(IProgressMonitor group, int ticks);
492     
493     protected abstract IChangeDescription createChangeDescription();
494     
495     public long getScheduleDelay() {
496         return scheduleDelay;
497     }
498     
499     protected void start() {
500         if(getState() == Job.NONE) {
501             if(shouldReschedule()) {
502                 schedule(getScheduleDelay());
503             }
504         }
505     }
506     
507     /**
508      * Specify the interval in seconds at which this job is scheduled.
509      * @param seconds delay specified in seconds
510      */

511     public void setRefreshInterval(long seconds) {
512         boolean restart = false;
513         if(getState() == Job.SLEEPING) {
514             restart = true;
515             cancel();
516         }
517         scheduleDelay = seconds * 1000;
518         if(restart) {
519             start();
520         }
521     }
522     
523     public void setRestartOnCancel(boolean restartOnCancel) {
524         this.restartOnCancel = restartOnCancel;
525     }
526     
527     public void setReschedule(boolean reschedule) {
528         this.reschedule = reschedule;
529     }
530     
531     public boolean shouldReschedule() {
532         return reschedule;
533     }
534     
535     public static void addRefreshListener(IRefreshSubscriberListener listener) {
536         synchronized(listeners) {
537             if(! listeners.contains(listener)) {
538                 listeners.add(listener);
539             }
540         }
541     }
542     
543     public static void removeRefreshListener(IRefreshSubscriberListener listener) {
544         synchronized(listeners) {
545             listeners.remove(listener);
546         }
547     }
548     
549     protected void notifyListeners(final int state, final IRefreshEvent event) {
550         // Get a snapshot of the listeners so the list doesn't change while we're firing
551
IRefreshSubscriberListener[] listenerArray;
552         synchronized (listeners) {
553             listenerArray = (IRefreshSubscriberListener[]) listeners.toArray(new IRefreshSubscriberListener[listeners.size()]);
554         }
555         // Notify each listener in a safe manner (i.e. so their exceptions don't kill us)
556
for (int i = 0; i < listenerArray.length; i++) {
557             IRefreshSubscriberListener listener = listenerArray[i];
558             Notification notification = new Notification() {
559                 protected void notify(IRefreshSubscriberListener listener) {
560                     switch (state) {
561                         case STARTED:
562                             listener.refreshStarted(event);
563                             break;
564                         case DONE:
565                             listener.refreshDone(event);
566                             break;
567                         default:
568                             break;
569                     }
570                 }
571             };
572             notification.run(listener);
573         }
574     }
575     
576     private boolean isJobModal() {
577         Boolean JavaDoc isModal = (Boolean JavaDoc)getProperty(IProgressConstants.PROPERTY_IN_DIALOG);
578         if(isModal == null) return false;
579         return isModal.booleanValue();
580     }
581
582     public ISynchronizeParticipant getParticipant() {
583         return participant;
584     }
585 }
586
Popular Tags