KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > model > AbstractProject


1 package hudson.model;
2
3 import hudson.FilePath;
4 import hudson.Launcher;
5 import hudson.Launcher.LocalLauncher;
6 import hudson.maven.MavenModule;
7 import hudson.model.Descriptor.FormException;
8 import hudson.model.Fingerprint.RangeSet;
9 import hudson.model.RunMap.Constructor;
10 import hudson.scm.NullSCM;
11 import hudson.scm.SCM;
12 import hudson.scm.SCMS;
13 import hudson.triggers.Trigger;
14 import hudson.triggers.Triggers;
15 import hudson.triggers.TriggerDescriptor;
16 import hudson.util.EditDistance;
17 import org.kohsuke.stapler.StaplerRequest;
18 import org.kohsuke.stapler.StaplerResponse;
19
20 import javax.servlet.ServletException JavaDoc;
21 import java.io.File JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Comparator JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.SortedMap JavaDoc;
28 import java.util.TreeMap JavaDoc;
29 import java.util.Vector JavaDoc;
30
31 /**
32  * Base implementation of {@link Job}s that build software.
33  *
34  * For now this is primarily the common part of {@link Project} and {@link MavenModule}.
35  *
36  * @author Kohsuke Kawaguchi
37  * @see AbstractBuild
38  */

39 public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
40
41     private SCM scm = new NullSCM();
42
43     /**
44      * All the builds keyed by their build number.
45      */

46     protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
47
48     /**
49      * The quiet period. Null to delegate to the system default.
50      */

51     private Integer JavaDoc quietPeriod = null;
52
53     /**
54      * If this project is configured to be only built on a certain node,
55      * this value will be set to that node. Null to indicate the affinity
56      * with the master node.
57      *
58      * see #canRoam
59      */

60     private String JavaDoc assignedNode;
61
62     /**
63      * True if this project can be built on any node.
64      *
65      * <p>
66      * This somewhat ugly flag combination is so that we can migrate
67      * existing Hudson installations nicely.
68      */

69     private boolean canRoam;
70
71     /**
72      * True to suspend new builds.
73      */

74     protected boolean disabled;
75
76     /**
77      * Identifies {@link JDK} to be used.
78      * Null if no explicit configuration is required.
79      *
80      * <p>
81      * Can't store {@link JDK} directly because {@link Hudson} and {@link Project}
82      * are saved independently.
83      *
84      * @see Hudson#getJDK(String)
85      */

86     private String JavaDoc jdk;
87
88     /**
89      * @deprecated
90      */

91     private transient boolean enableRemoteTrigger;
92
93     private BuildAuthorizationToken authToken = null;
94
95     /**
96      * List of all {@link Trigger}s for this project.
97      */

98     protected List JavaDoc<Trigger<?>> triggers = new Vector JavaDoc<Trigger<?>>();
99
100     protected AbstractProject(ItemGroup parent, String JavaDoc name) {
101         super(parent,name);
102
103         if(!Hudson.getInstance().getSlaves().isEmpty()) {
104             // if a new job is configured with Hudson that already has slave nodes
105
// make it roamable by default
106
canRoam = true;
107         }
108     }
109
110     @Override JavaDoc
111     public void onLoad(ItemGroup<? extends Item> parent, String JavaDoc name) throws IOException JavaDoc {
112         super.onLoad(parent, name);
113
114         this.builds = new RunMap<R>();
115         this.builds.load(this,new Constructor<R>() {
116             public R create(File JavaDoc dir) throws IOException JavaDoc {
117                 return loadBuild(dir);
118             }
119         });
120
121         if(triggers==null)
122             // it didn't exist in < 1.28
123
triggers = new Vector JavaDoc<Trigger<?>>();
124         for (Trigger t : triggers)
125             t.start(this,false);
126     }
127
128     /**
129      * If this project is configured to be always built on this node,
130      * return that {@link Node}. Otherwise null.
131      */

132     public Node getAssignedNode() {
133         if(canRoam)
134             return null;
135
136         if(assignedNode ==null)
137             return Hudson.getInstance();
138         return Hudson.getInstance().getSlave(assignedNode);
139     }
140
141     /**
142      * Gets the directory where the module is checked out.
143      */

