blob: 5fd4c32bd89ad7bd3d25f262847e0588b1c1f5e7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.hudson.tasklist;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import org.netbeans.api.project.Project;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.ui.spi.ProjectHudsonProvider;
import static org.netbeans.modules.hudson.tasklist.Bundle.*;
import org.netbeans.spi.tasklist.PushTaskScanner;
import org.netbeans.spi.tasklist.Task;
import org.netbeans.spi.tasklist.TaskScanningScope;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service=PushTaskScanner.class, path="TaskList/Scanners")
public class HudsonScanner extends PushTaskScanner implements Runnable {
private static final Logger LOG = Logger.getLogger(HudsonScanner.class.getName());
private RequestProcessor.Task task;
private Thread taskThread;
private TaskScanningScope scope;
private Callback callback;
private static class CacheEntry {
String jobUrl;
int lastSuccessfulBuild;
List<Task> tasks;
}
private final Map<Project,CacheEntry> cache = new WeakHashMap<Project,CacheEntry>();
@Messages({
"HudsonScanner.displayName=Hudson Warnings",
"HudsonScanner.description=Warnings and other action items coming from Hudson servers, currently supporting the Static Analysis plugin suite."
})
public HudsonScanner() {
super(HudsonScanner_displayName(), HudsonScanner_description(), null);
}
@Override public void setScope(TaskScanningScope scope, Callback callback) {
try {
// XXX make Installer.active into API
if (!NbPreferences.root().nodeExists("org/netbeans/modules/hudson/instances")) {
return; // avoid loading any more classes
}
} catch (BackingStoreException x) {
LOG.log(Level.INFO, null, x);
}
doSetScope(scope, callback);
}
// in a separate method to avoid resolving anything else if shortcut above is used
private void doSetScope(TaskScanningScope scope, Callback callback) {
if (task == null) {
task = new RequestProcessor(HudsonScanner.class).create(this);
task.setPriority(Thread.MIN_PRIORITY);
}
if (scope == null) {
LOG.fine("canceling scan");
task.cancel();
if (taskThread != null) {
taskThread.interrupt();
}
return;
}
this.scope = scope;
this.callback = callback;
// XXX also support individual FileObject scope
if (scope.getLookup().lookup(Project.class) == null) { // shortcut
LOG.finer("no projects to scan");
callback.clearAllTasks();
return;
}
LOG.fine("scheduling scan");
task.schedule(1000);
}
@Override public void run() {
callback.started();
taskThread = Thread.currentThread();
try {
final List<Task> tasks = new ArrayList<Task>();
for (Project p : scope.getLookup().lookupAll(Project.class)) {
if (Thread.interrupted()) {
return;
}
ProjectHudsonProvider.Association assoc = ProjectHudsonProvider.getDefault().findAssociation(p);
if (assoc == null) {
continue;
}
HudsonJob job = assoc.getJob();
if (job == null) {
continue;
}
CacheEntry entry = cache.get(p);
if (entry == null) {
entry = new CacheEntry();
cache.put(p, entry);
}
List<Task> cachedTasks = entry.tasks;
int lastSuccessfulBuild = job.getLastSuccessfulBuild();
String jobUrl = job.getUrl();
if (cachedTasks == null || lastSuccessfulBuild != entry.lastSuccessfulBuild || !jobUrl.equals(entry.jobUrl)) { // cache miss
final List<Task> newTasks = new ArrayList<Task>();
for (JobScanner s : Lookup.getDefault().lookupAll(JobScanner.class)) {
if (Thread.interrupted()) {
return;
}
try {
final AtomicInteger count = new AtomicInteger();
s.findTasks(p, job, lastSuccessfulBuild, new JobScanner.TaskAdder() {
@Override public void add(Task task) {
count.incrementAndGet();
newTasks.add(task);
tasks.add(task);
callback.setTasks(tasks);
}
});
LOG.log(Level.FINE, "discovered {0} tasks for {1} from {2}", new Object[] {count, p, s});
} catch (IOException x) {
LOG.log(Level.INFO, "from " + assoc, x);
} catch (RuntimeException x) {
LOG.log(Level.WARNING, "from " + assoc, x);
}
}
entry.tasks = newTasks;
entry.lastSuccessfulBuild = lastSuccessfulBuild;
entry.jobUrl = jobUrl;
} else { // cache hit
LOG.log(Level.FINE, "using {0} cached tasks for {1}", new Object[] {cachedTasks.size(), p});
tasks.addAll(cachedTasks);
callback.setTasks(tasks);
}
}
} finally {
taskThread = null;
callback.finished();
}
}
}