package hudson.model; import hudson.model.Descriptor.FormException; import hudson.model.Fingerprint.RangeSet; import hudson.tasks.BuildStep; import hudson.tasks.BuildTrigger; import hudson.tasks.BuildWrapper; import hudson.tasks.BuildWrappers; import hudson.tasks.Builder; import hudson.tasks.Fingerprinter; import hudson.tasks.Publisher; import hudson.triggers.Trigger; import hudson.util.EditDistance; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.Vector; /** * Buildable software project. * * @author Kohsuke Kawaguchi */ public class Project extends AbstractProject implements TopLevelItem { /** * List of active {@link Builder}s configured for this project. */ private List builders = new Vector(); /** * List of active {@link Publisher}s configured for this project. */ private List publishers = new Vector(); /** * List of active {@link BuildWrapper}s configured for this project. */ private List buildWrappers = new Vector(); /** * {@link Action}s contributed from {@link #triggers}, {@link #builders}, * and {@link #publishers}. * * We don't want to persist them separately, and these actions * come and go as configuration change, so it's kept separate. */ private transient /*final*/ List transientActions = new Vector(); /** * Creates a new project. */ public Project(Hudson parent,String name) { super(parent,name); } public void onLoad(String name) throws IOException { super.onLoad(name); if(buildWrappers==null) // it didn't exist in < 1.64 buildWrappers = new Vector(); updateTransientActions(); } @Override public BallColor getIconColor() { if(isDisabled()) // use grey to indicate that the build is disabled return BallColor.GREY; else return super.getIconColor(); } public synchronized Map,Builder> getBuilders() { return Descriptor.toMap(builders); } public synchronized Map,Publisher> getPublishers() { return Descriptor.toMap(publishers); } public synchronized Map,BuildWrapper> getBuildWrappers() { return Descriptor.toMap(buildWrappers); } /** * Adds a new {@link BuildStep} to this {@link Project} and saves the configuration. */ private void addPublisher(Publisher buildStep) throws IOException { addToList(buildStep,publishers); } /** * Removes a publisher from this project, if it's active. */ private void removePublisher(Descriptor descriptor) throws IOException { removeFromList(descriptor, publishers); } @Override public Build newBuild() throws IOException { Build lastBuild = new Build(this); builds.put(lastBuild); return lastBuild; } @Override protected Build loadBuild(File dir) throws IOException { return new Build(this,dir); } /** * Gets the dependency relationship map between this project (as the source) * and that project (as the sink.) * * @return * can be empty but not null. build number of this project to the build * numbers of that project. */ public SortedMap getRelationship(Project that) { TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); checkAndRecord(that, r, this.getBuilds()); // checkAndRecord(that, r, that.getBuilds()); return r; } public List getDownstreamProjects() { BuildTrigger buildTrigger = (BuildTrigger) getPublishers().get(BuildTrigger.DESCRIPTOR); if(buildTrigger==null) return new ArrayList(); else return buildTrigger.getChildProjects(); } public List getUpstreamProjects() { List r = new ArrayList(); for( Project p : Hudson.getInstance().getProjects() ) { synchronized(p) { for (BuildStep step : p.publishers) { if (step instanceof BuildTrigger) { BuildTrigger trigger = (BuildTrigger) step; if(trigger.getChildProjects().contains(this)) r.add(p); } } } } return r; } /** * Helper method for getDownstreamRelationship. * * For each given build, find the build number range of the given project and put that into the map. */ private void checkAndRecord(Project that, TreeMap r, Collection builds) { for (Build build : builds) { RangeSet rs = build.getDownstreamRelationship(that); if(rs==null || rs.isEmpty()) continue; int n = build.getNumber(); RangeSet value = r.get(n); if(value==null) r.put(n,rs); else value.add(rs); } } /** * Returns true if the fingerprint record is configured in this project. */ public boolean isFingerprintConfigured() { synchronized(publishers) { for (Publisher p : publishers) { if(p instanceof Fingerprinter) return true; } } return false; } // // // actions // // /** * Accepts submission from the configuration page. */ public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { Set upstream = Collections.emptySet(); synchronized(this) { try { if(!Hudson.adminCheck(req,rsp)) return; req.setCharacterEncoding("UTF-8"); buildDescribable(req, BuildWrappers.WRAPPERS, buildWrappers, "wrapper"); buildDescribable(req, BuildStep.BUILDERS, builders, "builder"); buildDescribable(req, BuildStep.PUBLISHERS, publishers, "publisher"); super.doConfigSubmit(req,rsp); updateTransientActions(); } catch (FormException e) { sendError(e,req,rsp); } } if(req.getParameter("pseudoUpstreamTrigger")!=null) { upstream = new HashSet(Project.fromNameList(req.getParameter("upstreamProjects"))); } // this needs to be done after we release the lock on this, // or otherwise we could dead-lock for (Project p : Hudson.getInstance().getProjects()) { boolean isUpstream = upstream.contains(p); synchronized(p) { List newChildProjects = p.getDownstreamProjects(); if(isUpstream) { if(!newChildProjects.contains(this)) newChildProjects.add(this); } else { newChildProjects.remove(this); } if(newChildProjects.isEmpty()) { p.removePublisher(BuildTrigger.DESCRIPTOR); } else { p.addPublisher(new BuildTrigger(newChildProjects)); } } } save(); // notify the queue as the project might be now tied to different node Hudson.getInstance().getQueue().scheduleMaintenance(); } private void updateTransientActions() { if(transientActions==null) transientActions = new Vector(); // happens when loaded from disk synchronized(transientActions) { transientActions.clear(); for (BuildStep step : builders) { Action a = step.getProjectAction(this); if(a!=null) transientActions.add(a); } for (BuildStep step : publishers) { Action a = step.getProjectAction(this); if(a!=null) transientActions.add(a); } for (Trigger trigger : triggers) { Action a = trigger.getProjectAction(); if(a!=null) transientActions.add(a); } } } public synchronized List getActions() { // add all the transient actions, too List actions = new Vector(super.getActions()); actions.addAll(transientActions); return actions; } public List getProminentActions() { List a = getActions(); List pa = new Vector(); for (Action action : a) { if(action instanceof ProminentProjectAction) pa.add((ProminentProjectAction) action); } return pa; } /** * @deprecated * left for legacy config file compatibility */ @Deprecated private transient String slave; /** * Converts a list of projects into a camma-separated names. */ public static String toNameList(Collection projects) { StringBuilder buf = new StringBuilder(); for (Project project : projects) { if(buf.length()>0) buf.append(", "); buf.append(project.getName()); } return buf.toString(); } /** * Does the opposite of {@link #toNameList(Collection)}. */ public static List fromNameList(String list) { Hudson hudson = Hudson.getInstance(); List r = new ArrayList(); StringTokenizer tokens = new StringTokenizer(list,","); while(tokens.hasMoreTokens()) { String projectName = tokens.nextToken().trim(); Job job = hudson.getJob(projectName); if(!(job instanceof Project)) { continue; // ignore this token } r.add((Project) job); } return r; } /** * Finds a {@link Project} that has the name closest to the given name. */ public static Project findNearest(String name) { List projects = Hudson.getInstance().getProjects(); String[] names = new String[projects.size()]; for( int i=0; i REVERSE_INTEGER_COMPARATOR = new Comparator() { public int compare(Integer o1, Integer o2) { return o2-o1; } }; public TopLevelItemDescriptor getDescriptor() { return DESCRIPTOR; } public static final TopLevelItemDescriptor DESCRIPTOR = new TopLevelItemDescriptor(Project.class) { public String getDisplayName() { return "Building a software project"; } public Project newInstance(String name) { return new Project(Hudson.getInstance(),name); } }; }