提交 380b7269 编写于 作者: O Olivier Lamy

Merge branch 'master' of github.com:hudson/hudson

......@@ -39,6 +39,15 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=rfe>
Added a new extension point to support external login mechanisms.
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.394>What's new in 1.394</a> <!--=DATE=--></h3>
<ul class=image>
<li class=bug> Parsing poms fails if a module is a path to a pom (and not to a directory)
(<a href="http://issues.hudson-ci.org/browse/HUDSON-8445">issue 8445</a>)
......@@ -54,14 +63,13 @@ Upcoming changes</a>
(<a href="http://issues.hudson-ci.org/browse/HUDSON-8462">issue 8462</a>)
<li class=rfe>
Block build when downstream projects are building.
(<a href="http://issues.hudson-ci.org/browse/HUDSON-7046">issue 7046</a>)
(<a href="http://issues.hudson-ci.org/browse/HUDSON-7046">issue 7046</a>)
<li class=bug> nonRecursive option is not honored anymore when parsing pom
(<a href="http://issues.hudson-ci.org/browse/HUDSON-8484">issue 8484</a>)
<li class=ref>
Maven 3 support : display same logging output as a maven build with the cli
(<a href="http://issues.hudson-ci.org/browse/HUDSON-8490">issue 8490</a>)
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.394>What's new in 1.394</a> <!--=DATE=--></h3>
<!--=RC-CHANGES=-->
</div><!--=END=-->
<h3><a name=v1.393>What's new in 1.393</a> (2011/01/09)</h3>
<ul class=image>
......
......@@ -4,7 +4,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jvnet.hudson.main</groupId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
<name>Hudson CLI</name>
......
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......@@ -61,7 +61,7 @@ THE SOFTWARE.
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<version>1.3</version>
<version>1.4</version>
<executions>
<execution>
<goals>
......@@ -410,7 +410,7 @@ THE SOFTWARE.
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<version>1.2</version>
<version>1.4</version>
</dependency>
<dependency><!-- until we get this version through Stapler -->
......@@ -730,7 +730,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>jmdns</artifactId>
<version>3.1.6-hudson-2</version>
<version>3.2.1-hudson-1</version>
</dependency>
<dependency>
<groupId>com.sun.winsw</groupId>
......
......@@ -30,6 +30,7 @@ import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
......@@ -1273,6 +1274,10 @@ public class Functions {
if (o instanceof Secret) return ((Secret)o).getEncryptedValue();
return o.toString();
}
public List filterDescriptors(Object context, Iterable descriptors) {
return DescriptorVisibilityFilter.apply(context,descriptors);
}
private static final Pattern SCHEME = Pattern.compile("[a-z]+://.+");
......
......@@ -303,6 +303,27 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
private static final long serialVersionUID = 1L;
}
/**
* Convenience method for subtypes to obtain environment variables of the client.
*/
protected String getClientEnvironmentVariable(String name) throws IOException, InterruptedException {
return channel.call(new GetEnvironmentVariable(name));
}
private static final class GetEnvironmentVariable implements Callable<String, IOException> {
private final String name;
private GetEnvironmentVariable(String name) {
this.name = name;
}
public String call() throws IOException {
return System.getenv(name);
}
private static final long serialVersionUID = 1L;
}
/**
* Creates a clone to be used to execute a command.
*/
......
......@@ -25,7 +25,10 @@ package hudson.cli;
import groovy.lang.GroovyShell;
import groovy.lang.Binding;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Run;
import hudson.remoting.Callable;
import hudson.AbortException;
import hudson.Extension;
......@@ -71,6 +74,17 @@ public class GroovyCommand extends CLICommand implements Serializable {
Binding binding = new Binding();
binding.setProperty("out",new PrintWriter(stdout,true));
String j = getClientEnvironmentVariable("JOB_NAME");
if (j!=null) {
Item job = Hudson.getInstance().getItemByFullName(j);
binding.setProperty("currentJob", job);
String b = getClientEnvironmentVariable("BUILD_NUMBER");
if (b!=null && job instanceof AbstractProject) {
Run r = ((AbstractProject) job).getBuildByNumber(Integer.parseInt(b));
binding.setProperty("currentBuild", r);
}
}
GroovyShell groovy = new GroovyShell(binding);
groovy.run(loadScript(),"RemoteClass",remaining.toArray(new String[remaining.size()]));
return 0;
......
......@@ -40,6 +40,7 @@ import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue.FlyweightTask;
import hudson.model.ResourceController;
import hudson.model.Result;
......@@ -55,6 +56,8 @@ import hudson.tasks.Publisher;
import hudson.triggers.Trigger;
import hudson.util.CopyOnWriteMap;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.FormValidation.Kind;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
......@@ -93,7 +96,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
private volatile AxisList axes = new AxisList();
/**
* The filter that is applied to combinatios. It is a Groovy if condition.
* The filter that is applied to combinations. It is a Groovy if condition.
* This can be null, which means "true".
*
* @see #getCombinationFilter()
......@@ -148,7 +151,11 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
private String customWorkspace;
public MatrixProject(String name) {
super(Hudson.getInstance(), name);
this(Hudson.getInstance(), name);
}
public MatrixProject(ItemGroup parent, String name) {
super(parent, name);
}
public AxisList getAxes() {
......@@ -460,6 +467,10 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
throw new UnsupportedOperationException();
}
public void onDeleted(MatrixConfiguration item) throws IOException {
// noop
}
public File getRootDirFor(Combination combination) {
File f = getConfigurationsDir();
for (Entry<String, String> e : combination.entrySet())
......@@ -468,11 +479,6 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return f;
}
@Override
public Hudson getParent() {
return Hudson.getInstance();
}
/**
* @see #getJDKs()
*/
......@@ -617,7 +623,10 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
private void checkAxisNames(Iterable<Axis> newAxes) throws FormException {
HashSet<String> axisNames = new HashSet<String>();
for (Axis a : newAxes) {
a.getDescriptor().doCheckName(a.getName());
FormValidation fv = a.getDescriptor().doCheckName(a.getName());
if (fv.kind!=Kind.OK)
throw new FormException(Messages.MatrixProject_DuplicateAxisName(),fv,"axis.name");
if (axisNames.contains(a.getName()))
throw new FormException(Messages.MatrixProject_DuplicateAxisName(),"axis.name");
axisNames.add(a.getName());
......@@ -648,8 +657,8 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return Messages.MatrixProject_DisplayName();
}
public MatrixProject newInstance(String name) {
return new MatrixProject(name);
public MatrixProject newInstance(ItemGroup parent, String name) {
return new MatrixProject(parent,name);
}
/**
......
......@@ -24,6 +24,7 @@
*/
package hudson.model;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.XmlFile;
import hudson.Util;
import hudson.Functions;
......@@ -115,6 +116,10 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
return parent.getRootDirFor(this);
}
/**
* This bridge method is to maintain binary compatibility with {@link TopLevelItem#getParent()}.
*/
@WithBridgeMethods(value=Hudson.class,castRequired=true)
public ItemGroup getParent() {
assert parent!=null;
return parent;
......@@ -407,12 +412,23 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
checkPermission(DELETE);
performDelete();
if(this instanceof TopLevelItem)
Hudson.getInstance().deleteJob((TopLevelItem)this);
try {
invokeOnDeleted();
} catch (AbstractMethodError e) {
// ignore
}
Hudson.getInstance().rebuildDependencyGraph();
}
/**
* A pointless function to work around what appears to be a HotSpot problem. See HUDSON-5756 and bug 6933067
* on BugParade for more details.
*/
private void invokeOnDeleted() throws IOException {
getParent().onDeleted(this);
}
/**
* Does the real job of deleting the item.
*/
......
......@@ -511,9 +511,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
@Exported
public String getIcon() {
if(isOffline())
return "computer-x.gif";
return "computer-x.png";
else
return "computer.gif";
return "computer.png";
}
public String getIconAltText() {
......
package hudson.model;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.scm.SCMDescriptor;
import java.util.ArrayList;
import java.util.List;
/**
* Hides {@link Descriptor}s from users.
*
* @author Kohsuke Kawaguchi
* @since 1.393
*/
public abstract class DescriptorVisibilityFilter implements ExtensionPoint {
/**
* Decides if the given descriptor should be visible to the user.
*
* @param context
* The object that indicates where the visibility of a descriptor is evaluated.
* For example, if Hudson is deciding whether a {@link FreeStyleProject} should gets a
* {@link SCMDescriptor}, the context object will be the {@link FreeStyleProject}.
* The caller can pass in null if there's no context.
* @param descriptor
* Descriptor whose visibility is evaluated. Never null.
*
* @return
* true to allow the descriptor to be visible. false to hide it.
* If any of the installed {@link DescriptorVisibilityFilter} returns false,
* the descriptor is not shown.
*/
public abstract boolean filter(Object context, Descriptor descriptor);
public static ExtensionList<DescriptorVisibilityFilter> all() {
return Hudson.getInstance().getExtensionList(DescriptorVisibilityFilter.class);
}
public static <T extends Descriptor> List<T> apply(Object context, Iterable<T> source) {
ExtensionList<DescriptorVisibilityFilter> filters = all();
List<T> r = new ArrayList<T>();
OUTER:
for (T d : source) {
for (DescriptorVisibilityFilter f : filters) {
if (!f.filter(context,d))
continue OUTER; // veto-ed. not shown
}
r.add(d);
}
return r;
}
}
......@@ -24,12 +24,20 @@
package hudson.model;
import hudson.EnvVars;
import hudson.model.Queue.Task;
import hudson.tasks.Builder;
import hudson.tasks.BuildWrapper;
/**
* {@link Action} that contributes environment variables during a build.
*
* <p>
* For example, your {@link Builder} can add an {@link EnvironmentContributingAction} so that
* the rest of the builders or publishers see some behavior changes.
*
* Another use case is for you to {@linkplain Queue#schedule(Task, int, Action...) submit a job} with
* {@link EnvironmentContributingAction}s.
*
* @author Kohsuke Kawaguchi
* @since 1.318
* @see AbstractBuild#getEnvironment(TaskListener)
......@@ -39,14 +47,10 @@ public interface EnvironmentContributingAction extends Action {
/**
* Called by {@link AbstractBuild} to allow plugins to contribute environment variables.
*
* <p>
* For example, your {@link Builder} can add an {@link EnvironmentContributingAction} so that
* the rest of the builders or publishers see some behavior changes.
*
* @param build
* The calling build. Never null.
* @param env
* Evironment variables should be added to this map.
* Environment variables should be added to this map.
*/
public void buildEnvVars(AbstractBuild<?,?> build, EnvVars env);
}
......@@ -42,12 +42,11 @@ import java.io.IOException;
*/
public class ExternalJob extends ViewJob<ExternalJob,ExternalRun> implements TopLevelItem {
public ExternalJob(String name) {
super(Hudson.getInstance(),name);
this(Hudson.getInstance(),name);
}
@Override
public Hudson getParent() {
return (Hudson)super.getParent();
public ExternalJob(ItemGroup parent, String name) {
super(parent,name);
}
@Override
......@@ -119,8 +118,8 @@ public class ExternalJob extends ViewJob<ExternalJob,ExternalRun> implements Top
return Messages.ExternalJob_DisplayName();
}
public ExternalJob newInstance(String name) {
return new ExternalJob(name);
public ExternalJob newInstance(ItemGroup parent, String name) {
return new ExternalJob(parent,name);
}
}
}
......@@ -46,18 +46,20 @@ public class FreeStyleProject extends Project<FreeStyleProject,FreeStyleBuild> i
*/
private String customWorkspace;
/**
* @deprecated as of 1.390
*/
public FreeStyleProject(Hudson parent, String name) {
super(parent, name);
}
@Override
protected Class<FreeStyleBuild> getBuildClass() {
return FreeStyleBuild.class;
public FreeStyleProject(ItemGroup parent, String name) {
super(parent, name);
}
@Override
public Hudson getParent() {
return Hudson.getInstance();
protected Class<FreeStyleBuild> getBuildClass() {
return FreeStyleBuild.class;
}
public String getCustomWorkspace() {
......@@ -109,8 +111,8 @@ public class FreeStyleProject extends Project<FreeStyleProject,FreeStyleBuild> i
return Messages.FreeStyleProject_DisplayName();
}
public FreeStyleProject newInstance(String name) {
return new FreeStyleProject(Hudson.getInstance(),name);
public FreeStyleProject newInstance(ItemGroup parent, String name) {
return new FreeStyleProject(parent,name);
}
}
}
/*
* The MIT License
*
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe,
* Stephen Connolly, Tom Huybrechts, Yahoo! Inc., Alan Harder
*
*
* 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
......@@ -81,6 +81,7 @@ import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.BasicAuthenticationFilter;
import hudson.security.FederatedLoginService;
import hudson.security.HudsonFilter;
import hudson.security.LegacyAuthorizationStrategy;
import hudson.security.LegacySecurityRealm;
......@@ -125,7 +126,6 @@ import hudson.util.TextFile;
import hudson.util.VersionNumber;
import hudson.util.XStream2;
import hudson.util.Service;
import hudson.util.IOUtils;
import hudson.views.DefaultMyViewsTabBar;
import hudson.views.DefaultViewsTabBar;
import hudson.views.MyViewsTabBar;
......@@ -263,7 +263,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
// this field needs to be at the very top so that other components can look at this value even during unmarshalling
private String version = "1.0";
/**
* Number of executors of the master node.
*/
......@@ -419,9 +419,9 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* This is {@link Integer} so that we can initialize it to '5' for upgrading users.
*/
/*package*/ Integer quietPeriod;
/**
* Global default for {@link AbstractProject#getScmCheckoutRetryCount()}
* Global default for {@link AbstractProject#getScmCheckoutRetryCount()}
*/
/*package*/ int scmCheckoutRetryCount;
......@@ -483,7 +483,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* {@link hudson.security.csrf.CrumbIssuer}
*/
private volatile CrumbIssuer crumbIssuer;
/**
* All labels known to Hudson. This allows us to reuse the same label instances
* as much as possible, even though that's not a strict requirement.
......@@ -538,6 +538,39 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
private transient final AdjunctManager adjuncts;
/**
* Code that handles {@link ItemGroup} work.
*/
private transient final ItemGroupMixIn itemGroupMixIn = new ItemGroupMixIn(this,this) {
@Override
protected void add(TopLevelItem item) {
items.put(item.getName(),item);
}
@Override
protected File getRootDirFor(String name) {
return Hudson.this.getRootDirFor(name);
}
/**
*send the browser to the config page
* use View to trim view/{default-view} from URL if possible
*/
@Override
protected String redirectAfterCreateItem(StaplerRequest req, TopLevelItem result) throws IOException {
String redirect = result.getUrl()+"configure";
List<Ancestor> ancestors = req.getAncestors();
for (int i = ancestors.size() - 1; i >= 0; i--) {
Object o = ancestors.get(i).getObject();
if (o instanceof View) {
redirect = req.getContextPath() + '/' + ((View)o).getUrl() + redirect;
break;
}
}
return redirect;
}
};
@CLIResolver
public static Hudson getInstance() {
return theInstance;
......@@ -586,12 +619,12 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
throw new IllegalStateException("second instance");
theInstance = this;
// doing this early allows InitStrategy to set environment upfront
// doing this early allows InitStrategy to set environment upfront
final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
Trigger.timer = new Timer("Hudson cron thread");
queue = new Queue(CONSISTENT_HASH?LoadBalancer.CONSISTENT_HASH:LoadBalancer.DEFAULT);
try {
dependencyGraph = DependencyGraph.EMPTY;
} catch (InternalError e) {
......@@ -725,7 +758,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*
* <p>
* At this point plugins are not loaded yet, so we fall back to the META-INF/services look up to discover implementations.
* As such there's no way for plugins to participate into this process.
* As such there's no way for plugins to participate into this process.
*/
private ReactorListener buildReactorListener() throws IOException {
List<ReactorListener> r = (List) Service.loadInstances(Thread.currentThread().getContextClassLoader(), InitReactorListener.class);
......@@ -959,7 +992,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
throw new AssertionError(type+" is missing its descriptor");
return d;
}
/**
* Gets the {@link Descriptor} instance in the current Hudson by its type.
*/
......@@ -1099,6 +1132,18 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
save();
}
public FederatedLoginService getFederatedLoginService(String name) {
for (FederatedLoginService fls : FederatedLoginService.all()) {
if (fls.getUrlName().equals(name))
return fls;
}
return null;
}
public List<FederatedLoginService> getFederatedLoginServices() {
return FederatedLoginService.all();
}
public Launcher createLauncher(TaskListener listener) {
return new LocalLauncher(listener).decorateFor(this);
}
......@@ -1214,7 +1259,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
if (item.hasPermission(Item.READ))
viewableItems.add(item);
}
return viewableItems;
}
......@@ -1222,7 +1267,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* Returns the read-only view of all the {@link TopLevelItem}s keyed by their names.
* <p>
* This method is efficient, as it doesn't involve any copying.
*
*
* @since 1.296
*/
public Map<String,TopLevelItem> getItemMap() {
......@@ -1336,7 +1381,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
views.remove(view);
save();
}
public ViewsTabBar getViewsTabBar() {
return viewsTabBar;
}
......@@ -1659,15 +1704,15 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public int getQuietPeriod() {
return quietPeriod!=null ? quietPeriod : 5;
}
/**
* Gets the global SCM check out retry count.
*/
public int getScmCheckoutRetryCount() {
return scmCheckoutRetryCount;
}
/**
* @deprecated
......@@ -1822,7 +1867,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public boolean isUseCrumbs() {
return crumbIssuer!=null;
}
/**
* Returns the constant that captures the three basic security modes
* in Hudson.
......@@ -1954,7 +1999,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*
* @return
* {@link InitMilestone#STARTED} even if the initialization hasn't been started, so that this method
* never returns null.
* never returns null.
*/
public InitMilestone getInitLevel() {
return initLevel;
......@@ -2072,25 +2117,8 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* @throws IllegalArgumentException
* if a project of the give name already exists.
*/
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify )
throws IOException {
if(items.containsKey(name))
throw new IllegalArgumentException("Project of the name "+name+" already exists");
TopLevelItem item;
try {
item = type.newInstance(name);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
item.onCreatedFromScratch();
item.save();
items.put(name,item);
if (notify)
ItemListener.fireOnCreated(item);
return item;
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException {
return itemGroupMixIn.createProject(type,name,notify);
}
/**
......@@ -2125,28 +2153,28 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
/**
* Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
* Called by {@link Job#renameTo(String)} to update relevant data structure.
* assumed to be synchronized on Hudson by the caller.
*/
/*package*/ void deleteJob(TopLevelItem item) throws IOException {
for (ItemListener l : ItemListener.all())
l.onDeleted(item);
public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException {
items.remove(oldName);
items.put(newName,job);
items.remove(item.getName());
for (View v : views)
v.onJobRenamed(item, item.getName(), null);
v.onJobRenamed(job, oldName, newName);
save();
}
/**
* Called by {@link Job#renameTo(String)} to update relevant data structure.
* assumed to be synchronized on Hudson by the caller.
* Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
*/
public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException {
items.remove(oldName);
items.put(newName,job);
public void onDeleted(TopLevelItem item) throws IOException {
for (ItemListener l : ItemListener.all())
l.onDeleted(item);
items.remove(item.getName());
for (View v : views)
v.onJobRenamed(job, oldName, newName);
v.onJobRenamed(item, item.getName(), null);
save();
}
......@@ -2463,7 +2491,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
label = json.optString("labelString","");
quietPeriod = json.getInt("quiet_period");
scmCheckoutRetryCount = json.getInt("retry_count");
systemMessage = Util.nullify(req.getParameter("system_message"));
......@@ -2501,7 +2529,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public CrumbIssuer getCrumbIssuer() {
return crumbIssuer;
}
public void setCrumbIssuer(CrumbIssuer issuer) {
crumbIssuer = issuer;
}
......@@ -2600,67 +2628,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
}
public synchronized Item doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
checkPermission(Job.CREATE);
TopLevelItem result;
String requestContentType = req.getContentType();
if(requestContentType==null) {
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST,"No Content-Type header set");
return null;
}
boolean isXmlSubmission = requestContentType.startsWith("application/xml") || requestContentType.startsWith("text/xml");
String name = req.getParameter("name");
if(name==null) {
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST,"Query parameter 'name' is required");
return null;
}
name = checkJobName(name);
String mode = req.getParameter("mode");
if(mode!=null && mode.equals("copy")) {
String from = req.getParameter("from");
TopLevelItem src = getItem(from);
if(src==null) {
rsp.setStatus(SC_BAD_REQUEST);
if(Util.fixEmpty(from)==null)
sendError("Specify which job to copy",req,rsp);
else
sendError("No such job: "+from,req,rsp);
return null;
}
result = copy(src,name);
} else {
if(isXmlSubmission) {
result = createProjectFromXML(name, req.getInputStream());
rsp.setStatus(HttpServletResponse.SC_OK);
return result;
} else {
if(mode==null) {
rsp.sendError(SC_BAD_REQUEST);
return null;
}
// create empty job and redirect to the project config screen
result = createProject(Items.getDescriptor(mode), name);
}
}
// send the browser to the config page
// use View to trim view/{default-view} from URL if possible
String redirect = result.getUrl()+"configure";
List<Ancestor> ancestors = req.getAncestors();
for (int i = ancestors.size() - 1; i >= 0; i--) {
Object o = ancestors.get(i).getObject();
if (o instanceof View) {
redirect = req.getContextPath() + '/' + ((View)o).getUrl() + redirect;
break;
}
}
rsp.sendRedirect2(redirect);
return result;
return itemGroupMixIn.createTopLevelItem(req, rsp);
}
/**
......@@ -2669,25 +2637,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* @since 1.319
*/
public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
// place it as config.xml
File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
configXml.getParentFile().mkdirs();
try {
IOUtils.copy(xml,configXml);
// load it
TopLevelItem result = (TopLevelItem)Items.load(this,configXml.getParentFile());
items.put(name,result);
ItemListener.fireOnCreated(result);
rebuildDependencyGraph();
return result;
} catch (IOException e) {
// if anything fails, delete the config file to avoid further confusion
Util.deleteRecursive(configXml.getParentFile());
throw e;
}
return itemGroupMixIn.createProjectFromXML(name,xml);
}
/**
......@@ -2702,19 +2652,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
*/
@SuppressWarnings({"unchecked"})
public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
T result = (T)createProject(src.getDescriptor(),name,false);
// copy config
Util.copyFile(Items.getConfigFile(src).getFile(),Items.getConfigFile(result).getFile());
// reload from the new config
result = (T)Items.load(this,result.getRootDir());
result.onCopiedFrom(src);
items.put(name,result);
ItemListener.fireOnCopied(src,result);
return result;
return itemGroupMixIn.copy(src,name);
}
// a little more convenient overloading that assumes the caller gives us the right type
......@@ -2897,7 +2835,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
// Parse the request
MultipartFormDataParser p = new MultipartFormDataParser(req);
if(Hudson.getInstance().isUseCrumbs() && !Hudson.getInstance().getCrumbIssuer().validateCrumb(req, p)) {
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
}
try {
rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
......@@ -2999,7 +2937,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* Queues up a restart of Hudson for when there are no builds running, if we can.
*
* This first replaces "app" to {@link HudsonIsRestarting}
*
*
* @since 1.332
*/
@CLIMethod(name="safe-restart")
......@@ -3055,7 +2993,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
lifecycle.verifyRestartable(); // verify that Hudson is restartable
// Quiet down so that we won't launch new builds.
isQuietingDown = true;
new Thread("safe-restart thread") {
final String exitUser = getAuthentication().getName();
@Override
......@@ -3105,7 +3043,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
System.exit(0);
}
/**
* Shutdown the system safely.
* @since 1.332
......@@ -3268,7 +3206,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
// this method can be used to check if a file exists anywhere in the file system,
// so it should be protected.
checkPermission(Item.CREATE);
if(fixEmpty(value)==null)
return FormValidation.ok();
......@@ -3470,6 +3408,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
|| rest.startsWith("/tcpSlaveAgentListener")
|| rest.startsWith("/cli")
|| rest.startsWith("/whoAmI")
|| rest.startsWith("/federatedLoginService/")
|| rest.startsWith("/securityRealm"))
return this; // URLs that are always visible without READ permission
throw e;
......@@ -3588,7 +3527,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
public static <T> T lookup(Class<T> type) {
return Hudson.getInstance().lookup.get(type);
}
/**
* @deprecated since 2007-12-18.
* Use {@link #checkPermission(Permission)}
......@@ -3764,7 +3703,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
* Automatically try to launch a slave when Hudson is initialized or a new slave is created.
*/
public static boolean AUTOMATIC_SLAVE_LAUNCH = true;
private static final Logger LOGGER = Logger.getLogger(Hudson.class.getName());
private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
......
......@@ -77,4 +77,9 @@ public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject
* Internal method. Called by {@link Item}s when they are renamed by users.
*/
void onRenamed(T item, String oldName, String newName) throws IOException;
/**
* Internal method. Called by {@link Item}s when they are deleted by users.
*/
void onDeleted(T item) throws IOException;
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, CloudBees, 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,21 +23,62 @@
*/
package hudson.model;
import hudson.Util;
import hudson.model.listeners.ItemListener;
import hudson.security.AccessControlled;
import hudson.util.CopyOnWriteMap;
import hudson.util.Function1;
import hudson.util.IOUtils;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* Defines a bunch of static methods to be used as a "mix-in" for {@link ItemGroup}
* implementations.
* implementations. Not meant for a consumption from outside {@link ItemGroup}s.
*
* @author Kohsuke Kawaguchi
*/
public class ItemGroupMixIn {
public abstract class ItemGroupMixIn {
/**
* {@link ItemGroup} for which we are working.
*/
private final ItemGroup parent;
private final AccessControlled acl;
protected ItemGroupMixIn(ItemGroup parent, AccessControlled acl) {
this.parent = parent;
this.acl = acl;
}
/*
* Callback methods to be implemented by the ItemGroup implementation.
*/
/**
* Adds a newly created item to the parent.
*/
protected abstract void add(TopLevelItem item);
/**
* Assigns the root directory for a prospective item.
*/
protected abstract File getRootDirFor(String name);
/*
* The rest is the methods that provide meat.
*/
/**
* Loads all the child {@link Item}s.
*
......@@ -73,4 +114,146 @@ public class ItemGroupMixIn {
return item.getName();
}
};
/**
* Creates a {@link TopLevelItem} from the submission of the '/lib/hudson/newFromList/formList'
* or throws an exception if it fails.
*/
public synchronized TopLevelItem createTopLevelItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
acl.checkPermission(Job.CREATE);
TopLevelItem result;
String requestContentType = req.getContentType();
if(requestContentType==null)
throw new Failure("No Content-Type header set");
boolean isXmlSubmission = requestContentType.startsWith("application/xml") || requestContentType.startsWith("text/xml");
String name = req.getParameter("name");
if(name==null)
throw new Failure("Query parameter 'name' is required");
{// check if the name looks good
Hudson.checkGoodName(name);
name = name.trim();
if(parent.getItem(name)!=null)
throw new Failure(Messages.Hudson_JobAlreadyExists(name));
}
String mode = req.getParameter("mode");
if(mode!=null && mode.equals("copy")) {
String from = req.getParameter("from");
// resolve a name to Item
Item src = parent.getItem(from);
if (src==null)
src = Hudson.getInstance().getItemByFullName(from);
if(src==null) {
if(Util.fixEmpty(from)==null)
throw new Failure("Specify which job to copy");
else
throw new Failure("No such job: "+from);
}
if (!(src instanceof TopLevelItem))
throw new Failure(from+" cannot be copied");
result = copy((TopLevelItem) src,name);
} else {
if(isXmlSubmission) {
result = createProjectFromXML(name, req.getInputStream());
rsp.setStatus(HttpServletResponse.SC_OK);
return result;
} else {
if(mode==null)
throw new Failure("No mode given");
// create empty job and redirect to the project config screen
result = createProject(Items.getDescriptor(mode), name, true);
}
}
rsp.sendRedirect2(redirectAfterCreateItem(req, result));
return result;
}
/**
* Computes the redirection target URL for the newly created {@link TopLevelItem}.
*/
protected String redirectAfterCreateItem(StaplerRequest req, TopLevelItem result) throws IOException {
return req.getContextPath()+'/'+result.getUrl()+"configure";
}
/**
* Copies an existing {@link TopLevelItem} to a new name.
*
* The caller is responsible for calling {@link ItemListener#fireOnCopied(Item, Item)}. This method
* cannot do that because it doesn't know how to make the newly added item reachable from the parent.
*/
@SuppressWarnings({"unchecked"})
public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException {
acl.checkPermission(Job.CREATE);
T result = (T)createProject(src.getDescriptor(),name,false);
// copy config
Util.copyFile(Items.getConfigFile(src).getFile(),Items.getConfigFile(result).getFile());
// reload from the new config
result = (T)Items.load(parent,result.getRootDir());
result.onCopiedFrom(src);
add(result);
ItemListener.fireOnCopied(src,result);
return result;
}
public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
acl.checkPermission(Job.CREATE);
// place it as config.xml
File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
configXml.getParentFile().mkdirs();
try {
IOUtils.copy(xml,configXml);
// load it
TopLevelItem result = (TopLevelItem)Items.load(parent,configXml.getParentFile());
add(result);
ItemListener.fireOnCreated(result);
Hudson.getInstance().rebuildDependencyGraph();
return result;
} catch (IOException e) {
// if anything fails, delete the config file to avoid further confusion
Util.deleteRecursive(configXml.getParentFile());
throw e;
}
}
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify )
throws IOException {
acl.checkPermission(Job.CREATE);
if(parent.getItem(name)!=null)
throw new IllegalArgumentException("Project of the name "+name+" already exists");
TopLevelItem item;
try {
item = type.newInstance(parent,name);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
item.onCreatedFromScratch();
item.save();
add(item);
if (notify)
ItemListener.fireOnCreated(item);
return item;
}
}
......@@ -24,15 +24,17 @@
package hudson.model;
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.XmlFile;
import hudson.matrix.Axis;
import hudson.util.XStream2;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
import hudson.util.DescriptorList;
import hudson.util.XStream2;
import java.io.File;
import java.io.IOException;
......
......@@ -469,6 +469,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
/**
* Renames a job.
*/
@Override
public void renameTo(String newName) throws IOException {
super.renameTo(newName);
}
......
......@@ -123,7 +123,7 @@ public class ListView extends View implements Saveable {
return !ViewJobFilter.all().isEmpty();
}
public Iterable<ViewJobFilter> getJobFilters() {
public DescribableList<ViewJobFilter, Descriptor<ViewJobFilter>> getJobFilters() {
return jobFilters;
}
......
......@@ -23,11 +23,14 @@
*/
package hudson.model;
import hudson.ExtensionPoint;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.matrix.MatrixConfiguration;
/**
* {@link Item} that can be directly displayed under {@link Hudson}.
* {@link Item} that can be directly displayed under {@link Hudson} or other containers.
* Ones that don't need to be under specific parent (say, unlike {@link MatrixConfiguration}),
* and thus can be freely moved, copied, etc.
*
* <p>
* To register a custom {@link TopLevelItem} class from a plugin, put {@link Extension} on your
......@@ -36,11 +39,6 @@ import hudson.Extension;
* @author Kohsuke Kawaguchi
*/
public interface TopLevelItem extends Item, ExtensionPoint, Describable<TopLevelItem> {
/**
* By definition the parent of the top-level item is always {@link Hudson}.
*/
Hudson getParent();
/**
*
* @see Describable#getDescriptor()
......
......@@ -81,9 +81,21 @@ public abstract class TopLevelItemDescriptor extends Descriptor<TopLevelItem> {
}
/**
* Creates a new {@link Job}.
* Creates a new {@link TopLevelItem}.
*
* @deprecated as of 1.390
* Use {@link #newInstance(ItemGroup, String)}
*/
public TopLevelItem newInstance(String name) {
return newInstance(Hudson.getInstance(), name);
}
/**
* Creates a new {@link TopLevelItem} for the specified parent.
*
* @since 1.390
*/
public abstract TopLevelItem newInstance(String name);
public abstract TopLevelItem newInstance(ItemGroup parent, String name);
/**
* Returns all the registered {@link TopLevelItem} descriptors.
......
......@@ -443,15 +443,18 @@ public class User extends AbstractModelObject implements AccessControlled, Savea
List<UserProperty> props = new ArrayList<UserProperty>();
int i = 0;
for (UserPropertyDescriptor d : UserProperty.all()) {
JSONObject o = json.getJSONObject("userProperty" + (i++));
UserProperty p = getProperty(d.clazz);
if (p != null) {
p = p.reconfigure(req, o);
} else {
p = d.newInstance(req, o);
JSONObject o = json.optJSONObject("userProperty" + (i++));
if (o!=null) {
if (p != null) {
p = p.reconfigure(req, o);
} else {
p = d.newInstance(req, o);
}
p.setUser(this);
}
p.setUser(this);
props.add(p);
}
this.properties = props;
......
......@@ -76,10 +76,17 @@ public abstract class ViewJob<JobT extends ViewJob<JobT,RunT>, RunT extends Run<
reloadThread.start();
}
/**
* @deprecated as of 1.390
*/
protected ViewJob(Hudson parent, String name) {
super(parent,name);
}
protected ViewJob(ItemGroup parent, String name) {
super(parent,name);
}
public boolean isBuildable() {
return false;
}
......
/*
* The MIT License
*
* Copyright (c) 2010, CloudBees, 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.security;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Hudson;
import hudson.model.User;
import hudson.model.UserProperty;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.userdetails.UserDetails;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import java.io.IOException;
/**
* Abstraction for a login mechanism through external authenticator/identity provider
* (instead of username/password.)
*
* <p>
* This extension point adds additional login mechanism for {@link SecurityRealm}s that
* authenticate the user via username/password (which typically extends from {@link AbstractPasswordBasedSecurityRealm}.)
* The intended use case is protocols like OpenID, OAuth, and other SSO-like services.
*
* <p>
* The basic abstraction is that:
*
* <ul>
* <li>
* The user can have (possibly multiple, possibly zero) opaque strings to their {@linkplain User} object.
* Such opaque strings are called "identifiers."
* Think of them as OpenID URLs, twitter account names, etc.
* Identifiers are only comparable within the same {@link FederatedLoginService} implementation.
*
* <li>
* After getting authenticated by some means, the user can add additional identifiers to their account.
* Your implementation would do protocol specific thing to verify that the user indeed owns the claimed identifier,
* create a {@link FederatedIdentity} instance,
* then call {@link FederatedIdentity#addToCurrentUser()} to record such association.
*
* <li>
* In the login page, instead of entering the username and password, the user opts for authenticating
* via other services. Think of OpenID, OAuth, your corporate SSO service, etc.
* The user proves (by your protocol specific way) that they own some identifier, then
* create a {@link FederatedIdentity} instance, and invoke {@link FederatedIdentity#signin()} to sign in that user.
*
* </ul>
*
*
* <h2>Views</h2>
* <dl>
* <dt>loginFragment.jelly
* <dd>
* Injected into the login form page, after the default "login" button but before
* the "create account" link. Use this to generate a button or a link so that the user
* can initiate login via your federated login service.
* </dl>
*
* <h2>URL Binding</h2>
* <p>
* Each {@link FederatedLoginService} is exposed to the URL space via {@link Hudson#getFederatedLoginService(String)}.
* So for example if your {@linkplain #getUrlName() url name} is "openid", this object gets
* "/federatedLoginService/openid" as the URL.
*
* @author Kohsuke Kawaguchi
* @since 1.394
*/
public abstract class FederatedLoginService implements ExtensionPoint {
/**
* Returns the url name that determines where this {@link FederatedLoginService} is mapped to in the URL space.
*
* <p>
* The object is bound to /federatedLoginService/URLNAME/. The url name needs to be unique among all
* {@link FederatedLoginService}s.
*/
public abstract String getUrlName();
/**
* Returns your implementation of {@link FederatedLoginServiceUserProperty} that stores
* opaque identifiers.
*/
public abstract Class<? extends FederatedLoginServiceUserProperty> getUserPropertyClass();
/**
* Identity information as obtained from {@link FederatedLoginService}.
*/
public abstract class FederatedIdentity {
/**
* Gets the string representation of the identity in the form that makes sense to the enclosing
* {@link FederatedLoginService}, such as full OpenID URL.
*
* @return must not be null.
*/
public abstract String getIdentifier();
/**
* Gets a short ID of this user, as a suitable candidate for {@link User#getId()}.
* This should be Unix username like token.
*
* @return null if this information is not available.
*/
public abstract String getNickname();
/**
* Gets a human readable full name of this user. Maps to {@link User#getDisplayName()}
*
* @return null if this information is not available.
*/
public abstract String getFullName();
/**
* Gets the e-mail address of this user, like "abc@def.com"
*
* @return null if this information is not available.
*/
public abstract String getEmailAddress();
/**
* Returns a human-readable pronoun that describes this kind of identifier.
* This is used for rendering UI. For example, "OpenID", "Twitter ID", etc.
*/
public abstract String getPronoun();
/**
* Locates the user who owns this identifier.
*/
public final User locateUser() {
Class<? extends FederatedLoginServiceUserProperty> pt = getUserPropertyClass();
String id = getIdentifier();
for (User u : User.getAll()) {
if (u.getProperty(pt).has(id))
return u;
}
return null;
}
/**
* Call this method to authenticate the user when you confirmed (via your protocol specific work) that
* the current HTTP request indeed owns this identifier.
*
* <p>
* This method will locate the user who owns this identifier, associate the credential with
* the current session. IOW, it signs in the user.
*
* @throws UnclaimedIdentityException
* If this identifier is not claimed by anyone. If you just let this exception propagate
* to the caller of your "doXyz" method, it will either render an error page or initiate
* a user registration session (provided that {@link SecurityRealm} supports that.)
*/
public User signin() throws UnclaimedIdentityException {
User u = locateUser();
if (u!=null) {
// login as this user
UserDetails d = Hudson.getInstance().getSecurityRealm().loadUserByUsername(u.getId());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(d,"",d.getAuthorities());
token.setDetails(d);
SecurityContextHolder.getContext().setAuthentication(token);
return u;
} else {
// Unassociated identity.
throw new UnclaimedIdentityException(this);
}
}
/**
* Your implementation will call this method to add this identifier to the current user
* of an already authenticated session.
*
* <p>
* This method will record the identifier in {@link FederatedLoginServiceUserProperty} so that
* in the future the user can login to Hudson with the identifier.
*/
public void addToCurrentUser() throws IOException {
User u = User.current();
if (u==null) throw new IllegalStateException("Current request is unauthenticated");
addTo(u);
}
/**
* Adds this identity to the specified user.
*/
public void addTo(User u) throws IOException {
FederatedLoginServiceUserProperty p = u.getProperty(getUserPropertyClass());
if (p==null) {
p = (FederatedLoginServiceUserProperty) UserProperty.all().find(getUserPropertyClass()).newInstance(u);
u.addProperty(p);
}
p.addIdentifier(getIdentifier());
}
@Override
public String toString() {
return getIdentifier();
}
}
/**
* Used in {@link FederatedIdentity#signin()} to indicate that the identifier is not currently
* associated with anyone.
*/
public static class UnclaimedIdentityException extends RuntimeException implements HttpResponse {
public final FederatedIdentity identity;
public UnclaimedIdentityException(FederatedIdentity identity) {
this.identity = identity;
}
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
SecurityRealm sr = Hudson.getInstance().getSecurityRealm();
if (sr.allowsSignup()) {
try {
sr.commenceSignup(identity).generateResponse(req,rsp,node);
return;
} catch (UnsupportedOperationException e) {
// fall through
}
}
// this security realm doesn't support user registration.
// just report an error
req.getView(this,"error").forward(req,rsp);
}
}
public static ExtensionList<FederatedLoginService> all() {
return Hudson.getInstance().getExtensionList(FederatedLoginService.class);
}
}
/*
* The MIT License
*
* Copyright (c) 2010, CloudBees, 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.security;
import hudson.model.UserProperty;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author Kohsuke Kawaguchi
* @since 1.394
* @see FederatedLoginService
*/
public class FederatedLoginServiceUserProperty extends UserProperty {
protected final Set<String> identifiers;
protected FederatedLoginServiceUserProperty(Collection<String> identifiers) {
this.identifiers = new HashSet<String>(identifiers);
}
public boolean has(String identifier) {
return identifiers.contains(identifier);
}
public Collection<String> getIdentifiers() {
return Collections.unmodifiableSet(identifiers);
}
public synchronized void addIdentifier(String id) throws IOException {
identifiers.add(id);
user.save();
}
}
......@@ -34,6 +34,7 @@ import hudson.model.ModelObject;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.tasks.Mailer;
import hudson.util.PluginServletFilter;
import hudson.util.Protector;
......@@ -51,6 +52,9 @@ import org.acegisecurity.providers.encoding.ShaPasswordEncoder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -147,21 +151,56 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
return u;
}
/**
* Show the sign up page with the data from the identity.
*/
@Override
public HttpResponse commenceSignup(final FederatedIdentity identity) {
// store the identity in the session so that we can use this later
Stapler.getCurrentRequest().getSession().setAttribute(FEDERATED_IDENTITY_SESSION_KEY,identity);
return new ForwardToView(this,"signupWithFederatedIdentity.jelly") {
@Override
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
SignupInfo si = new SignupInfo(identity);
si.errorMessage = Messages.HudsonPrivateSecurityRealm_WouldYouLikeToSignUp(identity.getPronoun(),identity.getIdentifier());
req.setAttribute("data", si);
super.generateResponse(req, rsp, node);
}
};
}
/**
* Creates an account and associates that with the given identity. Used in conjunction
* with {@link #commenceSignup(FederatedIdentity)}.
*/
public User doCreateAccountWithFederatedIdentity(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
User u = _doCreateAccount(req,rsp,"signupWithFederatedIdentity.jelly");
if (u!=null)
((FederatedIdentity)req.getSession().getAttribute(FEDERATED_IDENTITY_SESSION_KEY)).addTo(u);
return u;
}
private static final String FEDERATED_IDENTITY_SESSION_KEY = HudsonPrivateSecurityRealm.class.getName()+".federatedIdentity";
/**
* Creates an user account. Used for self-registration.
*/
public void doCreateAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if(!allowsSignup()) {
rsp.sendError(SC_UNAUTHORIZED,"User sign up is prohibited");
return;
}
public User doCreateAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
return _doCreateAccount(req, rsp, "signup.jelly");
}
private User _doCreateAccount(StaplerRequest req, StaplerResponse rsp, String formView) throws ServletException, IOException {
if(!allowsSignup())
throw HttpResponses.error(SC_UNAUTHORIZED,new Exception("User sign up is prohibited"));
boolean firstUser = !hasSomeUser();
User u = createAccount(req, rsp, true, "signup.jelly");
User u = createAccount(req, rsp, true, formView);
if(u!=null) {
if(firstUser)
tryToMakeAdmin(u); // the first user should be admin, or else there's a risk of lock out
loginAndTakeBack(req, rsp, u);
}
return u;
}
/**
......@@ -227,8 +266,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
private User createAccount(StaplerRequest req, StaplerResponse rsp, boolean selfRegistration, String formView) throws ServletException, IOException {
// form field validation
// this pattern needs to be generalized and moved to stapler
SignupInfo si = new SignupInfo();
req.bindParameters(si);
SignupInfo si = new SignupInfo(req);
if(selfRegistration && !validateCaptcha(si.captcha))
si.errorMessage = "Text didn't match the word shown in the image";
......@@ -328,6 +366,19 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
* To display an error message, set it here.
*/
public String errorMessage;
public SignupInfo() {
}
public SignupInfo(StaplerRequest req) {
req.bindParameters(this);
}
public SignupInfo(FederatedIdentity i) {
this.username = i.getNickname();
this.fullname = i.getFullName();
this.email = i.getEmailAddress();
}
}
/**
......
......@@ -33,6 +33,7 @@ import hudson.cli.CLICommand;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.util.DescriptorList;
import hudson.util.PluginServletFilter;
import hudson.util.spring.BeanBuilder;
......@@ -47,6 +48,7 @@ import static org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices.ACEGI
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
......@@ -299,6 +301,28 @@ public abstract class SecurityRealm extends AbstractDescribableImpl<SecurityReal
throw new UserMayOrMayNotExistException(groupname);
}
/**
* Starts the user registration process for a new user that has the given verified identity.
*
* <p>
* If the user logs in through a {@link FederatedLoginService}, verified that the current user
* owns an {@linkplain FederatedIdentity identity}, but no existing user account has claimed that identity,
* then this method is invoked.
*
* <p>
* The expected behaviour is to confirm that the user would like to create a new account, and
* associate this federated identity to the newly created account (via {@link FederatedIdentity#addToCurrentUser()}.
*
* @throws UnsupportedOperationException
* If this implementation doesn't support the signup through this mechanism.
* This is the default implementation.
*
* @since 1.394
*/
public HttpResponse commenceSignup(FederatedIdentity identity) {
throw new UnsupportedOperationException();
}
/**
* {@link DefaultManageableImageCaptchaService} holder to defer initialization.
*/
......
......@@ -140,7 +140,7 @@ public abstract class RetentionStrategy<T extends Computer> extends AbstractDesc
return 1;
}
@Extension
@Extension(ordinal=100)
public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
public String getDisplayName() {
return Messages.RetentionStrategy_Always_displayName();
......
......@@ -66,7 +66,7 @@ THE SOFTWARE.
<td><!-- config link -->
<j:if test="${c.hasPermission(c.CONFIGURE)}">
<a href="${rootURL}/${c.url}configure">
<img src="${imagesURL}/32x32/setting.gif"
<img src="${imagesURL}/32x32/setting.png"
title="${%Configure}" alt="${%Configure}"
border="0"/>
</a>
......
......@@ -55,6 +55,12 @@ THE SOFTWARE.
</script>
</form>
<j:forEach var="fls" items="${it.getFederatedLoginServices()}">
<div>
<st:include page="loginFragment.jelly" it="${fls}"/>
</div>
</j:forEach>
<j:if test="${it.securityRealm.allowsSignup()}">
<div style="margin-top:2em">
${%signUp}
......
......@@ -42,14 +42,16 @@ THE SOFTWARE.
<j:invokeStatic var="descriptors" className="hudson.model.UserProperty" method="all" />
<j:set var="instances" value="${it.properties}" />
<j:forEach var="d" items="${descriptors}" varStatus="loop">
<f:section title="${d.displayName}">
<j:set var="descriptor" value="${d}" />
<j:set var="instance" value="${instances[d]}" />
<f:rowSet name="userProperty${loop.index}">
<st:include from="${d}" page="${d.configPage}"/>
</f:rowSet>
</f:section>
<j:if test="${d.enabled}">
<f:section title="${d.displayName}">
<j:set var="descriptor" value="${d}" />
<j:set var="instance" value="${instances[d]}" />
<f:rowSet name="userProperty${loop.index}">
<st:include from="${d}" page="${d.configPage}"/>
</f:rowSet>
</f:section>
</j:if>
</j:forEach>
<f:block>
......
<!--
The MIT License
Copyright (c) 2010, CloudBees, 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.
-->
<!--
This is used to create the first user.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<st:statusCode value="403" />
<l:layout title="${%loginError(it.identifier.pronoun)}">
<l:main-panel>
<h1>${%loginError(it.identifier.pronoun)}</h1>
<p>${%blurb(it.identifier.pronoun, it.identifier.identifier)}</p>
</l:main-panel>
</l:layout>
</j:jelly>
\ No newline at end of file
loginError=Login Error: unassociated {0}
blurb={0} "{1}" is not associated with any of the Hudson user account. \
Note that if you already have a user account and is trying to associate a {0} with your account, \
this is a wrong place. To do so, <ol><li>Login <li>Click on your name <li>Click on the "Configure" link, and \
then <li>associate a new {0} from that page</ol>
......@@ -24,7 +24,7 @@ THE SOFTWARE.
<!-- tag file sed by both signup.jelly and addUser.jelly -->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true">
<l:layout norefresh="true" title="${%Sign up}">
<l:header>
<style>
<!-- match width with captcha image -->
......
<!--
The MIT License
Copyright (c) 2010, CloudBees, 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.
-->
<!--
User self sign up page.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccountWithFederatedIdentity" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
\ No newline at end of file
# 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.
Sign\ up=Tilmelding
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Simon Wiest
#
# 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.
Sign\ up=Registrieren
# The MIT License
#
# 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
# 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.
Sign\ up=Registrarse
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Eric Lefevre-Ardant
#
# 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.
Sign\ up=S''inscrire
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe
#
# 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.
Sign\ up=\u30b5\u30a4\u30f3\u30a2\u30c3\u30d7
\ No newline at end of file
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Reginaldo L. Russinholi, Cleiber Silva
#
# 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.
Sign\ up=Inscrever
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Oguz Dag
#
# 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.
Sign\ up=Kay\u0131t ol
# The MIT License
#
# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Simon Wiest
#
# 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.
Sign\ up=\u6ce8\u518c
......@@ -22,6 +22,7 @@
GlobalMatrixAuthorizationStrategy.DisplayName=Matrix-based security
HudsonPrivateSecurityRealm.WouldYouLikeToSignUp=This {0} {1} is new to Hudson. Would you like to sign up?
LegacyAuthorizationStrategy.DisplayName=Legacy mode
HudsonPrivateSecurityRealm.DisplayName=Hudson''s own user database
......
......@@ -26,7 +26,7 @@ THE SOFTWARE.
<td>
<j:if test="${job.buildable and job.hasPermission(job.BUILD)}">
<a href="${jobBaseUrl}${job.shortUrl}build?delay=0sec">
<img src="${imagesURL}/${subIconSize}/clock.gif"
<img src="${imagesURL}/${subIconSize}/clock.png"
title="${%Schedule a build}" alt="${%Schedule a build}"
border="0"/>
</a>
......
......@@ -33,6 +33,8 @@ THE SOFTWARE.
@checkUrl : form field validation url
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:s="/lib/form">
<j:set var="descriptors" value="${h.filterDescriptors(it,attrs.descriptors)}" />
<s:form method="post" action="createItem">
<s:entry title="${attrs.nameTitle}">
<s:textbox id="name" name="name" checkUrl="'${rootURL}/${attrs.checkUrl}?value='+encodeURIComponent(this.value)"
......@@ -40,7 +42,7 @@ THE SOFTWARE.
<script>$('name').focus();</script>
</s:entry>
<j:forEach var="descriptor" items="${attrs.descriptors}">
<j:forEach var="descriptor" items="${descriptors}">
<s:block>
<input type="radio" name="mode" value="${descriptor.class.name}" onchange="updateOk(this.form)" onclick="updateOk(this.form)" />
<st:nbsp/>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<artifactId>maven-plugin</artifactId>
......
......@@ -25,8 +25,8 @@ package hudson.maven;
import hudson.Launcher;
import hudson.maven.MavenBuild.ProxyImpl2;
import hudson.maven.util.ExecutionEventLogger;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.remoting.Channel;
......@@ -49,6 +49,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import org.apache.maven.cli.PrintStreamLogger;
import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.ExecutionListener;
......@@ -251,6 +252,8 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
private final Map<ModuleName,List<MavenReporter>> reporters = new HashMap<ModuleName,List<MavenReporter>>();
private final Map<ModuleName, Long> currentMojoStartPerModuleName = new ConcurrentHashMap<ModuleName, Long>();
private ExecutionEventLogger eventLogger;
public MavenExecutionListener(Maven3Builder maven3Builder) {
this.maven3Builder = maven3Builder;
......@@ -261,6 +264,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
executedMojosPerModule.put( e.getKey(), new CopyOnWriteArrayList<ExecutedMojo>() );
}
this.reporters.putAll( new HashMap<ModuleName, List<MavenReporter>>(maven3Builder.reporters) );
this.eventLogger = new ExecutionEventLogger( new PrintStreamLogger( maven3Builder.listener.getLogger() ) );
}
private MavenBuildProxy2 getMavenBuildProxy2(MavenProject mavenProject) {
......@@ -302,14 +306,14 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
* @see org.apache.maven.execution.ExecutionListener#projectDiscoveryStarted(org.apache.maven.execution.ExecutionEvent)
*/
public void projectDiscoveryStarted( ExecutionEvent event ) {
// no op
this.eventLogger.projectDiscoveryStarted( event );
}
/**
* @see org.apache.maven.execution.ExecutionListener#sessionStarted(org.apache.maven.execution.ExecutionEvent)
*/
public void sessionStarted( ExecutionEvent event ) {
// no op
this.eventLogger.sessionStarted( event );
}
/**
......@@ -317,6 +321,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
*/
public void sessionEnded( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println( "sessionEnded" );
this.eventLogger.sessionEnded( event );
}
/**
......@@ -325,16 +330,18 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
public void projectSkipped( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println("projectSkipped " + event.getProject().getGroupId()
+ ":" + event.getProject().getArtifactId()
+ ":" + event.getProject().getVersion());
+ ":" + event.getProject().getVersion());
this.eventLogger.projectSkipped( event );
}
/**
* @see org.apache.maven.execution.ExecutionListener#projectStarted(org.apache.maven.execution.ExecutionEvent)
*/
public void projectStarted( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println( "projectStarted " + event.getProject().getGroupId() + ":"
+ event.getProject().getArtifactId() + ":" + event.getProject().getVersion() );
reccordProjectStarted( event );
//maven3Builder.listener.getLogger().println( "projectStarted " + event.getProject().getGroupId() + ":"
// + event.getProject().getArtifactId() + ":" + event.getProject().getVersion() );
reccordProjectStarted( event );
this.eventLogger.projectStarted( event );
}
......@@ -381,6 +388,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
+ event.getProject().getArtifactId() + ":"
+ event.getProject().getVersion());
reccordProjectSucceeded( event );
this.eventLogger.projectSucceeded( event );
}
public void reccordProjectSucceeded( ExecutionEvent event ) {
......@@ -425,6 +433,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
+ ":" + event.getProject().getArtifactId()
+ ":" + event.getProject().getVersion());
reccordProjectFailed( event );
this.eventLogger.projectFailed( event );
}
public void reccordProjectFailed( ExecutionEvent event ) {
......@@ -467,17 +476,19 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
+ event.getMojoExecution().getArtifactId() + ":"
+ event.getMojoExecution().getVersion()
+ "(" + event.getMojoExecution().getExecutionId() + ")");
this.eventLogger.mojoSkipped( event );
}
/**
* @see org.apache.maven.execution.ExecutionListener#mojoStarted(org.apache.maven.execution.ExecutionEvent)
*/
public void mojoStarted( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println("mojoStarted " + event.getMojoExecution().getGroupId() + ":"
+ event.getMojoExecution().getArtifactId() + ":"
+ event.getMojoExecution().getVersion()
+ "(" + event.getMojoExecution().getExecutionId() + ")");
//maven3Builder.listener.getLogger().println("mojoStarted " + event.getMojoExecution().getGroupId() + ":"
// + event.getMojoExecution().getArtifactId() + ":"
// + event.getMojoExecution().getVersion()
// + "(" + event.getMojoExecution().getExecutionId() + ")");
reccordMojoStarted( event );
this.eventLogger.mojoStarted( event );
}
public void reccordMojoStarted( ExecutionEvent event ) {
......@@ -513,11 +524,12 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
* @see org.apache.maven.execution.ExecutionListener#mojoSucceeded(org.apache.maven.execution.ExecutionEvent)
*/
public void mojoSucceeded( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println("mojoSucceeded " + event.getMojoExecution().getGroupId() + ":"
+ event.getMojoExecution().getArtifactId() + ":"
+ event.getMojoExecution().getVersion()
+ "(" + event.getMojoExecution().getExecutionId() + ")");
//maven3Builder.listener.getLogger().println("mojoSucceeded " + event.getMojoExecution().getGroupId() + ":"
// + event.getMojoExecution().getArtifactId() + ":"
// + event.getMojoExecution().getVersion()
// + "(" + event.getMojoExecution().getExecutionId() + ")");
reccordMojoSucceeded( event );
this.eventLogger.mojoSucceeded( event );
}
public void reccordMojoSucceeded( ExecutionEvent event ) {
......@@ -573,6 +585,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
+ event.getMojoExecution().getVersion()
+ "(" + event.getMojoExecution().getExecutionId() + ")");
reccordMojoFailed( event );
this.eventLogger.mojoFailed( event );
}
public void reccordMojoFailed( ExecutionEvent event ) {
......@@ -660,7 +673,8 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
public void forkedProjectStarted( ExecutionEvent event ) {
maven3Builder.listener.getLogger().println( "forkedProjectStarted " + event.getProject().getGroupId() + ":"
+ event.getProject().getArtifactId() + event.getProject().getVersion() );
reccordProjectStarted( event );
reccordProjectStarted( event );
this.eventLogger.forkedProjectStarted( event );
}
/**
......@@ -672,6 +686,7 @@ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCal
+ event.getProject().getArtifactId()
+ event.getProject().getVersion());
reccordProjectSucceeded( event );
this.eventLogger.forkedProjectSucceeded( event );
}
/**
......
......@@ -225,6 +225,10 @@ public final class MavenModuleSet extends AbstractMavenProject<MavenModuleSet,Ma
new DescribableList<BuildWrapper, Descriptor<BuildWrapper>>(this);
public MavenModuleSet(String name) {
this(Hudson.getInstance(),name);
}
public MavenModuleSet(ItemGroup parent, String name) {
super(Hudson.getInstance(),name);
}
......@@ -233,10 +237,6 @@ public final class MavenModuleSet extends AbstractMavenProject<MavenModuleSet,Ma
return ".";
}
public Hudson getParent() {
return Hudson.getInstance();
}
public Collection<MavenModule> getItems() {
return modules.values();
}
......@@ -449,6 +449,10 @@ public final class MavenModuleSet extends AbstractMavenProject<MavenModuleSet,Ma
throw new UnsupportedOperationException();
}
public void onDeleted(MavenModule item) throws IOException {
// noop
}
public Collection<Job> getAllJobs() {
Set<Job> jobs = new HashSet<Job>(getItems());
jobs.add(this);
......@@ -902,8 +906,8 @@ public final class MavenModuleSet extends AbstractMavenProject<MavenModuleSet,Ma
return Messages.MavenModuleSet_DiplayName();
}
public MavenModuleSet newInstance(String name) {
return new MavenModuleSet(name);
public MavenModuleSet newInstance(ItemGroup parent, String name) {
return new MavenModuleSet(parent,name);
}
public Maven.DescriptorImpl getMavenDescriptor() {
......
......@@ -877,7 +877,6 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
void preBuild(MavenSession session, ReactorManager rm, EventDispatcher dispatcher) throws BuildFailureException, LifecycleExecutionException, IOException, InterruptedException {
// set all modules which are not actually being build (in incremental builds) to NOT_BUILD
@SuppressWarnings("unchecked")
List<MavenProject> projects = rm.getSortedProjects();
Set<ModuleName> buildingProjects = new HashSet<ModuleName>();
for (MavenProject p : projects) {
......@@ -1146,7 +1145,7 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
List<MavenProject> mps = new ArrayList<MavenProject>(0);
if (maven3OrLater) {
mps = embedder.readProjects( pom,true );
mps = embedder.readProjects( pom,!this.nonRecursive );
} else {
// http://issues.hudson-ci.org/browse/HUDSON-8390
......@@ -1156,7 +1155,9 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
rootProject = mavenProject;
mps.add( mavenProject );
reactorReader.addProject( mavenProject );
readChilds( mavenProject, embedder, mps, reactorReader );
if (!this.nonRecursive) {
readChilds( mavenProject, embedder, mps, reactorReader );
}
}
Map<String,MavenProject> canonicalPaths = new HashMap<String, MavenProject>( mps.size() );
for(MavenProject mp : mps) {
......@@ -1215,18 +1216,24 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
PomInfo pi = new PomInfo(mp, parent, relPath);
infos.add(pi);
for (String modulePath : mp.getModules())
{
if (StringUtils.isBlank( modulePath )) {
continue;
if(!this.nonRecursive) {
for (String modulePath : mp.getModules())
{
if (StringUtils.isBlank( modulePath )) {
continue;
}
File path = new File(mp.getBasedir(), modulePath);
// HUDSON-8391 : Modules are indexed by POM path thus
// by default we have to add the default pom.xml file
if(path.isDirectory())
path = new File(mp.getBasedir(), modulePath+"/pom.xml");
MavenProject child = abslPath.get( path.getCanonicalPath());
if (child == null) {
listener.getLogger().printf("Found a module with path " + modulePath + " but no associated project");
continue;
}
toPomInfo(child,pi,abslPath,infos);
}
File path = new File(mp.getBasedir(), modulePath);
// HUDSON-8391 : Modules are indexed by POM path thus
// by default we have to add the default pom.xml file
if(path.isDirectory())
path = new File(mp.getBasedir(), modulePath+"/pom.xml");
MavenProject child = abslPath.get( path.getCanonicalPath());
toPomInfo(child,pi,abslPath,infos);
}
}
......
......@@ -44,6 +44,7 @@ import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
......@@ -52,6 +53,8 @@ import org.apache.maven.artifact.versioning.ComparableVersion;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import static java.util.logging.Level.FINE;
/**
* @author Kohsuke Kawaguchi
*/
......@@ -185,25 +188,25 @@ public class MavenUtil {
: org.codehaus.plexus.logging.Logger.LEVEL_INFO );
mavenRequest.setMavenLoggerManager( logger );
ClassLoader cl = MavenUtil.class.getClassLoader();
ClassLoader mavenEmbedderClassLoader =
mavenEmbedderRequest.getClassLoader() == null ? new MaskingClassLoader( cl )
mavenEmbedderRequest.getClassLoader() == null ? new MaskingClassLoader( MavenUtil.class.getClassLoader() )
: mavenEmbedderRequest.getClassLoader();
{// are we loading the right components.xml? (and not from Maven that's running Jetty, if we are running in "mvn hudson-dev:run" or "mvn hpi:run"?
Enumeration<URL> e = mavenEmbedderClassLoader.getResources("META-INF/plexus/components.xml");
while (e.hasMoreElements()) {
URL url = e.nextElement();
LOGGER.fine("components.xml from "+url);
}
}
mavenRequest.setProcessPlugins( mavenEmbedderRequest.isProcessPlugins() );
mavenRequest.setResolveDependencies( mavenEmbedderRequest.isResolveDependencies() );
mavenRequest.setValidationLevel( mavenEmbedderRequest.getValidationLevel() );
// TODO check this MaskingClassLoader with maven 3 artifacts
MavenEmbedder maven = new MavenEmbedder( mavenEmbedderClassLoader, mavenRequest );
{
Enumeration<URL> e = cl.getResources("META-INF/plexus/components.xml");
while (e.hasMoreElements()) {
URL url = e.nextElement();
LOGGER.fine("components.xml from "+url);
}
}
return maven;
}
......
package hudson.maven.util;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.BuildSummary;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.logging.Logger;
/**
* Logs execution events to a user-supplied logger.
*
* @author Benjamin Bentmann
*/
public class ExecutionEventLogger
extends AbstractExecutionListener
{
private final Logger logger;
private static final int LINE_LENGTH = 72;
public ExecutionEventLogger( Logger logger )
{
if ( logger == null )
{
throw new IllegalArgumentException( "logger missing" );
}
this.logger = logger;
}
private static String chars( char c, int count )
{
StringBuilder buffer = new StringBuilder( count );
for ( int i = count; i > 0; i-- )
{
buffer.append( c );
}
return buffer.toString();
}
private static String getFormattedTime( long time )
{
String pattern = "s.SSS's'";
if ( time / 60000L > 0 )
{
pattern = "m:s" + pattern;
if ( time / 3600000L > 0 )
{
pattern = "H:m" + pattern;
}
}
DateFormat fmt = new SimpleDateFormat( pattern );
fmt.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
return fmt.format( new Date( time ) );
}
@Override
public void projectDiscoveryStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "Scanning for projects..." );
}
}
@Override
public void sessionStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() && event.getSession().getProjects().size() > 1 )
{
logger.info( chars( '-', LINE_LENGTH ) );
logger.info( "Reactor Build Order:" );
logger.info( "" );
for ( MavenProject project : event.getSession().getProjects() )
{
logger.info( project.getName() );
}
}
}
@Override
public void sessionEnded( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
if ( event.getSession().getProjects().size() > 1 )
{
logReactorSummary( event.getSession() );
}
logResult( event.getSession() );
logStats( event.getSession() );
logger.info( chars( '-', LINE_LENGTH ) );
}
}
private void logReactorSummary( MavenSession session )
{
logger.info( chars( '-', LINE_LENGTH ) );
logger.info( "Reactor Summary:" );
logger.info( "" );
MavenExecutionResult result = session.getResult();
for ( MavenProject project : session.getProjects() )
{
StringBuilder buffer = new StringBuilder( 128 );
buffer.append( project.getName() );
buffer.append( ' ' );
while ( buffer.length() < LINE_LENGTH - 21 )
{
buffer.append( '.' );
}
buffer.append( ' ' );
BuildSummary buildSummary = result.getBuildSummary( project );
if ( buildSummary == null )
{
buffer.append( "SKIPPED" );
}
else if ( buildSummary instanceof BuildSuccess )
{
buffer.append( "SUCCESS [" );
buffer.append( getFormattedTime( buildSummary.getTime() ) );
buffer.append( "]" );
}
else if ( buildSummary instanceof BuildFailure )
{
buffer.append( "FAILURE [" );
buffer.append( getFormattedTime( buildSummary.getTime() ) );
buffer.append( "]" );
}
logger.info( buffer.toString() );
}
}
private void logResult( MavenSession session )
{
logger.info( chars( '-', LINE_LENGTH ) );
if ( session.getResult().hasExceptions() )
{
logger.info( "BUILD FAILURE" );
}
else
{
logger.info( "BUILD SUCCESS" );
}
}
private void logStats( MavenSession session )
{
logger.info( chars( '-', LINE_LENGTH ) );
Date finish = new Date();
long time = finish.getTime() - session.getRequest().getStartTime().getTime();
String wallClock = session.getRequest().isThreadConfigurationPresent() ? " (Wall Clock)" : "";
logger.info( "Total time: " + getFormattedTime( time ) + wallClock );
logger.info( "Finished at: " + finish );
System.gc();
Runtime r = Runtime.getRuntime();
long MB = 1024 * 1024;
logger.info( "Final Memory: " + ( r.totalMemory() - r.freeMemory() ) / MB + "M/" + r.totalMemory() / MB + "M" );
}
@Override
public void projectSkipped( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( chars( ' ', LINE_LENGTH ) );
logger.info( chars( '-', LINE_LENGTH ) );
logger.info( "Skipping " + event.getProject().getName() );
logger.info( "This project has been banned from the build due to previous failures." );
logger.info( chars( '-', LINE_LENGTH ) );
}
}
@Override
public void projectStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( chars( ' ', LINE_LENGTH ) );
logger.info( chars( '-', LINE_LENGTH ) );
logger.info( "Building " + event.getProject().getName() + " " + event.getProject().getVersion() );
logger.info( chars( '-', LINE_LENGTH ) );
}
}
@Override
public void mojoSkipped( ExecutionEvent event )
{
if ( logger.isWarnEnabled() )
{
logger.warn( "Goal " + event.getMojoExecution().getGoal()
+ " requires online mode for execution but Maven is currently offline, skipping" );
}
}
@Override
public void mojoStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
StringBuilder buffer = new StringBuilder( 128 );
buffer.append( "--- " );
append( buffer, event.getMojoExecution() );
append( buffer, event.getProject() );
buffer.append( " ---" );
logger.info( "" );
logger.info( buffer.toString() );
}
}
@Override
public void forkStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
StringBuilder buffer = new StringBuilder( 128 );
buffer.append( ">>> " );
append( buffer, event.getMojoExecution() );
append( buffer, event.getProject() );
buffer.append( " >>>" );
logger.info( "" );
logger.info( buffer.toString() );
}
}
@Override
public void forkSucceeded( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
StringBuilder buffer = new StringBuilder( 128 );
buffer.append( "<<< " );
append( buffer, event.getMojoExecution() );
append( buffer, event.getProject() );
buffer.append( " <<<" );
logger.info( "" );
logger.info( buffer.toString() );
}
}
private void append( StringBuilder buffer, MojoExecution me )
{
buffer.append( me.getArtifactId() ).append( ':' ).append( me.getVersion() );
buffer.append( ':' ).append( me.getGoal() );
if ( me.getExecutionId() != null )
{
buffer.append( " (" ).append( me.getExecutionId() ).append( ')' );
}
}
private void append( StringBuilder buffer, MavenProject project )
{
buffer.append( " @ " ).append( project.getArtifactId() );
}
@Override
public void forkedProjectStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() && event.getMojoExecution().getForkedExecutions().size() > 1 )
{
logger.info( chars( ' ', LINE_LENGTH ) );
logger.info( chars( '>', LINE_LENGTH ) );
logger.info( "Forking " + event.getProject().getName() + " " + event.getProject().getVersion() );
logger.info( chars( '>', LINE_LENGTH ) );
}
}
}
......@@ -4,7 +4,7 @@
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<artifactId>maven3-agent</artifactId>
<name>Hudson Maven3 CLI Agent</name>
......
......@@ -4,7 +4,7 @@
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<artifactId>maven3-interceptor</artifactId>
<name>Hudson Maven3 Interceptor</name>
......
......@@ -32,7 +32,7 @@ THE SOFTWARE.
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Hudson main module</name>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<artifactId>pom</artifactId>
<groupId>org.jvnet.hudson.main</groupId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jvnet.hudson.main</groupId>
......
......@@ -30,6 +30,7 @@ import hudson.*;
import hudson.Util;
import hudson.model.*;
import hudson.model.Queue.Executable;
import hudson.security.ACL;
import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
......@@ -100,6 +101,7 @@ import net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.httpclient.NameValuePair;
......@@ -316,6 +318,8 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
@Override
protected void runTest() throws Throwable {
System.out.println("=== Starting "+ getClass().getSimpleName() + "." + getName());
// so that test code has all the access to the system
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
super.runTest();
}
......
......@@ -133,6 +133,19 @@ public class Maven3BuildTest extends HudsonTestCase {
assertFalse( mmsb.getProject().getModules().isEmpty());
}
@Bug(8484)
public void testMultiModMavenNonRecursive() throws Exception {
MavenInstallation mavenInstallation = configureMaven3();
MavenModuleSet m = createMavenProject();
m.setMaven( mavenInstallation.getName() );
m.getReporters().add(new TestReporter());
m.setScm(new ExtractResourceSCM(getClass().getResource("maven-multimod.zip")));
m.setGoals( "-N validate" );
assertTrue("MavenModuleSet.isNonRecursive() should be true", m.isNonRecursive());
buildAndAssertSuccess(m);
assertEquals("not only one module", 1, m.getModules().size());
}
private static class TestReporter extends MavenReporter {
@Override
public boolean end(MavenBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
......
......@@ -261,6 +261,18 @@ public class MavenMultiModuleTest extends HudsonTestCase {
}
}
@Bug(8484)
public void testMultiModMavenNonRecursive() throws Exception {
configureDefaultMaven("apache-maven-2.2.1", MavenInstallation.MAVEN_21);
MavenModuleSet m = createMavenProject();
m.getReporters().add(new TestReporter());
m.setScm(new ExtractResourceSCM(getClass().getResource("maven-multimod.zip")));
m.setGoals( "-N validate" );
assertTrue("MavenModuleSet.isNonRecursive() should be true", m.isNonRecursive());
buildAndAssertSuccess(m);
assertEquals("not only one module", 1, m.getModules().size());
}
/*
public void testParallelMultiModMavenWsExists() throws Exception {
configureDefaultMaven();
......
......@@ -206,8 +206,8 @@ public class QueueTest extends HudsonTestCase {
StringBuilder causes = new StringBuilder();
for (Cause c : ca.getCauses()) causes.append(c.getShortDescription() + "\n");
assertEquals("Build causes should have all items, even duplicates",
"Started by user anonymous\nStarted by an SCM change\n"
+ "Started by user anonymous\nStarted by timer\n"
"Started by user SYSTEM\nStarted by an SCM change\n"
+ "Started by user SYSTEM\nStarted by timer\n"
+ "Started by remote host 1.2.3.4 with note: test\n"
+ "Started by remote host 4.3.2.1 with note: test\n"
+ "Started by an SCM change\n"
......@@ -220,7 +220,7 @@ public class QueueTest extends HudsonTestCase {
WebClient wc = new WebClient();
String buildPage = wc.getPage(build, "").asText().replace('\n',' ');
assertTrue("Build page should combine duplicates and show counts: " + buildPage,
buildPage.contains("Started by user anonymous (2 times) "
buildPage.contains("Started by user SYSTEM (2 times) "
+ "Started by an SCM change (3 times) "
+ "Started by timer (2 times) "
+ "Started by remote host 1.2.3.4 with note: test (2 times) "
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
</parent>
<artifactId>ui-samples-plugin</artifactId>
......
......@@ -27,7 +27,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jvnet.hudson.main</groupId>
<artifactId>pom</artifactId>
<version>1.394-SNAPSHOT</version>
<version>1.395-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......@@ -135,7 +135,7 @@ THE SOFTWARE.
<!-- this is really just a patched version of maven-jetty-plugin to workaround issue #932 -->
<groupId>org.jvnet.hudson.tools</groupId>
<artifactId>maven-hudson-dev-plugin</artifactId>
<version>6.1.7</version>
<version>6.1.7-hudson-1</version>
<configuration>
<contextPath>${contextPath}</contextPath>
<connectors>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册