diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d9916aad3663165c4736240353e19734c3a034f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target +work +*.iml +*.iws +*.ipr diff --git a/cli/pom.xml b/cli/pom.xml index 85d1edd5e3e6a4f5f53958bb8049915dd4b4fc34..d34806892a960bb0c1085cad9dd9c8f75bc6e855 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -4,7 +4,7 @@ pom org.jvnet.hudson.main - 1.365-SNAPSHOT + 1.378-SNAPSHOT cli Hudson CLI diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java index d74c9c5d62b4ad1404564f9f83de267b46491fc6..fa62fa552067df9c6cca85bed98ae29ba5de250b 100644 --- a/cli/src/main/java/hudson/cli/CLI.java +++ b/cli/src/main/java/hudson/cli/CLI.java @@ -110,7 +110,11 @@ public class CLI { */ private int getCliTcpPort(String url) throws IOException { URLConnection head = new URL(url).openConnection(); - head.connect(); + try { + head.connect(); + } catch (IOException e) { + throw (IOException)new IOException("Failed to connect to "+url).initCause(e); + } String p = head.getHeaderField("X-Hudson-CLI-Port"); if(p==null) return -1; return Integer.parseInt(p); diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java index 92787ce09f68ad41302af1563b2a4563d3a3f75c..369448ec53fd9e7ce72d415596b8c43d4cc1524f 100644 --- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java +++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java @@ -11,6 +11,8 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import sun.misc.BASE64Encoder; + /** * Creates a capacity-unlimited bi-directional {@link InputStream}/{@link OutputStream} pair over * HTTP, which is a request/response protocol. @@ -34,6 +36,11 @@ public class FullDuplexHttpStream { public FullDuplexHttpStream(URL target) throws IOException { this.target = target; + String authorization = null; + if (target.getUserInfo() != null) { + authorization = new BASE64Encoder().encode(target.getUserInfo().getBytes()); + } + CrumbData crumbData = new CrumbData(); UUID uuid = UUID.randomUUID(); // so that the server can correlate those two connections @@ -44,6 +51,9 @@ public class FullDuplexHttpStream { con.setRequestMethod("POST"); con.addRequestProperty("Session", uuid.toString()); con.addRequestProperty("Side","download"); + if (authorization != null) { + con.addRequestProperty("Authorization", "Basic " + authorization); + } if(crumbData.isValid) { con.addRequestProperty(crumbData.crumbName, crumbData.crumb); } @@ -61,6 +71,10 @@ public class FullDuplexHttpStream { con.setRequestProperty("Content-type","application/octet-stream"); con.addRequestProperty("Session", uuid.toString()); con.addRequestProperty("Side","upload"); + if (authorization != null) { + con.addRequestProperty ("Authorization", "Basic " + authorization); + } + if(crumbData.isValid) { con.addRequestProperty(crumbData.crumbName, crumbData.crumb); } diff --git a/cli/src/main/resources/hudson/cli/Messages_da.properties b/cli/src/main/resources/hudson/cli/Messages_da.properties new file mode 100644 index 0000000000000000000000000000000000000000..264c9c146a1c8aa78b987fb6463d3d808fa5374d --- /dev/null +++ b/cli/src/main/resources/hudson/cli/Messages_da.properties @@ -0,0 +1,28 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc. Kohsuke Kawaguchi. Knud Poulsen. +# +# 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. + +CLI.VersionMismatch=Versionskonflikt. CLI''en fungerer ikke med denne Hudson server +CLI.Usage=Hudson CLI\n\ +Brug: java -jar hudson-cli.jar [-s URL] command [opts...] args...\n\ +Tilvalg:\n\ +De tilg\u00e6ngelige kommandoer afh\u00e6nger af serveren. K\u00f8r 'help' kommandoen for at se listen. +CLI.NoURL=Hverken -s eller HUDSON_URL milj\u00f8variablen er defineret diff --git a/cli/src/main/resources/hudson/cli/Messages_pt_BR.properties b/cli/src/main/resources/hudson/cli/Messages_pt_BR.properties new file mode 100644 index 0000000000000000000000000000000000000000..7152ef0f18a6c112cf6bcaf174183c748f704cd0 --- /dev/null +++ b/cli/src/main/resources/hudson/cli/Messages_pt_BR.properties @@ -0,0 +1,41 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Reginaldo L. Russinholi +# +# 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. + +# Version mismatch. This CLI cannot work with this Hudson server +CLI.VersionMismatch=A versão não coincide. Esta CLI não pode funcionar com este servidor Hudson +# Hudson CLI\n\ +# Usage: java -jar hudson-cli.jar [-s URL] command [opts...] args...\n\ +# Options:\n\ +# \ -s URL : specify the server URL (defaults to the HUDSON_URL env var)\n\ +# \n\ +# The available commands depend on the server. Run the 'help' command to\n\ +# see the list. +CLI.Usage=Hudson CLI\n\ + Uso: java -jar hudson-cli.jar [-s URL] comando [opções...] parâmetros...\n\ + Opções:\n\ + \ -s URL : a URL do servidor (por padrão a variável de ambiente HUDSON_URL é usada)\n\ + \n\ + Os comandos disponíveis dependem do servidor. Execute o comando 'help' para\n\ + ver a lista. + +# Neither -s nor the HUDSON_URL env var is specified. +CLI.NoURL=Não foi especificado nem '-s' e nem a variável de ambiente HUDSON_URL diff --git a/core/pom.xml b/core/pom.xml index 10651aa0bbca97efa25ff8b41be93f89bacf193e..5be6a866ce525856acb7b0bd7c00cafbc2e81250 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jvnet.hudson.main pom - 1.365-SNAPSHOT + 1.378-SNAPSHOT ../pom.xml @@ -38,6 +38,10 @@ THE SOFTWARE. Contains the core Hudson code and view files to render HTML. + + + true + @@ -54,10 +58,21 @@ THE SOFTWARE. + + com.infradna.tool + bridge-method-injector + 1.3 + + + + process + + + + org.kohsuke.stapler maven-stapler-plugin - 1.15 true @@ -67,7 +82,7 @@ THE SOFTWARE. - true + ${staplerFork} 128m @@ -100,18 +115,29 @@ THE SOFTWARE. - org.apache.maven.plugins - maven-antlr-plugin - 2.0-beta-1 - - ${basedir}/src/main/grammar - crontab.g - + org.codehaus.mojo + antlr-maven-plugin + 2.1 + cron + + generate + + + ${basedir}/src/main/grammar + crontab.g + + + + labelExpr generate + + ${basedir}/src/main/grammar + labelExpr.g + @@ -124,17 +150,17 @@ THE SOFTWARE. - + - + - + @@ -185,7 +211,7 @@ THE SOFTWARE. org.jvnet.hudson.tools extension-point-lister - 1.5 + 1.7 com.sun @@ -213,7 +239,7 @@ THE SOFTWARE. release - ${version} + ${project.version} @@ -356,7 +382,7 @@ THE SOFTWARE. org.kohsuke.stapler stapler-jelly - 1.144 + 1.150 commons-jelly @@ -371,7 +397,7 @@ THE SOFTWARE. org.kohsuke.stapler stapler-adjunct-timeline - 1.1 + 1.2 org.kohsuke.stapler @@ -381,6 +407,12 @@ THE SOFTWARE. test + + com.infradna.tool + bridge-method-annotation + 1.2 + + org.kohsuke.stapler json-lib @@ -429,7 +461,7 @@ THE SOFTWARE. org.apache.ant ant - 1.8.1 + 1.8.0 javax.servlet @@ -647,7 +679,7 @@ THE SOFTWARE. org.jvnet.hudson memory-monitor - 1.2 + 1.3 com.octo.captcha @@ -796,6 +828,7 @@ THE SOFTWARE. org.kohsuke.stapler maven-stapler-plugin + 1.15 /lib/.* @@ -804,6 +837,7 @@ THE SOFTWARE. maven-project-info-reports-plugin + 2.2 false diff --git a/core/src/main/grammar/labelExpr.g b/core/src/main/grammar/labelExpr.g new file mode 100644 index 0000000000000000000000000000000000000000..bbfa77784e843b7bfb7f82b1121b3f05ac5a0e57 --- /dev/null +++ b/core/src/main/grammar/labelExpr.g @@ -0,0 +1,115 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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. + */ +header { + package hudson.model.labels; + import hudson.model.Label; +} + +class LabelExpressionParser extends Parser; +options { + defaultErrorHandler=false; +} + +// order of precedence is as per http://en.wikipedia.org/wiki/Logical_connective#Order_of_precedence + +expr +returns [Label l] + : l=term1 EOF + ; + +term1 +returns [Label l] +{ Label r; } + : l=term2( IFF r=term2 {l=l.iff(r);} )? + ; + +term2 +returns [Label l] +{ Label r; } + : l=term3( IMPLIES r=term3 {l=l.implies(r);} )? + ; + +term3 +returns [Label l] +{ Label r; } + : l=term4 ( OR r=term4 {l=l.or(r);} )? + ; + +term4 +returns [Label l] +{ Label r; } + : l=term5 ( AND r=term5 {l=l.and(r);} )? + ; + +term5 +returns [Label l] +{ Label x; } + : l=term6 + | NOT x=term6 + { l=x.not(); } + ; + +term6 +returns [Label l] +options { generateAmbigWarnings=false; } + : LPAREN l=term1 RPAREN + { l=l.paren(); } + | a:ATOM + { l=LabelAtom.get(a.getText()); } + | s:STRINGLITERAL + { l=LabelAtom.get(hudson.util.QuotedStringTokenizer.unquote(s.getText())); } + ; + +class LabelExpressionLexer extends Lexer; + +AND: "&&"; +OR: "||"; +NOT: "!"; +IMPLIES:"->"; +IFF: "<->"; +LPAREN: "("; +RPAREN: ")"; + +protected +IDENTIFIER_PART + : ~( '&' | '|' | '!' | '<' | '>' | '(' | ')' | ' ' | '\t' | '\"' | '\'' ) + ; + +ATOM +/* the real check of valid identifier happens in LabelAtom.get() */ + : (IDENTIFIER_PART)+ + ; + +WS + : (' '|'\t')+ + { $setType(Token.SKIP); } + ; + +STRINGLITERAL + : '"' + ( '\\' ( 'b' | 't' | 'n' | 'f' | 'r' | '\"' | '\'' | '\\' ) /* escape */ + | ~( '\\' | '"' | '\r' | '\n' ) + )* + '"' + ; diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 7772ca4487b79c38d677cf236f55009646403ce7..57d7cff079fde195fd694a02e56b2a8dcbcae828 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -39,9 +39,12 @@ import java.io.Closeable; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; -import java.util.List; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; import java.util.jar.Manifest; import java.util.jar.Attributes; import java.util.logging.Logger; @@ -167,13 +170,28 @@ public class ClassicPluginStrategy implements PluginStrategy { ClassLoader dependencyLoader = new DependencyClassLoader(getBaseClassLoader(atts), archive, Util.join(dependencies,optionalDependencies)); return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL, - createClassLoader(paths, dependencyLoader), disableFile, dependencies, optionalDependencies); + createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies); + } + + @Deprecated + protected ClassLoader createClassLoader(List paths, ClassLoader parent) throws IOException { + return createClassLoader( paths, parent, null ); } /** * Creates the classloader that can load all the specified jar files and delegate to the given parent. */ - protected ClassLoader createClassLoader(List paths, ClassLoader parent) throws IOException { + protected ClassLoader createClassLoader(List paths, ClassLoader parent, Attributes atts) throws IOException { + if (atts != null) { + String usePluginFirstClassLoader = atts.getValue( "PluginFirstClassLoader" ); + if (Boolean.valueOf( usePluginFirstClassLoader )) { + PluginFirstClassLoader classLoader = new PluginFirstClassLoader(); + classLoader.setParentFirst( false ); + classLoader.setParent( parent ); + classLoader.addPathFiles( paths ); + return classLoader; + } + } if(useAntClassLoader) { // using AntClassLoader with Closeable so that we can predictably release jar files opened by URLClassLoader AntClassLoader2 classLoader = new AntClassLoader2(parent); @@ -380,7 +398,20 @@ public class ClassicPluginStrategy implements PluginStrategy { throw new ClassNotFoundException(name); } - // TODO: delegate resources? watch out for diamond dependencies + @Override + protected Enumeration findResources(String name) throws IOException { + HashSet result = new HashSet(); + for (Dependency dep : dependencies) { + PluginWrapper p = pluginManager.getPlugin(dep.shortName); + if (p!=null) { + Enumeration urls = p.classLoader.getResources(name); + while (urls != null && urls.hasMoreElements()) + result.add(urls.nextElement()); + } + } + + return Collections.enumeration(result); + } @Override protected URL findResource(String name) { diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java index 7641b1a5e0411e13346ce9522802be8b5f35da0a..535a58a814d0d026286e66f319674ca76ce01687 100644 --- a/core/src/main/java/hudson/DescriptorExtensionList.java +++ b/core/src/main/java/hudson/DescriptorExtensionList.java @@ -134,7 +134,20 @@ public class DescriptorExtensionList, D extends Descrip return d; return null; } - + + /** + * {@link #load()} in the descriptor is not a real load activity, so locking against "this" is enough. + */ + @Override + protected Object getLoadLock() { + return this; + } + + @Override + protected void scoutLoad() { + // no-op, since our load() doesn't by itself do any classloading + } + /** * Loading the descriptors in this case means filtering the descriptor from the master {@link ExtensionList}. */ diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java index 6204386c5119e4be0b5f27343f30e9bec741ae81..0070da51e1f12fdb9c6c8b74902b06589c4df3c4 100644 --- a/core/src/main/java/hudson/EnvVars.java +++ b/core/src/main/java/hudson/EnvVars.java @@ -226,7 +226,7 @@ public class EnvVars extends TreeMap { private static EnvVars initMaster() { EnvVars vars = new EnvVars(System.getenv()); vars.platform = Platform.current(); - if(Main.isUnitTest) + if(Main.isUnitTest || Main.isDevelopmentMode) // if unit test is launched with maven debug switch, // we need to prevent forked Maven processes from seeing it, or else // they'll hang diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index cc924da4afc3faaa60f3068fc39285f470a7a9dd..cc73ed2b5a3dd3d3a37eefc47cded2719a438678 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2010, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, Sun Microsystems, Inc., InfraDNA, 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 @@ -92,6 +92,39 @@ public abstract class ExtensionFinder implements ExtensionPoint { return find(type,hudson); } + /** + * Performs class initializations without creating instances. + * + * If two threads try to initialize classes in the opposite order, a dead lock will ensue, + * and we can get into a similar situation with {@link ExtensionFinder}s. + * + *

+ * That is, one thread can try to list extensions, which results in {@link ExtensionFinder} + * loading and initializing classes. This happens inside a context of a lock, so that + * another thread that tries to list the same extensions don't end up creating different + * extension instances. So this activity locks extension list first, then class initialization next. + * + *

+ * In the mean time, another thread can load and initialize a class, and that initialization + * can eventually results in listing up extensions, for example through static initializer. + * Such activitiy locks class initialization first, then locks extension list. + * + *

+ * This inconsistent locking order results in a dead lock, you see. + * + *

+ * So to reduce the likelihood, this method is called in prior to {@link #find(Class, Hudson)} invocation, + * but from outside the lock. The implementation is expected to perform all the class initialization activities + * from here. + * + *

+ * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208 for how to force a class initialization. + * Also see http://kohsuke.org/2010/09/01/deadlock-that-you-cant-avoid/ for how class initialization + * can results in a dead lock. + */ + public void scout(Class extensionType, Hudson hudson) { + } + /** * The default implementation that looks for the {@link Extension} marker. * @@ -132,6 +165,37 @@ public abstract class ExtensionFinder implements ExtensionPoint { return result; } + + @Override + public void scout(Class extensionType, Hudson hudson) { + ClassLoader cl = hudson.getPluginManager().uberClassLoader; + for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + extType = (Class) e; + } else + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + // accroding to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208 + // this appears to be the only way to force a class initialization + Class.forName(extType.getName(),true,extType.getClassLoader()); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to scout "+item.className(), e); + } catch (ClassNotFoundException e) { + LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); + } catch (LinkageError e) { + LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); + } + } + } } private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName()); diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index 5e3d6c76103bc3b6ea3d1b771bbfa6fb87899d2f..208431b164236c5fcb014da935db6362ef7fa8f9 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -209,7 +209,9 @@ public class ExtensionList extends AbstractList { if(Hudson.getInstance().getInitLevel().compareTo(InitMilestone.PLUGINS_PREPARED)<0) return legacyInstances; // can't perform the auto discovery until all plugins are loaded, so just make the legacy instances visible - synchronized (this) { + scoutLoad(); + + synchronized (getLoadLock()) { if(extensions==null) { List> r = load(); r.addAll(legacyInstances); @@ -219,6 +221,31 @@ public class ExtensionList extends AbstractList { } } + /** + * Chooses the object that locks the loading of the extension instances. + */ + protected Object getLoadLock() { + return hudson.lookup.setIfNull(Lock.class,new Lock()); + } + + /** + * Loading an {@link ExtensionList} can result in a nested loading of another {@link ExtensionList}. + * What that means is that we need a single lock that spans across all the {@link ExtensionList}s, + * or else we can end up in a dead lock. + */ + private static final class Lock {} + + /** + * See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does. + */ + protected void scoutLoad() { + if (LOGGER.isLoggable(Level.FINER)) + LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+extensionType, new Throwable()); + for (ExtensionFinder finder : finders()) { + finder.scout(extensionType, hudson); + } + } + /** * Loads all the extensions. */ diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index b60c16a80905fa852b39c6e23e7742f808731ff0..320f9cef23e6656195b8aaad4714e2fcb70036ee 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -2,7 +2,7 @@ * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, - * Yahoo! Inc., Stephen Connolly, Tom Huybrechts, Alan Harder + * Yahoo! Inc., Stephen Connolly, Tom Huybrechts, Alan Harder, Romain Seguy * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,7 @@ import hudson.console.ConsoleAnnotationDescriptor; import hudson.console.ConsoleAnnotatorFactory; import hudson.model.AbstractProject; import hudson.model.Action; +import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Item; @@ -43,7 +44,6 @@ import hudson.model.ParameterDefinition.ParameterDescriptor; import hudson.model.Project; import hudson.model.Run; import hudson.model.TopLevelItem; -import hudson.model.User; import hudson.model.View; import hudson.model.JDK; import hudson.search.SearchableModelObject; @@ -66,6 +66,7 @@ import hudson.util.Area; import hudson.util.Iterators; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; +import hudson.util.Secret; import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyTagException; @@ -680,6 +681,25 @@ public class Functions { return result; } + /** + * Gets all the descriptors sorted by their inheritance tree of {@link Describable} + * so that descriptors of similar types come nearby. + */ + public static Collection getSortedDescriptorsForGlobalConfig() { + Map r = new TreeMap(); + for (Descriptor d : Hudson.getInstance().getExtensionList(Descriptor.class)) { + if (d.getGlobalConfigPage()==null) continue; + r.put(buildSuperclassHierarchy(d.clazz, new StringBuilder()).toString(),d); + } + return r.values(); + } + + private static StringBuilder buildSuperclassHierarchy(Class c, StringBuilder buf) { + Class sc = c.getSuperclass(); + if (sc!=null) buildSuperclassHierarchy(sc,buf).append(':'); + return buf.append(c.getName()); + } + /** * Computes the path to the icon of the given action * from the context path. @@ -1177,23 +1197,17 @@ public class Functions { public static List> getCrumbIssuerDescriptors() { return CrumbIssuer.all(); } - + public static String getCrumb(StaplerRequest req) { - CrumbIssuer issuer = Hudson.getInstance().getCrumbIssuer(); - if (issuer != null) { - return issuer.getCrumb(req); - } - - return ""; + Hudson h = Hudson.getInstance(); + CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null; + return issuer != null ? issuer.getCrumb(req) : ""; } - + public static String getCrumbRequestField() { - CrumbIssuer issuer = Hudson.getInstance().getCrumbIssuer(); - if (issuer != null) { - return issuer.getDescriptor().getCrumbRequestField(); - } - - return ""; + Hudson h = Hudson.getInstance(); + CrumbIssuer issuer = h != null ? h.getCrumbIssuer() : null; + return issuer != null ? issuer.getDescriptor().getCrumbRequestField() : ""; } public static Date getCurrentTime() { @@ -1204,16 +1218,22 @@ public class Functions { * Generate a series of <script> tags to include script.js * from {@link ConsoleAnnotatorFactory}s and {@link ConsoleAnnotationDescriptor}s. */ - public static String generateConsoleAnnotationScript() { + public static String generateConsoleAnnotationScriptAndStylesheet() { String cp = Stapler.getCurrentRequest().getContextPath(); StringBuilder buf = new StringBuilder(); for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) { + String path = cp + "/extensionList/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName(); if (f.hasScript()) - buf.append(""); + buf.append(""); + if (f.hasStylesheet()) + buf.append(""); } for (ConsoleAnnotationDescriptor d : ConsoleAnnotationDescriptor.all()) { + String path = cp+"/descriptor/"+d.clazz.getName(); if (d.hasScript()) - buf.append(""); + buf.append(""); + if (d.hasStylesheet()) + buf.append(""); } return buf.toString(); } @@ -1234,6 +1254,15 @@ public class Functions { } } } + + /** + * Used by <f:password/> so that we send an encrypted value to the client. + */ + public String getPasswordValue(Object o) { + if (o==null) return null; + if (o instanceof Secret) return ((Secret)o).getEncryptedValue(); + return o.toString(); + } private static final Pattern SCHEME = Pattern.compile("[a-z]+://.+"); @@ -1243,4 +1272,20 @@ public class Functions { public static boolean getIsUnitTest() { return Main.isUnitTest; } + + /** + * Returns {@code true} if the {@link Run#ARTIFACTS} permission is enabled, + * {@code false} otherwise. + * + *

When the {@link Run#ARTIFACTS} permission is not turned on using the + * {@code hudson.security.ArtifactsPermission}, this permission must not be + * considered to be set to {@code false} for every user. It must rather be + * like if the permission doesn't exist at all (which means that every user + * has to have an access to the artifacts but the permission can't be + * configured in the security screen). Got it?

+ */ + public static boolean isArtifactsPermissionEnabled() { + return Boolean.getBoolean("hudson.security.ArtifactsPermission"); + } + } diff --git a/core/src/main/java/hudson/Lookup.java b/core/src/main/java/hudson/Lookup.java index 7635d9bdaef53e7986f7d41b09f1229ff14a754b..090f3eb28449490f2ed72c8d675a0cc21e773301 100644 --- a/core/src/main/java/hudson/Lookup.java +++ b/core/src/main/java/hudson/Lookup.java @@ -24,7 +24,6 @@ package hudson; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** @@ -33,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; * @author Kohsuke Kawaguchi */ public class Lookup { - private final Map data = new ConcurrentHashMap(); + private final ConcurrentHashMap data = new ConcurrentHashMap(); public T get(Class type) { return type.cast(data.get(type)); @@ -42,4 +41,17 @@ public class Lookup { public T set(Class type, T instance) { return type.cast(data.put(type,instance)); } + + /** + * Overwrites the value only if the current value is null. + * + * @return + * If the value was null, return the {@code instance} value. + * Otherwise return the current value, which is non-null. + */ + public T setIfNull(Class type, T instance) { + Object o = data.putIfAbsent(type, instance); + if (o!=null) return type.cast(o); + return instance; + } } diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java index 625f4df2ffeb93cecd441159681b5bf6f7a9a3d5..bdc00c48c1bd36be9b07898332d4b49b15b00da0 100644 --- a/core/src/main/java/hudson/Main.java +++ b/core/src/main/java/hudson/Main.java @@ -183,6 +183,11 @@ public class Main { */ public static boolean isUnitTest = false; + /** + * Set to true if we are running inside "mvn hpi:run" or "mvn hudson-dev:run" + */ + public static boolean isDevelopmentMode = Boolean.getBoolean(Main.class.getName()+".development"); + /** * Time out for socket connection to Hudson. */ diff --git a/core/src/main/java/hudson/PluginFirstClassLoader.java b/core/src/main/java/hudson/PluginFirstClassLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..41ca562e6b121cedaa18137a51b0dfaa27a054de --- /dev/null +++ b/core/src/main/java/hudson/PluginFirstClassLoader.java @@ -0,0 +1,105 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Olivier Lamy + * + * 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; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +import org.apache.tools.ant.AntClassLoader; + +/** + * classLoader which use first /WEB-INF/lib/*.jar and /WEB-INF/classes before core classLoader + * you must use the pluginFirstClassLoader true in the maven-hpi-plugin + * @author olamy + * @since 1.371 + */ +public class PluginFirstClassLoader + extends AntClassLoader + implements Closeable +{ + + private List urls = new ArrayList(); + + public void addPathFiles( Collection paths ) + throws IOException + { + for ( File f : paths ) + { + urls.add( f.toURI().toURL() ); + addPathFile( f ); + } + } + + /** + * @return List of jar used by the plugin /WEB-INF/lib/*.jar and classes directory /WEB-INF/classes + */ + public List getURLs() + { + return urls; + } + + public void close() + throws IOException + { + cleanup(); + } + + @Override + protected Enumeration findResources( String arg0, boolean arg1 ) + throws IOException + { + Enumeration enu = super.findResources( arg0, arg1 ); + return enu; + } + + @Override + protected Enumeration findResources( String name ) + throws IOException + { + Enumeration enu = super.findResources( name ); + return enu; + } + + @Override + public URL getResource( String arg0 ) + { + URL url = super.getResource( arg0 ); + return url; + } + + @Override + public InputStream getResourceAsStream( String name ) + { + InputStream is = super.getResourceAsStream( name ); + return is; + } + +} diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 23f19f683fb28352547f774c47d9df15d1bad3cf..c742d5808ee92d371951843b5b0338b09d109f57 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -494,6 +494,7 @@ public abstract class PluginManager extends AbstractModelObject { } rsp.sendRedirect("../updateCenter/"); } + /** * Bare-minimum configuration mechanism to change the update center. diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index b637c3720f9289d40b0e73456bfaddcab19cade1..a1090dea80218c03ab96b4f16ed14ff924cce7a7 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -46,6 +46,9 @@ import org.apache.commons.logging.LogFactory; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import java.util.Enumeration; +import java.util.jar.JarFile; + /** * Represents a Hudson plug-in and associated control information * for Hudson to control {@link Plugin}. @@ -68,7 +71,7 @@ import org.kohsuke.stapler.HttpResponses; * * @author Kohsuke Kawaguchi */ -public final class PluginWrapper implements Comparable { +public class PluginWrapper implements Comparable { /** * {@link PluginManager} to which this belongs to. */ @@ -173,7 +176,7 @@ public final class PluginWrapper implements Comparable { * @param dependencies a list of mandatory dependencies * @param optionalDependencies a list of optional dependencies */ - public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL baseResourceURL, + public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL baseResourceURL, ClassLoader classLoader, File disableFile, List dependencies, List optionalDependencies) { this.parent = parent; @@ -186,13 +189,23 @@ public final class PluginWrapper implements Comparable { this.active = !disableFile.exists(); this.dependencies = dependencies; this.optionalDependencies = optionalDependencies; - } + } /** * Returns the URL of the index page jelly script. */ public URL getIndexPage() { - return classLoader.getResource("index.jelly"); + // In the current impl dependencies are checked first, so the plugin itself + // will add the last entry in the getResources result. + URL idx = null; + try { + Enumeration en = classLoader.getResources("index.jelly"); + while (en.hasMoreElements()) + idx = en.nextElement(); + } catch (IOException ignore) { } + // In case plugin has dependencies but is missing its own index.jelly, + // check that result has this plugin's artifactId in it: + return idx != null && idx.toString().contains(shortName) ? idx : null; } private String computeShortName(Manifest manifest, File archive) { @@ -441,6 +454,10 @@ public final class PluginWrapper implements Comparable { public boolean hasUpdate() { return getUpdateInfo()!=null; } + + public boolean isPinned() { + return pinFile.exists(); + } /** * Sort by short name. @@ -449,6 +466,37 @@ public final class PluginWrapper implements Comparable { return shortName.compareToIgnoreCase(pw.shortName); } + /** + * returns true if backup of previous version of plugin exists + */ + public boolean isDowngradable() { + return getBackupFile().exists(); + } + + /** + * Where is the backup file? + */ + public File getBackupFile() { + return new File(Hudson.getInstance().getRootDir(),"plugins/"+getShortName() + ".bak"); + } + + /** + * returns the version of the backed up plugin, + * or null if there's no back up. + */ + public String getBackupVersion() { + if (getBackupFile().exists()) { + try { + JarFile backupPlugin = new JarFile(getBackupFile()); + return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version"); + } catch (IOException e) { + LOGGER.log(WARNING, "Failed to get backup version ", e); + return null; + } + } else { + return null; + } + } // // // Action methods diff --git a/core/src/main/java/hudson/RelativePath.java b/core/src/main/java/hudson/RelativePath.java new file mode 100644 index 0000000000000000000000000000000000000000..0beadad283df0c7689602b5d6e0b23b6259e9514 --- /dev/null +++ b/core/src/main/java/hudson/RelativePath.java @@ -0,0 +1,28 @@ +package hudson; + +import org.kohsuke.stapler.QueryParameter; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +/** + * Used in conjunction with {@link QueryParameter} to refer to + * nearby parameters that belong to different parents. + * + *

+ * Currently, "..", "../..", etc. are supported to indicate + * parameters that belong to the ancestors. + * + * @author Kohsuke Kawaguchi + * @since 1.376 + */ +@Documented +@Target(PARAMETER) +@Retention(RUNTIME) +public @interface RelativePath { + String value(); +} diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index c6b867029432713c3ce142baf3d858293422a2cc..f526f4c2625e109ee733be949794eb5dd54a3d49 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -249,6 +249,10 @@ public final class TcpSlaveAgentListener extends Thread { } } }); + } catch (AbortException e) { + logw.println(e.getMessage()); + logw.println("Failed to establish the connection with the slave"); + throw e; } catch (IOException e) { logw.println("Failed to establish the connection with the slave"); e.printStackTrace(logw); diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index ac9df5b648039e46e3e63a1d2d1ba2895fc255fb..820d6df597d4fa4d89086d72ecdb543cd0d431c7 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -886,6 +886,10 @@ public class Util { return l!=null ? l : Collections.emptySet(); } + public static Iterable fixNull(Iterable l) { + return l!=null ? l : Collections.emptySet(); + } + /** * Cuts all the leading path portion and get just the file name. */ @@ -1139,6 +1143,13 @@ public class Util { else return new File(p.substring(0,pos)+ext); } + /** + * Null-safe String intern method. + */ + public static String intern(String s) { + return s==null ? s : s.intern(); + } + public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT")); // Note: RFC822 dates must not be localized! diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java index cab42133d4978ac81b86efda41899a905058f1d8..a28229533c512c8a7fa2855d2f4071bf143244cd 100644 --- a/core/src/main/java/hudson/WebAppMain.java +++ b/core/src/main/java/hudson/WebAppMain.java @@ -25,7 +25,6 @@ package hudson; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; import com.thoughtworks.xstream.core.JVM; -import com.sun.jna.Native; import hudson.model.Hudson; import hudson.model.User; import hudson.triggers.SafeTimerTask; @@ -41,7 +40,6 @@ import hudson.util.IncompatibleAntVersionDetected; import hudson.util.HudsonFailedToLoad; import hudson.util.ChartUtil; import hudson.util.AWTProblem; -import hudson.util.JNADoublyLoaded; import org.jvnet.localizer.LocaleProvider; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; diff --git a/core/src/main/java/hudson/cli/BuildCommand.java b/core/src/main/java/hudson/cli/BuildCommand.java index 6d508efbf1a72ba15a20a527343c3403be1806b4..a214dd6b3a32c967d2bf541afde81fddae3f3527 100644 --- a/core/src/main/java/hudson/cli/BuildCommand.java +++ b/core/src/main/java/hudson/cli/BuildCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -32,6 +32,7 @@ import hudson.model.ParametersDefinitionProperty; import hudson.model.ParameterDefinition; import hudson.Extension; import hudson.AbortException; +import hudson.model.Item; import hudson.util.EditDistance; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; @@ -66,8 +67,9 @@ public class BuildCommand extends CLICommand { public Map parameters = new HashMap(); protected int run() throws Exception { - ParametersAction a = null; + job.checkPermission(Item.BUILD); + ParametersAction a = null; if (!parameters.isEmpty()) { ParametersDefinitionProperty pdp = job.getProperty(ParametersDefinitionProperty.class); if (pdp==null) @@ -106,6 +108,7 @@ public class BuildCommand extends CLICommand { ); } + // TODO: CLI can authenticate as different users, so should record which user here.. public static class CLICause extends Cause { public String getShortDescription() { return "Started by command line"; diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java index b24d783b7afa221688ab3015fa6c8a7e80039922..56f44b474d4297b2af37c7c22a681ac126b4fce2 100644 --- a/core/src/main/java/hudson/cli/CLICommand.java +++ b/core/src/main/java/hudson/cli/CLICommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -29,6 +29,7 @@ import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.cli.declarative.CLIMethod; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; +import hudson.cli.declarative.OptionHandlerExtension; import hudson.model.Hudson; import hudson.remoting.Callable; import hudson.remoting.Channel; @@ -38,14 +39,18 @@ import hudson.security.SecurityRealm; import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; +import org.jvnet.hudson.annotation_indexer.Index; +import org.jvnet.tiger_types.Types; import org.kohsuke.args4j.ClassParser; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.spi.OptionHandler; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.lang.reflect.Type; import java.util.List; import java.util.Locale; import java.util.logging.Logger; @@ -158,6 +163,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { this.stderr = stderr; this.locale = locale; this.channel = Channel.current(); + registerOptionHandlers(); CmdLineParser p = new CmdLineParser(this); // add options from the authenticator @@ -173,6 +179,8 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { if (auth==Hudson.ANONYMOUS) auth = loadStoredAuthentication(); sc.setAuthentication(auth); // run the CLI with the right credential + if (!(this instanceof LoginCommand || this instanceof HelpCommand)) + Hudson.getInstance().checkPermission(Hudson.READ); return run(); } catch (CmdLineException e) { stderr.println(e.getMessage()); @@ -308,7 +316,19 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { } } - + /** + * Auto-discovers {@link OptionHandler}s and add them to the given command line parser. + */ + protected void registerOptionHandlers() { + try { + for (Class c : Index.list(OptionHandlerExtension.class,Hudson.getInstance().pluginManager.uberClassLoader,Class.class)) { + Type t = Types.getBaseClass(c, OptionHandler.class); + CmdLineParser.registerHandler(Types.erasure(Types.getTypeArgument(t,0)), c); + } + } catch (IOException e) { + throw new Error(e); + } + } /** * Returns all the registered {@link CLICommand}s. @@ -334,4 +354,19 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { * which captures the identity of the client given by the transport layer. */ public static final ChannelProperty TRANSPORT_AUTHENTICATION = new ChannelProperty(Authentication.class,"transportAuthentication"); + + private static final ThreadLocal CURRENT_COMMAND = new ThreadLocal(); + + /*package*/ static CLICommand setCurrent(CLICommand cmd) { + CLICommand old = getCurrent(); + CURRENT_COMMAND.set(cmd); + return old; + } + + /** + * If the calling thread is in the middle of executing a CLI command, return it. Otherwise null. + */ + public static CLICommand getCurrent() { + return CURRENT_COMMAND.get(); + } } diff --git a/core/src/main/java/hudson/cli/CliManagerImpl.java b/core/src/main/java/hudson/cli/CliManagerImpl.java index 17f2d73fd4bd5d29719d5f41799a644df94302c7..f9b4fdcdad937c6219a5055517d69bfda1e4edbf 100644 --- a/core/src/main/java/hudson/cli/CliManagerImpl.java +++ b/core/src/main/java/hudson/cli/CliManagerImpl.java @@ -63,8 +63,12 @@ public class CliManagerImpl implements CliEntryPoint, Serializable { String subCmd = args.get(0); CLICommand cmd = CLICommand.clone(subCmd); if(cmd!=null) { - // execute the command, do so with the originator of the request as the principal - return cmd.main(args.subList(1,args.size()),locale, stdin, out, err); + final CLICommand old = CLICommand.setCurrent(cmd); + try { + return cmd.main(args.subList(1,args.size()),locale, stdin, out, err); + } finally { + CLICommand.setCurrent(old); + } } err.println("No such command: "+subCmd); diff --git a/core/src/main/java/hudson/cli/CommandDuringBuild.java b/core/src/main/java/hudson/cli/CommandDuringBuild.java new file mode 100644 index 0000000000000000000000000000000000000000..75542aeacd7612579af33f3c0e8d79783af465d8 --- /dev/null +++ b/core/src/main/java/hudson/cli/CommandDuringBuild.java @@ -0,0 +1,82 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.cli; + +import hudson.model.Hudson; +import hudson.model.Job; +import hudson.model.Run; +import hudson.remoting.Callable; +import org.kohsuke.args4j.CmdLineException; + +import java.io.IOException; + +/** + * Base class for those commands that are valid only during a build. + * + * @author Kohsuke Kawaguchi + */ +public abstract class CommandDuringBuild extends CLICommand { + /** + * This method makes sense only when called from within the build kicked by Hudson. + * We use the environment variables that Hudson sets to determine the build that is being run. + */ + protected Run getCurrentlyBuilding() throws CmdLineException { + try { + CLICommand c = CLICommand.getCurrent(); + if (c==null) throw new IllegalStateException("Not executing a CLI command"); + String[] envs = c.channel.call(new GetCharacteristicEnvironmentVariables()); + + if (envs[0]==null || envs[1]==null) + throw new CmdLineException("This CLI command works only when invoked from inside a build"); + + Job j = Hudson.getInstance().getItemByFullName(envs[0],Job.class); + if (j==null) throw new CmdLineException("No such job: "+envs[0]); + + try { + Run r = j.getBuildByNumber(Integer.parseInt(envs[1])); + if (r==null) throw new CmdLineException("No such build #"+envs[1]+" in "+envs[0]); + return r; + } catch (NumberFormatException e) { + throw new CmdLineException("Invalid build number: "+envs[1]); + } + } catch (IOException e) { + throw new CmdLineException("Failed to identify the build being executed",e); + } catch (InterruptedException e) { + throw new CmdLineException("Failed to identify the build being executed",e); + } + } + + /** + * Gets the environment variables that points to the build being executed. + */ + private static final class GetCharacteristicEnvironmentVariables implements Callable { + public String[] call() throws IOException { + return new String[] { + System.getenv("JOB_NAME"), + System.getenv("BUILD_NUMBER") + }; + } + } +} diff --git a/core/src/main/java/hudson/cli/CopyJobCommand.java b/core/src/main/java/hudson/cli/CopyJobCommand.java index 371f1380686018ad6db85052e479a6a1b03b7ff0..6564f9b07e43814eb29e8cc09ab09699f3cc183c 100644 --- a/core/src/main/java/hudson/cli/CopyJobCommand.java +++ b/core/src/main/java/hudson/cli/CopyJobCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -23,13 +23,12 @@ */ package hudson.cli; -import hudson.model.AbstractProject; import hudson.model.Hudson; import hudson.model.TopLevelItem; import hudson.Extension; +import hudson.model.Item; import org.kohsuke.args4j.Argument; -import java.io.Serializable; /** * Copies a job from CLI. @@ -51,6 +50,8 @@ public class CopyJobCommand extends CLICommand { protected int run() throws Exception { Hudson h = Hudson.getInstance(); + h.checkPermission(Item.CREATE); + if (h.getItem(dst)!=null) { stderr.println("Job '"+dst+"' already exists"); return -1; diff --git a/core/src/main/java/hudson/cli/CreateJobCommand.java b/core/src/main/java/hudson/cli/CreateJobCommand.java index 189e6a4ede71590fbd555eac605770d999386cee..3f0c522c02093e2a29f483983a3d68b46248668e 100644 --- a/core/src/main/java/hudson/cli/CreateJobCommand.java +++ b/core/src/main/java/hudson/cli/CreateJobCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -25,6 +25,7 @@ package hudson.cli; import hudson.model.Hudson; import hudson.Extension; +import hudson.model.Item; import org.kohsuke.args4j.Argument; /** @@ -44,6 +45,8 @@ public class CreateJobCommand extends CLICommand { protected int run() throws Exception { Hudson h = Hudson.getInstance(); + h.checkPermission(Item.CREATE); + if (h.getItem(name)!=null) { stderr.println("Job '"+name+"' already exists"); return -1; diff --git a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java index b52eb2d541a433ff4c39bb6eda0c7ad3bfbf66bb..eae61b4062c41646cf872182422bcde06b50355d 100644 --- a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java +++ b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -25,6 +25,7 @@ package hudson.cli; import hudson.Extension; import hudson.model.AbstractBuild; +import hudson.model.Run; import java.io.IOException; import java.io.PrintStream; @@ -51,6 +52,8 @@ public class DeleteBuildsCommand extends AbstractBuildRangeCommand { @Override protected int act(List> builds) throws IOException { + job.checkPermission(Run.DELETE); + for (AbstractBuild build : builds) build.delete(); diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java index fab488013b0fb2435033d42b3361ae1b73e6a565..a3703a8d8b0b68fdeabe5b798d187101461577e2 100644 --- a/core/src/main/java/hudson/cli/GroovyshCommand.java +++ b/core/src/main/java/hudson/cli/GroovyshCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -28,7 +28,6 @@ import hudson.model.Hudson; import hudson.remoting.ChannelClosedException; import groovy.lang.Binding; import groovy.lang.Closure; -import org.acegisecurity.Authentication; import org.codehaus.groovy.tools.shell.Groovysh; import org.codehaus.groovy.tools.shell.IO; import org.codehaus.groovy.tools.shell.Shell; @@ -60,6 +59,8 @@ public class GroovyshCommand extends CLICommand { public int main(List args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) { // this allows the caller to manipulate the JVM state, so require the admin privilege. Hudson.getInstance().checkPermission(Hudson.ADMINISTER); + // TODO: ^as this class overrides main() (which has authentication stuff), + // how to get ADMIN permission for this command? // this being remote means no jline capability is available System.setProperty("jline.terminal", UnsupportedTerminal.class.getName()); diff --git a/core/src/main/java/hudson/cli/HelpCommand.java b/core/src/main/java/hudson/cli/HelpCommand.java index 5f1f344e7b1e1426b03817699c5022663a5c3605..8f87fafc9a62dfc2122f57ed7db463cc6c1b3d05 100644 --- a/core/src/main/java/hudson/cli/HelpCommand.java +++ b/core/src/main/java/hudson/cli/HelpCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -24,6 +24,7 @@ package hudson.cli; import hudson.Extension; +import hudson.model.Hudson; import java.util.Map; import java.util.TreeMap; @@ -41,6 +42,12 @@ public class HelpCommand extends CLICommand { } protected int run() { + if (!Hudson.getInstance().hasPermission(Hudson.READ)) { + stderr.println("You must authenticate to access this Hudson.\n" + + "Use --username/--password/--password-file parameters or login command."); + return 0; + } + Map commands = new TreeMap(); for (CLICommand c : CLICommand.all()) commands.put(c.getName(),c); diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index 409c8914b21c2a4c78d8c0e09f5a70ce98fc13da..fac26b54060b3e51fdd147461237cdcdbb898deb 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -61,6 +61,9 @@ public class InstallPluginCommand extends CLICommand { public boolean restart; protected int run() throws Exception { + Hudson h = Hudson.getInstance(); + h.checkPermission(Hudson.ADMINISTER); + for (String source : sources) { // is this a file? FilePath f = new FilePath(channel, source); @@ -90,7 +93,7 @@ public class InstallPluginCommand extends CLICommand { } // is this a plugin the update center? - UpdateSite.Plugin p = Hudson.getInstance().getUpdateCenter().getPlugin(source); + UpdateSite.Plugin p = h.getUpdateCenter().getPlugin(source); if (p!=null) { stdout.println("Installing "+source+" from update center"); p.deploy().get(); @@ -102,7 +105,7 @@ public class InstallPluginCommand extends CLICommand { } if (restart) - Hudson.getInstance().restart(); + h.restart(); return 0; // all success } diff --git a/core/src/main/java/hudson/cli/InstallToolCommand.java b/core/src/main/java/hudson/cli/InstallToolCommand.java index 0320cca86d33f1a405a404becbdde658b388bb82..58693b84a914528981adc4e6cdda97a461ce6115 100644 --- a/core/src/main/java/hudson/cli/InstallToolCommand.java +++ b/core/src/main/java/hudson/cli/InstallToolCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -32,6 +32,7 @@ import hudson.model.Run; import hudson.model.Executor; import hudson.model.Node; import hudson.model.EnvironmentSpecific; +import hudson.model.Item; import hudson.remoting.Callable; import hudson.slaves.NodeSpecific; import hudson.util.EditDistance; @@ -63,6 +64,20 @@ public class InstallToolCommand extends CLICommand { } protected int run() throws Exception { + Hudson h = Hudson.getInstance(); + h.checkPermission(Hudson.READ); + + // where is this build running? + BuildIDs id = channel.call(new BuildIDs()); + + if (!id.isComplete()) + throw new AbortException("This command can be only invoked from a build executing inside Hudson"); + + AbstractProject p = Hudson.getInstance().getItemByFullName(id.job, AbstractProject.class); + if (p==null) + throw new AbortException("No such job found: "+id.job); + p.checkPermission(Item.CONFIGURE); + List toolTypes = new ArrayList(); for (ToolDescriptor d : ToolInstallation.all()) { toolTypes.add(d.getDisplayName()); @@ -71,7 +86,7 @@ public class InstallToolCommand extends CLICommand { for (ToolInstallation t : d.getInstallations()) { toolNames.add(t.getName()); if (t.getName().equals(toolName)) - return install(t); + return install(t, id, p); } // didn't find the right tool name @@ -96,16 +111,7 @@ public class InstallToolCommand extends CLICommand { /** * Performs an installation. */ - private int install(ToolInstallation t) throws IOException, InterruptedException { - // where is this build running? - BuildIDs id = channel.call(new BuildIDs()); - - if (!id.isComplete()) - throw new AbortException("This command can be only invoked from a build executing inside Hudson"); - - AbstractProject p = Hudson.getInstance().getItemByFullName(id.job, AbstractProject.class); - if (p==null) - throw new AbortException("No such job found: "+id.job); + private int install(ToolInstallation t, BuildIDs id, AbstractProject p) throws IOException, InterruptedException { Run b = p.getBuildByNumber(Integer.parseInt(id.number)); if (b==null) diff --git a/core/src/main/java/hudson/cli/ListChangesCommand.java b/core/src/main/java/hudson/cli/ListChangesCommand.java index e0b61ed2052e13d7394f92bee0daebdaec357233..93a286ae89db5511c253fe09000d7eade941876f 100644 --- a/core/src/main/java/hudson/cli/ListChangesCommand.java +++ b/core/src/main/java/hudson/cli/ListChangesCommand.java @@ -40,6 +40,8 @@ public class ListChangesCommand extends AbstractBuildRangeCommand { @Override protected int act(List> builds) throws IOException { + // Loading job for this CLI command requires Item.READ permission. + // No other permission check needed. switch (format) { case XML: PrintWriter w = new PrintWriter(stdout); diff --git a/core/src/main/java/hudson/cli/MailCommand.java b/core/src/main/java/hudson/cli/MailCommand.java index 0fa5d7e8ca45b4e6d23b59cfa38e7d4bb1c59ac5..44a1b8fd1d4851d5c443427cf119c24f04e9ecf4 100644 --- a/core/src/main/java/hudson/cli/MailCommand.java +++ b/core/src/main/java/hudson/cli/MailCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -25,6 +25,8 @@ package hudson.cli; import hudson.tasks.Mailer; import hudson.Extension; +import hudson.model.Hudson; +import hudson.model.Item; import javax.mail.internet.MimeMessage; import javax.mail.Transport; @@ -44,6 +46,7 @@ public class MailCommand extends CLICommand { } protected int run() throws Exception { + Hudson.getInstance().checkPermission(Item.CONFIGURE); Transport.send(new MimeMessage(Mailer.descriptor().createSession(),stdin)); return 0; } diff --git a/core/src/main/java/hudson/cli/SetBuildResultCommand.java b/core/src/main/java/hudson/cli/SetBuildResultCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..29aa4bf025654ec54eee3304527c2912612babb9 --- /dev/null +++ b/core/src/main/java/hudson/cli/SetBuildResultCommand.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.cli; + +import hudson.Extension; +import hudson.model.Item; +import hudson.model.Result; +import hudson.model.Run; +import org.kohsuke.args4j.Argument; + +/** + * Sets the result of the current build. Works only if invoked from within a build. + * + * @author Kohsuke Kawaguchi + */ +@Extension +public class SetBuildResultCommand extends CommandDuringBuild { + @Argument(metaVar="RESULT",required=true) + public Result result; + + @Override + public String getShortDescription() { + return "Sets the result of the current build. Works only if invoked from within a build."; + } + + @Override + protected int run() throws Exception { + Run r = getCurrentlyBuilding(); + r.getParent().checkPermission(Item.BUILD); + r.setResult(result); + return 0; + } +} diff --git a/core/src/main/java/hudson/cli/VersionCommand.java b/core/src/main/java/hudson/cli/VersionCommand.java index ce735a77f25fe3d97b79b34a9a5e28f25adf547d..fe7aa96121821621d8f99833a264e9884f4ce391 100644 --- a/core/src/main/java/hudson/cli/VersionCommand.java +++ b/core/src/main/java/hudson/cli/VersionCommand.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -39,6 +39,7 @@ public class VersionCommand extends CLICommand { } protected int run() { + // CLICommand.main checks Hudson.READ permission.. no other check needed. stdout.println(Hudson.VERSION); return 0; } diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index 495d31c80133586b0604e58c3c0e28e61fea9982..eb3da68bf546d5462f44b8ffad30ce717a99c53a 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, 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 @@ -112,7 +112,8 @@ public class CLIRegisterer extends ExtensionFinder { this.stderr = stderr; this.locale = locale; this.channel = Channel.current(); - + + registerOptionHandlers(); CmdLineParser parser = new CmdLineParser(null); try { SecurityContext sc = SecurityContextHolder.getContext(); @@ -147,7 +148,11 @@ public class CLIRegisterer extends ExtensionFinder { // fill up all the binders parser.parseArgument(args); - sc.setAuthentication(authenticator.authenticate()); // run the CLI with the right credential + Authentication auth = authenticator.authenticate(); + if (auth==Hudson.ANONYMOUS) + auth = loadStoredAuthentication(); + sc.setAuthentication(auth); // run the CLI with the right credential + hudson.checkPermission(Hudson.READ); // resolve them Object instance = null; diff --git a/core/src/main/java/hudson/cli/declarative/OptionHandlerExtension.java b/core/src/main/java/hudson/cli/declarative/OptionHandlerExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..b7a18e305b64c447bc58935e993a530708fce693 --- /dev/null +++ b/core/src/main/java/hudson/cli/declarative/OptionHandlerExtension.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.cli.declarative; + +import org.jvnet.hudson.annotation_indexer.Indexed; +import org.kohsuke.args4j.spi.OptionHandler; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * {@link OptionHandler}s that should be auto-discovered. + * + * @author Kohsuke Kawaguchi + */ +@Indexed +@Retention(RUNTIME) +@Target({TYPE}) +@Documented +public @interface OptionHandlerExtension { +} diff --git a/core/src/main/java/hudson/console/AnnotatedLargeText.java b/core/src/main/java/hudson/console/AnnotatedLargeText.java index 550f33c8b936b820fd1ed0a14b59abdf6807a841..79d5df71fb62d431db8dae4a0d0657f3fb603df0 100644 --- a/core/src/main/java/hudson/console/AnnotatedLargeText.java +++ b/core/src/main/java/hudson/console/AnnotatedLargeText.java @@ -45,7 +45,6 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.security.GeneralSecurityException; diff --git a/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java b/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java index 1779c659f304bbe824831bf87f2d9b89dcdc96fc..4fe50fba914d2456ccbb431307bf1d873efcb946 100644 --- a/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java +++ b/core/src/main/java/hudson/console/ConsoleAnnotationDescriptor.java @@ -61,16 +61,28 @@ public abstract class ConsoleAnnotationDescriptor extends Descriptor * To register, put @{@link Extension} on your {@link ConsoleAnnotatorFactory} subtype. * + *

Behaviour, JavaScript, and CSS

+ *

+ * {@link ConsoleNote} can have associated script.js and style.css (put them + * in the same resource directory that you normally put Jelly scripts), which will be loaded into + * the HTML page whenever the console notes are used. This allows you to use minimal markup in + * code generation, and do the styling in CSS and perform the rest of the interesting work as a CSS behaviour/JavaScript. + * * @author Kohsuke Kawaguchi * @since 1.349 */ @@ -91,12 +98,16 @@ public abstract class ConsoleAnnotatorFactory implements ExtensionPoint { * Returns true if this descriptor has a JavaScript to be inserted on applicable console page. */ public boolean hasScript() { - return getScriptJs() !=null; + return getResource("/script.js") !=null; + } + + public boolean hasStylesheet() { + return getResource("/style.css") !=null; } - private URL getScriptJs() { + private URL getResource(String fileName) { Class c = getClass(); - return c.getClassLoader().getResource(c.getName().replace('.','/').replace('$','/')+"/script.js"); + return c.getClassLoader().getResource(c.getName().replace('.','/').replace('$','/')+ fileName); } /** @@ -104,7 +115,12 @@ public abstract class ConsoleAnnotatorFactory implements ExtensionPoint { */ @WebMethod(name="script.js") public void doScriptJs(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { - rsp.serveFile(req,getScriptJs(), TimeUnit2.DAYS.toMillis(1)); + rsp.serveFile(req, getResource("/script.js"), TimeUnit2.DAYS.toMillis(1)); + } + + @WebMethod(name="style.css") + public void doStyleCss(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { + rsp.serveFile(req, getResource("/style.css"), TimeUnit2.DAYS.toMillis(1)); } /** diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index f74063dd26101b4c238b4d0acdcc66d2b391f9b6..e83101af622b5e8a73ecf822a2da50d43f5f54d8 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -106,6 +106,13 @@ import java.util.zip.GZIPOutputStream; * is also important, although {@link ConsoleNote}s that failed to deserialize will be simply ignored, so the * worst thing that can happen is that you just lose some notes. * + *

Behaviour, JavaScript, and CSS

+ *

+ * {@link ConsoleNote} can have associated script.js and style.css (put them + * in the same resource directory that you normally put Jelly scripts), which will be loaded into + * the HTML page whenever the console notes are used. This allows you to use minimal markup in + * code generation, and do the styling in CSS and perform the rest of the interesting work as a CSS behaviour/JavaScript. + * * @param * Contextual model object that this console is associated with, such as {@link Run}. * @@ -276,7 +283,9 @@ public abstract class ConsoleNote implements Serializable, Describable removeNotes(Collection logLines) { List r = new ArrayList(logLines.size()); @@ -287,6 +296,8 @@ public abstract class ConsoleNote implements Serializable, Describable0) { + char ch = line.charAt(slen-1); + if (ch=='\r' || ch=='\n') { + slen--; + continue; + } + break; + } + line = line.substring(0,slen); + return line; + } + private static final int LF = 0x0A; } diff --git a/core/src/main/java/hudson/init/InitializerFinder.java b/core/src/main/java/hudson/init/InitializerFinder.java index 6c89792c3d385455ac6f6cf809564cf5d5320193..7efdfe8b6d06a434b58565d3f6201fd11a4b754c 100644 --- a/core/src/main/java/hudson/init/InitializerFinder.java +++ b/core/src/main/java/hudson/init/InitializerFinder.java @@ -38,7 +38,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; import hudson.model.Hudson; diff --git a/core/src/main/java/hudson/init/impl/GroovyInitScript.java b/core/src/main/java/hudson/init/impl/GroovyInitScript.java index e5d37998d1ce2338fd29399892e968d5f09c8d5a..b8d29077150219a73619742285980cdec0b2a9e3 100644 --- a/core/src/main/java/hudson/init/impl/GroovyInitScript.java +++ b/core/src/main/java/hudson/init/impl/GroovyInitScript.java @@ -31,11 +31,9 @@ import java.io.IOException; import java.net.URL; import java.util.logging.Logger; -import hudson.FilePath; import hudson.model.Hudson; import static hudson.init.InitMilestone.JOB_LOADED; import hudson.init.Initializer; -import org.apache.commons.io.FileUtils; /** * Run the initialization script, if it exists. diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java index cd2d747abafba852dad159544928a2812d3531bc..e138a565ffcebe4cb98baeea192a8c14dbf982a5 100644 --- a/core/src/main/java/hudson/lifecycle/Lifecycle.java +++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java @@ -139,7 +139,17 @@ public abstract class Lifecycle implements ExtensionPoint { // but let's be defensive if(dest==null) throw new IOException("hudson.war location is not known."); + // backing up the old hudson.war before it gets lost due to upgrading + // (newly downloaded hudson.war and 'backup' (hudson.war.tmp) are the same files + // unless we are trying to rewrite hudson.war by a backup itself + File bak = new File(dest.getPath() + ".bak"); + if (!by.equals(bak)) + FileUtils.copyFile(dest, bak); + FileUtils.copyFile(by, dest); + // we don't want to keep backup if we are downgrading + if (by.equals(bak)&&bak.exists()) + bak.delete(); } /** diff --git a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java index 8251f3a7f0713fbfd9fb6b8459620845deeec5b0..160d06d6c1ffa3797cf80555b2c216e381a95463 100644 --- a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ */ package hudson.lifecycle; +import hudson.model.Hudson; import java.io.IOException; /** @@ -36,6 +37,9 @@ public class SolarisSMFLifecycle extends Lifecycle { */ @Override public void restart() throws IOException, InterruptedException { + Hudson h = Hudson.getInstance(); + if (h != null) + h.cleanUp(); System.exit(0); } } diff --git a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java index 23125b5af7b50a0fbfc0e96d3b251b1c867d1985..0d67fc1b919638465e523989281121ec8b0fc705 100644 --- a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java @@ -61,6 +61,10 @@ public class UnixLifecycle extends Lifecycle { @Override public void restart() throws IOException, InterruptedException { + Hudson h = Hudson.getInstance(); + if (h != null) + h.cleanUp(); + // close all files upon exec, except stdin, stdout, and stderr int sz = LIBC.getdtablesize(); for(int i=3; i, Iterable { +public class Axis extends AbstractDescribableImpl implements Comparable, Iterable, ExtensionPoint { /** * Name of this axis. * Used as a variable name. + * + * @deprecated as of 1.373 + * Use {@link #getName()} */ public final String name; /** * Possible values for this axis. + * + * @deprecated as of 1.373 + * Use {@link #getValues()} */ public final List values; @@ -74,29 +86,32 @@ public final class Axis implements Comparable, Iterable { * Axis with empty values need to be removed later. */ @DataBoundConstructor - public Axis(String name, String value) { + public Axis(String name, String valueString) { this.name = name; - this.values = new ArrayList(Arrays.asList(Util.tokenize(value))); + this.values = new ArrayList(Arrays.asList(Util.tokenize(valueString))); } /** - * Returns ture if this axis is a system-reserved axis - * that has special treatment. + * Returns true if this axis is a system-reserved axis + * that has used to have af special treatment. + * + * @deprecated as of 1.373 + * System vs user difference are generalized into extension point. */ public boolean isSystem() { - return name.equals("jdk") || name.equals("label"); + return false; } public Iterator iterator() { - return values.iterator(); + return getValues().iterator(); } public int size() { - return values.size(); + return getValues().size(); } public String value(int index) { - return values.get(index); + return getValues().get(index); } /** @@ -114,6 +129,26 @@ public final class Axis implements Comparable, Iterable { return this.name.compareTo(that.name); } + /** + * Name of this axis. + * Used as a variable name. + */ + public String getName() { + return name; + } + + /** + * Possible values for this axis. + */ + public List getValues() { + return Collections.unmodifiableList(values); + } + + @Override + public AxisDescriptor getDescriptor() { + return (AxisDescriptor)super.getDescriptor(); + } + @Override public String toString() { return new StringBuilder().append(name).append("={").append(Util.join(values,",")).append('}').toString(); @@ -154,4 +189,33 @@ public final class Axis implements Comparable, Iterable { return null; return new Axis(name,values); } + + /** + * Previously we used to persist {@link Axis}, but now those are divided into subtypes. + * So upon deserialization, resolve to the proper type. + */ + public Object readResolve() { + if (getClass()!=Axis.class) return this; + + if (getName().equals("jdk")) + return new JDKAxis(getValues()); + if (getName().equals("label")) + return new LabelAxis(getName(),getValues()); + return new TextAxis(getName(),getValues()); + } + + /** + * Returns all the registered {@link AxisDescriptor}s. + */ + public static DescriptorExtensionList all() { + return Hudson.getInstance().getDescriptorList(Axis.class); + } + + /** + * Converts the selected value (which is among {@link #values}) and adds that to the given map, + * which serves as the build variables. + */ + public void addBuildVariable(String value, Map map) { + map.put(name,value); + } } diff --git a/core/src/main/java/hudson/matrix/AxisDescriptor.java b/core/src/main/java/hudson/matrix/AxisDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..04ae7728fbd7800c8696b7560f96c72fab995c6b --- /dev/null +++ b/core/src/main/java/hudson/matrix/AxisDescriptor.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.matrix; + +import hudson.Util; +import hudson.model.Descriptor; +import hudson.model.Failure; +import hudson.model.Hudson; +import hudson.util.FormValidation; +import org.kohsuke.stapler.QueryParameter; + +/** + * {@link Descriptor} for {@link Axis} + * + * @author Kohsuke Kawaguchi + */ +public abstract class AxisDescriptor extends Descriptor { + protected AxisDescriptor(Class clazz) { + super(clazz); + } + + protected AxisDescriptor() { + } + + /** + * Return false if the user shouldn't be able to create thie axis from the UI. + */ + public boolean isInstantiable() { + return true; + } + + /** + * Makes sure that the given name is good as a axis name. + */ + public FormValidation doCheckName(@QueryParameter String value) { + if(Util.fixEmpty(value)==null) + return FormValidation.ok(); + + try { + Hudson.checkGoodName(value); + return FormValidation.ok(); + } catch (Failure e) { + return FormValidation.error(e.getMessage()); + } + } +} diff --git a/core/src/main/java/hudson/matrix/AxisList.java b/core/src/main/java/hudson/matrix/AxisList.java index 0ee30d2ce0537013dcfbe647c6d486fdd5d0b776..79f15cfb63e3db15d969ba2240bef3fb35c09bfe 100644 --- a/core/src/main/java/hudson/matrix/AxisList.java +++ b/core/src/main/java/hudson/matrix/AxisList.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,11 +24,12 @@ package hudson.matrix; import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.converters.Converter; +import hudson.Util; import hudson.util.RobustCollectionConverter; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.Arrays; @@ -41,7 +42,7 @@ public class AxisList extends ArrayList { public AxisList() { } - public AxisList(Collection c) { + public AxisList(Collection c) { super(c); } @@ -57,6 +58,13 @@ public class AxisList extends ArrayList { return null; } + /** + * Creates a subset of the list that only contains the type assignable to the specified type. + */ + public AxisList subList(Class subType) { + return new AxisList(Util.filter(this,subType)); + } + @Override public boolean add(Axis axis) { return axis!=null && super.add(axis); @@ -67,6 +75,7 @@ public class AxisList extends ArrayList { */ public Iterable list() { final int[] base = new int[size()]; + if (base.length==0) return Collections.emptyList(); int b = 1; for( int i=size()-1; i>=0; i-- ) { diff --git a/core/src/main/java/hudson/matrix/Combination.java b/core/src/main/java/hudson/matrix/Combination.java index 7ca23e27cc0edd6007cd6ac574ba715c26239f14..6f4066e5b504fb554c7b03bf80b5a57531b18588 100644 --- a/core/src/main/java/hudson/matrix/Combination.java +++ b/core/src/main/java/hudson/matrix/Combination.java @@ -25,6 +25,7 @@ package hudson.matrix; import hudson.Util; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -53,7 +54,7 @@ public final class Combination extends TreeMap implements Compara public Combination(AxisList axisList, List values) { for(int i=0; i implements Compara super.put(e.getKey(),e.getValue()); } + public String get(Axis a) { + return get(a.getName()); + } + /** * Obtains the continuous unique index number of this {@link Combination} * in the given {@link AxisList}. @@ -73,7 +78,7 @@ public final class Combination extends TreeMap implements Compara int r = 0; for (Axis a : axis) { r *= a.size(); - r += a.indexOf(get(a.name)); + r += a.indexOf(get(a)); } return r; } @@ -92,7 +97,7 @@ public final class Combination extends TreeMap implements Compara private long toModuloIndex(AxisList axis) { long r = 0; for (Axis a : axis) { - r += a.indexOf(get(a.name)); + r += a.indexOf(get(a)); r *= 31; } return r; @@ -150,12 +155,22 @@ public final class Combination extends TreeMap implements Compara StringBuilder buf = new StringBuilder(); for (Axis a : subset) { if(buf.length()>0) buf.append(','); - buf.append(a.name).append('=').append(get(a.name)); + buf.append(a.getName()).append('=').append(get(a)); } if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name. return buf.toString(); } - + + /** + * Gets the values that correspond to the specified axes, in their order. + */ + public List values(Collection axes) { + List r = new ArrayList(axes.size()); + for (Axis a : axes) + r.add(get(a)); + return r; + } + /** * Converts to the ID string representation: * axisName=value,axisName=value,... @@ -218,12 +233,12 @@ public final class Combination extends TreeMap implements Compara Map axisByValue = new HashMap(); for (Axis a : axes) { - for (String v : a.values) { + for (String v : a.getValues()) { Axis old = axisByValue.put(v,a); if(old!=null) { // these two axes have colliding values - nonUniqueAxes.add(old.name); - nonUniqueAxes.add(a.name); + nonUniqueAxes.add(old.getName()); + nonUniqueAxes.add(a.getName()); } } } diff --git a/core/src/test/java/hudson/scheduler/CrontabTest.java b/core/src/main/java/hudson/matrix/DefaultAxisDescriptor.java similarity index 64% rename from core/src/test/java/hudson/scheduler/CrontabTest.java rename to core/src/main/java/hudson/matrix/DefaultAxisDescriptor.java index b83642ccbcedf23564f16d181a3e413fc06f4c21..96fb461499571fb07e49acd69713dda9d2cbd16a 100644 --- a/core/src/test/java/hudson/scheduler/CrontabTest.java +++ b/core/src/main/java/hudson/matrix/DefaultAxisDescriptor.java @@ -1,18 +1,18 @@ /* * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * + * + * Copyright (c) 2010, InfraDNA, 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 @@ -21,27 +21,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package hudson.scheduler; +package hudson.matrix; -import antlr.ANTLRException; -import junit.framework.TestCase; +import hudson.Extension; /** + * {@link AxisDescriptor} for manually entered default axis. + * * @author Kohsuke Kawaguchi */ -public class CrontabTest extends TestCase { - public static void main(String[] args) throws ANTLRException { - for (String arg : args) { - CronTab ct = new CronTab(arg); - System.out.println(ct.toString()); - } +@Extension +public class DefaultAxisDescriptor extends AxisDescriptor { + public DefaultAxisDescriptor() { + super(Axis.class); } - public void test1() throws ANTLRException { - new CronTab("@yearly"); - new CronTab("@weekly"); - new CronTab("@midnight"); - new CronTab("@monthly"); - new CronTab("0 0 * 1-10/3 *"); + @Override + public String getDisplayName() { + return "Axis"; } + + @Override + public boolean isInstantiable() { + return false; + } } diff --git a/core/src/main/java/hudson/matrix/JDKAxis.java b/core/src/main/java/hudson/matrix/JDKAxis.java new file mode 100644 index 0000000000000000000000000000000000000000..6f80871e91a3334f358887d763c4a76111f727e1 --- /dev/null +++ b/core/src/main/java/hudson/matrix/JDKAxis.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.matrix; + +import hudson.Extension; +import hudson.model.Hudson; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.util.Arrays; +import java.util.List; + +/** + * {@link Axis} that selects available JDKs. + * + * @author Kohsuke Kawaguchi + */ +public class JDKAxis extends Axis { + /** + * JDK axis was used to be stored as a plain "Axis" with the name "jdk", + * so it cannot be configured by any other name. + */ + public JDKAxis(List values) { + super("jdk", values); + } + + @DataBoundConstructor + public JDKAxis(String[] values) { + super("jdk", Arrays.asList(values)); + } + + @Override + public boolean isSystem() { + return true; + } + + @Extension + public static class DescriptorImpl extends AxisDescriptor { + @Override + public String getDisplayName() { + return Messages.JDKAxis_DisplayName(); + } + + /** + * If there's no JDK configured, there's no point in this axis. + */ + @Override + public boolean isInstantiable() { + return !Hudson.getInstance().getJDKs().isEmpty(); + } + } +} diff --git a/core/src/main/java/hudson/matrix/LabelAxis.java b/core/src/main/java/hudson/matrix/LabelAxis.java new file mode 100644 index 0000000000000000000000000000000000000000..d71e8e37e225717dc7410a22b7c8874be6c45e86 --- /dev/null +++ b/core/src/main/java/hudson/matrix/LabelAxis.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.matrix; + +import hudson.Extension; +import hudson.model.Hudson; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.util.List; + +/** + * {@link Axis} that selects label expressions. + * + * @author Kohsuke Kawaguchi + */ +public class LabelAxis extends Axis { + @DataBoundConstructor + public LabelAxis(String name, List values) { + super(name, values); + } + + @Override + public boolean isSystem() { + return true; + } + + @Extension + public static class DescriptorImpl extends AxisDescriptor { + @Override + public String getDisplayName() { + return Messages.LabelAxis_DisplayName(); + } + + /** + * If there's no distributed build set up, it's pointless to provide this axis. + */ + @Override + public boolean isInstantiable() { + Hudson h = Hudson.getInstance(); + return !h.getNodes().isEmpty() || !h.clouds.isEmpty(); + } + } +} diff --git a/core/src/main/java/hudson/matrix/Layouter.java b/core/src/main/java/hudson/matrix/Layouter.java index f943e07f60cb3ca4c40956ba00beefce315c91d7..da01cd06c307a045ea9b9fe96e109922d05fbaee 100644 --- a/core/src/main/java/hudson/matrix/Layouter.java +++ b/core/src/main/java/hudson/matrix/Layouter.java @@ -87,8 +87,11 @@ public abstract class Layouter { z.add(nonTrivialAxes.get(0)); break; case 2: - x.add(nonTrivialAxes.get(0)); - y.add(nonTrivialAxes.get(1)); + // use the longer axis in Y + Axis a = nonTrivialAxes.get(0); + Axis b = nonTrivialAxes.get(1); + x.add(a.size() > b.size() ? b : a); + y.add(a.size() > b.size() ? a : b); break; default: // for size > 3, use x and y, and try to pack y more diff --git a/core/src/main/java/hudson/matrix/LinkedLogRotator.java b/core/src/main/java/hudson/matrix/LinkedLogRotator.java index 8aa421e9cb15e7f6f520a8c86e7ae0e13079b4d8..4f3d62a5d5386bff36bbe86369b5f0e5ad91c0a9 100644 --- a/core/src/main/java/hudson/matrix/LinkedLogRotator.java +++ b/core/src/main/java/hudson/matrix/LinkedLogRotator.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -40,15 +40,25 @@ import java.io.IOException; * @author Kohsuke Kawaguchi */ final class LinkedLogRotator extends LogRotator { + LinkedLogRotator(int artifactDaysToKeep, int artifactNumToKeep) { + super(-1, -1, artifactDaysToKeep, artifactNumToKeep); + } + + /** + * @deprecated since 1.369 + * Use {@link #LinkedLogRotator(int, int)} + */ LinkedLogRotator() { - super(-1,-1); + super(-1, -1, -1, -1); } @Override public void perform(Job _job) throws IOException, InterruptedException { - // copy it to the array because we'll be deleting builds as we go. + // Let superclass handle clearing artifacts, if configured: + super.perform(_job); MatrixConfiguration job = (MatrixConfiguration) _job; + // copy it to the array because we'll be deleting builds as we go. for( MatrixRun r : job.getBuilds().toArray(new MatrixRun[0]) ) { if(job.getParent().getBuildByNumber(r.getNumber())==null) r.delete(); diff --git a/core/src/main/java/hudson/matrix/MatrixBuild.java b/core/src/main/java/hudson/matrix/MatrixBuild.java index 0c0c0f5bf45802e794c5b49a2316f6911788fafa..3d13ad35b289541bdaf6a45a16078417feb9d24b 100644 --- a/core/src/main/java/hudson/matrix/MatrixBuild.java +++ b/core/src/main/java/hudson/matrix/MatrixBuild.java @@ -31,10 +31,13 @@ import hudson.model.Executor; import hudson.model.Fingerprint; import hudson.model.Hudson; import hudson.model.JobProperty; +import hudson.model.Node; import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Result; import hudson.model.Cause.UpstreamCause; +import hudson.slaves.WorkspaceList; +import hudson.slaves.WorkspaceList.Lease; import hudson.tasks.Publisher; import java.io.File; @@ -320,6 +323,17 @@ public class MatrixBuild extends AbstractBuild { for (MatrixAggregator a : aggregators) a.endBuild(); } + + @Override + protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws IOException, InterruptedException { + String customWorkspace = getProject().getCustomWorkspace(); + if (customWorkspace != null) { + // we allow custom workspaces to be concurrently used between jobs. + return Lease.createDummyLease(n.getRootPath().child(getEnvironment(listener).expand(customWorkspace))); + } + return super.decideWorkspace(n,wsl); + } + } /** diff --git a/core/src/main/java/hudson/matrix/MatrixConfiguration.java b/core/src/main/java/hudson/matrix/MatrixConfiguration.java index a1fee12e5099f201e57ce04b19f67248e0a0713e..b52d6f942ecc3b32b043aa1a53f1c5d41c0e6140 100644 --- a/core/src/main/java/hudson/matrix/MatrixConfiguration.java +++ b/core/src/main/java/hudson/matrix/MatrixConfiguration.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ */ package hudson.matrix; +import hudson.Util; import hudson.util.DescribableList; import hudson.model.AbstractBuild; import hudson.model.Cause; @@ -192,7 +193,9 @@ public class MatrixConfiguration extends Project @Override public Label getAssignedLabel() { - return Hudson.getInstance().getLabel(combination.get("label")); + // combine all the label axes by &&. + String expr = Util.join(combination.values(getParent().getAxes().subList(LabelAxis.class)), "&&"); + return Hudson.getInstance().getLabel(Util.fixEmpty(expr)); } @Override @@ -240,7 +243,9 @@ public class MatrixConfiguration extends Project @Override public LogRotator getLogRotator() { - return new LinkedLogRotator(); + LogRotator lr = getParent().getLogRotator(); + return new LinkedLogRotator(lr != null ? lr.getArtifactDaysToKeep() : -1, + lr != null ? lr.getArtifactNumToKeep() : -1); } @Override diff --git a/core/src/main/java/hudson/matrix/MatrixProject.java b/core/src/main/java/hudson/matrix/MatrixProject.java index 8187608c12a97e823530790a768321a8050e3935..67b0aa814f9315fcce91d1b60506cb20c0d51014 100644 --- a/core/src/main/java/hudson/matrix/MatrixProject.java +++ b/core/src/main/java/hudson/matrix/MatrixProject.java @@ -24,13 +24,14 @@ package hudson.matrix; import hudson.CopyOnWrite; -import hudson.XmlFile; -import hudson.Util; import hudson.Extension; +import hudson.Util; +import hudson.XmlFile; import hudson.model.AbstractProject; +import hudson.model.BuildableItemWithBuildWrappers; import hudson.model.DependencyGraph; import hudson.model.Descriptor; -import hudson.model.Failure; +import hudson.model.Descriptor.FormException; import hudson.model.Hudson; import hudson.model.Item; import hudson.model.ItemGroup; @@ -38,14 +39,12 @@ import hudson.model.Items; import hudson.model.JDK; import hudson.model.Job; import hudson.model.Label; +import hudson.model.Queue.FlyweightTask; +import hudson.model.ResourceController; import hudson.model.Result; import hudson.model.SCMedItem; import hudson.model.Saveable; import hudson.model.TopLevelItem; -import hudson.model.ResourceController; -import hudson.model.BuildableItemWithBuildWrappers; -import hudson.model.Queue.FlyweightTask; -import hudson.model.Descriptor.FormException; import hudson.tasks.BuildStep; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildWrapper; @@ -55,8 +54,13 @@ import hudson.tasks.Publisher; import hudson.triggers.Trigger; import hudson.util.CopyOnWriteMap; import hudson.util.DescribableList; -import hudson.util.FormValidation; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.TokenList; +import javax.servlet.ServletException; import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -65,24 +69,15 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.ServletException; - -import net.sf.json.JSONObject; - -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.TokenList; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.QueryParameter; +import static hudson.Util.*; /** * {@link Job} that allows you to run multiple different configurations @@ -92,9 +87,7 @@ import org.kohsuke.stapler.QueryParameter; */ public class MatrixProject extends AbstractProject implements TopLevelItem, SCMedItem, ItemGroup, Saveable, FlyweightTask, BuildableItemWithBuildWrappers { /** - * Other configuration axes. - * - * This also includes special axis "label" and "jdk" if they are configured. + * Configuration axes. */ private volatile AxisList axes = new AxisList(); @@ -148,6 +141,11 @@ public class MatrixProject extends AbstractProject im */ private Result touchStoneResultCondition; + /** + * See {@link #setCustomWorkspace(String)}. + */ + private String customWorkspace; + public MatrixProject(String name) { super(Hudson.getInstance(), name); } @@ -225,6 +223,28 @@ public class MatrixProject extends AbstractProject im this.touchStoneResultCondition = touchStoneResultCondition; } + public String getCustomWorkspace() { + return customWorkspace; + } + + /** + * User-specified workspace directory, or null if it's up to Hudson. + * + *

+ * Normally a matrix project uses the workspace location assigned by its parent container, + * but sometimes people have builds that have hard-coded paths. + * + *

+ * This is not {@link File} because it may have to hold a path representation on another OS. + * + *

+ * If this path is relative, it's resolved against {@link Node#getRootPath()} on the node where this workspace + * is prepared. + */ + public void setCustomWorkspace(String customWorkspace) throws IOException { + this.customWorkspace= customWorkspace; + } + @Override protected void updateTransientActions() { synchronized(transientActions) { @@ -243,6 +263,9 @@ public class MatrixProject extends AbstractProject im /** * Gets the subset of {@link AxisList} that are not system axes. + * + * @deprecated as of 1.373 + * System vs user difference are generalized into extension point. */ public List getUserAxes() { List r = new ArrayList(); @@ -474,12 +497,9 @@ public class MatrixProject extends AbstractProject im * @return never null */ public Set

,R extends Abs return getTimestamp(); } + /** + * Builds up a set of variable names that contain sensitive values that + * should not be exposed. The expection is that this set is populated with + * keys returned by {@link #getBuildVariables()} that should have their + * values masked for display purposes. + * + * @since 1.378 + */ + public Set getSensitiveBuildVariables() { + Set s = new HashSet(); + + ParametersAction parameters = getAction(ParametersAction.class); + if (parameters != null) { + for (ParameterValue p : parameters) { + if (p.isSensitive()) { + s.add(p.getName()); + } + } + } + + // Allow BuildWrappers to determine if any of their data is sensitive + if (project instanceof BuildableItemWithBuildWrappers) { + for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList()) { + bw.makeSensitiveBuildVariables(this, s); + } + } + + return s; + } + /** * Provides additional variables and their values to {@link Builder}s. * diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index db40bd7b70993cd38e4f5e94bc9ab73e61dcff40..c0ade3f6ab100bb46a359aea16df2702145aa356 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -1,7 +1,8 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, + * Daniel Dyer, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -75,6 +76,10 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet doSetName(name); } + public void onCreatedFromScratch() { + // noop + } + @Exported(visibility=999) public String getName() { return name; @@ -243,11 +248,13 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet /** * Deletes this item. */ + @CLIMethod(name="delete-job") public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException { checkPermission(DELETE); requirePOST(); delete(); - rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl()); + if (rsp != null) // null for CLI + rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl()); } public void delete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { @@ -262,7 +269,6 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet /** * Deletes this item. */ - @CLIMethod(name="delete-job") public synchronized void delete() throws IOException, InterruptedException { performDelete(); diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index a47b470a88d0534271404d87b4c2139bfe151204..a5a23010f9240f2e59e7cd8ddc1e0383604d090d 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1,7 +1,9 @@ /* * The MIT License * - * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, + * Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, + * Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +25,7 @@ */ package hudson.model; +import antlr.ANTLRException; import hudson.AbortException; import hudson.CopyOnWrite; import hudson.FeedAdapter; @@ -32,35 +35,36 @@ import hudson.Util; import hudson.cli.declarative.CLIMethod; import hudson.cli.declarative.CLIResolver; import hudson.diagnosis.OldDataMonitor; -import hudson.slaves.WorkspaceList; import hudson.model.Cause.LegacyCodeCause; -import hudson.model.Cause.UserCause; import hudson.model.Cause.RemoteCause; +import hudson.model.Cause.UserCause; import hudson.model.Descriptor.FormException; import hudson.model.Fingerprint.RangeSet; -import hudson.model.RunMap.Constructor; -import hudson.model.Queue.WaitingItem; import hudson.model.Queue.Executable; +import hudson.model.Queue.Task; +import hudson.model.queue.SubTask; +import hudson.model.Queue.WaitingItem; +import hudson.model.RunMap.Constructor; +import hudson.model.labels.LabelAtom; +import hudson.model.labels.LabelExpression; import hudson.model.queue.CauseOfBlockage; +import hudson.model.queue.SubTaskContributor; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; import hudson.scm.NullSCM; -import hudson.scm.SCM; -import hudson.scm.SCMS; import hudson.scm.PollingResult; +import hudson.scm.SCM; import hudson.scm.SCMRevisionState; -import static hudson.scm.PollingResult.NO_CHANGES; -import static hudson.scm.PollingResult.BUILD_NOW; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - +import hudson.scm.SCMS; import hudson.search.SearchIndexBuilder; import hudson.security.Permission; +import hudson.slaves.WorkspaceList; import hudson.tasks.BuildStep; +import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildTrigger; +import hudson.tasks.BuildWrapperDescriptor; import hudson.tasks.Mailer; import hudson.tasks.Publisher; -import hudson.tasks.BuildStepDescriptor; -import hudson.tasks.BuildWrapperDescriptor; import hudson.triggers.SCMTrigger; import hudson.triggers.Trigger; import hudson.triggers.TriggerDescriptor; @@ -72,14 +76,14 @@ import hudson.widgets.HistoryWidget; import net.sf.json.JSONObject; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.stapler.ForwardToView; +import org.kohsuke.stapler.HttpRedirect; +import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.HttpRedirect; -import org.kohsuke.stapler.ForwardToView; import javax.servlet.ServletException; import java.io.File; @@ -102,6 +106,9 @@ import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; +import static hudson.scm.PollingResult.*; +import static javax.servlet.http.HttpServletResponse.*; + /** * Base implementation of {@link Job}s that build software. * @@ -217,6 +224,13 @@ public abstract class AbstractProject

,R extends A } } + @Override + public void onCreatedFromScratch() { + super.onCreatedFromScratch(); + // solicit initial contributions, especially from TransientProjectActionFactory + updateTransientActions(); + } + @Override public void onLoad(ItemGroup parent, String name) throws IOException { super.onLoad(parent, name); @@ -286,6 +300,20 @@ public abstract class AbstractProject

,R extends A return Hudson.getInstance().getLabel(assignedNode); } + /** + * Gets the textual representation of the assigned label as it was entered by the user. + */ + public String getAssignedLabelString() { + if (canRoam || assignedNode==null) return null; + try { + LabelExpression.parseExpression(assignedNode); + return assignedNode; + } catch (ANTLRException e) { + // must be old label or host name that includes whitespace or other unsafe chars + return LabelAtom.escape(assignedNode); + } + } + /** * Sets the assigned label. */ @@ -296,7 +324,7 @@ public abstract class AbstractProject

,R extends A } else { canRoam = false; if(l==Hudson.getInstance().getSelfLabel()) assignedNode = null; - else assignedNode = l.getName(); + else assignedNode = l.getExpression(); } save(); } @@ -495,12 +523,10 @@ public abstract class AbstractProject

,R extends A save(); } - @CLIMethod(name="disable-job") public void disable() throws IOException { makeDisabled(true); } - @CLIMethod(name="enable-job") public void enable() throws IOException { makeDisabled(false); } @@ -891,6 +917,14 @@ public abstract class AbstractProject

,R extends A return b.getBuiltOn(); } + public Object getSameNodeConstraint() { + return this; // in this way, any member that wants to run with the main guy can nominate the project itself + } + + public final Task getOwnerTask() { + return this; + } + /** * {@inheritDoc} * @@ -974,6 +1008,16 @@ public abstract class AbstractProject

,R extends A return null; } + public List getSubTasks() { + List r = new ArrayList(); + r.add(this); + for (SubTaskContributor euc : SubTaskContributor.all()) + r.addAll(euc.forProject(this)); + for (JobProperty p : properties) + r.addAll(p.getSubTasks()); + return r; + } + public R createExecutable() throws IOException { if(isDisabled()) return null; return newBuild(); @@ -1393,6 +1437,17 @@ public abstract class AbstractProject

,R extends A return; } + if (!isBuildable()) + throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable")); + + Hudson.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req)); + rsp.forwardToPreviousPage(req); + } + + /** + * Computes the build cause, using RemoteCause or UserCause as appropriate. + */ + /*package*/ CauseAction getBuildCause(StaplerRequest req) { Cause cause; if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) { // Optional additional cause text when starting via token @@ -1401,12 +1456,7 @@ public abstract class AbstractProject

,R extends A } else { cause = new UserCause(); } - - if (!isBuildable()) - throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable")); - - Hudson.getInstance().getQueue().schedule(this, getDelay(req), new CauseAction(cause)); - rsp.forwardToPreviousPage(req); + return new CauseAction(cause); } /** @@ -1481,16 +1531,11 @@ public abstract class AbstractProject

,R extends A blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null; if(req.getParameter("hasSlaveAffinity")!=null) { - canRoam = false; - assignedNode = req.getParameter("slave"); - if(assignedNode !=null) { - if(Hudson.getInstance().getLabel(assignedNode).isEmpty()) - assignedNode = null; // no such label - } + assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString")); } else { - canRoam = true; assignedNode = null; } + canRoam = assignedNode==null; concurrentBuild = req.getSubmittedForm().has("concurrentBuild"); @@ -1563,6 +1608,7 @@ public abstract class AbstractProject

,R extends A } } + @CLIMethod(name="disable-job") public HttpResponse doDisable() throws IOException, ServletException { requirePOST(); checkPermission(CONFIGURE); @@ -1570,6 +1616,7 @@ public abstract class AbstractProject

,R extends A return new HttpRedirect("."); } + @CLIMethod(name="enable-job") public HttpResponse doEnable() throws IOException, ServletException { requirePOST(); checkPermission(CONFIGURE); @@ -1667,6 +1714,20 @@ public abstract class AbstractProject

,R extends A public boolean isApplicable(Descriptor descriptor) { return true; } + + public FormValidation doCheckAssignedLabelString(@QueryParameter String value) { + if (Util.fixEmpty(value)==null) + return FormValidation.ok(); // nothing typed yet + try { + Label.parseExpression(value); + } catch (ANTLRException e) { + return FormValidation.error(e,"Invalid boolean expression: "+e.getMessage()); + } + // TODO: if there's an atom in the expression that is empty, report it + if (Hudson.getInstance().getLabel(value).isEmpty()) + return FormValidation.warning("There's no slave/cloud that matches this assignment"); + return FormValidation.ok(); + } } /** diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java index 07105dbd33b197a0f874766a89a1605f70ed7780..053b8228ef4417cce6f5e40d5c67a7ca634d5219 100644 --- a/core/src/main/java/hudson/model/Api.java +++ b/core/src/main/java/hudson/model/Api.java @@ -44,7 +44,6 @@ import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.List; -import java.util.logging.Logger; /** * Used to expose remote access API for ".../api/" diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java new file mode 100644 index 0000000000000000000000000000000000000000..10888d20944b0600b02c2f71d0ec0c4ea358be9e --- /dev/null +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.model; + +import hudson.search.Search; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.export.Flavor; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Data representation of the auto-completion candidates. + *

+ * This object should be returned from your doAutoCompleteXYZ methods. + * + * @author Kohsuke Kawaguchi + */ +public class AutoCompletionCandidates implements HttpResponse { + private final List values = new ArrayList(); + + public AutoCompletionCandidates add(String v) { + values.add(v); + return this; + } + + public AutoCompletionCandidates add(String... v) { + values.addAll(Arrays.asList(v)); + return this; + } + + public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object o) throws IOException, ServletException { + Search.Result r = new Search.Result(); + for (String value : values) { + r.suggestions.add(new hudson.search.Search.Item(value)); + } + rsp.serveExposedBean(req,r, Flavor.JSON); + } +} diff --git a/core/src/main/java/hudson/model/BooleanParameterValue.java b/core/src/main/java/hudson/model/BooleanParameterValue.java index 2acb1d56814f986224b3b77f345d0081c14a7839..f03caa935ee5bbb68c24389655e5779df90bccaa 100755 --- a/core/src/main/java/hudson/model/BooleanParameterValue.java +++ b/core/src/main/java/hudson/model/BooleanParameterValue.java @@ -28,7 +28,6 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.export.Exported; import java.util.Locale; -import java.util.Map; import hudson.util.VariableResolver; diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java index b1524bf03df60bd5097a0eb307da70f2dc9150be..7d0531683016e019ec5f74e796343340d724e6de 100644 --- a/core/src/main/java/hudson/model/Build.java +++ b/core/src/main/java/hudson/model/Build.java @@ -146,7 +146,6 @@ public abstract class Build

,B extends Build> failed=true; } } - buildEnvironments = null; // WARNING The return in the finally clause will trump any return before if (failed) return FAILURE; } @@ -167,6 +166,7 @@ public abstract class Build

,B extends Build> performAllBuildSteps(listener, project.getPublishers(), false); performAllBuildSteps(listener, project.getProperties(), false); BuildTrigger.execute(Build.this, listener); + buildEnvironments = null; } private boolean build(BuildListener listener, Collection steps) throws IOException, InterruptedException { diff --git a/core/src/main/java/hudson/model/BuildTimelineWidget.java b/core/src/main/java/hudson/model/BuildTimelineWidget.java new file mode 100644 index 0000000000000000000000000000000000000000..ca1e226893dec7f421129ef420f92bf791ddb46c --- /dev/null +++ b/core/src/main/java/hudson/model/BuildTimelineWidget.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, 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.model; + +import hudson.util.RunList; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.koshuke.stapler.simile.timeline.Event; +import org.koshuke.stapler.simile.timeline.TimelineEventList; + +import java.io.IOException; +import java.util.Date; + +/** + * UI widget for showing the SMILE timeline control. + * + *

+ * Return this from your "getTimeline" method. + * + * @author Kohsuke Kawaguchi + * @since 1.372 + */ +public class BuildTimelineWidget { + protected final RunList builds; + + public BuildTimelineWidget(RunList builds) { + this.builds = builds; + } + + public Run getFirstBuild() { + return builds.getFirstBuild(); + } + + public Run getLastBuild() { + return builds.getLastBuild(); + } + + public TimelineEventList doData(StaplerRequest req, @QueryParameter long min, @QueryParameter long max) throws IOException { + TimelineEventList result = new TimelineEventList(); + for (Run r : builds.byTimestamp(min,max)) { + Event e = new Event(); + e.start = r.getTime(); + e.end = new Date(r.timestamp+r.getDuration()); + e.title = r.getFullDisplayName(); + // what to put in the description? + // e.description = "Longish description of event "+r.getFullDisplayName(); + // e.durationEvent = true; + e.link = req.getContextPath()+'/'+r.getUrl(); + BallColor c = r.getIconColor(); + e.color = String.format("#%06X",c.getBaseColor().darker().getRGB()&0xFFFFFF); + e.classname = "event-"+c.noAnime().toString()+" " + (c.isAnimated()?"animated":""); + result.add(e); + } + return result; + } + +} diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index 44d2f3c6e0f7f1d0169e40a59b5c8146eafda87d..d296f27dffc29d2e9f871cbf4e03384098d7a21d 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -60,7 +60,16 @@ public abstract class Cause { * By default, this method is used to render HTML as well. */ @Exported(visibility=3) - abstract public String getShortDescription(); + public abstract String getShortDescription(); + + /** + * Called when the cause is registered to {@link AbstractBuild}. + * + * @param build + * never null + * @since 1.376 + */ + public void onAddedTo(AbstractBuild build) {} /** * Report a line to the listener about this cause. diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java index 0baed2c71c2844588a80cf1056051602f13675d9..af2b9f6bc224bf21006d8be73a9df658a3e2587b 100644 --- a/core/src/main/java/hudson/model/CauseAction.java +++ b/core/src/main/java/hudson/model/CauseAction.java @@ -37,7 +37,7 @@ import java.util.List; import java.util.Map; @ExportedBean -public class CauseAction implements FoldableAction { +public class CauseAction implements FoldableAction, RunAction { /** * @deprecated since 2009-02-28 */ @@ -95,6 +95,26 @@ public class CauseAction implements FoldableAction { return causes.get(0).getShortDescription(); } + public void onLoad() { + // noop + } + + public void onBuildComplete() { + // noop + } + + /** + * When hooked up to build, notify {@link Cause}s. + */ + public void onAttached(Run owner) { + if (owner instanceof AbstractBuild) {// this should be always true but being defensive here + AbstractBuild b = (AbstractBuild) owner; + for (Cause c : causes) { + c.onAddedTo(b); + } + } + } + public void foldIntoExisting(hudson.model.Queue.Item item, Task owner, List otherActions) { CauseAction existing = item.getAction(CauseAction.class); if (existing!=null) { diff --git a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java index 1dae77d6a56d1e23ba8478ebb20587d5b7128183..82271214781c3213f4822b2dfa19551810d17b74 100755 --- a/core/src/main/java/hudson/model/ChoiceParameterDefinition.java +++ b/core/src/main/java/hudson/model/ChoiceParameterDefinition.java @@ -6,12 +6,10 @@ import org.kohsuke.stapler.export.Exported; import org.apache.commons.lang.StringUtils; import net.sf.json.JSONObject; import hudson.Extension; -import hudson.cli.CLICommand; import java.util.ArrayList; import java.util.List; import java.util.Arrays; -import java.io.IOException; /** * @author huybrechts diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index a79cdbb6cb463049709ceb98caf16b0f4bc4530c..cb32a629a68a39df17fdc612746dcdb367a97acb 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1,7 +1,8 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, + * Red Hat, Inc., Seiji Sogabe, Stephen Connolly, Thomas J. Black, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,6 +29,7 @@ import hudson.Util; import hudson.cli.declarative.CLIMethod; import hudson.console.AnnotatedLargeText; import hudson.model.Descriptor.FormException; +import hudson.model.queue.WorkUnit; import hudson.node_monitors.NodeMonitor; import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; @@ -281,6 +283,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @CLIMethod(name="connect-node") public void cliConnect(@Option(name="-f",usage="Cancel any currently pending connect operation and retry from scratch") boolean force) throws ExecutionException, InterruptedException { + checkPermission(Hudson.ADMINISTER); connect(force).get(); } @@ -336,6 +339,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @CLIMethod(name="disconnect-node") public void cliDisconnect(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException { + checkPermission(Hudson.ADMINISTER); disconnect(new ByCLI(cause)).get(); } @@ -344,11 +348,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces */ @CLIMethod(name="offline-node") public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException { + checkPermission(Hudson.ADMINISTER); setTemporarilyOffline(true,new ByCLI(cause)); } @CLIMethod(name="online-node") public void cliOnline() throws ExecutionException, InterruptedException { + checkPermission(Hudson.ADMINISTER); setTemporarilyOffline(false,null); } @@ -390,6 +396,10 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces return getNode().getSelfLabel().loadStatistics; } + public BuildTimelineWidget getTimeline() { + return new BuildTimelineWidget(getBuilds()); + } + /** * {@inheritDoc} */ @@ -782,7 +792,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces LOGGER.fine(address+" is not an IPv4 address"); continue; } - if(!ia.isReachable(3000)) { + if(!ComputerPinger.checkIsReachable(ia, 3)) { LOGGER.fine(address+" didn't respond to ping"); continue; } @@ -805,7 +815,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces /** * Starts executing a fly-weight task. */ - /*package*/ final void startFlyWeightTask(Queue.BuildableItem p) { + /*package*/ final void startFlyWeightTask(WorkUnit p) { OneOffExecutor e = new OneOffExecutor(this, p); e.start(); oneOffExecutors.add(e); diff --git a/core/src/main/java/hudson/model/ComputerPinger.java b/core/src/main/java/hudson/model/ComputerPinger.java new file mode 100644 index 0000000000000000000000000000000000000000..57ea2385a16713d7016d25f8597ec971eb35f4b4 --- /dev/null +++ b/core/src/main/java/hudson/model/ComputerPinger.java @@ -0,0 +1,65 @@ +package hudson.model; + +import hudson.Extension; +import hudson.ExtensionList; +import hudson.ExtensionPoint; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.logging.Logger; + +/** + * A way to see if a computer is reachable. + * + * @since 1.378 + */ +public abstract class ComputerPinger implements ExtensionPoint { + /** + * Is the specified address reachable? + * + * @param ia The address to check. + * @param timeout Timeout in seconds. + */ + public abstract boolean isReachable(InetAddress ia, int timeout) throws IOException; + + /** + * Get all registered instances. + */ + public static ExtensionList all() { + return Hudson.getInstance().getExtensionList(ComputerPinger.class); + } + + /** + * Is this computer reachable via the given address? + * + * @param ia The address to check. + * @param timeout Timeout in seconds. + */ + public static boolean checkIsReachable(InetAddress ia, int timeout) throws IOException { + for (ComputerPinger pinger : ComputerPinger.all()) { + try { + if (pinger.isReachable(ia, timeout)) { + return true; + } + } catch (IOException e) { + LOGGER.fine("Error checking reachability with " + pinger + ": " + e.getMessage()); + } + } + + return false; + } + + /** + * Default pinger - use Java built-in functionality. This doesn't always work, + * a host may be reachable even if this returns false. + */ + @Extension + public static class BuiltInComputerPinger extends ComputerPinger { + @Override + public boolean isReachable(InetAddress ia, int timeout) throws IOException { + return ia.isReachable(timeout * 1000); + } + } + + private static final Logger LOGGER = Logger.getLogger(ComputerPinger.class.getName()); +} diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java index 8f2e15422ff870fc7bce1cf67cb85c00a8ed243e..4d3f59d21894cee8fd998847ddcd935194666da5 100644 --- a/core/src/main/java/hudson/model/ComputerSet.java +++ b/core/src/main/java/hudson/model/ComputerSet.java @@ -301,7 +301,7 @@ public final class ComputerSet extends AbstractModelObject { /** * Accepts submission from the configuration page. */ - public final synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException { + public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException { BulkChange bc = new BulkChange(MONITORS_OWNER); try { Hudson.getInstance().checkPermission(Hudson.ADMINISTER); diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index 6afed9cfce6136e043b49ee65d6089c706403cbe..396ae5af4d20309082474a56802c74bf47cc67ab 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -23,6 +23,7 @@ */ package hudson.model; +import hudson.RelativePath; import hudson.XmlFile; import hudson.BulkChange; import hudson.Util; @@ -300,6 +301,10 @@ public abstract class Descriptor> implements Saveable { if (name==null || name.length()==0) continue; // unknown parameter name. we'll report the error when the form is submitted. + RelativePath rp = p.annotation(RelativePath.class); + if (rp!=null) + name = rp.value()+'/'+name; + if (query.length()==0) query.append("+qs(this)"); if (name.equals("value")) { @@ -346,6 +351,10 @@ public abstract class Descriptor> implements Saveable { if (name==null || name.length()==0) continue; // unknown parameter name. we'll report the error when the form is submitted. + RelativePath rp = p.annotation(RelativePath.class); + if (rp!=null) + name = rp.value()+'/'+name; + depends.add(name); continue; } @@ -357,6 +366,19 @@ public abstract class Descriptor> implements Saveable { return depends; } + /** + * Computes the auto-completion setting + */ + public void calcAutoCompleteSettings(String field, Map attributes) { + String capitalizedFieldName = StringUtils.capitalize(field); + String methodName = "doAutoComplete" + capitalizedFieldName; + Method method = ReflectionUtils.getPublicMethodNamed(getClass(), methodName); + if(method==null) + return; // no auto-completion + + attributes.put("autoCompleteUrl", String.format("%s/%s/autoComplete%s", getCurrentDescriptorByNameUrl(), getDescriptorUrl(), capitalizedFieldName)); + } + /** * Used by Jelly to abstract away the handlign of global.jelly vs config.jelly databinding difference. */ @@ -455,25 +477,39 @@ public abstract class Descriptor> implements Saveable { if(!Modifier.isAbstract(m.getDeclaringClass().getModifiers())) { // this class overrides newInstance(StaplerRequest). // maintain the backward compatible behavior - return newInstance(req); + return verifyNewInstance(newInstance(req)); } else { if (req==null) { // yes, req is supposed to be always non-null, but see the note above - return clazz.newInstance(); + return verifyNewInstance(clazz.newInstance()); } // new behavior as of 1.206 - return req.bindJSON(clazz,formData); + return verifyNewInstance(req.bindJSON(clazz,formData)); } } catch (NoSuchMethodException e) { throw new AssertionError(e); // impossible } catch (InstantiationException e) { - throw new Error(e); + throw new Error("Failed to instantiate "+clazz+" from "+formData,e); } catch (IllegalAccessException e) { - throw new Error(e); + throw new Error("Failed to instantiate "+clazz+" from "+formData,e); + } catch (RuntimeException e) { + throw new RuntimeException("Failed to instantiate "+clazz+" from "+formData,e); } } + /** + * Look out for a typical error a plugin developer makes. + * See http://hudson.361315.n4.nabble.com/Help-Hint-needed-Post-build-action-doesn-t-stay-activated-td2308833.html + */ + private T verifyNewInstance(T t) { + if (t!=null && t.getDescriptor()!=this) { + // TODO: should this be a fatal error? + LOGGER.warning("Father of "+ t+" and its getDescriptor() points to two different instances. Probably malplaced @Extension. See http://hudson.361315.n4.nabble.com/Help-Hint-needed-Post-build-action-doesn-t-stay-activated-td2308833.html"); + } + return t; + } + /** * Returns the resource path to the help screen HTML, if any. * @@ -570,22 +606,26 @@ public abstract class Descriptor> implements Saveable { } public String getGlobalConfigPage() { - return getViewPage(clazz, "global.jelly"); + return getViewPage(clazz, "global.jelly",null); } - protected final String getViewPage(Class clazz, String pageName) { + private String getViewPage(Class clazz, String pageName, String defaultValue) { while(clazz!=Object.class) { String name = clazz.getName().replace('.', '/').replace('$', '/') + "/" + pageName; if(clazz.getClassLoader().getResource(name)!=null) return '/'+name; clazz = clazz.getSuperclass(); } + return defaultValue; + } + + protected final String getViewPage(Class clazz, String pageName) { // We didn't find the configuration page. // Either this is non-fatal, in which case it doesn't matter what string we return so long as // it doesn't exist. // Or this error is fatal, in which case we want the developer to see what page he's missing. // so we put the page name. - return pageName; + return getViewPage(clazz,pageName,pageName); } diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java index a6cc8aa1b4cced10d8c55f8731056290f8d431e1..7e1fdee56e6aedf88fb01f3bc27b25b8d4b7654a 100644 --- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java +++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java @@ -129,7 +129,7 @@ public final class DirectoryBrowserSupport implements HttpResponse { * Instead of calling this method explicitly, just return the {@link DirectoryBrowserSupport} object * from the {@code doXYZ} method and let Stapler generate a response for you. */ - public final void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException { + public void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException { // handle form submission String pattern = req.getParameter("pattern"); if(pattern==null) diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index 0e918e1424b470c50f02f876881864535655d82b..98817a388b5376c31988a66b657ef27887bda11a 100644 --- a/core/src/main/java/hudson/model/DownloadService.java +++ b/core/src/main/java/hudson/model/DownloadService.java @@ -30,7 +30,6 @@ import hudson.util.IOUtils; import hudson.util.QuotedStringTokenizer; import hudson.util.TextFile; import hudson.util.TimeUnit2; -import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.Stapler; import java.io.File; diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index 192bb7dbfadda1369d99d73581b11facbb8cb150..4f021a4de3f572426d0b3505353ce22eba7ad80c 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -26,6 +26,9 @@ package hudson.model; import hudson.Util; import hudson.model.Queue.*; import hudson.FilePath; +import hudson.model.queue.SubTask; +import hudson.model.queue.Tasks; +import hudson.model.queue.WorkUnit; import hudson.util.TimeUnit2; import hudson.util.InterceptingProxy; import hudson.security.ACL; @@ -67,6 +70,8 @@ public class Executor extends Thread implements ModelObject { */ private volatile Queue.Executable executable; + private volatile WorkUnit workUnit; + private Throwable causeOfDeath; public Executor(Computer owner, int n) { @@ -85,6 +90,7 @@ public class Executor extends Thread implements ModelObject { finishTime = System.currentTimeMillis(); while(shouldRun()) { executable = null; + workUnit = null; synchronized(owner) { if(owner.getNumExecutors(), Stapl /** * Does this {@link View} has any associated user information recorded? */ - public final boolean hasPeople() { + public boolean hasPeople() { return View.People.isApplicable(items.values()); } @@ -1095,6 +1096,8 @@ public final class Hudson extends Node implements ItemGroup, Stapl } } getQueue().scheduleMaintenance(); + for (ComputerListener cl : ComputerListener.all()) + cl.onConfigurationChange(); } private void updateComputer(Node n, Map byNameMap, Set used) { @@ -1257,10 +1260,12 @@ public final class Hudson extends Node implements ItemGroup, Stapl if(v.getViewName().equals(name)) return v; } - // Fallback to subview of primary view if it is a ViewGroup - View pv = getPrimaryView(); - if (pv instanceof ViewGroup) - return ((ViewGroup)pv).getView(name); + if (name != null && !name.equals(primaryView)) { + // Fallback to subview of primary view if it is a ViewGroup + View pv = getPrimaryView(); + if (pv instanceof ViewGroup) + return ((ViewGroup)pv).getView(name); + } return null; } @@ -1335,7 +1340,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl } @CLIResolver - public Computer getComputer(String name) { + public Computer getComputer(@Argument(required=true,metaVar="NAME",usage="Node name") String name) { if(name.equals("(master)")) name = ""; @@ -1354,21 +1359,45 @@ public final class Hudson extends Node implements ItemGroup, Stapl return new ComputerSet(); } + /** * Gets the label that exists on this system by the name. * - * @return null if no name is null. - * @see Label#parse(String) + * @return null if name is null. + * @see Label#parseExpression(String) (String) */ - public Label getLabel(String name) { - if(name==null) return null; + public Label getLabel(String expr) { + if(expr==null) return null; while(true) { - Label l = labels.get(name); + Label l = labels.get(expr); if(l!=null) return l; // non-existent - labels.putIfAbsent(name,new Label(name)); + try { + labels.putIfAbsent(expr,Label.parseExpression(expr)); + } catch (ANTLRException e) { + // laxly accept it as a single label atom for backward compatibility + return getLabelAtom(expr); + } + } + } + + /** + * Returns the label atom of the given name. + */ + public LabelAtom getLabelAtom(String name) { + if (name==null) return null; + + while(true) { + Label l = labels.get(name); + if(l!=null) + return (LabelAtom)l; + + // non-existent + LabelAtom la = new LabelAtom(name); + if (labels.putIfAbsent(name, la)==null) + la.load(); } } @@ -1384,6 +1413,15 @@ public final class Hudson extends Node implements ItemGroup, Stapl return r; } + public Set getLabelAtoms() { + Set r = new TreeSet(); + for (Label l : labels.values()) { + if(!l.isEmpty() && l instanceof LabelAtom) + r.add((LabelAtom)l); + } + return r; + } + public Queue getQueue() { return queue; } @@ -1996,7 +2034,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl } catch (Exception e) { throw new IllegalArgumentException(e); } - + item.onCreatedFromScratch(); item.save(); items.put(name,item); @@ -2084,8 +2122,8 @@ public final class Hudson extends Node implements ItemGroup, Stapl } @Override - public Label getSelfLabel() { - return getLabel("master"); + public LabelAtom getSelfLabel() { + return getLabelAtom("master"); } public Computer createComputer() { @@ -2350,35 +2388,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl jdks.addAll(req.bindJSONToList(JDK.class,json.get("jdks"))); boolean result = true; - - for( Descriptor d : Builder.all() ) - result &= configureDescriptor(req,json,d); - - for( Descriptor d : Publisher.all() ) - result &= configureDescriptor(req,json,d); - - for( Descriptor d : BuildWrapper.all() ) - result &= configureDescriptor(req,json,d); - - for( SCMDescriptor scmd : SCM.all() ) - result &= configureDescriptor(req,json,scmd); - - for( TriggerDescriptor d : Trigger.all() ) - result &= configureDescriptor(req,json,d); - - for( JobPropertyDescriptor d : JobPropertyDescriptor.all() ) - result &= configureDescriptor(req,json,d); - - for( PageDecorator d : PageDecorator.all() ) - result &= configureDescriptor(req,json,d); - - for( Descriptor d : CrumbIssuer.all() ) - result &= configureDescriptor(req,json, d); - - for( ToolDescriptor d : ToolInstallation.all() ) - result &= configureDescriptor(req,json,d); - - for( TopLevelItemDescriptor d : TopLevelItemDescriptor.all() ) + for( Descriptor d : Functions.getSortedDescriptorsForGlobalConfig() ) result &= configureDescriptor(req,json,d); for( JSONObject o : StructuredForm.toList(json,"plugin")) @@ -2464,7 +2474,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl doQuietDown().generateResponse(null,rsp,this); } - public synchronized HttpRedirect doQuietDown() { + public synchronized HttpRedirect doQuietDown() throws IOException { try { return doQuietDown(false,0); } catch (InterruptedException e) { @@ -2473,32 +2483,19 @@ public final class Hudson extends Node implements ItemGroup, Stapl } @CLIMethod(name="quiet-down") - public synchronized HttpRedirect doQuietDown( + public HttpRedirect doQuietDown( @Option(name="-block",usage="Block until the system really quiets down and no builds are running") @QueryParameter boolean block, - @Option(name="-timeout",usage="If non-zero, only block up to the specified number of milliseconds") @QueryParameter int timeout) throws InterruptedException { - checkPermission(ADMINISTER); - isQuietingDown = true; + @Option(name="-timeout",usage="If non-zero, only block up to the specified number of milliseconds") @QueryParameter int timeout) throws InterruptedException, IOException { + synchronized (this) { + checkPermission(ADMINISTER); + isQuietingDown = true; + } if (block) { - LOGGER.info("Entering the blocking quiet down mode"); - long start = System.currentTimeMillis(); - int cnt=0; - while (true) { - if (!isQuietingDown) { - LOGGER.info("Quiet mode cancelled"); - break; - } - if (overallLoad.computeTotalExecutors() <= overallLoad.computeIdleExecutors()) {// should be really == but be defensive - LOGGER.info("System became fully quiet"); - break; - } - if (timeout>0 && start+timeout<=System.currentTimeMillis()) { - LOGGER.info("Quiet mode time out after "+timeout+"ms"); - break; - } - + if (timeout > 0) timeout += System.currentTimeMillis(); + while (isQuietingDown + && (timeout <= 0 || System.currentTimeMillis() < timeout) + && RestartListener.isAllReady()) { Thread.sleep(1000); - if (((cnt++)%60)==0) - LOGGER.info("Waiting for all the jobs to finish. Total="+overallLoad.computeTotalExecutors()+" idle="+overallLoad.computeIdleExecutors()); } } return new HttpRedirect("."); @@ -2838,19 +2835,33 @@ public final class Hudson extends Node implements ItemGroup, Stapl * For debugging. Expose URL to perform GC. */ public void doGc(StaplerResponse rsp) throws IOException { + checkPermission(Hudson.ADMINISTER); System.gc(); rsp.setStatus(HttpServletResponse.SC_OK); rsp.setContentType("text/plain"); rsp.getWriter().println("GCed"); } + /** + * Simulates OutOfMemoryError. + * Useful to make sure OutOfMemoryHeapDump setting. + */ + public void doSimulateOutOfMemory() throws IOException { + checkPermission(ADMINISTER); + + System.out.println("Creating artificial OutOfMemoryError situation"); + List args = new ArrayList(); + while (true) + args.add(new byte[1024*1024]); + } + private transient final Map duplexChannels = new HashMap(); /** * Handles HTTP requests for duplex channels for CLI. */ public void doCli(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException { - if(!"POST".equals(Stapler.getCurrentRequest().getMethod())) { + if (!"POST".equals(req.getMethod())) { // for GET request, serve _cli.jelly, assuming this is a browser checkPermission(READ); req.getView(this,"_cli.jelly").forward(req,rsp); @@ -2894,16 +2905,18 @@ public final class Hudson extends Node implements ItemGroup, Stapl * * This first replaces "app" to {@link HudsonIsRestarting} */ + @CLIMethod(name="restart") public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException { checkPermission(ADMINISTER); - if(Stapler.getCurrentRequest().getMethod().equals("GET")) { + if (req != null && req.getMethod().equals("GET")) { req.getView(this,"_restart.jelly").forward(req,rsp); return; } restart(); - rsp.sendRedirect2("."); + if (rsp != null) // null for CLI + rsp.sendRedirect2("."); } /** @@ -2913,28 +2926,30 @@ public final class Hudson extends Node implements ItemGroup, Stapl * * @since 1.332 */ + @CLIMethod(name="safe-restart") public void doSafeRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException { checkPermission(ADMINISTER); - if(Stapler.getCurrentRequest().getMethod().equals("GET")) { + if (req != null && req.getMethod().equals("GET")) { req.getView(this,"_safeRestart.jelly").forward(req,rsp); return; } safeRestart(); - rsp.sendRedirect2("."); + if (rsp != null) // null for CLI + rsp.sendRedirect2("."); } /** * Performs a restart. */ - @CLIMethod(name="restart") public void restart() throws RestartNotSupportedException { final Lifecycle lifecycle = Lifecycle.get(); lifecycle.verifyRestartable(); // verify that Hudson is restartable servletContext.setAttribute("app",new HudsonIsRestarting()); new Thread("restart thread") { + final String exitUser = getAuthentication().getName(); @Override public void run() { try { @@ -2942,6 +2957,9 @@ public final class Hudson extends Node implements ItemGroup, Stapl // give some time for the browser to load the "reloading" page Thread.sleep(5000); + LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser)); + for (RestartListener listener : RestartListener.all()) + listener.onRestart(); lifecycle.restart(); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Failed to restart Hudson",e); @@ -2956,7 +2974,6 @@ public final class Hudson extends Node implements ItemGroup, Stapl * Queues up a restart to be performed once there are no builds currently running. * @since 1.332 */ - @CLIMethod(name="safe-restart") public void safeRestart() throws RestartNotSupportedException { final Lifecycle lifecycle = Lifecycle.get(); lifecycle.verifyRestartable(); // verify that Hudson is restartable @@ -2964,6 +2981,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl isQuietingDown = true; new Thread("safe-restart thread") { + final String exitUser = getAuthentication().getName(); @Override public void run() { try { @@ -2976,8 +2994,11 @@ public final class Hudson extends Node implements ItemGroup, Stapl if (isQuietingDown) { servletContext.setAttribute("app",new HudsonIsRestarting()); // give some time for the browser to load the "reloading" page - LOGGER.info("Restart in 5 seconds"); - Thread.sleep(5000); + LOGGER.info("Restart in 10 seconds"); + Thread.sleep(10000); + LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser)); + for (RestartListener listener : RestartListener.all()) + listener.onRestart(); lifecycle.restart(); } else { LOGGER.info("Safe-restart mode cancelled"); @@ -2998,7 +3019,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException { checkPermission(ADMINISTER); LOGGER.severe(String.format("Shutting down VM as requested by %s from %s", - getAuthentication(), req.getRemoteAddr())); + getAuthentication().getName(), req.getRemoteAddr())); rsp.setStatus(HttpServletResponse.SC_OK); rsp.setContentType("text/plain"); PrintWriter w = rsp.getWriter(); @@ -3021,7 +3042,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl w.println("Shutting down as soon as all jobs are complete"); w.close(); isQuietingDown = true; - final String exitUser = getAuthentication().toString(); + final String exitUser = getAuthentication().getName(); final String exitAddr = req.getRemoteAddr().toString(); new Thread("safe-exit thread") { @Override @@ -3037,6 +3058,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl } // Make sure isQuietingDown is still true. if (isQuietingDown) { + cleanUp(); System.exit(0); } } catch (InterruptedException e) { diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index 3917c1f707e478708eb315becdcf4e799a86276f..d35476dc4349675cd2c78bed7ee45a798afc6a1f 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -177,6 +177,12 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont */ void onCopiedFrom(Item src); + /** + * When an item is created from scratch (instead of copied), + * this method will be invoked. Used as the post-construction initialization. + */ + void onCreatedFromScratch(); + /** * Save the settings to a file. * diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index d34d43d63f70752100148f579d501c959b069e1d..060a713c4de3d4d4bb1dd021a7f0ac9c76d3e562 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -27,7 +27,6 @@ import com.thoughtworks.xstream.XStream; import hudson.XmlFile; import hudson.DescriptorExtensionList; import hudson.Extension; -import hudson.scm.RepositoryBrowser; import hudson.matrix.MatrixProject; import hudson.matrix.MatrixConfiguration; import hudson.matrix.Axis; @@ -67,7 +66,7 @@ public class Items { } /** - * Converts a list of items into a camma-separated full names. + * Converts a list of items into a comma-separated list of full names. */ public static String toNameList(Collection items) { StringBuilder buf = new StringBuilder(); @@ -82,7 +81,7 @@ public class Items { /** * Does the opposite of {@link #toNameList(Collection)}. */ - public static List fromNameList(String list,Class type) { + public static List fromNameList(String list, Class type) { Hudson hudson = Hudson.getInstance(); List r = new ArrayList(); diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index f79db3021a78f1f52596f79e56f4ba8e9bfff26f..4ea567d005b2ec556b45daa61b0f96768c1dd484 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -26,6 +26,7 @@ package hudson.model; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import hudson.ExtensionPoint; import hudson.Util; import hudson.XmlFile; @@ -66,12 +67,9 @@ import java.io.IOException; import java.io.StringWriter; import java.io.PrintWriter; import java.net.URLEncoder; -import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.SortedMap; @@ -100,15 +98,12 @@ import org.jfree.chart.renderer.category.StackedAreaRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.ui.RectangleInsets; import org.jvnet.localizer.Localizable; -import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.WebMethod; import org.kohsuke.stapler.export.Exported; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; -import org.koshuke.stapler.simile.timeline.Event; -import org.koshuke.stapler.simile.timeline.TimelineEventList; /** * A job is an runnable entity under the monitoring of Hudson. @@ -585,13 +580,14 @@ public abstract class Job, RunT extends Run getBuilds() { - return new ArrayList(_getRuns().values()); + @WithBridgeMethods(List.class) + public RunList getBuilds() { + return RunList.fromRuns(_getRuns().values()); } /** @@ -643,32 +639,12 @@ public abstract class Job, RunT extends Run getBuildsByTimestamp(long start, long end) { - final List builds = getBuilds(); - AbstractList TIMESTAMP_ADAPTER = new AbstractList() { - public Long get(int index) { - return builds.get(index).timestamp; - } - - public int size() { - return builds.size(); - } - }; - Comparator DESCENDING_ORDER = new Comparator() { - public int compare(Long o1, Long o2) { - if (o1 > o2) return -1; - if (o1 < o2) return +1; - return 0; - } - }; - - int s = Collections.binarySearch(TIMESTAMP_ADAPTER, start, DESCENDING_ORDER); - if (s<0) s=-(s+1); // min is inclusive - int e = Collections.binarySearch(TIMESTAMP_ADAPTER, end, DESCENDING_ORDER); - if (e<0) e=-(e+1); else e++; // max is exclusive, so the exact match should be excluded - - return builds.subList(e,s); + @WithBridgeMethods(List.class) + public RunList getBuildsByTimestamp(long start, long end) { + return getBuilds().byTimestamp(start,end); } @CLIResolver @@ -1364,12 +1340,12 @@ public abstract class Job, RunT extends Run, RunT extends Run> implements Describable getProjectActions(AbstractProject project) { return getJobActions((J)project); } + + /** + * Contributes {@link SubTask}s to {@link AbstractProject#getSubTasks()} + * + * @since 1.FATTASK + */ + public Collection getSubTasks() { + return Collections.emptyList(); + } } diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index c4a548cd2c8aa7893e8a522ecd704a4ab842b8a1..528c59117883cfa46d371baf8a3493320208eda1 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,13 +23,23 @@ */ package hudson.model; +import antlr.ANTLRException; import hudson.Util; import static hudson.Util.fixNull; + +import hudson.model.labels.LabelAtom; +import hudson.model.labels.LabelExpression; +import hudson.model.labels.LabelExpressionLexer; +import hudson.model.labels.LabelExpressionParser; +import hudson.model.labels.LabelOperatorPrecedence; import hudson.slaves.NodeProvisioner; import hudson.slaves.Cloud; +import hudson.util.QuotedStringTokenizer; +import hudson.util.VariableResolver; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -52,14 +62,17 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader; * @see Hudson#getLabel(String) */ @ExportedBean -public class Label implements Comparable