KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > model > Hudson


1 package hudson.model;
2
3 import com.thoughtworks.xstream.XStream;
4 import groovy.lang.GroovyShell;
5 import hudson.FeedAdapter;
6 import hudson.Launcher;
7 import hudson.Launcher.LocalLauncher;
8 import hudson.Plugin;
9 import hudson.PluginManager;
10 import hudson.PluginWrapper;
11 import hudson.Util;
12 import hudson.XmlFile;
13 import hudson.FilePath;
14 import hudson.TcpSlaveAgentListener;
15 import static hudson.Util.fixEmpty;
16 import hudson.model.Descriptor.FormException;
17 import hudson.model.listeners.ItemListener;
18 import hudson.model.listeners.JobListener;
19 import hudson.model.listeners.JobListener.JobListenerAdapter;
20 import hudson.model.listeners.SCMListener;
21 import hudson.remoting.LocalChannel;
22 import hudson.remoting.VirtualChannel;
23 import hudson.scm.CVSSCM;
24 import hudson.scm.SCM;
25 import hudson.scm.SCMS;
26 import hudson.tasks.BuildStep;
27 import hudson.tasks.BuildWrapper;
28 import hudson.tasks.BuildWrappers;
29 import hudson.tasks.Builder;
30 import hudson.tasks.Mailer;
31 import hudson.tasks.Publisher;
32 import hudson.triggers.Trigger;
33 import hudson.triggers.Triggers;
34 import hudson.triggers.TriggerDescriptor;
35 import hudson.util.CopyOnWriteList;
36 import hudson.util.CopyOnWriteMap;
37 import hudson.util.FormFieldValidator;
38 import hudson.util.XStream2;
39 import org.apache.commons.fileupload.FileItem;
40 import org.apache.commons.fileupload.FileUploadException;
41 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
42 import org.apache.commons.fileupload.servlet.ServletFileUpload;
43 import org.kohsuke.stapler.StaplerRequest;
44 import org.kohsuke.stapler.StaplerResponse;
45
46 import javax.servlet.ServletContext JavaDoc;
47 import javax.servlet.ServletException JavaDoc;
48 import javax.servlet.http.Cookie JavaDoc;
49 import javax.servlet.http.HttpServletRequest JavaDoc;
50 import javax.servlet.http.HttpServletResponse JavaDoc;
51 import javax.servlet.http.HttpSession JavaDoc;
52 import java.io.File JavaDoc;
53 import java.io.FileFilter JavaDoc;
54 import java.io.FileInputStream JavaDoc;
55 import java.io.IOException JavaDoc;
56 import java.io.PrintWriter JavaDoc;
57 import java.io.StringWriter JavaDoc;
58 import java.text.ParseException JavaDoc;
59 import java.util.ArrayList JavaDoc;
60 import java.util.Arrays JavaDoc;
61 import java.util.Calendar JavaDoc;
62 import java.util.Collection JavaDoc;
63 import java.util.Collections JavaDoc;
64 import java.util.Comparator JavaDoc;
65 import java.util.GregorianCalendar JavaDoc;
66 import java.util.HashMap JavaDoc;
67 import java.util.HashSet JavaDoc;
68 import java.util.Iterator JavaDoc;
69 import java.util.List JavaDoc;
70 import java.util.Map JavaDoc;
71 import java.util.Map.Entry;
72 import java.util.Set JavaDoc;
73 import java.util.TreeSet JavaDoc;
74 import java.util.Vector JavaDoc;
75 import java.util.StringTokenizer JavaDoc;
76 import java.util.Stack JavaDoc;
77 import java.util.logging.Level JavaDoc;
78 import java.util.logging.LogRecord JavaDoc;
79
80 /**
81  * Root object of the system.
82  *
83  * @author Kohsuke Kawaguchi
84  */

