提交 720be7d7 编写于 作者: K kohsuke

Merged revisions 23183,23185,23245,23256,23266,23279-23284,23286 via svnmerge from

https://www.dev.java.net/svn/hudson/branches/multiple-update-sources

........
  r23183 | abayer | 2009-10-25 11:53:17 -0700 (Sun, 25 Oct 2009) | 1 line
  
  First chunk of work towards supporting multiple update centers - infrastructure in place for it, and default update center treated as only one of multiple - have to use slightly modified update-center.json though
........
  r23185 | abayer | 2009-10-25 12:25:49 -0700 (Sun, 25 Oct 2009) | 1 line
  
  a number of tweaks - among other things, now works properly with additional update centers defined in hudson.model.UpdateCenter.xml - try using http://andrewbayer.com/images/hello-world/helloWorld-update-center.json as second update center
........
  r23245 | abayer | 2009-10-27 13:45:25 -0700 (Tue, 27 Oct 2009) | 1 line
  
  Modified to support existing default update-center.json
........
  r23256 | kohsuke | 2009-10-27 18:30:15 -0700 (Tue, 27 Oct 2009) | 7 lines
  
  - moved data binding of update-center.json to UpdateSource.
  - moved some of the UpdateSource property into JSON, to improve the user experience when adding an update source.
    the user should just type in one URL, and everything else should happen automatically.
  - it doesn't make sense for UpdateSource to rely on UpdateCenterConfiguration, since behaviors cannot be
    modified per UpdateSource basis. Instead, leaving it in UpdateCenter allows us to maintain backward compatible
    behaviors with the existing custom UpdateCenterConfiguration implementation.
........
  r23266 | kohsuke | 2009-10-27 19:29:24 -0700 (Tue, 27 Oct 2009) | 1 line
  
  serve id inside JSON.
........
  r23279 | kohsuke | 2009-10-28 10:44:00 -0700 (Wed, 28 Oct 2009) | 1 line
  
  Do not special-case "default" ID. Receive hudson.war updates from wherever that provides one.
........
  r23280 | kohsuke | 2009-10-28 10:50:57 -0700 (Wed, 28 Oct 2009) | 1 line
  
  formatting change.
........
  r23281 | kohsuke | 2009-10-28 11:30:40 -0700 (Wed, 28 Oct 2009) | 1 line
  
  typo
........
  r23282 | kohsuke | 2009-10-28 11:45:06 -0700 (Wed, 28 Oct 2009) | 2 lines
  
  - added the UI to remove sites.
  - persistence of UpdateSource happens more automatically now.
........
  r23283 | kohsuke | 2009-10-28 11:50:04 -0700 (Wed, 28 Oct 2009) | 1 line
  
  UpdateSource -> UpdateSite to align terminology with Eclipse.
........
  r23284 | kohsuke | 2009-10-28 11:55:03 -0700 (Wed, 28 Oct 2009) | 5 lines
  
  Actually, for now, just having a programmatic modification to UpdateSite would satisfy the primary use case.
  
  And I'd like to upgrade YUI to a more recent version so that we can use its DataTable for UI. Plus the single update center model encourages the community to bring the code into the Hudson project, which is something we'd like to keep.
  
  So all in all, I'm postponing the UI work.
........
  r23286 | kohsuke | 2009-10-28 13:07:19 -0700 (Wed, 28 Oct 2009) | 1 line
  
  Renaming and bug fixes.