144     public abstract FilePath getWorkspace();
145
146     /**
147      * Returns the root directory of the checked-out module.
148      * <p>
149      * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
150      * and so on exists.
151      */

152     public FilePath getModuleRoot() {
153         return getScm().getModuleRoot(getWorkspace());
154     }
155
156     public int getQuietPeriod() {
157         return quietPeriod!=null ? quietPeriod : Hudson.getInstance().getQuietPeriod();
158     }
159
160     // ugly name because of EL
161
public boolean getHasCustomQuietPeriod() {
162         return quietPeriod!=null;
163     }
164
165     public final boolean isBuildable() {
166         return true;
167     }
168
169     public boolean isDisabled() {
170         return disabled;
171     }
172
173     /**
174      * Schedules a build of this project.
175      *
176      * @return
177      * true if the project is actually added to the queue.
178      * false if the queue contained it and therefore the add()
179      * was noop
180      */

181     public boolean scheduleBuild() {
182         if(isDisabled()) return false;
183         return Hudson.getInstance().getQueue().add(this);
184     }
185
186     /**
187      * Returns true if the build is in the queue.
188      */

189     @Override JavaDoc
190     public boolean isInQueue() {
191         return Hudson.getInstance().getQueue().contains(this);
192     }
193
194     /**
195      * Returns true if a build of this project is in progress.
196      */

197     public boolean isBuilding() {
198         R b = getLastBuild();
199         return b!=null && b.isBuilding();
200     }
201
202     public JDK getJDK() {
203         return Hudson.getInstance().getJDK(jdk);
204     }
205
206     /**
207      * Overwrites the JDK setting.
208      */

209     public synchronized void setJDK(JDK jdk) throws IOException JavaDoc {
210         this.jdk = jdk.getName();
211         save();
212     }
213
214     public BuildAuthorizationToken getAuthToken() {
215         return authToken;
216     }
217
218     public SortedMap JavaDoc<Integer JavaDoc, ? extends R> _getRuns() {
219         return builds.getView();
220     }
221
222     public void removeRun(R run) {
223         this.builds.remove(run);
224     }
225
226     /**
227      * Creates a new build of this project for immediate execution.
228      */

229     protected abstract R newBuild() throws IOException JavaDoc;
230
231     /**
232      * Loads an existing build record from disk.
233      */

234     protected abstract R loadBuild(File JavaDoc dir) throws IOException JavaDoc;
235
236     /**
237      * Gets the {@link Node} where this project was last built on.
238      *
239      * @return
240      * null if no information is available (for example,
241      * if no build was done yet.)
242      */

243     public Node getLastBuiltOn() {
244         // where was it built on?
245
AbstractBuild b = getLastBuild();
246         if(b==null)
247             return null;
248         else
249             return b.getBuiltOn();
250     }
251
252     /**
253      * Returns true if this project's build execution should be blocked
254      * for temporary reasons. This method is used by {@link Queue}.
255      *
256      * <p>
257      * A project must be blocked if its own previous build is in progress,
258      * but derived classes can also check other conditions.
259      */

260     protected boolean isBuildBlocked() {
261         return isBuilding();
262     }
263
264     public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File JavaDoc changelogFile) throws IOException JavaDoc {
265         if(scm==null)
266             return true; // no SCM
267

268         try {
269             FilePath workspace = getWorkspace();
270             workspace.mkdirs();
271
272             return scm.checkout(build, launcher, workspace, listener, changelogFile);
273         } catch (InterruptedException JavaDoc e) {
274             e.printStackTrace(listener.fatalError("SCM check out aborted"));
275             return false;
276         }
277     }
278
279     /**
280      * Checks if there's any update in SCM, and returns true if any is found.
281      *
282      * <p>
283      * The caller is responsible for coordinating the mutual exclusion between
284      * a build and polling, as both touches the workspace.
285      */