85 public final class Hudson extends View implements ItemGroup<TopLevelItem>, Node {
86     private transient final Queue queue = new Queue();
87
88     /**
89      * {@link Computer}s in this Hudson system. Read-only.
90      */

91     private transient final Map<Node,Computer> computers = new CopyOnWriteMap.Hash<Node,Computer>();
92
93     /**
94      * Number of executors of the master node.
95      */

96     private int numExecutors = 2;
97
98     /**
99      * False to enable anyone to do anything.
100      */

101     private boolean useSecurity = false;
102
103     /**
104      * Message displayed in the top page.
105      */

106     private String JavaDoc systemMessage;
107
108     /**
109      * Root directory of the system.
110      */

111     public transient final File root;
112
113     /**
114      * All {@link Item}s keyed by their {@link Item#getName() name}s.
115      */

116     /*package*/ transient final Map<String JavaDoc,TopLevelItem> items = new CopyOnWriteMap.Tree<String JavaDoc,TopLevelItem>();
117
118     /**
119      * The sole instance.
120      */

121     private static Hudson theInstance;
122
123     private transient boolean isQuietingDown;
124     private transient boolean terminating;
125
126     private List<JDK> jdks = new ArrayList JavaDoc<JDK>();
127
128     private transient volatile DependencyGraph dependencyGraph = DependencyGraph.EMPTY;
129
130     /**
131      * Set of installed cluster nodes.
132      *
133      * We use this field with copy-on-write semantics.
134      * This field has mutable list (to keep the serialization look clean),
135      * but it shall never be modified. Only new completely populated slave
136      * list can be set here.
137      */

138     private volatile List<Slave> slaves;
139
140     /**
141      * Quiet period.
142      *
143      * This is {@link Integer} so that we can initialize it to '5' for upgrading users.
144      */

145     /*package*/ Integer JavaDoc quietPeriod;
146
147     /**
148      * {@link ListView}s.
149      */

150     private List<ListView> views; // can't initialize it eagerly for backward compatibility
151

152     private transient final FingerprintMap fingerprintMap = new FingerprintMap();
153
154     /**
155      * Loaded plugins.
156      */

157     public transient final PluginManager pluginManager;
158
159     public transient final TcpSlaveAgentListener tcpSlaveAgentListener;
160
161     /**
162      * List of registered {@link JobListener}s.
163      */

164     private transient final CopyOnWriteList<ItemListener> viewItemListeners = new CopyOnWriteList<ItemListener>();
165
166     /**
167      * List of registered {@link SCMListener}s.
168      */

169     private transient final CopyOnWriteList<SCMListener> scmListeners = new CopyOnWriteList<SCMListener>();
170
171     public static Hudson getInstance() {
172         return theInstance;
173     }
174
175
176     public Hudson(File root, ServletContext JavaDoc context) throws IOException JavaDoc {
177         this.root = root;
178         if(theInstance!=null)
179             throw new IllegalStateException JavaDoc("second instance");
180         theInstance = this;
181
182         // load plugins.
183
pluginManager = new PluginManager(context);
184
185         tcpSlaveAgentListener = new TcpSlaveAgentListener();
186
187         // work around to have MavenModule register itself until we either move it to a plugin
188
// or make it a part of the core.
189
Items.LIST.hashCode();
190
191         load();
192         if(slaves==null) slaves = new ArrayList JavaDoc<Slave>();
193         updateComputerList();
194
195         getQueue().load();
196
197         for (ItemListener l : viewItemListeners)
198             l.onLoaded();
199     }
200
201     public TcpSlaveAgentListener getTcpSlaveAgentListener() {
202         return tcpSlaveAgentListener;
203     }
204
205     /**
206      * If you are calling this on Hudson something is wrong.
207      *
208      * @deprecated
209      */

210     @Deprecated JavaDoc
211     public String JavaDoc getNodeName() {
212         return "";
213     }
214
215     public String JavaDoc getNodeDescription() {
216         return "the master Hudson node";
217     }
218
219     public String JavaDoc getDescription() {
220         return systemMessage;
221     }
222
223     public PluginManager getPluginManager() {
224         return pluginManager;
225     }
226
227     /**
228      * Gets the SCM descriptor by name. Primarily used for making them web-visible.
229      */

230     public Descriptor<SCM> getScm(String JavaDoc shortClassName) {
231         return findDescriptor(shortClassName,SCMS.SCMS);
232     }
233
234     /**
235      * Gets the builder descriptor by name. Primarily used for making them web-visible.
236      */

237     public Descriptor<Builder> getBuilder(String JavaDoc shortClassName) {
238         return findDescriptor(shortClassName, BuildStep.BUILDERS);
239     }
240
241     /**
242      * Gets the publisher descriptor by name. Primarily used for making them web-visible.
243      */

244     public Descriptor<Publisher> getPublisher(String JavaDoc shortClassName) {
245         return findDescriptor(shortClassName, BuildStep.PUBLISHERS);
246     }
247
248     /**
249      * Gets the trigger descriptor by name. Primarily used for making them web-visible.
250      */

251     public TriggerDescriptor getTrigger(String JavaDoc shortClassName) {
252         return (TriggerDescriptor)findDescriptor(shortClassName, Triggers.TRIGGERS);
253     }
254
255     /**
256      * Finds a descriptor that has the specified name.
257      */

258     private <T extends Describable<T>>
259     Descriptor<T> findDescriptor(String JavaDoc shortClassName, Collection JavaDoc<? extends Descriptor<T>> descriptors) {
260         String JavaDoc name = '.'+shortClassName;
261         for (Descriptor<T> d : descriptors) {
262             if(d.clazz.getName().endsWith(name))
263                 return d;
264         }
265         return null;
266     }
267
268     /**
269      * Adds a new {@link JobListener}.
270      *
271      * @deprecated
272      * Use {@code getJobListners().add(l)} instead.
273      */

274     public void addListener(JobListener l) {
275         viewItemListeners.add(new JobListenerAdapter(l));
276     }
277
278     /**
279      * Deletes an existing {@link JobListener}.
280      *
281      * @deprecated
282      * Use {@code getJobListners().remove(l)} instead.
283      */

284     public boolean removeListener(JobListener l ) {
285         return viewItemListeners.remove(new JobListenerAdapter(l));
286     }
287
288     /**
289      * Gets all the installed {@link ItemListener}s.
290      */

291     public CopyOnWriteList<ItemListener> getJobListeners() {
292         return viewItemListeners;
293     }
294
295     /**
296      * Gets all the installed {@link SCMListener}s.
297      */

298     public CopyOnWriteList<SCMListener> getSCMListeners() {
299         return scmListeners;
300     }
301
302     /**
303      * Gets the plugin object from its short name.
304      *
305      * <p>
306      * This allows URL <tt>hudson/plugin/ID</tt> to be served by the views
307      * of the plugin class.
308      */

309     public Plugin getPlugin(String JavaDoc shortName) {
310         PluginWrapper p = pluginManager.getPlugin(shortName);
311         if(p==null) return null;
312         return p.getPlugin();
313     }
314
315     /**
316      * Synonym to {@link #getNodeDescription()}.
317      */

318     public String JavaDoc getSystemMessage() {
319         return systemMessage;
320     }
321
322     public Launcher createLauncher(TaskListener listener) {
323         return new LocalLauncher(listener);
324     }
325
326     /**
327      * Updates {@link #computers} by using {@link #getSlaves()}.
328      *
329      * <p>
330      * This method tries to reuse existing {@link Computer} objects
331      * so that we won't upset {@link Executor}s running in it.
332      */

333     private void updateComputerList() throws IOException JavaDoc {
334         synchronized(computers) {// this synchronization is still necessary so that no two update happens concurrently
335
Map<String JavaDoc,Computer> byName = new HashMap JavaDoc<String JavaDoc,Computer>();
336             for (Computer c : computers.values()) {
337                 if(c.getNode()==null)
338                     continue; // this computer is gone
339
byName.put(c.getNode().getNodeName(),c);
340             }
341
342             Set JavaDoc<Computer> old = new HashSet JavaDoc<Computer>(computers.values());
343             Set JavaDoc<Computer> used = new HashSet JavaDoc<Computer>();
344
345             updateComputer(this, byName, used);
346             for (Slave s : getSlaves())
347                 updateComputer(s, byName, used);
348
349             // find out what computers are removed, and kill off all executors.
350
// when all executors exit, it will be removed from the computers map.
351
// so don't remove too quickly
352
old.removeAll(used);
353             for (Computer c : old) {
354                 c.kill();
355             }
356         }
357         getQueue().scheduleMaintenance();
358     }
359
360     private void updateComputer(Node n, Map<String JavaDoc,Computer> byNameMap, Set JavaDoc<Computer> used) {
361         Computer c;
362         c = byNameMap.get(n.getNodeName());
363         if (c!=null) {
364             c.setNode(n); // reuse
365
} else {
366             if(n.getNumExecutors()>0)
367                 computers.put(n,c=n.createComputer());
368         }
369         used.add(c);
370     }
371
372     /*package*/ void removeComputer(Computer computer) {
373         Iterator JavaDoc<Entry<Node,Computer>> itr=computers.entrySet().iterator();
374         while(itr.hasNext()) {
375             Entry<Node, Computer> e = itr.next();
376             if(e.getValue()==computer) {
377                 computers.remove(e.getKey());
378                 return;
379             }
380         }
381         throw new IllegalStateException JavaDoc("Trying to remove unknown computer");
382     }
383
384     public String JavaDoc getFullName() {
385         return "";
386     }
387
388     /**
389      * Gets just the immediate children of {@link Hudson}.
390      *
391      * @see #getAllItems(Class)
392      */

393     public List<TopLevelItem> getItems() {
394         return new ArrayList JavaDoc<TopLevelItem>(items.values());
395     }
396
397     /**
398      * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
399      * and filter them by the given type.
400      */

401     public <T extends Item> List<T> getAllItems(Class JavaDoc<T> type) {
402         List<T> r = new ArrayList JavaDoc<T>();
403
404         Stack JavaDoc<ItemGroup> q = new Stack JavaDoc<ItemGroup>();
405         q.push(this);
406
407         while(!q.isEmpty()) {
408             ItemGroup<?> parent = q.pop();
409             for (Item i : parent.getItems()) {
410                 if(type.isInstance(i))
411                     r.add(type.cast(i));
412                 if(i instanceof ItemGroup)
413                     q.push((ItemGroup)i);
414             }
415         }
416
417         return r;
418     }
419
420     /**
421      * Gets the list of all the projects.
422      *
423      * <p>
424      * Since {@link Project} can only show up under {@link Hudson},
425      * no need to search recursively.
426      */

427     public List<Project> getProjects() {
428         return Util.createSubList(items.values(),Project.class);
429     }
430
431     /**
432      * Gets the names of all the {@link Job}s.
433      */

434     public Collection JavaDoc<String JavaDoc> getJobNames() {
435         List<String JavaDoc> names = new ArrayList JavaDoc<String JavaDoc>();
436         for (Job j : getAllItems(Job.class))
437             names.add(j.getName());
438         return names;
439     }
440
441     /**
442      * Gets the names of all the {@link TopLevelItem}s.
443      */

444     public Collection JavaDoc<String JavaDoc> getTopLevelItemNames() {
445         List<String JavaDoc> names = new ArrayList JavaDoc<String JavaDoc>();
446         for (TopLevelItem j : items.values())
447             names.add(j.getName());
448         return names;
449     }
450
451     /**
452      * Every job belongs to us.
453      *
454      * @deprecated
455      * why are you calling a method that always return true?
456      */

457     @Deprecated JavaDoc
458     public boolean contains(TopLevelItem view) {
459         return true;
460     }
461
462     public synchronized View getView(String JavaDoc name) {
463         if(views!=null) {
464             for (ListView v : views) {
465                 if(v.getViewName().equals(name))
466                     return v;
467             }
468         }
469         if(this.getViewName().equals(name))
470             return this;
471         else
472             return null;
473     }
474
475     /**
476      * Gets the read-only list of all {@link View}s.
477      */

478     public synchronized View[] getViews() {
479         if(views==null)
480             views = new ArrayList JavaDoc<ListView>();
481         View[] r = new View[views.size()+1];
482         views.toArray(r);
483         // sort Views and put "all" at the very beginning
484
r[r.length-1] = r[0];
485         Arrays.sort(r,1,r.length, View.SORTER);
486         r[0] = this;
487         return r;
488     }
489
490     public synchronized void deleteView(ListView view) throws IOException JavaDoc {
491         if(views!=null) {
492             views.remove(view);
493             save();
494         }
495     }
496
497     public String JavaDoc getViewName() {
498         return "All";
499     }
500
501     /**
502      * Gets the read-only list of all {@link Computer}s.
503      */

504     public Computer[] getComputers() {
505         Computer[] r = computers.values().toArray(new Computer[computers.size()]);
506         Arrays.sort(r,new Comparator JavaDoc<Computer>() {
507             public int compare(Computer lhs, Computer rhs) {
508                 if(lhs.getNode()==Hudson.this) return -1;
509                 if(rhs.getNode()==Hudson.this) return 1;
510                 return lhs.getDisplayName().compareTo(rhs.getDisplayName());
511             }
512         });
513         return r;
514     }
515
516     public Computer getComputer(String JavaDoc name) {
517         for (Computer c : computers.values()) {
518             if(c.getNode().getNodeName().equals(name))
519                 return c;
520         }
521         return null;
522     }
523
524     public Queue getQueue() {
525         return queue;
526     }
527
528     public String JavaDoc getDisplayName() {
529         return "Hudson";
530     }
531
532     public List<JDK> getJDKs() {
533         if(jdks==null)
534             jdks = new ArrayList JavaDoc<JDK>();
535         return jdks;
536     }
537
538     /**
539      * Gets the JDK installation of the given name, or returns null.
540      */

541     public JDK getJDK(String JavaDoc name) {
542         for (JDK j : getJDKs()) {
543             if(j.getName().equals(name))
544                 return j;
545         }
546         return null;
547     }
548
549     /**
550      * Gets the slave node of the give name, hooked under this Hudson.
551      */

552     public Slave getSlave(String JavaDoc name) {
553         for (Slave s : getSlaves()) {
554             if(s.getNodeName().equals(name))
555                 return s;
556         }
557         return null;
558     }
559
560     public List<Slave> getSlaves() {
561         return Collections.unmodifiableList(slaves);
562     }
563
564     /**
565      * Gets the system default quiet period.
566      */

567     public int getQuietPeriod() {
568         return quietPeriod!=null ? quietPeriod : 5;
569     }
570
571     /**
572      * @deprecated
573      * Why are you calling a method that always returns ""?
574      * Perhaps you meant {@link #getRootUrl()}.
575      */

576     public String JavaDoc getUrl() {
577         return "";
578     }
579
580     public String JavaDoc getUrlChildPrefix() {
581         return "job";
582     }
583
584     /**
585      * Gets the absolute URL of Hudson,
586      * such as "http://localhost/hudson/".
587      *
588      * <p>
589      * Also note that when serving user requests from HTTP, you should always use
590      * {@link HttpServletRequest} to determine the full URL, instead of using this
591      * (this is because one host may have multiple names, and {@link HttpServletRequest}
592      * accurately represents what the current user used.)
593      *
594      * <p>
595      * This information is rather only meant to be useful for sending out messages
596      * via non-HTTP channels, like SMTP or IRC, with a link back to Hudson website.
597      *
598      * @return
599      * This method returns null if this parameter is not configured by the user.
600      * The caller must gracefully deal with this situation.
601      * The returned URL will always have the trailing '/'.
602      * @since 1.66
603      */

604     public String JavaDoc getRootUrl() {
605         // for compatibility. the actual data is stored in Mailer
606
return Mailer.DESCRIPTOR.getUrl();
607     }
608
609     public File getRootDir() {
610         return root;
611     }
612
613     public FilePath getWorkspaceFor(TopLevelItem item) {
614         return new FilePath(new File(item.getRootDir(),"workspace"));
615     }
616
617     public boolean isUseSecurity() {
618         return useSecurity;
619     }
620
621     public void setUseSecurity(boolean useSecurity) {
622         this.useSecurity = useSecurity;
623     }
624
625     /**
626      * Returns true if Hudson is quieting down.
627      * <p>
628      * No further jobs will be executed unless it
629      * can be finished while other current pending builds
630      * are still in progress.
631      */

632     public boolean isQuietingDown() {
633         return isQuietingDown;
634     }
635
636     /**
637      * Returns true if the container initiated the termination of the web application.
638      */

639     public boolean isTerminating() {
640         return terminating;
641     }
642
643     /**
644      * @deprecated
645      * Left only for the compatibility of URLs.
646      * Should not be invoked for any other purpose.
647      */

648     public TopLevelItem getJob(String JavaDoc name) {
649         return getItem(name);
650     }
651
652     /**
653      * Gets the {@link TopLevelItem} of the given name.
654      */

655     public TopLevelItem getItem(String JavaDoc name) {
656         return items.get(name);
657     }
658
659     public File getRootDirFor(TopLevelItem child) {
660         return new File(new File(getRootDir(),"jobs"),child.getName());
661     }
662
663     /**
664      * Gets the {@link Item} object by its full name.
665      * Full names are like path names, where each name of {@link Item} is
666      * combined by '/'.
667      *
668      * @return
669      * null if either such {@link Item} doesn't exist under the given full name,
670      * or it exists but it's no an instance of the given type.
671      */

672     public <T extends Item> T getItemByFullName(String JavaDoc fullName, Class JavaDoc<T> type) {
673         StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(fullName,"/");
674         ItemGroup parent = this;
675
676         while(true) {
677             Item item = parent.getItem(tokens.nextToken());
678             if(!tokens.hasMoreTokens()) {
679                 if(type.isInstance(item))
680                     return type.cast(item);
681                 else
682                     return null;
683             }
684
685             if(!(item instanceof ItemGroup))
686                 return null; // this item can't have any children
687

688             parent = (ItemGroup) item;
689         }
690     }
691
692     public Item getItemByFullName(String JavaDoc fullName) {
693         return getItemByFullName(fullName,Item.class);
694     }
695
696     /**
697      * Gets the user of the given name.
698      *
699      * @return
700      * This method returns a non-null object for any user name, without validation.
701      */

702     public User getUser(String JavaDoc name) {
703         return User.get(name);
704     }
705
706     /**
707      * Creates a new job.
708      *
709      * @throws IllegalArgumentException
710      * if the project of the given name already exists.
711      */

712     public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String JavaDoc name ) throws IOException JavaDoc {
713         if(items.containsKey(name))
714             throw new IllegalArgumentException JavaDoc();
715
716         TopLevelItem item;
717         try {
718             item = type.newInstance(name);
719         } catch (Exception JavaDoc e) {
720             throw new IllegalArgumentException JavaDoc(e);
721         }
722
723         item.save();
724         items.put(name,item);
725         return item;
726     }
727
728     /**
729      * Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
730      */

