blob: 671134bd1ebe8cf7bab06f9b0c2e81b08e5562ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.client.busy;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.rt.client.ClientJob;
import org.eclipse.scout.rt.client.ClientSyncJob;
import org.eclipse.scout.rt.client.IClientSession;
import org.eclipse.scout.rt.client.ui.form.fields.smartfield.ContentAssistTreeForm;
/**
* <p>
* Shows blocking progress when {@link ClientSyncJob} or {@link ClientJob} with {@link ClientJob#isSync()} are doing a
* long operation.
* </p>
* <p>
* The decision whether or not the progress should be visible is made in the acceptor
* {@link AbstractBusyHandler#acceptJob(Job)}
* </p>
* <p>
* The strategy to display busy and blocking progress can be changed by overriding {@link #runBusy(Object)} and
* {@link #runBusy(IRunnableWithProgress)}.
* </p>
* Implementations are ui specific an can be found in the swing, swt, rwt implementation sof scout.
* <p>
* This abstract implementation is Thread-safe.
*
* @author imo
* @since 3.8
*/
public abstract class AbstractBusyHandler implements IBusyHandler {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractBusyHandler.class);
private static final QualifiedName TIMER_PROPERTY = new QualifiedName(AbstractBusyHandler.class.getName(), "timer");
private static final QualifiedName BUSY_OPERATION_PROPERTY = new QualifiedName(AbstractBusyHandler.class.getName(), "busy");
private final IClientSession m_session;
private final Object m_stateLock = new Object();
private final Set<Job> m_list = Collections.synchronizedSet(new HashSet<Job>());
private long m_shortOperationMillis = 200L;
private long m_longOperationMillis = 3000L;
private boolean m_enabled = true;
private int m_blockingCount;
private final Object m_blockingCountLock = new Object();
public AbstractBusyHandler(IClientSession session) {
m_session = session;
}
@Override
public boolean acceptJob(final Job job) {
if (job == null) {
return false;
}
if (job instanceof ClientJob && ((ClientJob) job).isSync()) {
return true;
}
return false;
}
@Override
public void onJobBegin(Job job) {
addTimer(job);
}
@Override
public void onJobEnd(Job job) {
removeTimer(job);
//avoid unnecessary locks
if (isBusyOperationNoLock(job)) {
removeBusyOperation(job);
}
}
@Override
public void onBlockingBegin() {
synchronized (m_blockingCountLock) {
m_blockingCount++;
}
}
@Override
public void onBlockingEnd() {
synchronized (m_blockingCountLock) {
if (m_blockingCount == 0) {
return;
}
m_blockingCount--;
if (m_blockingCount == 0) {
m_blockingCountLock.notifyAll();
}
}
}
@Override
public boolean isBlocking() {
synchronized (m_blockingCountLock) {
return m_blockingCount > 0;
}
}
@Override
public void waitForBlockingToEnd() {
synchronized (m_blockingCountLock) {
while (isBlocking()) {
LOG.debug("Waiting for the application to exit blocking mode");
try {
m_blockingCountLock.wait();
}
catch (InterruptedException e) {
LOG.warn("Interrupted while waiting for the application to exit blocking mode.");
}
}
}
}
@Override
public final Object getStateLock() {
return m_stateLock;
}
@Override
public void cancel() {
synchronized (getStateLock()) {
for (Job job : m_list) {
try {
job.cancel();
}
catch (Throwable t) {
//nop
}
}
}
}
/**
* This method is called directly before calling {@link #runBusy()} and can be used to late-check if busy handling is
* really necessary at that point in time.
* <p>
* The default checks if the job is in a smart tree operation and ignores busy, see
* {@link ContentAssistTreeForm#JOB_PROPERTY_LOAD_TREE}.
*/
protected boolean shouldRunBusy(Job job) {
Boolean b = (Boolean) job.getProperty(ContentAssistTreeForm.JOB_PROPERTY_LOAD_TREE);
if (b != null && b.booleanValue()) {
return false;
}
return true;
}
/**
* This method is called directly from the job listener after {@link #getShortOperationMillis()}.
* <p>
* You may call {@link #runDefaultBusy(Object, IProgressMonitor)} to use default handling.
* <p>
* {@link #getStateLock()} can be used synchronized to check {@link #isBusy()}
* <p>
* Be careful what to do, since this might be expensive. The default starts a {@link BusyJob} or a subclass of the
* {@link BusyJob}
*/
protected abstract void runBusy(Job job);
/**
* @retrun true if blocking is active
*/
@Override
public final boolean isBusy() {
return m_list.size() > 0;
}
@Override
public long getShortOperationMillis() {
return m_shortOperationMillis;
}
public void setShortOperationMillis(long shortOperationMillis) {
m_shortOperationMillis = shortOperationMillis;
}
@Override
public long getLongOperationMillis() {
return m_longOperationMillis;
}
public void setLongOperationMillis(long longOperationMillis) {
m_longOperationMillis = longOperationMillis;
}
@Override
public boolean isEnabled() {
return m_enabled;
}
@Override
public void setEnabled(boolean enabled) {
m_enabled = enabled;
}
private void addTimer(Job job) {
P_TimerJob t = new P_TimerJob(job);
job.setProperty(TIMER_PROPERTY, t);
t.schedule(getShortOperationMillis());
}
private void removeTimer(Job job) {
P_TimerJob t = (P_TimerJob) job.getProperty(TIMER_PROPERTY);
if (t != null) {
t.cancel();
job.setProperty(TIMER_PROPERTY, null);
}
}
private P_TimerJob getTimer(Job job) {
return (P_TimerJob) job.getProperty(TIMER_PROPERTY);
}
private void addBusyOperation(Job job) {
int oldSize, newSize;
synchronized (getStateLock()) {
job.setProperty(BUSY_OPERATION_PROPERTY, "true");
oldSize = m_list.size();
m_list.add(job);
newSize = m_list.size();
getStateLock().notifyAll();
}
if (oldSize == 0 && newSize == 1) {
runBusy(job);
}
}
private void removeBusyOperation(Job job) {
synchronized (getStateLock()) {
job.setProperty(BUSY_OPERATION_PROPERTY, null);
m_list.remove(job);
getStateLock().notifyAll();
}
}
private boolean isBusyOperationNoLock(Job job) {
return "true".equals(job.getProperty(BUSY_OPERATION_PROPERTY));
}
private static boolean isJobActive(final Job job) {
if (job.getState() != Job.RUNNING) {
return false;
}
if (job instanceof ClientJob && ((ClientJob) job).isWaitFor()) {
return false;
}
return true;
}
private class P_TimerJob extends Job {
private final Job m_job;
public P_TimerJob(Job job) {
super("TimerJob");
setSystem(true);
m_job = job;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (P_TimerJob.this != getTimer(m_job)) {
return Status.OK_STATUS;
}
removeTimer(m_job);
if (isJobActive(m_job)) {
if (!isEnabled() || !shouldRunBusy(m_job)) {
return Status.OK_STATUS;
}
addBusyOperation(m_job);
}
//double check after queuing (avoid unnecessary locks)
if (!isJobActive(m_job)) {
removeBusyOperation(m_job);
}
return Status.OK_STATUS;
}
}
}