286     public boolean pollSCMChanges( TaskListener listener ) {
287         if(scm==null) {
288             listener.getLogger().println("No SCM");
289             return false;
290         }
291         if(isDisabled()) {
292             listener.getLogger().println("Build disabled");
293             return false;
294         }
295
296         try {
297             FilePath workspace = getWorkspace();
298             if(!workspace.exists()) {
299                 // no workspace. build now, or nothing will ever be built
300
listener.getLogger().println("No workspace is available, so can't check for updates.");
301                 listener.getLogger().println("Scheduling a new build to get a workspace.");
302                 return true;
303             }
304
305             // TODO: do this by using the right slave
306
return scm.pollChanges(this, new LocalLauncher(listener), workspace, listener );
307         } catch (IOException JavaDoc e) {
308             e.printStackTrace(listener.fatalError(e.getMessage()));
309             return false;
310         } catch (InterruptedException JavaDoc e) {
311             e.printStackTrace(listener.fatalError("SCM polling aborted"));
312             return false;
313         }
314     }
315
316     public SCM getScm() {
317         return scm;
318     }
319
320     public void setScm(SCM scm) {
321         this.scm = scm;
322     }
323
324     /**
325      * Adds a new {@link Trigger} to this {@link Project} if not active yet.
326      */

327     public void addTrigger(Trigger<?> trigger) throws IOException JavaDoc {
328         addToList(trigger,triggers);
329     }
330
331     public void removeTrigger(TriggerDescriptor trigger) throws IOException JavaDoc {
332         removeFromList(trigger,triggers);
333     }
334
335     protected final synchronized <T extends Describable<T>>
336     void addToList( T item, List JavaDoc<T> collection ) throws IOException JavaDoc {
337         for( int i=0; i<collection.size(); i++ ) {
338             if(collection.get(i).getDescriptor()==item.getDescriptor()) {
339                 // replace
340
collection.set(i,item);
341                 save();
342                 return;
343             }
344         }
345         // add
346
collection.add(item);
347         save();
348     }
349
350     protected final synchronized <T extends Describable<T>>
351     void removeFromList(Descriptor<T> item, List JavaDoc<T> collection) throws IOException JavaDoc {
352         for( int i=0; i< collection.size(); i++ ) {
353             if(collection.get(i).getDescriptor()==item) {
354                 // found it
355
collection.remove(i);
356                 save();
357                 return;
358             }
359         }
360     }
361
362     public synchronized Map JavaDoc<TriggerDescriptor,Trigger> getTriggers() {
363         return (Map JavaDoc)Descriptor.toMap(triggers);
364     }
365
366 //
367
//
368
// fingerprint related
369
//
370
//
371
/**
372      * True if the builds of this project produces {@link Fingerprint} records.
373      */

374     public abstract boolean isFingerprintConfigured();
375
376     /**
377      * Gets the other {@link AbstractProject}s that should be built
378      * when a build of this project is completed.
379      */

380     public final List JavaDoc<AbstractProject> getDownstreamProjects() {
381         return Hudson.getInstance().getDependencyGraph().getDownstream(this);
382     }
383
384     public final List JavaDoc<AbstractProject> getUpstreamProjects() {
385         return Hudson.getInstance().getDependencyGraph().getUpstream(this);
386     }
387
388     /**
389      * Gets the dependency relationship map between this project (as the source)
390      * and that project (as the sink.)
391      *
392      * @return
393      * can be empty but not null. build number of this project to the build
394      * numbers of that project.
395      */

396     public SortedMap JavaDoc<Integer JavaDoc, RangeSet> getRelationship(AbstractProject that) {
397         TreeMap JavaDoc<Integer JavaDoc,RangeSet> r = new TreeMap JavaDoc<Integer JavaDoc,RangeSet>(REVERSE_INTEGER_COMPARATOR);
398
399         checkAndRecord(that, r, this.getBuilds());
400         // checkAndRecord(that, r, that.getBuilds());
401

402         return r;
403     }
404
405     /**
406      * Helper method for getDownstreamRelationship.
407      *
408      * For each given build, find the build number range of the given project and put that into the map.
409      */