731     /*package*/ void deleteJob(TopLevelItem item) throws IOException JavaDoc {
732         for (ItemListener l : viewItemListeners)
733             l.onDeleted(item);
734
735         items.remove(item.getName());
736         if(views!=null) {
737             for (ListView v : views) {
738                 synchronized(v) {
739                     v.jobNames.remove(item.getName());
740                 }
741             }
742             save();
743         }
744     }
745
746     /**
747      * Called by {@link Job#renameTo(String)} to update relevant data structure.
748      * assumed to be synchronized on Hudson by the caller.
749      */

750     /*package*/ void onRenamed(TopLevelItem job, String JavaDoc oldName, String JavaDoc newName) throws IOException JavaDoc {
751         items.remove(oldName);
752         items.put(newName,job);
753
754         if(views!=null) {
755             for (ListView v : views) {
756                 synchronized(v) {
757                     if(v.jobNames.remove(oldName))
758                         v.jobNames.add(newName);
759                 }
760             }
761             save();
762         }
763     }
764
765     public FingerprintMap getFingerprintMap() {
766         return fingerprintMap;
767     }
768
769     // if no finger print matches, display "not found page".
770
public Object JavaDoc getFingerprint( String JavaDoc md5sum ) throws IOException JavaDoc {
771         Fingerprint r = fingerprintMap.get(md5sum);
772         if(r==null) return new NoFingerprintMatch(md5sum);
773         else return r;
774     }
775
776     /**
777      * Gets a {@link Fingerprint} object if it exists.
778      * Otherwise null.
779      */

