org.netbeans.modules.hudson.impl.HudsonInstanceImpl Maven / Gradle / Ivy
/*
* 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.impl;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.hudson.api.ConnectionBuilder;
import org.netbeans.modules.hudson.api.HudsonChangeListener;
import org.netbeans.modules.hudson.api.HudsonFolder;
import org.netbeans.modules.hudson.api.HudsonInstance;
import org.netbeans.modules.hudson.api.HudsonJob;
import org.netbeans.modules.hudson.api.HudsonJobBuild;
import org.netbeans.modules.hudson.api.HudsonMavenModuleBuild;
import org.netbeans.modules.hudson.api.HudsonVersion;
import org.netbeans.modules.hudson.api.HudsonView;
import org.netbeans.modules.hudson.api.ui.OpenableInBrowser;
import org.netbeans.modules.hudson.constants.HudsonInstanceConstants;
import static org.netbeans.modules.hudson.constants.HudsonInstanceConstants.*;
import static org.netbeans.modules.hudson.constants.HudsonJobConstants.*;
import org.netbeans.modules.hudson.spi.BuilderConnector;
import org.netbeans.modules.hudson.spi.RemoteFileSystem;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
/**
* Implementation of the HudsonInstacne
*
* @author Michal Mocnak
*/
public final class HudsonInstanceImpl implements HudsonInstance, OpenableInBrowser {
private static final Logger LOG = Logger.getLogger(HudsonInstanceImpl.class.getName());
private HudsonInstanceProperties properties;
private BuilderConnector builderConnector;
private Persistence persistence;
private HudsonVersion version;
private boolean connected;
private boolean forbidden;
private boolean terminated;
private final RequestProcessor RP;
private final Task synchronization;
private Collection jobs = new ArrayList();
private Collection folders = new ArrayList();
private Collection views = new ArrayList();
private HudsonView primaryView;
private final Collection listeners = new ArrayList();
/**
* Must be kept here, not in {@link HudsonJobImpl}, because that is transient
* and this should persist across refreshes.
*/
private final Map> workspaces = new HashMap>();
private final Map> artifacts = new HashMap>();
private HudsonInstanceImpl(HudsonInstanceProperties properties, boolean interactive, BuilderConnector builderConnector, Persistence persistence) {
this.builderConnector = builderConnector;
this.properties = properties;
this.persistence = persistence != null ? persistence : Persistence.persistent();
RP = new RequestProcessor(getUrl(), 1, true);
final AtomicBoolean firstSynch = new AtomicBoolean(interactive); // #200643
synchronization = RP.create(new Runnable() {
private boolean firstRun = true;
@Override
public void run() {
String s = getProperties().get(INSTANCE_SYNC);
int pause = Integer.parseInt(s) * 60 * 1000;
if (pause > 0 || firstSynch.compareAndSet(true, false)) {
doSynchronize(false, firstRun);
firstRun = false;
}
if (pause > 0) {
synchronization.schedule(pause);
}
}
});
synchronization.schedule(0);
this.properties.addPropertyChangeListener(new PropertyChangeListener() {
@Override public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(INSTANCE_SYNC)) {
synchronization.schedule(0);
}
}
});
}
@Override public boolean isPersisted() {
return properties.isPersisted();
}
public void makePersistent() {
if (isPersisted()) {
return;
}
properties.put(INSTANCE_PERSISTED, TRUE);
fireContentChanges();
}
@Override public Preferences prefs() {
return properties.getPreferences();
}
public static HudsonInstanceImpl createHudsonInstance(String name, String url, BuilderConnector client, int sync) {
HudsonInstanceProperties hudsonInstanceProperties =
new HudsonInstanceProperties(name, url,
Integer.toBinaryString(sync));
HudsonInstanceImpl instance = new HudsonInstanceImpl(
hudsonInstanceProperties, true, client, null);
if (null == HudsonManagerImpl.getDefault().addInstance(instance)) {
return null;
}
return instance;
}
public static HudsonInstanceImpl createHudsonInstance(String name, String url, String sync) {
return createHudsonInstance(new HudsonInstanceProperties(name, url, sync), true, null);
}
public static HudsonInstanceImpl createHudsonInstance(HudsonInstanceProperties properties, boolean interactive) {
return createHudsonInstance(properties, interactive, Persistence.persistent());
}
public static HudsonInstanceImpl createHudsonInstance(HudsonInstanceProperties properties, boolean interactive, Persistence persistence) {
HudsonConnector connector = new HudsonConnector(properties.get(HudsonInstanceConstants.INSTANCE_URL));
HudsonInstanceImpl instance = new HudsonInstanceImpl(properties, interactive, connector, persistence);
assert instance.getName() != null;
assert instance.getUrl() != null;
assert instance.getProperties().get(INSTANCE_SYNC) != null;
if (null == HudsonManagerImpl.getDefault().addInstance(instance)) {
return null;
}
return instance;
}
public void terminate() {
// Clear all
synchronized (this) {
RP.stop();
terminated = true;
connected = false;
forbidden = false;
version = null;
jobs.clear();
folders.clear();
views.clear();
primaryView = null;
}
// Fire changes
fireStateChanges();
fireContentChanges();
}
public BuilderConnector getBuilderConnector() {
return builderConnector;
}
public synchronized void changeBuilderConnector(BuilderConnector connector) {
assert !(connector instanceof HudsonConnector);
this.builderConnector = connector;
this.jobs.clear();
folders.clear();
this.views.clear();
synchronize(false);
}
@Override public HudsonVersion getVersion() {
return version;
}
@Override public boolean isConnected() {
return connected;
}
public boolean isForbidden() {
return forbidden;
}
public HudsonInstanceProperties getProperties() {
return properties;
}
@Override public String getName() {
return getProperties().get(INSTANCE_NAME);
}
@Override public String getUrl() {
String url = getProperties().get(INSTANCE_URL);
assert url.endsWith("/") : url;
return url;
}
@Override public synchronized Collection getJobs() {
return new ArrayList(jobs);
}
@Override public synchronized Collection getFolders() {
return new ArrayList(folders);
}
boolean isSalient(HudsonJobImpl job) {
HudsonInstanceProperties props = getProperties();
if (HudsonInstanceProperties.split(props.get(INSTANCE_SUPPRESSED_JOBS)).contains(job.getName())) {
return false;
}
List preferred = HudsonInstanceProperties.split(props.get(INSTANCE_PREF_JOBS));
if (!preferred.isEmpty()) {
return preferred.contains(job.getName());
}
return true;
}
void setSalient(HudsonJobImpl job, boolean salient) {
HudsonInstanceProperties props = getProperties();
List preferred = new ArrayList(HudsonInstanceProperties.split(props.get(INSTANCE_PREF_JOBS)));
if (salient && !preferred.isEmpty() && !preferred.contains(job.getName())) {
List list = new ArrayList(preferred);
list.add(job.getName());
props.put(INSTANCE_PREF_JOBS, HudsonInstanceProperties.join(list));
}
List suppressed = new ArrayList(HudsonInstanceProperties.split(props.get(INSTANCE_SUPPRESSED_JOBS)));
if (salient) {
suppressed.remove(job.getName());
} else if (!suppressed.contains(job.getName())) {
suppressed.add(job.getName());
}
props.put(INSTANCE_SUPPRESSED_JOBS, HudsonInstanceProperties.join(suppressed));
fireContentChanges();
}
public @Override synchronized Collection getViews() {
return new ArrayList(views);
}
public @Override synchronized HudsonView getPrimaryView() {
if (primaryView == null) {
primaryView = new HudsonViewImpl(this, "All", getUrl()); // NOI18N
}
return primaryView;
}
synchronized void setViews(Collection views, HudsonView primaryView) {
this.views = views;
this.primaryView = primaryView;
}
/**
* Initiate synchronization: fetching refreshed job data from the server.
* Will run asynchronously.
* @param authentication to prompt for login if the anonymous user cannot even see the job list; set to true for explicit user gesture, false otherwise
*/
@Override
public void synchronize(final boolean authentication) {
if (terminated) {
return;
}
RP.post(new Runnable() {
@Override public void run() {
doSynchronize(authentication, true);
}
});
}
@Messages({"# {0} - server label", "MSG_Synchronizing=Synchronizing {0}"})
private void doSynchronize(final boolean authentication,
final boolean showProgress) {
final AtomicReference synchThread = new AtomicReference();
final AtomicReference handle = new AtomicReference();
ProgressHandle handleObject = ProgressHandleFactory.createHandle(
Bundle.MSG_Synchronizing(getName()),
new Cancellable() {
@Override
public boolean cancel() {
Thread t = synchThread.get();
if (t != null) {
LOG.log(Level.FINE,
"Cancelling synchronization of {0}",//NOI18N
getUrl());
if (!isPersisted()) {
properties.put(INSTANCE_SYNC, "0"); //NOI18N
}
t.interrupt();
handle.get().finish();
return true;
} else {
return false;
}
}
});
handleObject.setInitialDelay(showProgress ? 100 : 30000);
handle.set(handleObject);
handle.get().start();
if (authentication) {
ConnectionBuilder.clearRejectedAuthentication();
}
synchThread.set(Thread.currentThread());
try {
// Get actual views
Collection oldViews = getViews();
// Retrieve jobs
BuilderConnector.InstanceData instanceData =
getBuilderConnector().getInstanceData(
authentication);
configureViews(instanceData.getViewsData());
Collection retrieved = createJobs(
instanceData.getJobsData());
Collection retrievedFolders = createFolders(instanceData.getFoldersData());
// Exit when instance is terminated
if (terminated) {
return;
}
// Set connected and version
connected = getBuilderConnector().isConnected();
version = getBuilderConnector().getHudsonVersion(authentication);
forbidden = getBuilderConnector().isForbidden();
// Update state
fireStateChanges();
synchronized (workspaces) {
Iterator>> it = workspaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry> entry = it.next();
RemoteFileSystem fs = entry.getValue().get();
if (fs != null) {
fs.refreshAll();
} else {
it.remove();
}
}
}
synchronized (this) {
// When there are no changes return and do not fire changes
if (jobs.equals(retrieved)
&& folders.equals(retrievedFolders)
&& oldViews.equals(views)) {
return;
}
// Update jobs
jobs = retrieved;
folders = retrievedFolders;
}
// Fire all changes
fireContentChanges();
} finally {
handle.get().finish();
}
}
@Override public void addHudsonChangeListener(HudsonChangeListener l) {
if (l != null) {
synchronized (listeners) {
listeners.add(l);
}
}
}
@Override public void removeHudsonChangeListener(HudsonChangeListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
private void fireStateChanges() {
ArrayList tempList;
synchronized (listeners) {
tempList = new ArrayList(listeners);
}
for (HudsonChangeListener l : tempList) {
l.stateChanged();
}
}
private void fireContentChanges() {
ArrayList tempList;
synchronized (listeners) {
tempList = new ArrayList(listeners);
}
for (HudsonChangeListener l : tempList) {
l.contentChanged();
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof HudsonInstance && getUrl().equals(((HudsonInstance) obj).getUrl());
}
@Override
public int hashCode() {
return getUrl().hashCode();
}
public @Override String toString() {
return getUrl();
}
@Override public int compareTo(HudsonInstance o) {
return getName().compareTo(o.getName());
}
/* access from HudsonJobImpl */ FileSystem getRemoteWorkspace(final HudsonJob job) {
return getFileSystemFromCache(workspaces, job.getName(), new Callable() {
@Override public RemoteFileSystem call() throws Exception {
return builderConnector.getWorkspace(job);
}
});
}
/* access from HudsonJobBuildImpl */ FileSystem getArtifacts(final HudsonJobBuild build) {
return getFileSystemFromCache(artifacts, build.getJob().getName() + "/" + build.getNumber(), new Callable() { // NOI18N
@Override public RemoteFileSystem call() throws Exception {
return builderConnector.getArtifacts(build);
}
});
}
/* access from HudsonJobBuildImpl */ FileSystem getArtifacts(final HudsonMavenModuleBuild module) {
return getFileSystemFromCache(artifacts, module.getBuild().getJob().getName() + "/" + // NOI18N
module.getBuild().getNumber() + "/" + module.getName(), // NOI18N
new Callable() {
@Override public RemoteFileSystem call() throws Exception {
return builderConnector.getArtifacts(module);
}
});
}
private static FileSystem getFileSystemFromCache(Map> cache, String key, Callable create) {
synchronized (cache) {
RemoteFileSystem fs = cache.containsKey(key) ? cache.get(key).get() : null;
if (fs == null) {
try {
fs = create.call();
if (fs == null) {
return null;
}
cache.put(key, new WeakReference(fs));
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
return FileUtil.createMemoryFileSystem();
}
}
return fs;
}
}
public Collection createJobs(
Collection data) {
Collection jobList = new ArrayList();
for (BuilderConnector.JobData jd : data) {
HudsonJobImpl job = new HudsonJobImpl(this);
if (jd.isSecured()) {
job.putProperty(JOB_COLOR, HudsonJob.Color.secured);
}
job.putProperty(JOB_NAME, jd.getJobName());
job.putProperty(JOB_URL, jd.getJobUrl());
if (jd.getColor() != null) { // may be null, see #230406
job.putProperty(JOB_COLOR, jd.getColor());
}
job.putProperty(JOB_DISPLAY_NAME, jd.getDisplayName() == null
? jd.getJobName() : jd.getDisplayName());
job.putProperty(JOB_BUILDABLE, jd.isBuildable());
job.putProperty(JOB_IN_QUEUE, jd.isInQueue());
job.putProperty(JOB_LAST_BUILD, jd.getLastBuild());
job.putProperty(JOB_LAST_FAILED_BUILD, jd.getLastFailedBuild());
job.putProperty(JOB_LAST_STABLE_BUILD, jd.getLastStableBuild());
job.putProperty(JOB_LAST_SUCCESSFUL_BUILD, jd.getLastSuccessfulBuild());
job.putProperty(JOB_LAST_COMPLETED_BUILD, jd.getLastCompletedBuild());
for (BuilderConnector.ModuleData md : jd.getModules()) {
job.addModule(md.getName(), md.getDisplayName(), md.getColor(), md.getUrl());
}
for (HudsonView v : this.getViews()) {
/* https://github.com/hudson/hudson/commit/105f2b09cf1376f9fe4dbf80c5bdb7a0d30ba1c1#commitcomment-447142 */
if (jd.isSecured() || jd.getViews().contains(v.getName())) {
job.addView(v);
}
}
jobList.add(job);
}
return jobList;
}
public Collection createFolders(Collection foldersData) {
Collection result = new ArrayList();
for (BuilderConnector.FolderData datum : foldersData) {
result.add(new HudsonFolderImpl(this, datum.getName(), datum.getUrl()));
}
return result;
}
private void configureViews(Collection viewsData) {
Collection viewList = new ArrayList();
HudsonView foundPrimaryView = null;
for (BuilderConnector.ViewData viewData: viewsData) {
HudsonViewImpl view = new HudsonViewImpl(this, viewData.getName(),
viewData.getUrl());
viewList.add(view);
if (viewData.isPrimary()) {
foundPrimaryView = view;
}
}
this.setViews(viewList, foundPrimaryView);
}
public Persistence getPersistence() {
return persistence;
}
@Override
public List getPreferredJobs() {
String preferred = properties.get(INSTANCE_PREF_JOBS);
if (preferred == null) {
return null;
} else {
return HudsonInstanceProperties.split(preferred);
}
}
@Override
public void setPreferredJobs(List preferredJobs) {
if (preferredJobs == null) {
properties.put(INSTANCE_PREF_JOBS, null);
} else {
properties.put(INSTANCE_PREF_JOBS,
HudsonInstanceProperties.join(preferredJobs));
}
}
@Override
public int getSyncInterval() {
return Integer.parseInt(getProperties().get(
HudsonInstanceConstants.INSTANCE_SYNC));
}
@Override
public void setSyncInterval(int syncInterval) {
getProperties().put(HudsonInstanceConstants.INSTANCE_SYNC,
Integer.toString(syncInterval));
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
getProperties().addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
getProperties().removePropertyChangeListener(listener);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy