| /******************************************************************************* |
| * Copyright (c) 2007, 2020 Innoopract Informationssysteme GmbH and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Innoopract Informationssysteme GmbH - initial API and implementation |
| * EclipseSource - ongoing development |
| ******************************************************************************/ |
| package org.eclipse.rap.ui.internal.progress; |
| |
| import java.lang.reflect.Field; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.jobs.IJobChangeEvent; |
| import org.eclipse.core.runtime.jobs.IJobChangeListener; |
| import org.eclipse.core.runtime.jobs.IJobManager; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.jobs.JobChangeAdapter; |
| import org.eclipse.core.runtime.jobs.ProgressProvider; |
| import org.eclipse.rap.rwt.RWT; |
| import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleUtil; |
| import org.eclipse.rap.rwt.internal.serverpush.ServerPushManager; |
| import org.eclipse.rap.rwt.internal.service.ContextProvider; |
| import org.eclipse.rap.rwt.service.UISession; |
| import org.eclipse.rap.rwt.service.UISessionEvent; |
| import org.eclipse.rap.rwt.service.UISessionListener; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.internal.progress.ProgressManager; |
| import org.eclipse.ui.progress.UIJob; |
| |
| |
| public class JobManagerAdapter extends ProgressProvider implements IJobChangeListener { |
| |
| private static JobManagerAdapter _instance; |
| private final Map jobs; |
| private final ProgressManager defaultProgressManager; |
| final Object lock; |
| |
| public static synchronized JobManagerAdapter getInstance() { |
| if( _instance == null ) { |
| _instance = new JobManagerAdapter(); |
| } |
| return _instance; |
| } |
| |
| private JobManagerAdapter() { |
| // To avoid deadlocks we have to use the same synchronization lock. |
| // If anyone has a better idea - you're welcome. |
| IJobManager jobManager = Job.getJobManager(); |
| Class clazz = jobManager.getClass(); |
| try { |
| Field jobManagerLock = clazz.getDeclaredField( "lock" ); |
| jobManagerLock.setAccessible( true ); |
| lock = jobManagerLock.get( jobManager ); |
| } catch( final Throwable thr ) { |
| String msg = "Could not initialize synchronization lock."; |
| throw new IllegalStateException( msg ); |
| } |
| jobs = new HashMap(); |
| defaultProgressManager = new ProgressManager(); |
| Job.getJobManager().setProgressProvider( this ); |
| Job.getJobManager().addJobChangeListener( this ); |
| } |
| |
| /////////////////// |
| // ProgressProvider |
| |
| @Override |
| public IProgressMonitor createMonitor( final Job job ) { |
| IProgressMonitor result = null; |
| ProgressManager manager = findSessionProgressManager( job ); |
| if( manager != null ) { |
| result = manager.createMonitor( job ); |
| } |
| return result; |
| } |
| |
| @Override |
| public IProgressMonitor createMonitor( final Job job, |
| final IProgressMonitor group, |
| final int ticks ) |
| { |
| IProgressMonitor result = null; |
| ProgressManager manager = findSessionProgressManager( job ); |
| if( manager != null ) { |
| result = manager.createMonitor( job, group, ticks ); |
| } |
| return result; |
| } |
| |
| @Override |
| public IProgressMonitor createProgressGroup() { |
| return defaultProgressManager.createProgressGroup(); |
| } |
| |
| /////////////////////////////// |
| // interface IJobChangeListener |
| |
| @Override |
| public void aboutToRun( final IJobChangeEvent event ) { |
| ProgressManager manager = findProgressManager( event.getJob() ); |
| manager.changeListener.aboutToRun( event ); |
| } |
| |
| @Override |
| public void awake( final IJobChangeEvent event ) { |
| ProgressManager manager = findProgressManager( event.getJob() ); |
| manager.changeListener.awake( event ); |
| } |
| |
| @Override |
| public void done( final IJobChangeEvent event ) { |
| final ProgressManager[] manager = new ProgressManager[ 1 ]; |
| Display display = null; |
| synchronized( lock ) { |
| try { |
| manager[ 0 ] = findProgressManager( event.getJob() ); |
| display = ( Display )jobs.get( event.getJob() ); |
| } finally { |
| jobs.remove( event.getJob() ); |
| } |
| } |
| if( display != null && !display.isDisposed() ) { |
| display.asyncExec( new Runnable() { |
| @Override |
| public void run() { |
| ServerPushManager.getInstance().deactivateServerPushFor( event.getJob() ); |
| manager[ 0 ].changeListener.done( event ); |
| } |
| } ); |
| } else { |
| // RAP [rh] fixes bug 283595 |
| manager[ 0 ].changeListener.done( event ); |
| } |
| } |
| |
| @Override |
| public void running( final IJobChangeEvent event ) { |
| ProgressManager manager = findProgressManager( event.getJob() ); |
| manager.changeListener.running( event ); |
| } |
| |
| @Override |
| public void scheduled( final IJobChangeEvent event ) { |
| ProgressManager manager; |
| Display display = findDisplay( event.getJob() ); |
| synchronized( lock ) { |
| if( display != null && !display.isDisposed() ) { |
| jobs.put( event.getJob(), display ); |
| Runnable runnable = new Runnable() { |
| |
| @Override |
| public void run() { |
| bindToSession( event.getJob() ); |
| ServerPushManager.getInstance().activateServerPushFor( event.getJob() ); |
| } |
| }; |
| RWT.getUISession( display ).exec( runnable ); |
| } |
| manager = findProgressManager( event.getJob() ); |
| } |
| manager.changeListener.scheduled( event ); |
| } |
| |
| @Override |
| public void sleeping( final IJobChangeEvent event ) { |
| ProgressManager manager = findProgressManager( event.getJob() ); |
| manager.changeListener.sleeping( event ); |
| } |
| |
| ////////////////// |
| // helping methods |
| |
| private ProgressManager findProgressManager( final Job job ) { |
| ProgressManager result = findSessionProgressManager( job ); |
| if( result == null ) { |
| result = defaultProgressManager; |
| } |
| return result; |
| } |
| |
| private ProgressManager findSessionProgressManager( final Job job ) { |
| synchronized( lock ) { |
| final ProgressManager result[] = new ProgressManager[ 1 ]; |
| Display display = ( Display )jobs.get( job ); |
| if( display != null ) { |
| RWT.getUISession( display ).exec( new Runnable() { |
| @Override |
| public void run() { |
| result[ 0 ] = ProgressManager.getInstance(); |
| } |
| } ); |
| if( result[ 0 ] == null ) { |
| String msg = "ProgressManager must not be null."; |
| throw new IllegalStateException( msg ); |
| } |
| } else { |
| result[ 0 ] = null; |
| } |
| return result[ 0 ]; |
| } |
| } |
| |
| private static Display findDisplay( final Job job ) { |
| Display result = null; |
| if( ContextProvider.hasContext() ) { |
| result = LifeCycleUtil.getSessionDisplay(); |
| } else { |
| if( job instanceof UIJob ) { |
| UIJob uiJob = ( UIJob )job; |
| result = uiJob.getDisplay(); |
| if( result == null ) { |
| String msg = "UIJob " |
| + uiJob.getName() |
| + " cannot be scheduled without an associated display."; |
| throw new IllegalStateException( msg ); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private void bindToSession( final Job job ) { |
| final AtomicBoolean jobDone = new AtomicBoolean(); |
| final UISession uiSession = RWT.getUISession(); |
| final UISessionListener cleanupListener = new UISessionListener() { |
| |
| @Override |
| public void beforeDestroy( UISessionEvent event ) { |
| if( !jobDone.get() ) { |
| try { |
| cleanup( job ); |
| } finally { |
| synchronized( lock ) { |
| jobs.remove( job ); |
| } |
| } |
| } |
| } |
| |
| private void cleanup( final Job jobToRemove ) { |
| // //////////////////////////////////////////////////////////////////// |
| // TODO [fappel]: Very ugly hack to avoid a memory leak. |
| // As a job can not be removed from the |
| // running set directly, I use reflection. Jobs |
| // can be catched in the set on session timeouts. |
| // Don't know a proper solution yet. |
| // Note that this is still under investigation. |
| Display display = ( Display )jobs.get( jobToRemove ); |
| if( display != null ) { |
| RWT.getUISession( display ).exec( new Runnable() { |
| @Override |
| public void run() { |
| jobToRemove.cancel(); |
| jobToRemove.addJobChangeListener( new JobCanceler() ); |
| } |
| } ); |
| } |
| try { |
| IJobManager jobManager = Job.getJobManager(); |
| Class clazz = jobManager.getClass(); |
| Field running = clazz.getDeclaredField( "running" ); |
| running.setAccessible( true ); |
| Set set = ( Set )running.get( jobManager ); |
| synchronized( lock ) { |
| set.remove( job ); |
| // still sometimes job get catched - use the job marker adapter |
| // to check whether they can be eliminated |
| Object[] runningJobs = set.toArray(); |
| for( int i = 0; i < runningJobs.length; i++ ) { |
| Job toCheck = ( Job )runningJobs[ i ]; |
| IJobMarker marker = toCheck.getAdapter( IJobMarker.class ); |
| if( marker != null && marker.canBeRemoved() ) { |
| set.remove( toCheck ); |
| } |
| } |
| } |
| } catch( final Throwable thr ) { |
| // TODO [fappel]: exception handling |
| thr.printStackTrace(); |
| } |
| } |
| }; |
| uiSession.addUISessionListener( cleanupListener ); |
| job.addJobChangeListener( new JobChangeAdapter() { |
| @Override |
| public void done( final IJobChangeEvent event ) { |
| jobDone.set( true ); |
| uiSession.removeUISessionListener( cleanupListener ); |
| } |
| } ); |
| } |
| } |