780     public Fingerprint _getFingerprint( String JavaDoc md5sum ) throws IOException JavaDoc {
781         return fingerprintMap.get(md5sum);
782     }
783
784     /**
785      * The file we save our configuration.
786      */

787     private XmlFile getConfigFile() {
788         return new XmlFile(XSTREAM, new File(root,"config.xml"));
789     }
790
791     public int getNumExecutors() {
792         return numExecutors;
793     }
794
795     public Mode getMode() {
796         return Mode.NORMAL;
797     }
798
799     public Computer createComputer() {
800         return new MasterComputer();
801     }
802
803     private synchronized void load() throws IOException JavaDoc {
804         XmlFile cfg = getConfigFile();
805         if(cfg.exists())
806             cfg.unmarshal(this);
807
808         File projectsDir = new File(root,"jobs");
809         if(!projectsDir.isDirectory() && !projectsDir.mkdirs()) {
810             if(projectsDir.exists())
811                 throw new IOException JavaDoc(projectsDir+" is not a directory");
812             throw new IOException JavaDoc("Unable to create "+projectsDir+"\nPermission issue? Please create this directory manually.");
813         }
814         File[] subdirs = projectsDir.listFiles(new FileFilter JavaDoc() {
815             public boolean accept(File child) {
816                 return child.isDirectory();
817             }
818         });
819         items.clear();
820         for (File subdir : subdirs) {
821             try {
822                 TopLevelItem item = (TopLevelItem)Items.load(this,subdir);
823                 items.put(item.getName(), item);
824             } catch (IOException JavaDoc e) {
825                 e.printStackTrace(); // TODO: logging
826
}
827         }
828         rebuildDependencyGraph();
829     }
830
831     /**
832      * Save the settings to a file.
833      */

