提交 b3f88eff 编写于 作者: J Jesse Glick

Merge branch 'master' into track-plugins-15003

......@@ -68,6 +68,8 @@ Upcoming changes</a>
Command line options to control the HTTP request handling thread behavior weren't working.
<li class=rfe>
Default max # of concurrent HTTP request handling threads were brought down to a sane number (from 1000(!) to 20)
<li class=rfe>
Display non-default update site URLs in the Advanced tab of Plugin Manager. (Currently not configurable from this UI.)
</ul>
</div><!--=TRUNK-END=-->
......
......@@ -27,6 +27,7 @@ package hudson;
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
import hudson.model.AbstractDescribableImpl;
import jenkins.model.Jenkins;
import hudson.model.TaskListener;
import hudson.model.AbstractProject;
......@@ -828,7 +829,16 @@ public final class FilePath implements Serializable {
if(channel!=null) {
// run this on a remote system
try {
return channel.call(new FileCallableWrapper<T>(callable,cl));
DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable, cl);
Jenkins instance = Jenkins.getInstance();
if (instance != null) { // this happens during unit tests
ExtensionList<FileCallableWrapperFactory> factories = instance.getExtensionList(FileCallableWrapperFactory.class);
for (FileCallableWrapperFactory factory : factories) {
wrapper = factory.wrap(wrapper);
}
}
return channel.call(wrapper);
} catch (TunneledInterruptedException e) {
throw (InterruptedException)new InterruptedException().initCause(e);
} catch (AbortException e) {
......@@ -843,6 +853,60 @@ public final class FilePath implements Serializable {
}
}
/**
* This extension point allows to contribute a wrapper around a fileCallable so that a plugin can "intercept" a
* call.
* <p>The {@link #wrap(hudson.remoting.DelegatingCallable)} method itself will be executed on master
* (and may collect contextual data if needed) and the returned wrapper will be executed on remote.
*
* @since 1.482
* @see AbstractInterceptorCallableWrapper
*/
public static abstract class FileCallableWrapperFactory implements ExtensionPoint {
public abstract <T> DelegatingCallable<T,IOException> wrap(DelegatingCallable<T,IOException> callable);
}
/**
* Abstract {@link DelegatingCallable} that exposes an Before/After pattern for
* {@link hudson.FilePath.FileCallableWrapperFactory} that want to implement AOP-style interceptors
* @since 1.482
*/
public abstract class AbstractInterceptorCallableWrapper<T> implements DelegatingCallable<T, IOException> {
private final DelegatingCallable<T, IOException> callable;
public AbstractInterceptorCallableWrapper(DelegatingCallable<T, IOException> callable) {
this.callable = callable;
}
@Override
public final ClassLoader getClassLoader() {
return callable.getClassLoader();
}
public final T call() throws IOException {
before();
try {
return callable.call();
} finally {
after();
}
}
/**
* Executed before the actual FileCallable is invoked. This code will run on remote
*/
protected void before() {}
/**
* Executed after the actual FileCallable is invoked (even if this one failed). This code will run on remote
*/
protected void after() {}
}
/**
* Executes some program on the machine that this {@link FilePath} exists,
* so that one can perform local file operations.
......
......@@ -667,10 +667,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
UpdateCenter uc = hudson.getUpdateCenter();
PersistedList<UpdateSite> sites = uc.getSites();
for (UpdateSite s : sites) {
if (s.getId().equals("default"))
if (s.getId().equals(UpdateCenter.ID_DEFAULT))
sites.remove(s);
}
sites.add(new UpdateSite("default",site));
sites.add(new UpdateSite(UpdateCenter.ID_DEFAULT, site));
return HttpResponses.redirectToContextRoot();
}
......
......@@ -111,6 +111,12 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
public class UpdateCenter extends AbstractModelObject implements Saveable, OnMaster {
private static final String UPDATE_CENTER_URL = System.getProperty(UpdateCenter.class.getName()+".updateCenterUrl","http://updates.jenkins-ci.org/");
/**
* {@link #getId} of the default update site.
* @since 1.483
*/
public static final String ID_DEFAULT = "default";
/**
* {@link ExecutorService} that performs installation.
......@@ -243,11 +249,11 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
return sites.toList();
}
/**
* Alias for {@link #getById}.
*/
public UpdateSite getSite(String id) {
for (UpdateSite site : sites)
if (site.getId().equals(id))
return site;
return null;
return getById(id);
}
/**
......@@ -479,7 +485,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
* Loads the data from the disk into this object.
*/
public synchronized void load() throws IOException {
UpdateSite defaultSite = new UpdateSite("default", config.getUpdateCenterUrl() + "update-center.json");
UpdateSite defaultSite = new UpdateSite(ID_DEFAULT, config.getUpdateCenterUrl() + "update-center.json");
XmlFile file = getConfigFile();
if(file.exists()) {
try {
......
......@@ -48,6 +48,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
......@@ -378,7 +379,7 @@ public class UpdateSite {
* Is this the legacy default update center site?
*/
public boolean isLegacyDefault() {
return id.equals("default") && url.startsWith("http://hudson-ci.org/") || url.startsWith("http://updates.hudson-labs.org/");
return id.equals(UpdateCenter.ID_DEFAULT) && url.startsWith("http://hudson-ci.org/") || url.startsWith("http://updates.hudson-labs.org/");
}
/**
......@@ -407,8 +408,8 @@ public class UpdateSite {
Data(JSONObject o) {
this.sourceId = (String)o.get("id");
if (sourceId.equals("default")) {
core = new Entry(sourceId, o.getJSONObject("core"));
if (sourceId.equals(UpdateCenter.ID_DEFAULT)) {
core = new Entry(sourceId, o.getJSONObject("core"), url);
}
else {
core = null;
......@@ -460,10 +461,21 @@ public class UpdateSite {
public final String url;
public Entry(String sourceId, JSONObject o) {
this(sourceId, o, null);
}
Entry(String sourceId, JSONObject o, String baseURL) {
this.sourceId = sourceId;
this.name = o.getString("name");
this.version = o.getString("version");
this.url = o.getString("url");
String url = o.getString("url");
if (!URI.create(url).isAbsolute()) {
if (baseURL == null) {
throw new IllegalArgumentException("Cannot resolve " + url + " without a base URL");
}
url = URI.create(baseURL).resolve(url).toString();
}
this.url = url;
}
/**
......@@ -535,7 +547,7 @@ public class UpdateSite {
@DataBoundConstructor
public Plugin(String sourceId, JSONObject o) {
super(sourceId, o);
super(sourceId, o, UpdateSite.this.url);
this.wiki = get(o,"wiki");
this.title = get(o,"title");
this.excerpt = get(o,"excerpt");
......
......@@ -126,6 +126,7 @@ public class UsageStatistics extends PageDecorator {
JSONObject o = new JSONObject();
o.put("stat",1);
o.put("install", Util.getDigestOf(h.getSecretKey()));
o.put("servletContainer",h.servletContext.getServerInfo());
o.put("version", Jenkins.VERSION);
List<JSONObject> nodes = new ArrayList<JSONObject>();
......
......@@ -69,12 +69,28 @@ THE SOFTWARE.
<h1>${%Update Site}</h1>
<f:form method="post" action="siteConfigure">
<f:entry title="${%URL}" >
<f:textbox name="site" value="${app.updateCenter.getSite('default').url}" />
<f:textbox name="site" value="${app.updateCenter.getSite(app.updateCenter.ID_DEFAULT).url}" />
</f:entry>
<f:block>
<f:submit value="${%Submit}" />
</f:block>
</f:form>
<j:set var="hasNonDefault" value="${false}"/>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.id != app.updateCenter.ID_DEFAULT}">
<j:set var="hasNonDefault" value="${true}"/>
</j:if>
</j:forEach>
<j:if test="${hasNonDefault}">
<h2>${%Other Sites}</h2>
<ul>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.id != app.updateCenter.ID_DEFAULT}">
<li>${site.url}</li>
</j:if>
</j:forEach>
</ul>
</j:if>
</l:hasPermission>
</td>
</tr>
......
......@@ -25,21 +25,22 @@ package hudson.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import junit.framework.TestCase;
import hudson.XmlFile;
import hudson.matrix.MatrixRun;
import hudson.model.Result;
import hudson.model.Run;
import org.jvnet.hudson.test.Bug;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.CauseOfInterruption;
import jenkins.model.InterruptedBuildAction;
import junit.framework.TestCase;
import org.apache.commons.io.FileUtils;
import org.jvnet.hudson.test.Bug;
/**
* Tests for XML serialization of java objects.
......@@ -276,4 +277,38 @@ public class XStream2Test extends TestCase {
(CauseOfInterruption.UserInterruption) action.getCauses().get(0);
assertNotNull(cause);
}
public static class Foo2 {
ConcurrentHashMap<String,String> m = new ConcurrentHashMap<String,String>();
}
/**
* Tests that ConcurrentHashMap is serialized into a more compact format,
* but still can deserialize to older, verbose format.
*/
public void testConcurrentHashMapSerialization() throws Exception {
Foo2 foo = new Foo2();
foo.m.put("abc","def");
foo.m.put("ghi","jkl");
File v = File.createTempFile("hashmap", "xml");
try {
new XmlFile(v).write(foo);
// should serialize like map
String xml = FileUtils.readFileToString(v);
assertFalse(xml.contains("java.util.concurrent"));
//System.out.println(xml);
Foo2 deserialized = (Foo2) new XStream2().fromXML(xml);
assertEquals(2,deserialized.m.size());
assertEquals("def", deserialized.m.get("abc"));
assertEquals("jkl", deserialized.m.get("ghi"));
} finally {
v.delete();
}
// should be able to read in old data just fine
Foo2 map = (Foo2) new XStream2().fromXML(getClass().getResourceAsStream("old-concurrentHashMap.xml"));
assertEquals(1,map.m.size());
assertEquals("def",map.m.get("abc"));
}
}
package jenkins;
import net.sf.json.JSONObject;
import org.junit.Test;
import org.jvnet.hudson.test.Bug;
/**
* @author Kohsuke Kawaguchi
*/
public class ResilientJsonObjectTest {
public static class Foo { public int a; }
/**
* {@link JSONObject} databinding should be able to ignore non-existent fields.
*/
@Test
@Bug(15105)
public void databindingShouldIgnoreUnrecognizedJsonProperty() {
JSONObject o = JSONObject.fromObject("{a:1,b:2}");
Foo f = (Foo)JSONObject.toBean(o,Foo.class);
assert f.a == 1;
}
}
<hudson.util.XStreamTest_-Foo>
<hudson.util.XStream2Test_-Foo2>
<m serialization="custom">
<unserializable-parents/>
<java.util.concurrent.ConcurrentHashMap>
......@@ -222,4 +222,4 @@
<null/>
</java.util.concurrent.ConcurrentHashMap>
</m>
</hudson.util.XStreamTest_-Foo>
\ No newline at end of file
</hudson.util.XStream2Test_-Foo2>
\ No newline at end of file
......@@ -209,7 +209,7 @@ public class RedeployPublisher extends Recorder {
String privateRepository = null;
FilePath remoteSettingsFromConfig = null;
File tmpSettings = File.createTempFile( "jenkins", "temp-settings.xml" );
File tmpSettings = null;
try {
AbstractProject project = build.getProject();
......@@ -269,29 +269,25 @@ public class RedeployPublisher extends Recorder {
// assume that build was made on master
buildNode = Jenkins.getInstance();
}
m = mavenModuleSet.getMaven().forNode(buildNode, listener);
if (StringUtils.isBlank( altSettingsPath ) ) {
// get userHome from the node where job has been executed
String remoteUserHome = build.getWorkspace().act( new GetUserHome() );
altSettingsPath = remoteUserHome + "/.m2/settings.xml";
}
// we copy this file in the master in a temporary file
FilePath filePath = new FilePath( tmpSettings );
// we copy this file in the master in a temporary file
FilePath remoteSettings = build.getWorkspace().child( altSettingsPath );
if (!remoteSettings.exists()) {
// JENKINS-9084 we finally use $M2_HOME/conf/settings.xml as maven do
String mavenHome =
((MavenModuleSet) project).getMaven().forNode(buildNode, listener ).getHome();
String settingsPath = mavenHome + "/conf/settings.xml";
remoteSettings = build.getWorkspace().child( settingsPath);
if (remoteSettings != null) {
listener.getLogger().println( "Maven RedeployPublisher use " + (buildNode != null ? buildNode.getNodeName() : "local" )
+ " maven settings from : " + remoteSettings.getRemote() );
tmpSettings = File.createTempFile( "jenkins", "temp-settings.xml" );
FilePath filePath = new FilePath( tmpSettings );
remoteSettings.copyTo( filePath );
settingsLoc = tmpSettings;
}
listener.getLogger().println( "Maven RedeployPublisher use remote " + (buildNode != null ? buildNode.getNodeName() : "local" )
+ " maven settings from : " + remoteSettings.getRemote() );
remoteSettings.copyTo( filePath );
settingsLoc = tmpSettings;
}
MavenEmbedderRequest mavenEmbedderRequest = new MavenEmbedderRequest(listener,
......
......@@ -235,6 +235,7 @@ THE SOFTWARE.
<includes>
<include>**/UseRecipesWithJenkinsRuleTest.java</include>
<include>hudson/model/CauseTest.java</include>
<include>hudson/model/UpdateSiteTest.java</include>
<include>hudson/triggers/TriggerStartTest.java</include>
</includes>
</configuration>
......@@ -280,6 +281,7 @@ THE SOFTWARE.
<excludes>
<exclude>org/jvnet/hudson/main/UseRecipesWithJenkinsRuleTest.class</exclude>
<exclude>hudson/model/CauseTest.class</exclude>
<exclude>hudson/model/UpdateSiteTest.class</exclude>
<exclude>hudson/triggers/TriggerStartTest.class</exclude>
</excludes>
</configuration>
......
{
"core": {
"buildDate": "Dec 31, 1969",
"name": "core",
"url": "jenkins.war",
"version": "23"
},
"id": "default",
"plugins": {
"tasks": {
"buildDate": "Dec 17, 2008",
"dependencies": [],
"developers": [{"name": "lokadm"}],
"excerpt": " This plug-in scans for open tasks in a specified set of files in the project modules and visualizes the results. ",
"name": "tasks",
"requiredCore": "1.264",
"sha1": "wtzlciUKiMcg90H5CTYkGX6+r8Y=",
"title": "Hudson Task Scanner Plug-in",
"url": "tasks.hpi",
"version": "2.23"
},
"dummy": {
"buildDate": "Dec 17, 2008",
"dependencies": [],
"developers": [],
"excerpt": "…",
"name": "dummy",
"requiredCore": "1.100",
"sha1": "wtzlcjUKiMcg90H5CTYkGX6+r8Y=",
"title": "Dummy",
"url": "http://nowhere.net/dummy.hpi",
"version": "1.0"
}
},
"updateCenterVersion": 1
}
/*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
* Copyright 2012 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
......@@ -21,53 +21,47 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;
import hudson.XmlFile;
import junit.framework.TestCase;
import org.apache.commons.io.FileUtils;
package hudson.model;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebRequestSettings;
import hudson.model.UpdateSite.Data;
import hudson.util.PersistedList;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import org.apache.commons.io.IOUtils;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
/**
* @author Kohsuke Kawaguchi
*/
public class XStreamTest extends TestCase {
private XStream2 xstream = new XStream2();
public static class Foo {
ConcurrentHashMap<String,String> m = new ConcurrentHashMap<String,String>();
}
public class UpdateSiteTest {
/**
* Tests that ConcurrentHashMap is serialized into a more compact format,
* but still can deserialize to older, verbose format.
*/
public void testConcurrentHashMapSerialization() throws Exception {
Foo foo = new Foo();
foo.m.put("abc","def");
foo.m.put("ghi","jkl");
File v = File.createTempFile("hashmap", "xml");
try {
new XmlFile(v).write(foo);
@Rule public JenkinsRule j = new JenkinsRule();
// should serialize like map
String xml = FileUtils.readFileToString(v);
assertFalse(xml.contains("java.util.concurrent"));
//System.out.println(xml);
Foo deserialized = (Foo)xstream.fromXML(xml);
assertEquals(2,deserialized.m.size());
assertEquals("def", deserialized.m.get("abc"));
assertEquals("jkl", deserialized.m.get("ghi"));
} finally {
v.delete();
@Test public void relativeURLs() throws Exception {
PersistedList<UpdateSite> sites = j.jenkins.getUpdateCenter().getSites();
sites.clear();
URL url = UpdateSiteTest.class.getResource("/plugins/tasks-update-center.json");
UpdateSite site = new UpdateSite(UpdateCenter.ID_DEFAULT, url.toString());
sites.add(site);
UpdateSite.signatureCheck = false;
{ // XXX pending UpdateSite.updateDirectly:
j.jenkins.setCrumbIssuer(null);
WebRequestSettings wrs = new WebRequestSettings(new URL(j.getURL(), "/updateCenter/byId/default/postBack"), HttpMethod.POST);
wrs.setRequestBody(IOUtils.toString(url.openStream()));
Page p = j.createWebClient().getPage(wrs);
assertEquals(/* FormValidation.OK */"<div/>", p.getWebResponse().getContentAsString());
}
// should be able to read in old data just fine
Foo map = (Foo)new XStream2().fromXML(getClass().getResourceAsStream("old-concurrentHashMap.xml"));
assertEquals(1,map.m.size());
assertEquals("def",map.m.get("abc"));
Data data = site.getData();
assertNotNull(data);
assertEquals(new URL(url, "jenkins.war").toString(), data.core.url);
assertEquals(new HashSet<String>(Arrays.asList("tasks", "dummy")), data.plugins.keySet());
assertEquals(new URL(url, "tasks.hpi").toString(), data.plugins.get("tasks").url);
assertEquals("http://nowhere.net/dummy.hpi", data.plugins.get("dummy").url);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册