/*******************************************************************************
*
* Copyright (c) 2004-2013 Oracle Corporation.
*
* 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:
*
* Kohsuke Kawaguchi, Roy Varghese
*
*
*******************************************************************************/
package hudson.model;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.SortedMap;
import hudson.model.Descriptor.FormException;
/**
* {@link Job} that monitors activities that happen outside Hudson, which
* requires occasional batch reload activity to obtain the up-to-date
* information.
*
* <p> This can be used as a base class to derive custom {@link Job} type.
*
* @author Kohsuke Kawaguchi
*/
public abstract class ViewJob<JobT extends ViewJob<JobT, RunT>,
RunT extends Run<JobT, RunT> & BuildNavigable>
extends Job<JobT, RunT> {
/**
* We occasionally update the list of {@link Run}s from a file system. The
* next scheduled update time.
*/
private transient long nextUpdate = 0;
/**
* All {@link Run}s. Copy-on-write semantics.
*/
protected transient /*almost final*/ RunMap<JobT,RunT> runs ;
private transient boolean notLoaded = true;
/**
* If the reloading of runs are in progress (in another thread, set to
* true.)
*/
private transient volatile boolean reloadingInProgress;
/**
* {@link ExternalJob}s that need to be reloaded.
*
* This is a set, so no {@link ExternalJob}s are scheduled twice, yet it's
* order is predictable, avoiding starvation.
*/
private static final LinkedHashSet<ViewJob> reloadQueue = new LinkedHashSet<ViewJob>();
/*package*/ static final Thread reloadThread = new ReloadThread();
static {
reloadThread.start();
}
/**
* @deprecated as of 1.390
*/
protected ViewJob(Hudson parent, String name) {
super(parent, name);
initRuns();
}
protected ViewJob(ItemGroup parent, String name) {
super(parent, name);
initRuns();
}
public boolean isBuildable() {
return false;
}
@Override
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
super.onLoad(parent, name);
notLoaded = true;
}
private void initRuns() {
if (runs == null) {
runs = new RunMap(this);
}
}
protected SortedMap<Integer, RunT> _getRuns() {
if (notLoaded || runs == null) {
// if none is loaded yet, do so immediately.
synchronized (this) {
initRuns();
if (notLoaded) {
notLoaded = false;
_reload();
}
}
}
if (nextUpdate < System.currentTimeMillis()) {
if (!reloadingInProgress) {
// schedule a new reloading operation.
// we don't want to block the current thread,
// so reloading is done asynchronously.
reloadingInProgress = true;
synchronized (reloadQueue) {
reloadQueue.add(this);
reloadQueue.notify();
}
}
}
return runs;
}
@Override
public BuildHistory<JobT, RunT> getBuildHistoryData() {
return (BuildHistory<JobT,RunT>)_getRuns();
}
public void removeRun(RunT run) {
// reload the info next time
nextUpdate = 0;
}
private void _reload() {
try {
reload();
} finally {
reloadingInProgress = false;
nextUpdate = reloadPeriodically ? System.currentTimeMillis() + 1000 * 60 : Long.MAX_VALUE;
}
}
/**
* Reloads the list of {@link Run}s. This operation can take a long time.
*
* <p> The loaded {@link Run}s should be set to {@link #runs}.
*/
protected abstract void reload();
@Override
protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
super.submit(req, rsp);
// make sure to reload to reflect this config change.
nextUpdate = 0;
}
/**
* Thread that reloads the {@link Run}s.
*/
private static final class ReloadThread extends Thread {
private ReloadThread() {
setName("ViewJob reload thread");
}
private ViewJob getNext() throws InterruptedException {
synchronized (reloadQueue) {
// reload operations might eat InterruptException,
// so check the status every so often
while (reloadQueue.isEmpty() && !terminating()) {
reloadQueue.wait(60 * 1000);
}
if (terminating()) {
throw new InterruptedException(); // terminate now
}
ViewJob job = reloadQueue.iterator().next();
reloadQueue.remove(job);
return job;
}
}
private boolean terminating() {
return Hudson.getInstance().isTerminating();
}
@Override
public void run() {
while (!terminating()) {
try {
getNext()._reload();
} catch (InterruptedException e) {
// treat this as a death signal
return;
} catch (Throwable t) {
// otherwise ignore any error
t.printStackTrace();
}
}
}
}
// private static final Logger logger = Logger.getLogger(ViewJob.class.getName());
/**
* In the very old version of Hudson, an external job submission was just
* creating files on the file system, so we needed to periodically reload
* the jobs from a file system to pick up new records.
*
* <p> We then switched to submission via HTTP, so this reloading is no
* longer necessary, so only do this when explicitly requested.
*
*/
public static boolean reloadPeriodically = Boolean.getBoolean(ViewJob.class.getName() + ".reloadPeriodically");
}