834     public synchronized void save() throws IOException JavaDoc {
835         getConfigFile().write(this);
836     }
837
838
839     /**
840      * Called to shut down the system.
841      */

842     public void cleanUp() {
843         terminating = true;
844         for( Computer c : computers.values() ) {
845             c.interrupt();
846             c.kill();
847         }
848         ExternalJob.reloadThread.interrupt();
849         Trigger.timer.cancel();
850         tcpSlaveAgentListener.shutdown();
851
852         if(pluginManager!=null) // be defensive. there could be some ugly timing related issues
853
pluginManager.stop();
854
855         getQueue().save();
856     }
857
858
859
860 //
861
//
862
// actions
863
//
864
//
865
/**
866      * Accepts submission from the configuration page.
867      */

868     public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
869         try {
870             if(!Hudson.adminCheck(req,rsp))
871                 return;
872
873             req.setCharacterEncoding("UTF-8");
874
875             useSecurity = req.getParameter("use_security")!=null;
876
877             numExecutors = Integer.parseInt(req.getParameter("numExecutors"));
878             quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
879
880             systemMessage = Util.nullify(req.getParameter("system_message"));
881
882             {// update slave list
883
List<Slave> newSlaves = new ArrayList JavaDoc<Slave>();
884                 String JavaDoc[] names = req.getParameterValues("slave.name");
885                 if(names!=null) {
886                     for(int i=0;i< names.length;i++) {
887                         newSlaves.add(req.bindParameters(Slave.class,"slave.",i));
888                     }
889                 }
890                 this.slaves = newSlaves;
891                 updateComputerList();
892             }
893
894             {// update JDK installations
895
jdks.clear();
896                 String JavaDoc[] names = req.getParameterValues("jdk_name");
897                 String JavaDoc[] homes = req.getParameterValues("jdk_home");
898                 if(names!=null && homes!=null) {
899                     int len = Math.min(names.length,homes.length);
900                     for(int i=0;i<len;i++) {
901                         jdks.add(new JDK(names[i],homes[i]));
902                     }
903                 }
904             }
905
906             boolean result = true;
907
908             for( Descriptor<Builder> d : BuildStep.BUILDERS )
909                 result &= d.configure(req);
910
911             for( Descriptor<Publisher> d : BuildStep.PUBLISHERS )
912                 result &= d.configure(req);
913
914             for( Descriptor<BuildWrapper> d : BuildWrappers.WRAPPERS )
915                 result &= d.configure(req);
916
917             for( Descriptor<SCM> scmd : SCMS.SCMS )
918                 result &= scmd.configure(req);
919
920             for( TriggerDescriptor d : Triggers.TRIGGERS )
921                 result &= d.configure(req);
922
923             for( JobPropertyDescriptor d : Jobs.PROPERTIES )
924                 result &= d.configure(req);
925
926             save();
927             if(result)
928                 rsp.sendRedirect("."); // go to the top page
929
else
930                 rsp.sendRedirect("configure"); // back to config
931
} catch (FormException e) {
932             sendError(e,req,rsp);
933         }
934     }
935
936     /**
937      * Accepts the new description.
938      */