410     private void checkAndRecord(AbstractProject that, TreeMap JavaDoc<Integer JavaDoc, RangeSet> r, Collection JavaDoc<R> builds) {
411         for (R build : builds) {
412             RangeSet rs = build.getDownstreamRelationship(that);
413             if(rs==null || rs.isEmpty())
414                 continue;
415
416             int n = build.getNumber();
417
418             RangeSet value = r.get(n);
419             if(value==null)
420                 r.put(n,rs);
421             else
422                 value.add(rs);
423         }
424     }
425
426     /**
427      * Builds the dependency graph.
428      * @see DependencyGraph
429      */

430     protected abstract void buildDependencyGraph(DependencyGraph graph);
431
432 //
433
//
434
// actions
435
//
436
//
437
/**
438      * Schedules a new build command.
439      */

440     public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
441         BuildAuthorizationToken.startBuildIfAuthorized(authToken,this,req,rsp);
442     }
443
444     /**
445      * Cancels a scheduled build.
446      */

447     public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
448         if(!Hudson.adminCheck(req,rsp))
449             return;
450
451         Hudson.getInstance().getQueue().cancel(this);
452         rsp.forwardToPreviousPage(req);
453     }
454
455     public synchronized void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException JavaDoc, ServletException JavaDoc {
456         super.doConfigSubmit(req, rsp);
457
458         disabled = req.getParameter("disable")!=null;
459
460         jdk = req.getParameter("jdk");
461         if(req.getParameter("hasCustomQuietPeriod")!=null) {
462             quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
463         } else {
464             quietPeriod = null;
465         }
466
467         if(req.getParameter("hasSlaveAffinity")!=null) {
468             canRoam = false;
469             assignedNode = req.getParameter("slave");
470             if(assignedNode !=null) {
471                 if(Hudson.getInstance().getSlave(assignedNode)==null) {
472                     assignedNode = null; // no such slave
473
}
474             }
475         } else {
476             canRoam = true;
477             assignedNode = null;
478         }
479
480         authToken = BuildAuthorizationToken.create(req);
481
482         try {
483             setScm(SCMS.parseSCM(req));
484
485             for (Trigger t : triggers)
486                 t.stop();
487             buildDescribable(req, Triggers.getApplicableTriggers(this), triggers, "trigger");
488             for (Trigger t : triggers)
489                 t.start(this,true);
490         } catch (FormException e) {
491             throw new ServletException JavaDoc(e);
492         }
493     }
494
495     protected final <T extends Describable<T>> void buildDescribable(StaplerRequest req, List JavaDoc<? extends Descriptor<T>> descriptors, List JavaDoc<T> result, String JavaDoc prefix)
496         throws FormException {
497
498         result.clear();
499         for( int i=0; i< descriptors.size(); i++ ) {
500             if(req.getParameter(prefix +i)!=null) {
501                 T instance = descriptors.get(i).newInstance(req);
502                 result.add(instance);
503             }
504         }
505     }
506
507     /**
508      * Serves the workspace files.
509      */

510     public void doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc, InterruptedException JavaDoc {
511         FilePath ws = getWorkspace();
512         if(!ws.exists()) {
513             // if there's no workspace, report a nice error message
514
rsp.forward(this,"noWorkspace",req);
515         } else {
516             new DirectoryBrowserSupport(this).serveFile(req, rsp, ws, "folder.gif", true);
517         }
518     }
519
520     /**
521      * Finds a {@link AbstractProject} that has the name closest to the given name.
522      */

523     public static AbstractProject findNearest(String JavaDoc name) {
524         List JavaDoc<AbstractProject> projects = Hudson.getInstance().getAllItems(AbstractProject.class);
525         String JavaDoc[] names = new String JavaDoc[projects.size()];
526         for( int i=0; i<projects.size(); i++ )
527             names[i] = projects.get(i).getName();
528
529         String JavaDoc nearest = EditDistance.findNearest(name, names);
530         return (AbstractProject)Hudson.getInstance().getItem(nearest);
531     }
532
533     private static final Comparator JavaDoc<Integer JavaDoc> REVERSE_INTEGER_COMPARATOR = new Comparator JavaDoc<Integer JavaDoc>() {
534         public int compare(Integer JavaDoc o1, Integer JavaDoc o2) {
535             return o2-o1;
536         }
537     };
538 }
539
Popular Tags