blob: fd32753cd01f3c96c2ac95a97d359ce898e07d65 [file] [log] [blame]
/*******************************************************************************
* 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 );
}
} );
}
}