939     public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
940         if(!Hudson.adminCheck(req,rsp))
941             return;
942
943         req.setCharacterEncoding("UTF-8");
944         systemMessage = req.getParameter("description");
945         save();
946         rsp.sendRedirect(".");
947     }
948
949     public synchronized void doQuietDown( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
950         if(!Hudson.adminCheck(req,rsp))
951             return;
952         isQuietingDown = true;
953         rsp.sendRedirect2(".");
954     }
955
956     public synchronized void doCancelQuietDown( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
957         if(!Hudson.adminCheck(req,rsp))
958             return;
959         isQuietingDown = false;
960         getQueue().scheduleMaintenance();
961         rsp.sendRedirect2(".");
962     }
963
964     public synchronized Item doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
965         if(!Hudson.adminCheck(req,rsp))
966             return null;
967
968         req.setCharacterEncoding("UTF-8");
969         String JavaDoc name = req.getParameter("name").trim();
970         String JavaDoc mode = req.getParameter("mode");
971
972         try {
973             checkGoodName(name);
974         } catch (ParseException JavaDoc e) {
975             sendError(e,req,rsp);
976             return null;
977         }
978
979         if(getItem(name)!=null) {
980             sendError("A job already exists with the name '"+name+"'",req,rsp);
981             return null;
982         }
983
984         if(mode==null) {
985             rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
986             return null;
987         }
988
989         TopLevelItem result;
990
991         if(mode.equals("copyJob")) {
992             TopLevelItem src = getItem(req.getParameter("from"));
993             if(src==null) {
994                 rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
995                 return null;
996             }
997
998             result = createProject(src.getDescriptor(),name);
999
1000            // copy config
1001
Util.copyFile(Items.getConfigFile(src).getFile(),Items.getConfigFile(result).getFile());
1002
1003            // reload from the new config
1004
result = (TopLevelItem)Items.load(this,result.getRootDir());
1005            result.onCopiedFrom(src);
1006            items.put(name,result);
1007        } else {
1008            // redirect to the project config screen
1009
result = createProject(Items.getDescriptor(mode), name);
1010        }
1011
1012        for (ItemListener l : viewItemListeners)
1013            l.onCreated(result);
1014
1015        rsp.sendRedirect2(req.getContextPath()+'/'+result.getUrl()+"configure");
1016        return result;
1017    }
1018
1019    public synchronized void doCreateView( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1020        if(!Hudson.adminCheck(req,rsp))
1021            return;
1022
1023        req.setCharacterEncoding("UTF-8");
1024
1025        String JavaDoc name = req.getParameter("name");
1026
1027        try {
1028            checkGoodName(name);
1029        } catch (ParseException JavaDoc e) {
1030            sendError(e, req, rsp);
1031            return;
1032        }
1033
1034        ListView v = new ListView(this, name);
1035        if(views==null)
1036            views = new Vector JavaDoc<ListView>();
1037        views.add(v);
1038        save();
1039
1040        // redirect to the config screen
1041
rsp.sendRedirect2("./"+v.getUrl()+"configure");
1042    }
1043
1044    /**
1045     * Check if the given name is suitable as a name
1046     * for job, view, etc.
1047     *
1048     * @throws ParseException
1049     * if the given name is not good
1050     */

1051    public static void checkGoodName(String JavaDoc name) throws ParseException JavaDoc {
1052        if(name==null || name.length()==0)
1053            throw new ParseException JavaDoc("No name is specified",0);
1054
1055        for( int i=0; i<name.length(); i++ ) {
1056            char ch = name.charAt(i);
1057            if(Character.isISOControl(ch))
1058                throw new ParseException JavaDoc("No control code is allowed",i);
1059            if("?*()/\\%!@#$^&|<>".indexOf(ch)!=-1)
1060                throw new ParseException JavaDoc("'"+ch+"' is an unsafe character",i);
1061        }
1062
1063        // looks good
1064
}
1065
1066    /**
1067     * Called once the user logs in. Just forward to the top page.
1068     */

1069    public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1070        if(req.getUserPrincipal()==null)
1071            rsp.sendRedirect2("noPrincipal");
1072        else
1073            rsp.sendRedirect2(".");
1074    }
1075
1076    /**
1077     * Called once the user logs in. Just forward to the top page.
1078     */

1079    public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1080        HttpSession JavaDoc session = req.getSession(false);
1081        if(session!=null)
1082            session.invalidate();
1083        rsp.sendRedirect2(req.getContextPath()+"/");
1084    }
1085
1086    /**
1087     * RSS feed for log entries.
1088     */