........


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@23326 71c3de6d-444a-0410-be80-ed276b4c234a
上级 938cb6ba
......@@ -1067,13 +1067,6 @@ public class Functions {
return null;
}
/**
* Gets the URL for the update center server
*/
public String getUpdateCenterUrl() {
return Hudson.getInstance().getUpdateCenter().getUrl();
}
/**
* If the given href link is matching the current page, return true.
*
......
......@@ -26,6 +26,7 @@ package hudson;
import hudson.model.AbstractModelObject;
import hudson.model.Failure;
import hudson.model.Hudson;
import hudson.model.UpdateSite;
import hudson.model.UpdateCenter;
import hudson.util.Service;
import org.apache.commons.fileupload.FileItem;
......@@ -352,6 +353,25 @@ public final class PluginManager extends AbstractModelObject {
LogFactory.release(uberClassLoader);
}
public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
if (req.hasParameter("remove")) {
UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
BulkChange bc = new BulkChange(uc);
try {
for (String id : req.getParameterValues("sources"))
uc.getSites().remove(uc.getById(id));
} finally {
bc.commit();
}
} else
if (req.hasParameter("add"))
return new HttpRedirect("addSite");
return new HttpRedirect("./sites");
}
/**
* Performs the installation of the plugins.
*/
......@@ -361,10 +381,13 @@ public final class PluginManager extends AbstractModelObject {
String n = en.nextElement();
if(n.startsWith("plugin.")) {
n = n.substring(7);
UpdateCenter.Plugin p = Hudson.getInstance().getUpdateCenter().getPlugin(n);
if(p==null)
throw new Failure("No such plugin: "+n);
p.deploy();
if (n.indexOf(".") > 0) {
String[] pluginInfo = n.split("\\.");
UpdateSite.Plugin p = Hudson.getInstance().getUpdateCenter().getById(pluginInfo[1]).getPlugin(pluginInfo[0]);
if(p==null)
throw new Failure("No such plugin: "+n);
p.deploy();
}
}
}
rsp.sendRedirect("../updateCenter/");
......@@ -376,9 +399,9 @@ public final class PluginManager extends AbstractModelObject {
@QueryParameter("proxy.userName") String userName,
@QueryParameter("proxy.password") String password,
StaplerResponse rsp) throws IOException {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
Hudson hudson = Hudson.getInstance();
hudson.checkPermission(Hudson.ADMINISTER);
server = Util.fixEmptyAndTrim(server);
if(server==null) {
hudson.proxy = null;
......
......@@ -25,6 +25,7 @@ package hudson;
import hudson.model.Hudson;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import java.io.File;
import java.io.FileOutputStream;
......@@ -252,7 +253,7 @@ public final class PluginWrapper {
if(url!=null) return url;
// fallback to update center metadata
UpdateCenter.Plugin ui = getInfo();
UpdateSite.Plugin ui = getInfo();
if(ui!=null) return ui.wiki;
return null;
......@@ -360,23 +361,23 @@ public final class PluginWrapper {
/**
* If the plugin has {@link #getUpdateInfo() an update},
* returns the {@link UpdateCenter.Plugin} object.
* returns the {@link UpdateSite.Plugin} object.
*
* @return
* This method may return null — for example,
* the user may have installed a plugin locally developed.
*/
public UpdateCenter.Plugin getUpdateInfo() {
public UpdateSite.Plugin getUpdateInfo() {
UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
UpdateCenter.Plugin p = uc.getPlugin(getShortName());
UpdateSite.Plugin p = uc.getPlugin(getShortName());
if(p!=null && p.isNewerThan(getVersion())) return p;
return null;
}
/**
* returns the {@link UpdateCenter.Plugin} object, or null.
* returns the {@link UpdateSite.Plugin} object, or null.
*/
public UpdateCenter.Plugin getInfo() {
public UpdateSite.Plugin getInfo() {
UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
return uc.getPlugin(getShortName());
}
......
......@@ -140,7 +140,7 @@ public final class XmlFile {
*
* @return
* The unmarshalled object. Usually the same as <tt>o</tt>, but would be different
* if the XML representation if completely new.
* if the XML representation is completely new.
*/
public Object unmarshal( Object o ) throws IOException {
Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
......
......@@ -152,7 +152,7 @@ public class DownloadService extends PageDecorator {
* URL to download.
*/
public String getUrl() {
return Hudson.getInstance().getUpdateCenter().getUrl()+"updates/"+url;
return Hudson.getInstance().getUpdateCenter().getDefaultBaseUrl()+"updates/"+url;
}
/**
......
......@@ -490,7 +490,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
private transient final String secretKey;
private transient final UpdateCenter updateCenter = new UpdateCenter(this);
private transient final UpdateCenter updateCenter = new UpdateCenter();
/**
* True if the user opted out from the statistics tracking. We'll never send anything if this is true.
......@@ -522,7 +522,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
Trigger.timer = new Timer("Hudson cron thread");
queue = new Queue(CONSISTENT_HASH?LoadBalancer.CONSISTENT_HASH:LoadBalancer.DEFAULT);
try {
dependencyGraph = DependencyGraph.EMPTY;
} catch (InternalError e) {
......@@ -549,7 +549,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
} catch (IOException e) {
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
}
// load plugins.
pluginManager = new PluginManager(context);
pluginManager.initialize();
......@@ -620,6 +620,8 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"),Messages.Hudson_USER_CONTENT_README());
}
updateCenter.load(); // this has to wait until after all plugins load, to let custom UpdateCenterConfiguration take effect first.
Trigger.init();
// pending SEZPOZ-8
// // invoke post initialization methods
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc., Seiji Sogabe,
* Andrew Bayer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.PluginWrapper;
import hudson.PluginManager;
import hudson.model.UpdateCenter.UpdateCenterJob;
import hudson.lifecycle.Lifecycle;
import hudson.util.TextFile;
import hudson.util.VersionNumber;
import static hudson.util.TimeUnit2.DAYS;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerResponse;
import org.jvnet.hudson.crypto.CertificateUtil;
import org.jvnet.hudson.crypto.SignatureOutputStream;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.io.output.TeeOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.DigestOutputStream;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.TrustAnchor;
import com.trilead.ssh2.crypto.Base64;
import javax.servlet.ServletContext;
/**
* Source of the update center information, like "http://hudson-ci.org/update-center.json"
*
* <p>
* Hudson can have multiple {@link UpdateSite}s registered in the system, so that it can pick up plugins
* from different locations.
*
* @author Andrew Bayer
* @author Kohsuke Kawaguchi
* @since 1.MULTIUPDATE
*/
public class UpdateSite {
/**
* What's the time stamp of data file?
*/
private transient long dataTimestamp = -1;
/**
* When was the last time we asked a browser to check the data for us?
*
* <p>
* There's normally some delay between when we send HTML that includes the check code,
* until we get the data back, so this variable is used to avoid asking too many browseres
* all at once.
*/
private transient volatile long lastAttempt = -1;
/**
* ID string for this update source.
*/
private final String id;
/**
* Path to <tt>update-center.json</tt>, like <tt>http://hudson-ci.org/update-center.json</tt>.
*/
private final String url;
public UpdateSite(String id, String url) {
this.id = id;
this.url = url;
}
/**
* When read back from XML, initialize them back to -1.
*/
private Object readResolve() {
dataTimestamp = lastAttempt = -1;
return this;
}
/**
* Get ID string.
*/
public String getId() {
return id;
}
public long getDataTimestamp() {
return dataTimestamp;
}
/**
* This is the endpoint that receives the update center data file from the browser.
*/
public void doPostBack(@QueryParameter String json) throws IOException, GeneralSecurityException {
dataTimestamp = System.currentTimeMillis();
JSONObject o = JSONObject.fromObject(json);
int v = o.getInt("updateCenterVersion");
if(v !=1) {
LOGGER.warning("Unrecognized update center version: "+v);
return;
}
if (signatureCheck)
verifySignature(o);
LOGGER.info("Obtained the latest update center data file for UpdateSource "+ id);
getDataFile().write(json);
}
/**
* Verifies the signature in the update center data file.
*/
private boolean verifySignature(JSONObject o) throws GeneralSecurityException, IOException {
JSONObject signature = o.getJSONObject("signature");
if (signature.isNullObject()) {
LOGGER.severe("No signature block found");
return false;
}
o.remove("signature");
List<X509Certificate> certs = new ArrayList<X509Certificate>();
{// load and verify certificates
CertificateFactory cf = CertificateFactory.getInstance("X509");
for (Object cert : o.getJSONArray("certificates")) {
X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray())));
c.checkValidity();
certs.add(c);
}
// all default root CAs in JVM are trusted, plus certs bundled in Hudson
Set<TrustAnchor> anchors = CertificateUtil.getDefaultRootCAs();
ServletContext context = Hudson.getInstance().servletContext;
for (String cert : (Set<String>) context.getResourcePaths("/WEB-INF/update-center-rootCAs")) {
if (cert.endsWith(".txt")) continue; // skip text files that are meant to be documentation
anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(context.getResourceAsStream(cert)),null));
}
CertificateUtil.validatePath(certs);
}
// this is for computing a digest to check sanity
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1);
// this is for computing a signature
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(certs.get(0));
SignatureOutputStream sos = new SignatureOutputStream(sig);
o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8"));
// did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n
// (which is more likely than someone tampering with update center), we can tell
String computedDigest = new String(Base64.encode(sha1.digest()));
String providedDigest = signature.getString("digest");
if (!computedDigest.equalsIgnoreCase(providedDigest)) {
LOGGER.severe("Digest mismatch: "+computedDigest+" vs "+providedDigest);
return false;
}
if (!sig.verify(Base64.decode(signature.getString("signature").toCharArray()))) {
LOGGER.severe("Signature in the update center doesn't match with the certificate");
return false;
}
return true;
}
/**
* Returns true if it's time for us to check for new version.
*/
public boolean isDue() {
if(neverUpdate) return false;
if(dataTimestamp==-1)
dataTimestamp = getDataFile().file.lastModified();
long now = System.currentTimeMillis();
boolean due = now - dataTimestamp > DAY && now - lastAttempt > 15000;
if(due) lastAttempt = now;
return due;
}
/**
* Loads the update center data, if any.
*
* @return null if no data is available.
*/
public Data getData() {
TextFile df = getDataFile();
if(df.exists()) {
try {
return new Data(JSONObject.fromObject(df.read()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE,"Failed to parse "+df,e);
df.delete(); // if we keep this file, it will cause repeated failures
return null;
}
} else {
return null;
}
}
/**
* Returns a list of plugins that should be shown in the "available" tab.
* These are "all plugins - installed plugins".
*/
public List<Plugin> getAvailables() {
List<Plugin> r = new ArrayList<Plugin>();
Data data = getData();
if(data ==null) return Collections.emptyList();
for (Plugin p : data.plugins.values()) {
if(p.getInstalled()==null)
r.add(p);
}
return r;
}
/**
* Gets the information about a specific plugin.
*
* @param artifactId
* The short name of the plugin. Corresponds to {@link PluginWrapper#getShortName()}.
*
* @return
* null if no such information is found.
*/
public Plugin getPlugin(String artifactId) {
Data dt = getData();
if(dt==null) return null;
return dt.plugins.get(artifactId);
}
/**
* Returns an "always up" server for Internet connectivity testing, or null if we are going to skip the test.
*/
public String getConnectionCheckUrl() {
Data dt = getData();
if(dt==null) return "http://www.google.com/";
return dt.connectionCheckUrl;
}
/**
* This is where we store the update center data.
*/
private TextFile getDataFile() {
return new TextFile(new File(Hudson.getInstance().getRootDir(),
"updates/" + getId()+".json"));
}
/**
* Returns the list of plugins that are updates to currently installed ones.
*
* @return
* can be empty but never null.
*/
public List<Plugin> getUpdates() {
Data data = getData();
if(data==null) return Collections.emptyList(); // fail to determine
List<Plugin> r = new ArrayList<Plugin>();
for (PluginWrapper pw : Hudson.getInstance().getPluginManager().getPlugins()) {
Plugin p = pw.getUpdateInfo();
if(p!=null) r.add(p);
}
return r;
}
/**
* Does any of the plugin has updates?
*/
public boolean hasUpdates() {
Data data = getData();
if(data==null) return false;
for (PluginWrapper pw : Hudson.getInstance().getPluginManager().getPlugins()) {
if(!pw.isBundled() && pw.getUpdateInfo()!=null)
// do not advertize updates to bundled plugins, since we generally want users to get them
// as a part of hudson.war updates. This also avoids unnecessary pinning of plugins.
return true;
}
return false;
}
/**
* Exposed to get rid of hardcoding of the URL that serves up update-center.json
* in Javascript.
*/
public String getUrl() {
return url;
}
/**
* In-memory representation of the update center data.
*/
public final class Data {
/**
* The {@link UpdateSite} ID.
*/
public final String sourceId;
/**
* The latest hudson.war.
*/
public final Entry core;
/**
* Plugins in the repository, keyed by their artifact IDs.
*/
public final Map<String,Plugin> plugins = new TreeMap<String,Plugin>(String.CASE_INSENSITIVE_ORDER);
/**
* If this is non-null, Hudson is going to check the connectivity to this URL to make sure
* the network connection is up. Null to skip the check.
*/
public final String connectionCheckUrl;
Data(JSONObject o) {
this.sourceId = (String)o.get("id");
if (sourceId.equals("default")) {
core = new Entry(sourceId, o.getJSONObject("core"));
}
else {
core = null;
}
for(Map.Entry<String,JSONObject> e : (Set<Map.Entry<String,JSONObject>>)o.getJSONObject("plugins").entrySet()) {
plugins.put(e.getKey(),new Plugin(sourceId, e.getValue()));
}
connectionCheckUrl = (String)o.get("connectionCheckUrl");
}
/**
* Is there a new version of the core?
*/
public boolean hasCoreUpdates() {
return core != null && core.isNewerThan(Hudson.VERSION);
}
/**
* Do we support upgrade?
*/
public boolean canUpgrade() {
return Lifecycle.get().canRewriteHudsonWar();
}
}
public static class Entry {
/**
* {@link UpdateSite} ID.
*/
public final String sourceId;
/**
* Artifact ID.
*/
public final String name;
/**
* The version.
*/
public final String version;
/**
* Download URL.
*/
public final String url;
public Entry(String sourceId, JSONObject o) {
this.sourceId = sourceId;
this.name = o.getString("name");
this.version = o.getString("version");
this.url = o.getString("url");
}
/**
* Checks if the specified "current version" is older than the version of this entry.
*
* @param currentVersion
* The string that represents the version number to be compared.
* @return
* true if the version listed in this entry is newer.
* false otherwise, including the situation where the strings couldn't be parsed as version numbers.
*/
public boolean isNewerThan(String currentVersion) {
return isNewerThan(currentVersion, version);
}
/**
* Compares two versions - returns true if the first version is newer than the second.
*
* @param firstVersion
* The first version to test against.
* @param secondVersion
* The second version to test against.
* @return
* True if the first version is newer than the second version. False in all other cases.
*/
private static boolean isNewerThan(String firstVersion, String secondVersion) {
try {
return new VersionNumber(firstVersion).compareTo(new VersionNumber(secondVersion)) < 0;
} catch (IllegalArgumentException e) {
// couldn't parse as the version number.
return false;
}
}
}
public final class Plugin extends Entry {
/**
* Optional URL to the Wiki page that discusses this plugin.
*/
public final String wiki;
/**
* Human readable title of the plugin, taken from Wiki page.
* Can be null.
*
* <p>
* beware of XSS vulnerability since this data comes from Wiki
*/
public final String title;
/**
* Optional excerpt string.
*/
public final String excerpt;
/**
* Optional version # from which this plugin release is configuration-compatible.
*/
public final String compatibleSinceVersion;
@DataBoundConstructor
public Plugin(String sourceId, JSONObject o) {
super(sourceId, o);
this.wiki = get(o,"wiki");
this.title = get(o,"title");
this.excerpt = get(o,"excerpt");
this.compatibleSinceVersion = get(o,"compatibleSinceVersion");
}
private String get(JSONObject o, String prop) {
if(o.has(prop))
return o.getString(prop);
else
return null;
}
public String getDisplayName() {
if(title!=null) return title;
return name;
}
/**
* If some version of this plugin is currently installed, return {@link PluginWrapper}.
* Otherwise null.
*/
public PluginWrapper getInstalled() {
PluginManager pm = Hudson.getInstance().getPluginManager();
return pm.getPlugin(name);
}
/**
* If the plugin is already installed, and the new version of the plugin has a "compatibleSinceVersion"
* value (i.e., it's only directly compatible with that version or later), this will check to
* see if the installed version is older than the compatible-since version. If it is older, it'll return false.
* If it's not older, or it's not installed, or it's installed but there's no compatibleSinceVersion
* specified, it'll return true.
*/
public boolean isCompatibleWithInstalledVersion() {
PluginWrapper installedVersion = getInstalled();
if (installedVersion != null) {
if (compatibleSinceVersion != null) {
if (new VersionNumber(installedVersion.getVersion())
.isOlderThan(new VersionNumber(compatibleSinceVersion))) {
return false;
}
}
}
return true;
}
/**
* @deprecated as of 1.326
* Use {@link #deploy()}.
*/
public void install() {
deploy();
}
/**
* Schedules the installation of this plugin.
*
* <p>
* This is mainly intended to be called from the UI. The actual installation work happens
* asynchronously in another thread.
*/
public Future<UpdateCenterJob> deploy() {
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
return uc.addJob(uc.new InstallationJob(this, UpdateSite.this, Hudson.getAuthentication()));
}
/**
* Making the installation web bound.
*/
public void doInstall(StaplerResponse rsp) throws IOException {
deploy();
rsp.sendRedirect2("../..");
}
}
private static final long DAY = DAYS.toMillis(1);
private static final Logger LOGGER = Logger.getLogger(UpdateSite.class.getName());
public static boolean neverUpdate = Boolean.getBoolean(UpdateCenter.class.getName()+".never");
/**
* Off by default until we know this is reasonably working.
*/
public static boolean signatureCheck = Boolean.getBoolean(UpdateCenter.class.getName()+".signatureCheck");
}
......@@ -61,10 +61,7 @@ import java.util.Map;
*
* @author Kohsuke Kawaguchi
*/
public class DescribableList<T extends Describable<T>, D extends Descriptor<T>> implements Iterable<T> {
private final CopyOnWriteList<T> data = new CopyOnWriteList<T>();
private Saveable owner;
public class DescribableList<T extends Describable<T>, D extends Descriptor<T>> extends PersistedList<T> {
protected DescribableList() {
}
......@@ -88,20 +85,6 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
this.owner = owner;
}
public void setOwner(Saveable owner) {
this.owner = owner;
}
public void add(T item) throws IOException {
data.add(item);
onModified();
}
public void addAll(Collection<? extends T> items) throws IOException {
data.addAll(items);
onModified();
}
/**
* Removes all instances of the same type, then add the new one.
*/
......@@ -111,11 +94,6 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
onModified();
}
public void replaceBy(Collection<? extends T> col) throws IOException {
data.replaceBy(col);
onModified();
}
public T get(D descriptor) {
for (T t : data)
if(t.getDescriptor()==descriptor)
......@@ -123,57 +101,10 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
return null;
}
public <U extends T> U get(Class<U> type) {
for (T t : data)
if(type.isInstance(t))
return type.cast(t);
return null;
}
/**
* Gets all instances that matches the given type.
*/
public <U extends T> List<U> getAll(Class<U> type) {
List<U> r = new ArrayList<U>();
for (T t : data)
if(type.isInstance(t))
r.add(type.cast(t));
return r;
}
public boolean contains(D d) {
return get(d)!=null;
}
public int size() {
return data.size();
}
/**
* Removes an instance by its type.
*/
public void remove(Class<? extends T> type) throws IOException {
for (T t : data) {
if(t.getClass()==type) {
data.remove(t);
onModified();
return;
}
}
}
public void removeAll(Class<? extends T> type) throws IOException {
boolean modified=false;
for (T t : data) {
if(t.getClass()==type) {
data.remove(t);
modified=true;
}
}
if(modified)
onModified();
}
public void remove(D descriptor) throws IOException {
for (T t : data) {
if(t.getDescriptor()==descriptor) {
......@@ -184,44 +115,11 @@ public class DescribableList<T extends Describable<T>, D extends Descriptor<T>>
}
}
public void clear() {
data.clear();
}
public Iterator<T> iterator() {
return data.iterator();
}
/**
* Called when a list is mutated.
*/
protected void onModified() throws IOException {
owner.save();
}
@SuppressWarnings("unchecked")
public Map<D,T> toMap() {
return (Map)Descriptor.toMap(data);
}
/**
* Returns the snapshot view of instances as list.
*/
public List<T> toList() {
return data.getView();
}
/**
* Gets all the {@link Describable}s in an array.
*/
public T[] toArray(T[] array) {
return data.toArray(array);
}
public void addAllTo(Collection<? super T> dst) {
data.addAllTo(dst);
}
/**
* Rebuilds the list by creating a fresh instances from the submitted form.
*
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
import hudson.model.Describable;
import hudson.model.Saveable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* Collection whose change is notified to the parent object for persistence.
*
* @author Kohsuke Kawaguchi
* @since 1.MULTISOURCE
*/
public class PersistedList<T> implements Iterable<T> {
protected final CopyOnWriteList<T> data = new CopyOnWriteList<T>();
protected Saveable owner;
protected PersistedList() {
}
public PersistedList(Saveable owner) {
setOwner(owner);
}
public void setOwner(Saveable owner) {
this.owner = owner;
}
public void add(T item) throws IOException {
data.add(item);
onModified();
}
public void addAll(Collection<? extends T> items) throws IOException {
data.addAll(items);
onModified();
}
public void replaceBy(Collection<? extends T> col) throws IOException {
data.replaceBy(col);
onModified();
}
public <U extends T> U get(Class<U> type) {
for (T t : data)
if(type.isInstance(t))
return type.cast(t);
return null;
}
/**
* Gets all instances that matches the given type.
*/
public <U extends T> List<U> getAll(Class<U> type) {
List<U> r = new ArrayList<U>();
for (T t : data)
if(type.isInstance(t))
r.add(type.cast(t));
return r;
}
public int size() {
return data.size();
}
/**
* Removes an instance by its type.
*/
public void remove(Class<? extends T> type) throws IOException {
for (T t : data) {
if(t.getClass()==type) {
data.remove(t);
onModified();
return;
}
}
}
public boolean remove(T o) throws IOException {
boolean b = data.remove(o);
if (b) onModified();
return b;
}
public void removeAll(Class<? extends T> type) throws IOException {
boolean modified=false;
for (T t : data) {
if(t.getClass()==type) {
data.remove(t);
modified=true;
}
}
if(modified)
onModified();
}
public void clear() {
data.clear();
}
public Iterator<T> iterator() {
return data.iterator();
}
/**
* Called when a list is mutated.
*/
protected void onModified() throws IOException {
owner.save();
}
/**
* Returns the snapshot view of instances as list.
*/
public List<T> toList() {
return data.getView();
}
/**
* Gets all the {@link Describable}s in an array.
*/
public T[] toArray(T[] array) {
return data.toArray(array);
}
public void addAllTo(Collection<? super T> dst) {
data.addAllTo(dst);
}
public boolean isEmpty() {
return data.isEmpty();
}
/**
* {@link Converter} implementation for XStream.
*
* Serializaion form is compatible with plain {@link List}.
*/
public static class ConverterImpl extends AbstractCollectionConverter {
CopyOnWriteList.ConverterImpl copyOnWriteListConverter;
public ConverterImpl(Mapper mapper) {
super(mapper);
copyOnWriteListConverter = new CopyOnWriteList.ConverterImpl(mapper());
}
public boolean canConvert(Class type) {
// handle subtypes in case the onModified method is overridden.
return PersistedList.class.isAssignableFrom(type);
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
for (Object o : (PersistedList) source)
writeItem(o, context, writer);
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
CopyOnWriteList core = copyOnWriteListConverter.unmarshal(reader, context);
try {
PersistedList r = (PersistedList)context.getRequiredType().newInstance();
r.data.replaceBy(core);
return r;
} catch (InstantiationException e) {
InstantiationError x = new InstantiationError();
x.initCause(e);
throw x;
} catch (IllegalAccessException e) {
IllegalAccessError x = new IllegalAccessError();
x.initCause(e);
throw x;
}
}
}
}
......@@ -38,7 +38,7 @@ THE SOFTWARE.
</tr>
</table>
<script>
updateCenter.completionHandler = function() {
downloadService.completionHandler = function() {
$$('completionMarker').innerHTML = "${%Done}";
}
</script>
......
<!--
The MIT License
Copyright (c) 2004-2009, Sun Microsystems, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!--
Add/remove update center sites
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout title="Update Center" permission="${app.ADMINISTER}" norefresh="true">
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<form method="post" action="updateSources">
<local:tabBar page="sites" xmlns:local="/hudson/PluginManager">
<tr style="border-top: 0px;">
<th colspan="2"/>
</tr>
<tr style="border-bottom:none">
<td colspan="2">
<p>
This Hudson is configured to receive updates from the following sourcse:
</p>
</td>
</tr>
<tr style="border-top: none">
<td>
<select multiple="true" style="width:100%" name="sources">
<j:forEach var="s" items="${app.updateCenter.sources}">
<option value="${s.id}">${s.url}</option>
</j:forEach>
</select>
</td>
<td id="buttonBar" style="width:13em;">
<style>
#buttonBar INPUT {
width: 10em;
}
</style>
<input type="submit" value="${%Add...}" name="add" />
<br />
<input type="submit" value="${%Remove}" name="remove" disabled="${empty(app.updateCenter.sources)?'true':null}"/>
</td>
</tr>
</local:tabBar>
</form>
</l:main-panel>
</l:layout>
</j:jelly>
......@@ -30,6 +30,7 @@ THE SOFTWARE.
<l:tab name="${%Updates}" active="${page=='updates'}" href="." />
<l:tab name="${%Available}" active="${page=='available'}" href="./available" />
<l:tab name="${%Installed}" active="${page=='installed'}" href="./installed" />
<!--<l:tab name="${%Sites}" active="${page=='sites'}" href="./sites" />-->
<l:tab name="${%Advanced}" active="${page=='advanced'}" href="./advanced" />
</l:tabBar>
<table id="plugins" class="sortable pane bigtable" style="margin-top:0px; border-top: none;">
......
......@@ -45,7 +45,7 @@ THE SOFTWARE.
<j:when test="${!empty(list)}">
<j:forEach var="p" items="${list}">
<tr>
<td class="pane" align="center"><input type="checkbox" name="plugin.${p.name}"/></td>
<td class="pane" align="center"><input type="checkbox" name="plugin.${p.name}.${p.sourceId}"/></td>
<td class="pane">
<div>
<a href="${p.wiki}"><st:out value="${p.displayName}"/></a>
......@@ -54,9 +54,9 @@ THE SOFTWARE.
<div class="excerpt">${p.excerpt}</div>
</j:if>
<j:if test="${!p.isCompatibleWithInstalledVersion()}">
<div class="compatWarning">${%compatWarning}</div>
</j:if>
</td>
<div class="compatWarning">${%compatWarning}</div>
</j:if>
</td>
<td class="pane"><st:out value="${p.version}" /></td>
<j:if test="${attrs.page=='updates'}">
<td>
......
......@@ -30,12 +30,18 @@ THE SOFTWARE.
This file is pulled into the layout.jelly
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:if test="${app.updateCenter.due or forcedUpdateCheck}">
<script>
updateCenter.postBackURL = "${rootURL}/updateCenter/postBack";
updateCenter.info = { version:"${h.version}" };
updateCenter.url = "${h.updateCenterUrl}";
Behaviour.addLoadEvent(updateCenter.checkUpdates);
</script>
</j:if>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.due or forcedUpdateCheck}">
<script>
Behaviour.addLoadEvent(function() {
downloadService.download(
"${site.id}",
"${site.url}",
{version:"${h.version}"},
"${rootURL}/updateCenter/byId/${site.id}/postBack",
null);
});
</script>
</j:if>
</j:forEach>
</j:jelly>
......@@ -49,8 +49,8 @@ public class UpdateCenterTest extends TestCase {
String jsonp = IOUtils.toString(url.openStream());
String json = jsonp.substring(jsonp.indexOf('(')+1,jsonp.lastIndexOf(')'));
UpdateCenter uc = new UpdateCenter(null);
UpdateCenter.Data data = uc.new Data(JSONObject.fromObject(json));
UpdateSite us = new UpdateSite("default", url.toExternalForm());
UpdateSite.Data data = us.new Data(JSONObject.fromObject(json));
assertTrue(data.core.url.startsWith("https://hudson.dev.java.net/"));
assertTrue(data.plugins.containsKey("rake"));
System.out.println(data.core.url);
......
......@@ -53,10 +53,10 @@ import hudson.model.Run;
import hudson.model.Saveable;
import hudson.model.TaskListener;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import hudson.model.AbstractProject;
import hudson.model.View;
import hudson.model.RootAction;
import hudson.model.UpdateCenter.UpdateCenterConfiguration;
import hudson.model.Node.Mode;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.CommandLauncher;
......@@ -238,11 +238,10 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
// load updates from local proxy to avoid network traffic.
final String updateCenterUrl = "http://localhost:"+JavaNetReverseProxy.getInstance().localPort+"/";
hudson.getUpdateCenter().configure(new UpdateCenterConfiguration() {
@Override public String getUpdateCenterUrl() {
return updateCenterUrl;
}
});
List<UpdateSite> newSites = new ArrayList<UpdateSite>();
newSites.add(new UpdateSite("default", updateCenterUrl));
hudson.getUpdateCenter().replaceSources(newSites);
// don't waste bandwidth talking to the update center
DownloadService.neverUpdate = true;
UpdateCenter.neverUpdate = true;
......
......@@ -1552,6 +1552,11 @@ var downloadService = {
},
post : function(id,data) {
if (data==undefined) {
// default to id in data
data = id;
id = data.id;
}
var o = this.continuations[id];
new Ajax.Request(o.postBack, {
parameters:{json:Object.toJSON(data)},
......@@ -1563,28 +1568,8 @@ var downloadService = {
}
};
// update center service. for historical reasons,
// this is separate from downloadSerivce
var updateCenter = {
postBackURL : null,
info: {},
completionHandler: null,
url: "http://hudson-ci.org/",
checkUpdates : function() {
loadScript(updateCenter.url+"update-center.json?"+Hash.toQueryString(updateCenter.info));
},
post : function(data) {
new Ajax.Request(updateCenter.postBackURL, {
parameters:{json:Object.toJSON(data)},
onSuccess: function() {
if(updateCenter.completionHandler!=null)
updateCenter.completionHandler();
}
});
}
};
// update center service. to remain compatible with earlier version of Hudson, aliased.
var updateCenter = downloadService;
/*
redirects to a page once the page is ready.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册