1089    public void doLogRss( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1090        List<LogRecord JavaDoc> logs = logRecords;
1091
1092        // filter log records based on the log level
1093
String JavaDoc level = req.getParameter("level");
1094        if(level!=null) {
1095            Level JavaDoc threshold = Level.parse(level);
1096            List<LogRecord JavaDoc> filtered = new ArrayList JavaDoc<LogRecord JavaDoc>();
1097            for (LogRecord JavaDoc r : logs) {
1098                if(r.getLevel().intValue() >= threshold.intValue())
1099                    filtered.add(r);
1100            }
1101            logs = filtered;
1102        }
1103
1104        RSS.forwardToRss("Hudson log","", logs, new FeedAdapter<LogRecord JavaDoc>() {
1105            public String JavaDoc getEntryTitle(LogRecord JavaDoc entry) {
1106                return entry.getMessage();
1107            }
1108
1109            public String JavaDoc getEntryUrl(LogRecord JavaDoc entry) {
1110                return "log"; // TODO: one URL for one log entry?
1111
}
1112
1113            public String JavaDoc getEntryID(LogRecord JavaDoc entry) {
1114                return String.valueOf(entry.getSequenceNumber());
1115            }
1116
1117            public Calendar JavaDoc getEntryTimestamp(LogRecord JavaDoc entry) {
1118                GregorianCalendar JavaDoc cal = new GregorianCalendar JavaDoc();
1119                cal.setTimeInMillis(entry.getMillis());
1120                return cal;
1121            }
1122        },req,rsp);
1123    }
1124
1125    /**
1126     * Reloads the configuration.
1127     */

1128    public synchronized void doReload( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1129        if(!Hudson.adminCheck(req,rsp))
1130            return;
1131
1132        load();
1133        rsp.sendRedirect2(req.getContextPath()+"/");
1134    }
1135
1136    /**
1137     * Uploads a plugin.
1138     */

1139    public void doUploadPlugin( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1140        try {
1141            if(!Hudson.adminCheck(req,rsp))
1142                return;
1143
1144            ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
1145
1146            // Parse the request
1147
FileItem fileItem = (FileItem) upload.parseRequest(req).get(0);
1148            String JavaDoc fileName = Util.getFileName(fileItem.getName());
1149            if(!fileName.endsWith(".hpi")) {
1150                sendError(fileName+" is not a Hudson plugin",req,rsp);
1151                return;
1152            }
1153            fileItem.write(new File(getPluginManager().rootDir, fileName));
1154
1155            fileItem.delete();
1156
1157            rsp.sendRedirect2("managePlugins");
1158        } catch (IOException JavaDoc e) {
1159            throw e;
1160        } catch (Exception JavaDoc e) {// grrr. fileItem.write throws this
1161
throw new ServletException JavaDoc(e);
1162        }
1163    }
1164
1165    /**
1166     * Do a finger-print check.
1167     */

1168    public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1169        try {
1170            ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
1171
1172            // Parse the request
1173
@SuppressWarnings JavaDoc("unchecked") // pre-generics lib
1174
List<FileItem> items = upload.parseRequest(req);
1175
1176            rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
1177                Util.getDigestOf(items.get(0).getInputStream())+'/');
1178
1179            // if an error occur and we fail to do this, it will still be cleaned up
1180
// when GC-ed.
1181
for (FileItem item : items)
1182                item.delete();
1183        } catch (FileUploadException e) {
1184            throw new ServletException JavaDoc(e); // I'm not sure what the implication of this
1185
}
1186    }
1187
1188    /**
1189     * Serves static resources without the "Last-Modified" header to work around
1190     * a bug in Firefox.
1191     *
1192     * <p>
1193     * See https://bugzilla.mozilla.org/show_bug.cgi?id=89419
1194     */

1195    public void doNocacheImages( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1196        String JavaDoc path = req.getRestOfPath();
1197
1198        if(path.length()==0)
1199            path = "/";
1200
1201        if(path.indexOf("..")!=-1 || path.length()<1) {
1202            // don't serve anything other than files in the artifacts dir
1203
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1204            return;
1205        }
1206
1207        File f = new File(req.getServletContext().getRealPath("/images"),path.substring(1));
1208        if(!f.exists()) {
1209            rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
1210            return;
1211        }
1212
1213        if(f.isDirectory()) {
1214            // listing not allowed
1215
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
1216            return;
1217        }
1218
1219        FileInputStream JavaDoc in = new FileInputStream JavaDoc(f);
1220        // serve the file
1221
String JavaDoc contentType = req.getServletContext().getMimeType(f.getPath());
1222        rsp.setContentType(contentType);
1223        rsp.setContentLength((int)f.length());
1224        byte[] buf = new byte[1024];
1225        int len;
1226        while((len=in.read(buf))>0)
1227            rsp.getOutputStream().write(buf,0,len);
1228        in.close();
1229    }
1230
1231    /**
1232     * For debugging. Expose URL to perform GC.
1233     */

1234    public void doGc( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1235        System.gc();
1236        rsp.setStatus(HttpServletResponse.SC_OK);
1237        rsp.setContentType("text/plain");
1238        rsp.getWriter().println("GCed");
1239    }
1240
1241    /**
1242     * For system diagnostics.
1243     * Run arbitrary Groovy script.
1244     */

1245    public void doScript( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1246        if(!adminCheck(req,rsp))
1247            return; // ability to run arbitrary script is dangerous
1248

1249        String JavaDoc text = req.getParameter("script");
1250        if(text!=null) {
1251            GroovyShell shell = new GroovyShell();
1252
1253            StringWriter JavaDoc out = new StringWriter JavaDoc();
1254            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(out);
1255            shell.setVariable("out", pw);
1256            try {
1257                Object JavaDoc output = shell.evaluate(text);
1258                if(output!=null)
1259                pw.println("Result: "+output);
1260            } catch (Throwable JavaDoc t) {
1261                t.printStackTrace(pw);
1262            }
1263            req.setAttribute("output",out);
1264        }
1265
1266        req.getView(this,"_script.jelly").forward(req,rsp);
1267    }
1268
1269    /**
1270     * Changes the icon size by changing the cookie
1271     */

1272    public void doIconSize( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1273        rsp.addCookie(new Cookie JavaDoc("iconSize",req.getQueryString()));
1274        String JavaDoc ref = req.getHeader("Referer");
1275        if(ref==null) ref=".";
1276        rsp.sendRedirect2(ref);
1277    }
1278
1279    public void doFingerprintCleanup( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1280        FingerprintCleanupThread.invoke();
1281        rsp.setStatus(HttpServletResponse.SC_OK);
1282        rsp.setContentType("text/plain");
1283        rsp.getWriter().println("Invoked");
1284    }
1285
1286    public void doWorkspaceCleanup( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
1287        WorkspaceCleanupThread.invoke();
1288        rsp.setStatus(HttpServletResponse.SC_OK);
1289        rsp.setContentType("text/plain");
1290        rsp.getWriter().println("Invoked");
1291    }
1292
1293    /**
1294     * Checks if the path is a valid path.
1295     */

1296    public void doCheckLocalFSRoot( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1297        // this can be used to check the existence of a file on the server, so needs to be protected
1298
new FormFieldValidator(req,rsp,true) {
1299            public void check() throws IOException JavaDoc, ServletException JavaDoc {
1300                File f = getFileParameter("value");
1301                if(f.isDirectory()) {// OK
1302
ok();
1303                } else {// nope
1304
if(f.exists()) {
1305                        error(f+" is not a directory");
1306                    } else {
1307                        error("No such directory: "+f);
1308                    }
1309                }
1310            }
1311        }.process();
1312    }
1313
1314    /**
1315     * Checks if the JAVA_HOME is a valid JAVA_HOME path.
1316     */

1317    public void doJavaHomeCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
1318        // this can be used to check the existence of a file on the server, so needs to be protected
1319
new FormFieldValidator(req,rsp,true) {
1320            public void check() throws IOException JavaDoc, ServletException JavaDoc {
1321                File f = getFileParameter("value");
1322                if(!f.isDirectory()) {
1323                    error(f+" is not a directory");
1324                    return;
1325                }
1326
1327                File toolsJar = new File(f,"lib/tools.jar");
1328                if(!toolsJar.exists()) {
1329                    error(f+" doesn't look like a JDK directory");
1330                    return;
1331                }
1332
1333                ok();
1334            }
1335        }.process();
1336    }
1337
1338    /**
1339     * Checks if the top-level item with the given name exists.
1340     */

1341    public void doItemExistsCheck(StaplerRequest req, StaplerResponse rsp) throws IOException JavaDoc, ServletException JavaDoc {
1342        // this method can be used to check if a file exists anywhere in the file system,
1343
// so it should be protected.
1344
new FormFieldValidator(req,rsp,true) {
1345            protected void check() throws IOException JavaDoc, ServletException JavaDoc {
1346                String JavaDoc job = fixEmpty(request.getParameter("value"));
1347                if(job==null) {
1348                    ok(); // nothing is entered yet
1349
return;
1350                }
1351
1352                if(getItem(job)==null)
1353                    ok();
1354                else
1355                    error("Job named "+job+" already exists");
1356            }
1357        }.process();
1358    }
1359
1360
1361    public static boolean isWindows() {
1362        return File.pathSeparatorChar==';';
1363    }
1364
1365
1366    /**
1367     * Returns all {@code CVSROOT} strings used in the current Hudson installation.
1368     *
1369     * <p>
1370     * Ideally this shouldn't be defined in here
1371     * but EL doesn't provide a convenient way of invoking a static function,
1372     * so I'm putting it here for now.
1373     */

1374    public Set JavaDoc<String JavaDoc> getAllCvsRoots() {
1375        Set JavaDoc<String JavaDoc> r = new TreeSet JavaDoc<String JavaDoc>();
1376        for( AbstractProject p : getAllItems(AbstractProject.class) ) {
1377            SCM scm = p.getScm();
1378            if (scm instanceof CVSSCM) {
1379                CVSSCM cvsscm = (CVSSCM) scm;
1380                r.add(cvsscm.getCvsRoot());
1381            }
1382        }
1383
1384        return r;
1385    }
1386
1387    /**
1388     * Rebuilds the dependency map.
1389     */

1390    public void rebuildDependencyGraph() {
1391        dependencyGraph = new DependencyGraph();
1392    }
1393
1394    public DependencyGraph getDependencyGraph() {
1395        return dependencyGraph;
1396    }
1397
1398    public static final class MasterComputer extends Computer {
1399        private MasterComputer() {
1400            super(Hudson.getInstance());
1401        }
1402
1403        @Override JavaDoc
1404        public VirtualChannel getChannel() {
1405            return localChannel;
1406        }
1407
1408        public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException JavaDoc, ServletException JavaDoc {
1409            // this computer never returns null from channel, so
1410
// this method shall never be invoked.
1411
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
1412        }
1413
1414        /**
1415         * {@link LocalChannel} instance that can be used to execute programs locally.
1416         */

1417        public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
1418    }
1419
1420    public static boolean adminCheck(StaplerRequest req,StaplerResponse rsp) throws IOException JavaDoc {
1421        if(!getInstance().isUseSecurity())
1422            return true;
1423
1424        if(req.isUserInRole("admin"))
1425            return true;
1426
1427        rsp.sendError(StaplerResponse.SC_FORBIDDEN);
1428        return false;
1429    }
1430
1431    /**
1432     * Live view of recent {@link LogRecord}s produced by Hudson.
1433     */

1434    public static List<LogRecord JavaDoc> logRecords = Collections.emptyList(); // initialized to dummy value to avoid NPE
1435

1436    /**
1437     * Thread-safe reusable {@link XStream}.
1438     */

1439    private static final XStream XSTREAM = new XStream2();
1440
1441    static {
1442        XSTREAM.alias("hudson",Hudson.class);
1443        XSTREAM.alias("slave",Slave.class);
1444        XSTREAM.alias("jdk",JDK.class);
1445        // for backward compatibility with <1.75, recognize the tag name "view" as well.
1446
XSTREAM.alias("view", ListView.class);
1447        XSTREAM.alias("listView", ListView.class);
1448    }
1449}
1450
Popular Tags