File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\Run.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2012, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Daniel Dyer, Red Hat, Inc., Tom Huybrechts, Romain Seguy, Yahoo! Inc.,
6 * Darek Ostolski, CloudBees, Inc.
7 *
8 * Copyright (c) 2012, Martin Schroeder, Intel Mobile Communications GmbH
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 */
28 package hudson.model;
29
30 import hudson.console.ConsoleLogFilter;
31 import hudson.Functions;
32 import hudson.AbortException;
33 import hudson.BulkChange;
34 import hudson.EnvVars;
35 import hudson.ExtensionPoint;
36 import hudson.FeedAdapter;
37 import hudson.FilePath;
38 import hudson.Util;
39 import hudson.XmlFile;
40 import hudson.cli.declarative.CLIMethod;
41 import hudson.console.AnnotatedLargeText;
42 import hudson.console.ConsoleNote;
43 import hudson.matrix.MatrixBuild;
44 import hudson.matrix.MatrixRun;
45 import hudson.model.Descriptor.FormException;
46 import hudson.model.listeners.RunListener;
47 import hudson.model.listeners.SaveableListener;
48 import hudson.security.PermissionScope;
49 import hudson.search.SearchIndexBuilder;
50 import hudson.security.ACL;
51 import hudson.security.AccessControlled;
52 import hudson.security.Permission;
53 import hudson.security.PermissionGroup;
54 import hudson.tasks.BuildWrapper;
55 import hudson.tasks.BuildStep;
56 import hudson.tasks.test.AbstractTestResultAction;
57 import hudson.util.FlushProofOutputStream;
58 import hudson.util.FormApply;
59 import hudson.util.IOException2;
60 import hudson.util.LogTaskListener;
61 import hudson.util.XStream2;
62 import hudson.util.ProcessTree;
63
64 import java.io.BufferedReader;
65 import java.io.File;
66 import java.io.FileInputStream;
67 import java.io.IOException;
68 import java.io.InputStream;
69 import java.io.InputStreamReader;
70 import java.io.PrintWriter;
71 import java.io.Reader;
72 import java.io.Writer;
73 import java.nio.charset.Charset;
74 import java.text.DateFormat;
75 import java.text.ParseException;
76 import java.text.SimpleDateFormat;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Calendar;
80 import java.util.Collections;
81 import java.util.Comparator;
82 import java.util.Date;
83 import java.util.GregorianCalendar;
84 import java.util.HashMap;
85 import java.util.LinkedHashMap;
86 import java.util.LinkedList;
87 import java.util.List;
88 import java.util.Locale;
89 import java.util.Map;
90 import java.util.Set;
91 import java.util.HashSet;
92 import java.util.logging.Level;
93 import java.util.logging.Logger;
94 import java.util.zip.GZIPInputStream;
95
96 import javax.servlet.ServletException;
97 import javax.servlet.http.HttpServletResponse;
98
99 import jenkins.model.BuildDiscarder;
100 import jenkins.model.Jenkins;
101 import jenkins.model.JenkinsLocationConfiguration;
102 import jenkins.util.io.OnMaster;
103 import net.sf.json.JSONObject;
104 import org.apache.commons.io.IOUtils;
105 import org.apache.commons.jelly.XMLOutput;
106 import org.kohsuke.accmod.Restricted;
107 import org.kohsuke.accmod.restrictions.NoExternalUse;
108 import org.kohsuke.stapler.*;
109 import org.kohsuke.stapler.export.Exported;
110 import org.kohsuke.stapler.export.ExportedBean;
111
112 import com.thoughtworks.xstream.XStream;
113 import java.io.ByteArrayInputStream;
114 import org.kohsuke.stapler.interceptor.RequirePOST;
115
116 import java.io.FileOutputStream;
117 import java.io.OutputStream;
118
119 import static java.util.logging.Level.*;
120 import javax.annotation.CheckForNull;
121 import javax.annotation.Nonnull;
122
123 /**
124 * A particular execution of {@link Job}.
125 *
126 * <p>
127 * Custom {@link Run} type is always used in conjunction with
128 * a custom {@link Job} type, so there's no separate registration
129 * mechanism for custom {@link Run} types.
130 *
131 * @author Kohsuke Kawaguchi
132 * @see RunListener
133 */
134 @ExportedBean
135 public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,RunT>>
136 extends Actionable implements ExtensionPoint, Comparable<RunT>, AccessControlled, PersistenceRoot, DescriptorByNameOwner, OnMaster {
137
138 protected transient final JobT project;
139
140 /**
141 * Build number.
142 *
143 * <p>
144 * In earlier versions &lt; 1.24, this number is not unique nor continuous,
145 * but going forward, it will, and this really replaces the build id.
146 */
147 public /*final*/ int number;
148
149 /**
150 * Previous build. Can be null.
151 * These two fields are maintained and updated by {@link RunMap}.
152 *
153 * External code should use {@link #getPreviousBuild()}
154 */
155 @Restricted(NoExternalUse.class)
156 protected volatile transient RunT previousBuild;
157
158 /**
159 * Next build. Can be null.
160 *
161 * External code should use {@link #getNextBuild()}
162 */
163 @Restricted(NoExternalUse.class)
164 protected volatile transient RunT nextBuild;
165
166 /**
167 * Pointer to the next younger build in progress. This data structure is lazily updated,
168 * so it may point to the build that's already completed. This pointer is set to 'this'
169 * if the computation determines that everything earlier than this build is already completed.
170 */
171 /* does not compile on JDK 7: private*/ volatile transient RunT previousBuildInProgress;
172
173 /**
174 * When the build is scheduled.
175 */
176 protected transient final long timestamp;
177
178 /**
179 * When the build has started running.
180 *
181 * For historical reasons, 0 means no value is recorded.
182 *
183 * @see #getStartTimeInMillis()
184 */
185 private long startTime;
186
187 /**
188 * The build result.
189 * This value may change while the state is in {@link State#BUILDING}.
190 */
191 protected volatile Result result;
192
193 /**
194 * Human-readable description. Can be null.
195 */
196 protected volatile String description;
197
198 /**
199 * Human-readable name of this build. Can be null.
200 * If non-null, this text is displayed instead of "#NNN", which is the default.
201 * @since 1.390
202 */
203 private volatile String displayName;
204
205 /**
206 * The current build state.
207 */
208 protected volatile transient State state;
209
210 private static enum State {
211 /**
212 * Build is created/queued but we haven't started building it.
213 */
214 NOT_STARTED,
215 /**
216 * Build is in progress.
217 */
218 BUILDING,
219 /**
220 * Build is completed now, and the status is determined,
221 * but log files are still being updated.
222 */
223 POST_PRODUCTION,
224 /**
225 * Build is completed now, and log file is closed.
226 */
227 COMPLETED
228 }
229
230 /**
231 * Number of milli-seconds it took to run this build.
232 */
233 protected long duration;
234
235 /**
236 * Charset in which the log file is written.
237 * For compatibility reason, this field may be null.
238 * For persistence, this field is string and not {@link Charset}.
239 *
240 * @see #getCharset()
241 * @since 1.257
242 */
243 protected String charset;
244
245 /**
246 * Keeps this log entries.
247 */
248 private boolean keepLog;
249
250 /**
251 * If the build is in progress, remember {@link RunExecution} that's running it.
252 * This field is not persisted.
253 */
254 private volatile transient RunExecution runner;
255
256 private static final SimpleDateFormat CANONICAL_ID_FORMATTER = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
257 protected static final ThreadLocal<SimpleDateFormat> ID_FORMATTER = new IDFormatterProvider();
258 private static final class IDFormatterProvider extends ThreadLocal<SimpleDateFormat> {
259 @Override
260 protected SimpleDateFormat initialValue() {
261 synchronized (CANONICAL_ID_FORMATTER) {
262 return (SimpleDateFormat) CANONICAL_ID_FORMATTER.clone();
263 }
264 }
265 };
266
267 /**
268 * Creates a new {@link Run}.
269 */
270 protected Run(JobT job) throws IOException {
271 this(job, new GregorianCalendar());
272 this.number = project.assignBuildNumber();
273 }
274
275 /**
276 * Constructor for creating a {@link Run} object in
277 * an arbitrary state.
278 */
279 protected Run(JobT job, Calendar timestamp) {
280 this(job,timestamp.getTimeInMillis());
281 }
282
283 protected Run(JobT job, long timestamp) {
284 this.project = job;
285 this.timestamp = timestamp;
286 this.state = State.NOT_STARTED;
287 getRootDir().mkdirs();
288 }
289
290 /**
291 * Loads a run from a log file.
292 */
293 protected Run(JobT project, File buildDir) throws IOException {
294 this(project, parseTimestampFromBuildDir(buildDir));
295 this.previousBuildInProgress = _this(); // loaded builds are always completed
296 reload();
297 }
298
299 /**
300 * Reloads the build record from disk.
301 *
302 * @since 1.410
303 */
304 public void reload() throws IOException {
305 this.state = State.COMPLETED;
306 this.result = Result.FAILURE; // defensive measure. value should be overwritten by unmarshal, but just in case the saved data is inconsistent
307 getDataFile().unmarshal(this); // load the rest of the data
308
309 // not calling onLoad upon reload. partly because we don't want to call that from Run constructor,
310 // and partly because some existing use of onLoad isn't assuming that it can be invoked multiple times.
311 }
312
313 /**
314 * Called after the build is loaded and the object is added to the build list.
315 */
316 protected void onLoad() {
317 for (Action a : getActions())
318 if (a instanceof RunAction)
319 ((RunAction) a).onLoad();
320 }
321
322 /**
323 * Return all transient actions associated with this build.
324 *
325 * @return the list can be empty but never null. read only.
326 */
327 public List<Action> getTransientActions() {
328 List<Action> actions = new ArrayList<Action>();
329 for (TransientBuildActionFactory factory: TransientBuildActionFactory.all()) {
330 actions.addAll(factory.createFor(this));
331 }
332 return Collections.unmodifiableList(actions);
333 }
334
335 @Override
336 public void addAction(Action a) {
337 super.addAction(a);
338 if (a instanceof RunAction)
339 ((RunAction) a).onAttached(this);
340 }
341
342 /*package*/ static long parseTimestampFromBuildDir(File buildDir) throws IOException {
343 try {
344 // canonicalization to ensure we are looking at the ID in the directory name
345 // as opposed to build numbers which are used in symlinks
346 return ID_FORMATTER.get().parse(buildDir.getCanonicalFile().getName()).getTime();
347 } catch (ParseException e) {
348 throw new IOException2("Invalid directory name "+buildDir,e);
349 } catch (NumberFormatException e) {
350 throw new IOException2("Invalid directory name "+buildDir,e);
351 }
352 }
353
354 /**
355 * Obtains 'this' in a more type safe signature.
356 */
357 @SuppressWarnings({"unchecked"})
358 protected RunT _this() {
359 return (RunT)this;
360 }
361
362 /**
363 * Ordering based on build numbers.
364 */
365 public int compareTo(RunT that) {
366 return this.number - that.number;
367 }
368
369 /**
370 * Returns the build result.
371 *
372 * <p>
373 * When a build is {@link #isBuilding() in progress}, this method
374 * returns an intermediate result.
375 */
376 @Exported
377 public Result getResult() {
378 return result;
379 }
380
381 public void setResult(Result r) {
382 // state can change only when we are building
383 assert state==State.BUILDING;
384
385 // result can only get worse
386 if(result==null) {
387 result = r;
388 LOGGER.log(FINE, toString()+" : result is set to "+r,new Exception());
389 } else {
390 if(r.isWorseThan(result)) {
391 LOGGER.log(FINE, toString()+" : result is set to "+r,new Exception());
392 result = r;
393 }
394 }
395 }
396
397 /**
398 * Gets the subset of {@link #getActions()} that consists of {@link BuildBadgeAction}s.
399 */
400 public List<BuildBadgeAction> getBadgeActions() {
401 List<BuildBadgeAction> r = null;
402 for (Action a : getActions()) {
403 if(a instanceof BuildBadgeAction) {
404 if(r==null)
405 r = new ArrayList<BuildBadgeAction>();
406 r.add((BuildBadgeAction)a);
407 }
408 }
409 if(isKeepLog()) {
410 if(r==null)
411 r = new ArrayList<BuildBadgeAction>();
412 r.add(new KeepLogBuildBadge());
413 }
414 if(r==null) return Collections.emptyList();
415 else return r;
416 }
417
418 /**
419 * Returns true if the build is not completed yet.
420 * This includes "not started yet" state.
421 */
422 @Exported
423 public boolean isBuilding() {
424 return state.compareTo(State.POST_PRODUCTION) < 0;
425 }
426
427 /**
428 * Returns true if the log file is still being updated.
429 */
430 public boolean isLogUpdated() {
431 return state.compareTo(State.COMPLETED) < 0;
432 }
433
434 /**
435 * Gets the {@link Executor} building this job, if it's being built.
436 * Otherwise null.
437 *
438 * This method looks for {@link Executor} who's {@linkplain Executor#getCurrentExecutable() assigned to this build},
439 * and because of that this might not be necessarily in sync with the return value of {@link #isBuilding()} &mdash;
440 * an executor holds on to {@lnk Run} some more time even after the build is finished (for example to
441 * perform {@linkplain State#POST_PRODUCTION post-production processing}.)
442 */
443 @Exported
444 public @CheckForNull Executor getExecutor() {
445 Jenkins j = Jenkins.getInstance();
446 if (j == null) {
447 return null;
448 }
449 for (Computer c : j.getComputers()) {
450 for (Executor e : c.getExecutors()) {
451 if(e.getCurrentExecutable()==this)
452 return e;
453 }
454 }
455 return null;
456 }
457
458 /**
459 * Gets the one off {@link Executor} building this job, if it's being built.
460 * Otherwise null.
461 * @since 1.433
462 */
463 public Executor getOneOffExecutor() {
464 for( Computer c : Jenkins.getInstance().getComputers() ) {
465 for (Executor e : c.getOneOffExecutors()) {
466 if(e.getCurrentExecutable()==this)
467 return e;
468 }
469 }
470 return null;
471 }
472
473 /**
474 * Gets the charset in which the log file is written.
475 * @return never null.
476 * @since 1.257
477 */
478 public final Charset getCharset() {
479 if(charset==null) return Charset.defaultCharset();
480 return Charset.forName(charset);
481 }
482
483 /**
484 * Returns the {@link Cause}s that tirggered a build.
485 *
486 * <p>
487 * If a build sits in the queue for a long time, multiple build requests made during this period
488 * are all rolled up into one build, hence this method may return a list.
489 *
490 * @return
491 * can be empty but never null. read-only.
492 * @since 1.321
493 */
494 public List<Cause> getCauses() {
495 CauseAction a = getAction(CauseAction.class);
496 if (a==null) return Collections.emptyList();
497 return Collections.unmodifiableList(a.getCauses());
498 }
499
500 /**
501 * Returns a {@link Cause} of a particular type.
502 *
503 * @since 1.362
504 */
505 public <T extends Cause> T getCause(Class<T> type) {
506 for (Cause c : getCauses())
507 if (type.isInstance(c))
508 return type.cast(c);
509 return null;
510 }
511
512 /**
513 * Returns true if this log file should be kept and not deleted.
514 *
515 * This is used as a signal to the {@link BuildDiscarder}.
516 */
517 @Exported
518 public final boolean isKeepLog() {
519 return getWhyKeepLog()!=null;
520 }
521
522 /**
523 * If {@link #isKeepLog()} returns true, returns a human readable
524 * one-line string that explains why it's being kept.
525 */
526 public String getWhyKeepLog() {
527 if(keepLog)
528 return Messages.Run_MarkedExplicitly();
529 return null; // not marked at all
530 }
531
532 /**
533 * The project this build is for.
534 */
535 public JobT getParent() {
536 return project;
537 }
538
539 /**
540 * When the build is scheduled.
541 *
542 * @see #getStartTimeInMillis()
543 */
544 @Exported
545 public Calendar getTimestamp() {
546 GregorianCalendar c = new GregorianCalendar();
547 c.setTimeInMillis(timestamp);
548 return c;
549 }
550
551 /**
552 * Same as {@link #getTimestamp()} but in a different type.
553 */
554 public final Date getTime() {
555 return new Date(timestamp);
556 }
557
558 /**
559 * Same as {@link #getTimestamp()} but in a different type, that is since the time of the epoc.
560 */
561 public final long getTimeInMillis() {
562 return timestamp;
563 }
564
565 /**
566 * When the build has started running in an executor.
567 *
568 * For example, if a build is scheduled 1pm, and stayed in the queue for 1 hour (say, no idle slaves),
569 * then this method returns 2pm, which is the time the job moved from the queue to the building state.
570 *
571 * @see #getTimestamp()
572 */
573 public final long getStartTimeInMillis() {
574 if (startTime==0) return timestamp; // fallback: approximate by the queuing time
575 return startTime;
576 }
577
578 @Exported
579 public String getDescription() {
580 return description;
581 }
582
583
584 /**
585 * Returns the length-limited description.
586 * @return The length-limited description.
587 */
588 public String getTruncatedDescription() {
589 final int maxDescrLength = 100;
590 if (description == null || description.length() < maxDescrLength) {
591 return description;
592 }
593
594 final String ending = "...";
595 final int sz = description.length(), maxTruncLength = maxDescrLength - ending.length();
596
597 boolean inTag = false;
598 int displayChars = 0;
599 int lastTruncatablePoint = -1;
600
601 for (int i=0; i<sz; i++) {
602 char ch = description.charAt(i);
603 if(ch == '<') {
604 inTag = true;
605 } else if (ch == '>') {
606 inTag = false;
607 if (displayChars <= maxTruncLength) {
608 lastTruncatablePoint = i + 1;
609 }
610 }
611 if (!inTag) {
612 displayChars++;
613 if (displayChars <= maxTruncLength && ch == ' ') {
614 lastTruncatablePoint = i;
615 }
616 }
617 }
618
619 String truncDesc = description;
620
621 // Could not find a preferred truncable index, force a trunc at maxTruncLength
622 if (lastTruncatablePoint == -1)
623 lastTruncatablePoint = maxTruncLength;
624
625 if (displayChars >= maxDescrLength) {
626 truncDesc = truncDesc.substring(0, lastTruncatablePoint) + ending;
627 }
628
629 return truncDesc;
630
631 }
632
633 /**
634 * Gets the string that says how long since this build has started.
635 *
636 * @return
637 * string like "3 minutes" "1 day" etc.
638 */
639 public String getTimestampString() {
640 long duration = new GregorianCalendar().getTimeInMillis()-timestamp;
641 return Util.getPastTimeString(duration);
642 }
643
644 /**
645 * Returns the timestamp formatted in xs:dateTime.
646 */
647 public String getTimestampString2() {
648 return Util.XS_DATETIME_FORMATTER.format(new Date(timestamp));
649 }
650
651 /**
652 * Gets the string that says how long the build took to run.
653 */
654 public String getDurationString() {
655 if(isBuilding())
656 return Messages.Run_InProgressDuration(
657 Util.getTimeSpanString(System.currentTimeMillis()-timestamp));
658 return Util.getTimeSpanString(duration);
659 }
660
661 /**
662 * Gets the millisecond it took to build.
663 */
664 @Exported
665 public long getDuration() {
666 return duration;
667 }
668
669 /**
670 * Gets the icon color for display.
671 */
672 public BallColor getIconColor() {
673 if(!isBuilding()) {
674 // already built
675 return getResult().color;
676 }
677
678 // a new build is in progress
679 BallColor baseColor;
680 RunT pb = getPreviousBuild();
681 if(pb==null)
682 baseColor = BallColor.GREY;
683 else
684 baseColor = pb.getIconColor();
685
686 return baseColor.anime();
687 }
688
689 /**
690 * Returns true if the build is still queued and hasn't started yet.
691 */
692 public boolean hasntStartedYet() {
693 return state ==State.NOT_STARTED;
694 }
695
696 @Override
697 public String toString() {
698 return getFullDisplayName();
699 }
700
701 @Exported
702 public String getFullDisplayName() {
703 return project.getFullDisplayName()+' '+getDisplayName();
704 }
705
706 public String getDisplayName() {
707 return displayName!=null ? displayName : "#"+number;
708 }
709
710 public boolean hasCustomDisplayName() {
711 return displayName!=null;
712 }
713
714 /**
715 * @param value
716 * Set to null to revert back to the default "#NNN".
717 */
718 public void setDisplayName(String value) throws IOException {
719 checkPermission(UPDATE);
720 this.displayName = value;
721 save();
722 }
723
724 @Exported(visibility=2)
725 public int getNumber() {
726 return number;
727 }
728
729 /**
730 * Called by {@link RunMap} to drop bi-directional links in preparation for
731 * deleting a build.
732 */
733 /*package*/ void dropLinks() {
734 if(nextBuild!=null)
735 nextBuild.previousBuild = previousBuild;
736 if(previousBuild!=null)
737 previousBuild.nextBuild = nextBuild;
738 }
739
740 public RunT getPreviousBuild() {
741 return previousBuild;
742 }
743
744 /**
745 * Gets the most recent {@linkplain #isBuilding() completed} build excluding 'this' Run itself.
746 */
747 public final RunT getPreviousCompletedBuild() {
748 RunT r=getPreviousBuild();
749 while (r!=null && r.isBuilding())
750 r=r.getPreviousBuild();
751 return r;
752 }
753
754 /**
755 * Obtains the next younger build in progress. It uses a skip-pointer so that we can compute this without
756 * O(n) computation time. This method also fixes up the skip list as we go, in a way that's concurrency safe.
757 *
758 * <p>
759 * We basically follow the existing skip list, and wherever we find a non-optimal pointer, we remember them
760 * in 'fixUp' and update them later.
761 */
762 public final RunT getPreviousBuildInProgress() {
763 if(previousBuildInProgress==this) return null; // the most common case
764
765 List<RunT> fixUp = new ArrayList<RunT>();
766 RunT r = _this(); // 'r' is the source of the pointer (so that we can add it to fix up if we find that the target of the pointer is inefficient.)
767 RunT answer;
768 while (true) {
769 RunT n = r.previousBuildInProgress;
770 if (n==null) {// no field computed yet.
771 n=r.getPreviousBuild();
772 fixUp.add(r);
773 }
774 if (r==n || n==null) {
775 // this indicates that we know there's no build in progress beyond this point
776 answer = null;
777 break;
778 }
779 if (n.isBuilding()) {
780 // we now know 'n' is the target we wanted
781 answer = n;
782 break;
783 }
784
785 fixUp.add(r); // r contains the stale 'previousBuildInProgress' back pointer
786 r = n;
787 }
788
789 // fix up so that the next look up will run faster
790 for (RunT f : fixUp)
791 f.previousBuildInProgress = answer==null ? f : answer;
792 return answer;
793 }
794
795 /**
796 * Returns the last build that was actually built - i.e., skipping any with Result.NOT_BUILT
797 */
798 public RunT getPreviousBuiltBuild() {
799 RunT r=getPreviousBuild();
800 // in certain situations (aborted m2 builds) r.getResult() can still be null, although it should theoretically never happen
801 while( r!=null && (r.getResult() == null || r.getResult()==Result.NOT_BUILT) )
802 r=r.getPreviousBuild();
803 return r;
804 }
805
806 /**
807 * Returns the last build that didn't fail before this build.
808 */
809 public RunT getPreviousNotFailedBuild() {
810 RunT r=getPreviousBuild();
811 while( r!=null && r.getResult()==Result.FAILURE )
812 r=r.getPreviousBuild();
813 return r;
814 }
815
816 /**
817 * Returns the last failed build before this build.
818 */
819 public RunT getPreviousFailedBuild() {
820 RunT r=getPreviousBuild();
821 while( r!=null && r.getResult()!=Result.FAILURE )
822 r=r.getPreviousBuild();
823 return r;
824 }
825
826 /**
827 * Returns the last successful build before this build.
828 * @since 1.383
829 */
830 public RunT getPreviousSuccessfulBuild() {
831 RunT r=getPreviousBuild();
832 while( r!=null && r.getResult()!=Result.SUCCESS )
833 r=r.getPreviousBuild();
834 return r;
835 }
836
837 /**
838 * Returns the last 'numberOfBuilds' builds with a build result >= 'threshold'.
839 *
840 * @param numberOfBuilds the desired number of builds
841 * @param threshold the build result threshold
842 * @return a list with the builds (youngest build first).
843 * May be smaller than 'numberOfBuilds' or even empty
844 * if not enough builds satisfying the threshold have been found. Never null.
845 * @since 1.383
846 */
847 public List<RunT> getPreviousBuildsOverThreshold(int numberOfBuilds, Result threshold) {
848 List<RunT> builds = new ArrayList<RunT>(numberOfBuilds);
849
850 RunT r = getPreviousBuild();
851 while (r != null && builds.size() < numberOfBuilds) {
852 if (!r.isBuilding() &&
853 (r.getResult() != null && r.getResult().isBetterOrEqualTo(threshold))) {
854 builds.add(r);
855 }
856 r = r.getPreviousBuild();
857 }
858
859 return builds;
860 }
861
862 public RunT getNextBuild() {
863 return nextBuild;
864 }
865
866 /**
867 * Returns the URL of this {@link Run}, relative to the context root of Hudson.
868 *
869 * @return
870 * String like "job/foo/32/" with trailing slash but no leading slash.
871 */
872 // I really messed this up. I'm hoping to fix this some time
873 // it shouldn't have trailing '/', and instead it should have leading '/'
874 public String getUrl() {
875
876 // RUN may be accessed using permalinks, as "/lastSuccessful" or other, so try to retrieve this base URL
877 // looking for "this" in the current request ancestors
878 // @see also {@link AbstractItem#getUrl}
879 StaplerRequest req = Stapler.getCurrentRequest();
880 if (req != null) {
881 String seed = Functions.getNearestAncestorUrl(req,this);
882 if(seed!=null) {
883 // trim off the context path portion and leading '/', but add trailing '/'
884 return seed.substring(req.getContextPath().length()+1)+'/';
885 }
886 }
887
888 return project.getUrl()+getNumber()+'/';
889 }
890
891 /**
892 * Obtains the absolute URL to this build.
893 *
894 * @deprecated
895 * This method shall <b>NEVER</b> be used during HTML page rendering, as it's too easy for
896 * misconfiguration to break this value, with network set up like Apache reverse proxy.
897 * This method is only intended for the remote API clients who cannot resolve relative references.
898 */
899 @Exported(visibility=2,name="url")
900 public final String getAbsoluteUrl() {
901 return project.getAbsoluteUrl()+getNumber()+'/';
902 }
903
904 public final String getSearchUrl() {
905 return getNumber()+"/";
906 }
907
908 /**
909 * Unique ID of this build.
910 */
911 @Exported
912 public String getId() {
913 return ID_FORMATTER.get().format(new Date(timestamp));
914 }
915
916 /**
917 * Get the date formatter used to convert the directory name in to a timestamp
918 * This is nasty exposure of private data, but needed all the time the directory
919 * containing the build is used as it's timestamp.
920 */
921 public static DateFormat getIDFormatter() {
922 return ID_FORMATTER.get();
923 }
924
925 public Descriptor getDescriptorByName(String className) {
926 return Jenkins.getInstance().getDescriptorByName(className);
927 }
928
929 /**
930 * Root directory of this {@link Run} on the master.
931 *
932 * Files related to this {@link Run} should be stored below this directory.
933 */
934 public File getRootDir() {
935 return new File(project.getBuildDir(),getId());
936 }
937
938 /**
939 * Gets the directory where the artifacts are archived.
940 */
941 public File getArtifactsDir() {
942 return new File(getRootDir(),"archive");
943 }
944
945 /**
946 * Gets the artifacts (relative to {@link #getArtifactsDir()}.
947 */
948 @Exported
949 public List<Artifact> getArtifacts() {
950 return getArtifactsUpTo(Integer.MAX_VALUE);
951 }
952
953 /**
954 * Gets the first N artifacts.
955 */
956 public List<Artifact> getArtifactsUpTo(int n) {
957 ArtifactList r = new ArtifactList();
958 addArtifacts(getArtifactsDir(),"","",r,null,n);
959 r.computeDisplayName();
960 return r;
961 }
962
963 /**
964 * Returns true if this run has any artifacts.
965 *
966 * <p>
967 * The strange method name is so that we can access it from EL.
968 */
969 public boolean getHasArtifacts() {
970 return !getArtifactsUpTo(1).isEmpty();
971 }
972
973 private int addArtifacts( File dir, String path, String pathHref, ArtifactList r, Artifact parent, int upTo ) {
974 String[] children = dir.list();
975 if(children==null) return 0;
976 Arrays.sort(children, String.CASE_INSENSITIVE_ORDER);
977
978 int n = 0;
979 for (String child : children) {
980 String childPath = path + child;
981 String childHref = pathHref + Util.rawEncode(child);
982 File sub = new File(dir, child);
983 String length = sub.isFile() ? String.valueOf(sub.length()) : "";
984 boolean collapsed = (children.length==1 && parent!=null);
985 Artifact a;
986 if (collapsed) {
987 // Collapse single items into parent node where possible:
988 a = new Artifact(parent.getFileName() + '/' + child, childPath,
989 sub.isDirectory() ? null : childHref, length,
990 parent.getTreeNodeId());
991 r.tree.put(a, r.tree.remove(parent));
992 } else {
993 // Use null href for a directory:
994 a = new Artifact(child, childPath,
995 sub.isDirectory() ? null : childHref, length,
996 "n" + ++r.idSeq);
997 r.tree.put(a, parent!=null ? parent.getTreeNodeId() : null);
998 }
999 if (sub.isDirectory()) {
1000 n += addArtifacts(sub, childPath + '/', childHref + '/', r, a, upTo-n);
1001 if (n>=upTo) break;
1002 } else {
1003 // Don't store collapsed path in ArrayList (for correct data in external API)
1004 r.add(collapsed ? new Artifact(child, a.relativePath, a.href, length, a.treeNodeId) : a);
1005 if (++n>=upTo) break;
1006 }
1007 }
1008 return n;
1009 }
1010
1011 /**
1012 * Maximum number of artifacts to list before using switching to the tree view.
1013 */
1014 public static final int LIST_CUTOFF = Integer.parseInt(System.getProperty("hudson.model.Run.ArtifactList.listCutoff", "16"));
1015
1016 /**
1017 * Maximum number of artifacts to show in tree view before just showing a link.
1018 */
1019 public static final int TREE_CUTOFF = Integer.parseInt(System.getProperty("hudson.model.Run.ArtifactList.treeCutoff", "40"));
1020
1021 // ..and then "too many"
1022
1023 public final class ArtifactList extends ArrayList<Artifact> {
1024 private static final long serialVersionUID = 1L;
1025 /**
1026 * Map of Artifact to treeNodeId of parent node in tree view.
1027 * Contains Artifact objects for directories and files (the ArrayList contains only files).
1028 */
1029 private LinkedHashMap<Artifact,String> tree = new LinkedHashMap<Artifact,String>();
1030 private int idSeq = 0;
1031
1032 public Map<Artifact,String> getTree() {
1033 return tree;
1034 }
1035
1036 public void computeDisplayName() {
1037 if(size()>LIST_CUTOFF) return; // we are not going to display file names, so no point in computing this
1038
1039 int maxDepth = 0;
1040 int[] len = new int[size()];
1041 String[][] tokens = new String[size()][];
1042 for( int i=0; i<tokens.length; i++ ) {
1043 tokens[i] = get(i).relativePath.split("[\\\\/]+");
1044 maxDepth = Math.max(maxDepth,tokens[i].length);
1045 len[i] = 1;
1046 }
1047
1048 boolean collision;
1049 int depth=0;
1050 do {
1051 collision = false;
1052 Map<String,Integer/*index*/> names = new HashMap<String,Integer>();
1053 for (int i = 0; i < tokens.length; i++) {
1054 String[] token = tokens[i];
1055 String displayName = combineLast(token,len[i]);
1056 Integer j = names.put(displayName, i);
1057 if(j!=null) {
1058 collision = true;
1059 if(j>=0)
1060 len[j]++;
1061 len[i]++;
1062 names.put(displayName,-1); // occupy this name but don't let len[i] incremented with additional collisions
1063 }
1064 }
1065 } while(collision && depth++<maxDepth);
1066
1067 for (int i = 0; i < tokens.length; i++)
1068 get(i).displayPath = combineLast(tokens[i],len[i]);
1069
1070 // OUTER:
1071 // for( int n=1; n<maxLen; n++ ) {
1072 // // if we just display the last n token, would it be suffice for disambiguation?
1073 // Set<String> names = new HashSet<String>();
1074 // for (String[] token : tokens) {
1075 // if(!names.add(combineLast(token,n)))
1076 // continue OUTER; // collision. Increase n and try again
1077 // }
1078 //
1079 // // this n successfully diambiguates
1080 // for (int i = 0; i < tokens.length; i++) {
1081 // String[] token = tokens[i];
1082 // get(i).displayPath = combineLast(token,n);
1083 // }
1084 // return;
1085 // }
1086
1087 // // it's impossible to get here, as that means
1088 // // we have the same artifacts archived twice, but be defensive
1089 // for (Artifact a : this)
1090 // a.displayPath = a.relativePath;
1091 }
1092
1093 /**
1094 * Combines last N token into the "a/b/c" form.
1095 */
1096 private String combineLast(String[] token, int n) {
1097 StringBuilder buf = new StringBuilder();
1098 for( int i=Math.max(0,token.length-n); i<token.length; i++ ) {
1099 if(buf.length()>0) buf.append('/');
1100 buf.append(token[i]);
1101 }
1102 return buf.toString();
1103 }
1104 }
1105
1106 /**
1107 * A build artifact.
1108 */
1109 @ExportedBean
1110 public class Artifact {
1111 /**
1112 * Relative path name from {@link Run#getArtifactsDir()}
1113 */
1114 @Exported(visibility=3)
1115 public final String relativePath;
1116
1117 /**
1118 * Truncated form of {@link #relativePath} just enough
1119 * to disambiguate {@link Artifact}s.
1120 */
1121 /*package*/ String displayPath;
1122
1123 /**
1124 * The filename of the artifact.
1125 * (though when directories with single items are collapsed for tree view, name may
1126 * include multiple path components, like "dist/pkg/mypkg")
1127 */
1128 private String name;
1129
1130 /**
1131 * Properly encoded relativePath for use in URLs. This field is null for directories.
1132 */
1133 private String href;
1134
1135 /**
1136 * Id of this node for use in tree view.
1137 */
1138 private String treeNodeId;
1139
1140 /**
1141 *length of this artifact for files.
1142 */
1143 private String length;
1144
1145 /*package for test*/ Artifact(String name, String relativePath, String href, String len, String treeNodeId) {
1146 this.name = name;
1147 this.relativePath = relativePath;
1148 this.href = href;
1149 this.treeNodeId = treeNodeId;
1150 this.length = len;
1151 }
1152
1153 /**
1154 * Gets the artifact file.
1155 */
1156 public File getFile() {
1157 return new File(getArtifactsDir(),relativePath);
1158 }
1159
1160 /**
1161 * Returns just the file name portion, without the path.
1162 */
1163 @Exported(visibility=3)
1164 public String getFileName() {
1165 return name;
1166 }
1167
1168 @Exported(visibility=3)
1169 public String getDisplayPath() {
1170 return displayPath;
1171 }
1172
1173 public String getHref() {
1174 return href;
1175 }
1176
1177 public String getLength() {
1178 return length;
1179 }
1180
1181 public long getFileSize(){
1182 return Long.decode(length);
1183 }
1184
1185 public String getTreeNodeId() {
1186 return treeNodeId;
1187 }
1188
1189 @Override
1190 public String toString() {
1191 return relativePath;
1192 }
1193 }
1194
1195 /**
1196 * Returns the log file.
1197 */
1198 public File getLogFile() {
1199 File rawF = new File(getRootDir(), "log");
1200 if (rawF.isFile()) {
1201 return rawF;
1202 }
1203 File gzF = new File(getRootDir(), "log.gz");
1204 if (gzF.isFile()) {
1205 return gzF;
1206 }
1207 //If both fail, return the standard, uncompressed log file
1208 return rawF;
1209 }
1210
1211 /**
1212 * Returns an input stream that reads from the log file.
1213 * It will use a gzip-compressed log file (log.gz) if that exists.
1214 *
1215 * @throws IOException
1216 * @return an input stream from the log file, or null if none exists
1217 * @since 1.349
1218 */
1219 public InputStream getLogInputStream() throws IOException {
1220 File logFile = getLogFile();
1221
1222 if (logFile != null && logFile.exists() ) {
1223 // Checking if a ".gz" file was return
1224 FileInputStream fis = new FileInputStream(logFile);
1225 if (logFile.getName().endsWith(".gz")) {
1226 return new GZIPInputStream(fis);
1227 } else {
1228 return fis;
1229 }
1230 }
1231
1232 String message = "No such file: " + logFile;
1233 return new ByteArrayInputStream(charset != null ? message.getBytes(charset) : message.getBytes());
1234 }
1235
1236 public Reader getLogReader() throws IOException {
1237 if (charset==null) return new InputStreamReader(getLogInputStream());
1238 else return new InputStreamReader(getLogInputStream(),charset);
1239 }
1240
1241 /**
1242 * Used from <tt>console.jelly</tt> to write annotated log to the given output.
1243 *
1244 * @since 1.349
1245 */
1246 public void writeLogTo(long offset, XMLOutput out) throws IOException {
1247 try {
1248 getLogText().writeHtmlTo(offset,out.asWriter());
1249 } catch (IOException e) {
1250 // try to fall back to the old getLogInputStream()
1251 // mainly to support .gz compressed files
1252 // In this case, console annotation handling will be turned off.
1253 InputStream input = getLogInputStream();
1254 try {
1255 IOUtils.copy(input, out.asWriter());
1256 } finally {
1257 IOUtils.closeQuietly(input);
1258 }
1259 }
1260 }
1261
1262 /**
1263 * Writes the complete log from the start to finish to the {@link OutputStream}.
1264 *
1265 * If someone is still writing to the log, this method will not return until the whole log
1266 * file gets written out.
1267 */
1268 public void writeWholeLogTo(OutputStream out) throws IOException, InterruptedException {
1269 long pos = 0;
1270 AnnotatedLargeText logText;
1271 do {
1272 logText = getLogText();
1273 pos = logText.writeLogTo(pos, out);
1274 } while (!logText.isComplete());
1275 }
1276
1277 /**
1278 * Used to URL-bind {@link AnnotatedLargeText}.
1279 */
1280 public AnnotatedLargeText getLogText() {
1281 return new AnnotatedLargeText(getLogFile(),getCharset(),!isLogUpdated(),this);
1282 }
1283
1284 @Override
1285 protected SearchIndexBuilder makeSearchIndex() {
1286 SearchIndexBuilder builder = super.makeSearchIndex()
1287 .add("console")
1288 .add("changes");
1289 for (Action a : getActions()) {
1290 if(a.getIconFileName()!=null)
1291 builder.add(a.getUrlName());
1292 }
1293 return builder;
1294 }
1295
1296 public Api getApi() {
1297 return new Api(this);
1298 }
1299
1300 public void checkPermission(Permission p) {
1301 getACL().checkPermission(p);
1302 }
1303
1304 public boolean hasPermission(Permission p) {
1305 return getACL().hasPermission(p);
1306 }
1307
1308 public ACL getACL() {
1309 // for now, don't maintain ACL per run, and do it at project level
1310 return getParent().getACL();
1311 }
1312
1313 /**
1314 * Deletes this build's artifacts.
1315 *
1316 * @throws IOException
1317 * if we fail to delete.
1318 *
1319 * @since 1.350
1320 */
1321 public synchronized void deleteArtifacts() throws IOException {
1322 File artifactsDir = getArtifactsDir();
1323
1324 Util.deleteContentsRecursive(artifactsDir);
1325 }
1326
1327 /**
1328 * Deletes this build and its entire log
1329 *
1330 * @throws IOException
1331 * if we fail to delete.
1332 */
1333 public synchronized void delete() throws IOException {
1334 RunListener.fireDeleted(this);
1335
1336 // if we have a symlink, delete it, too
1337 File link = new File(project.getBuildDir(), String.valueOf(getNumber()));
1338 link.delete();
1339
1340 File rootDir = getRootDir();
1341 File tmp = new File(rootDir.getParentFile(),'.'+rootDir.getName());
1342
1343 if (tmp.exists()) {
1344 Util.deleteRecursive(tmp);
1345 }
1346 // XXX on Java 7 prefer: Files.move(rootDir.toPath(), tmp.toPath(), StandardCopyOption.ATOMIC_MOVE)
1347 boolean renamingSucceeded = rootDir.renameTo(tmp);
1348 Util.deleteRecursive(tmp);
1349 // some user reported that they see some left-over .xyz files in the workspace,
1350 // so just to make sure we've really deleted it, schedule the deletion on VM exit, too.
1351 if(tmp.exists())
1352 tmp.deleteOnExit();
1353
1354 if(!renamingSucceeded)
1355 throw new IOException(rootDir+" is in use");
1356
1357 removeRunFromParent();
1358 }
1359
1360 @SuppressWarnings("unchecked") // seems this is too clever for Java's type system?
1361 private void removeRunFromParent() {
1362 getParent().removeRun((RunT)this);
1363 }
1364
1365
1366 /**
1367 * @see CheckPoint#report()
1368 */
1369 /*package*/ static void reportCheckpoint(CheckPoint id) {
1370 RunnerStack.INSTANCE.peek().checkpoints.report(id);
1371 }
1372
1373 /**
1374 * @see CheckPoint#block()
1375 */
1376 /*package*/ static void waitForCheckpoint(CheckPoint id) throws InterruptedException {
1377 while(true) {
1378 Run b = RunnerStack.INSTANCE.peek().getBuild().getPreviousBuildInProgress();
1379 if(b==null) return; // no pending earlier build
1380 Run.RunExecution runner = b.runner;
1381 if(runner==null) {
1382 // polled at the wrong moment. try again.
1383 Thread.sleep(0);
1384 continue;
1385 }
1386 if(runner.checkpoints.waitForCheckPoint(id))
1387 return; // confirmed that the previous build reached the check point
1388
1389 // the previous build finished without ever reaching the check point. try again.
1390 }
1391 }
1392
1393 /**
1394 * @deprecated as of 1.467
1395 * Please use {@link RunExecution}
1396 */
1397 protected abstract class Runner extends RunExecution {}
1398
1399 /**
1400 * Object that lives while the build is executed, to keep track of things that
1401 * are needed only during the build.
1402 */
1403 public abstract class RunExecution {
1404 /**
1405 * Keeps track of the check points attained by a build, and abstracts away the synchronization needed to
1406 * maintain this data structure.
1407 */
1408 private final class CheckpointSet {
1409 /**
1410 * Stages of the builds that this runner has completed. This is used for concurrent {@link RunExecution}s to
1411 * coordinate and serialize their executions where necessary.
1412 */
1413 private final Set<CheckPoint> checkpoints = new HashSet<CheckPoint>();
1414
1415 private boolean allDone;
1416
1417 protected synchronized void report(CheckPoint identifier) {
1418 checkpoints.add(identifier);
1419 notifyAll();
1420 }
1421
1422 protected synchronized boolean waitForCheckPoint(CheckPoint identifier) throws InterruptedException {
1423 final Thread t = Thread.currentThread();
1424 final String oldName = t.getName();
1425 t.setName(oldName+" : waiting for "+identifier+" on "+getFullDisplayName());
1426 try {
1427 while(!allDone && !checkpoints.contains(identifier))
1428 wait();
1429 return checkpoints.contains(identifier);
1430 } finally {
1431 t.setName(oldName);
1432 }
1433 }
1434
1435 /**
1436 * Notifies that the build is fully completed and all the checkpoint locks be released.
1437 */
1438 private synchronized void allDone() {
1439 allDone = true;
1440 notifyAll();
1441 }
1442 }
1443
1444 private final CheckpointSet checkpoints = new CheckpointSet();
1445
1446 private final Map<Object,Object> attributes = new HashMap<Object, Object>();
1447
1448 /**
1449 * Performs the main build and returns the status code.
1450 *
1451 * @throws Exception
1452 * exception will be recorded and the build will be considered a failure.
1453 */
1454 public abstract Result run( BuildListener listener ) throws Exception, RunnerAbortedException;
1455
1456 /**
1457 * Performs the post-build action.
1458 * <p>
1459 * This method is called after {@linkplain #run(BuildListener) the main portion of the build is completed.}
1460 * This is a good opportunity to do notifications based on the result
1461 * of the build. When this method is called, the build is not really
1462 * finalized yet, and the build is still considered in progress --- for example,
1463 * even if the build is successful, this build still won't be picked up
1464 * by {@link Job#getLastSuccessfulBuild()}.
1465 */
1466 public abstract void post( BuildListener listener ) throws Exception;
1467
1468 /**
1469 * Performs final clean up action.
1470 * <p>
1471 * This method is called after {@link #post(BuildListener)},
1472 * after the build result is fully finalized. This is the point
1473 * where the build is already considered completed.
1474 * <p>
1475 * Among other things, this is often a necessary pre-condition
1476 * before invoking other builds that depend on this build.
1477 */
1478 public abstract void cleanUp(@Nonnull BuildListener listener) throws Exception;
1479
1480 public RunT getBuild() {
1481 return _this();
1482 }
1483
1484 public JobT getProject() {
1485 return _this().getParent();
1486 }
1487
1488 /**
1489 * Bag of stuff to allow plugins to store state for the duration of a build
1490 * without persisting it.
1491 *
1492 * @since 1.473
1493 */
1494 public Map<Object,Object> getAttributes() {
1495 return attributes;
1496 }
1497 }
1498
1499 /**
1500 * Used in {@link RunExecution#run} to indicates that a fatal error in a build
1501 * is reported to {@link BuildListener} and the build should be simply aborted
1502 * without further recording a stack trace.
1503 */
1504 public static final class RunnerAbortedException extends RuntimeException {
1505 private static final long serialVersionUID = 1L;
1506 }
1507
1508 /**
1509 * @deprecated as of 1.467
1510 * Use {@link #execute(RunExecution)}
1511 */
1512 protected final void run(Runner job) {
1513 execute(job);
1514 }
1515
1516 protected final void execute(RunExecution job) {
1517 if(result!=null)
1518 return; // already built.
1519
1520 StreamBuildListener listener=null;
1521
1522 runner = job;
1523 onStartBuilding();
1524 try {
1525 // to set the state to COMPLETE in the end, even if the thread dies abnormally.
1526 // otherwise the queue state becomes inconsistent
1527
1528 long start = System.currentTimeMillis();
1529
1530 try {
1531 try {
1532 Computer computer = Computer.currentComputer();
1533 Charset charset = null;
1534 if (computer != null) {
1535 charset = computer.getDefaultCharset();
1536 this.charset = charset.name();
1537 }
1538
1539 // don't do buffering so that what's written to the listener
1540 // gets reflected to the file immediately, which can then be
1541 // served to the browser immediately
1542 OutputStream logger = new FileOutputStream(getLogFile());
1543 RunT build = job.getBuild();
1544
1545 // Global log filters
1546 for (ConsoleLogFilter filter : ConsoleLogFilter.all()) {
1547 logger = filter.decorateLogger((AbstractBuild) build, logger);
1548 }
1549
1550 // Project specific log filters
1551 if (project instanceof BuildableItemWithBuildWrappers && build instanceof AbstractBuild) {
1552 BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
1553 for (BuildWrapper bw : biwbw.getBuildWrappersList()) {
1554 logger = bw.decorateLogger((AbstractBuild) build, logger);
1555 }
1556 }
1557
1558 listener = new StreamBuildListener(logger,charset);
1559
1560 listener.started(getCauses());
1561
1562 RunListener.fireStarted(this,listener);
1563
1564 // create a symlink from build number to ID.
1565 Util.createSymlink(getParent().getBuildDir(),getId(),String.valueOf(getNumber()),listener);
1566
1567 setResult(job.run(listener));
1568
1569 LOGGER.info(toString()+" main build action completed: "+result);
1570 CheckPoint.MAIN_COMPLETED.report();
1571 } catch (ThreadDeath t) {
1572 throw t;
1573 } catch( AbortException e ) {// orderly abortion.
1574 result = Result.FAILURE;
1575 listener.error(e.getMessage());
1576 LOGGER.log(FINE, "Build "+this+" aborted",e);
1577 } catch( RunnerAbortedException e ) {// orderly abortion.
1578 result = Result.FAILURE;
1579 LOGGER.log(FINE, "Build "+this+" aborted",e);
1580 } catch( InterruptedException e) {
1581 // aborted
1582 result = Executor.currentExecutor().abortResult();
1583 listener.getLogger().println(Messages.Run_BuildAborted());
1584 Executor.currentExecutor().recordCauseOfInterruption(Run.this,listener);
1585 LOGGER.log(Level.INFO,toString()+" aborted",e);
1586 } catch( Throwable e ) {
1587 handleFatalBuildProblem(listener,e);
1588 result = Result.FAILURE;
1589 }
1590
1591 // even if the main build fails fatally, try to run post build processing
1592 job.post(listener);
1593
1594 } catch (ThreadDeath t) {
1595 throw t;
1596 } catch( Throwable e ) {
1597 handleFatalBuildProblem(listener,e);
1598 result = Result.FAILURE;
1599 } finally {
1600 long end = System.currentTimeMillis();
1601 duration = Math.max(end - start, 0); // @see HUDSON-5844
1602
1603 // advance the state.
1604 // the significance of doing this is that Jenkins
1605 // will now see this build as completed.
1606 // things like triggering other builds requires this as pre-condition.
1607 // see issue #980.
1608 state = State.POST_PRODUCTION;
1609
1610 if (listener != null) {
1611 try {
1612 job.cleanUp(listener);
1613 } catch (Exception e) {
1614 handleFatalBuildProblem(listener,e);
1615 // too late to update the result now
1616 }
1617 RunListener.fireCompleted(this,listener);
1618 listener.finished(result);
1619 listener.closeQuietly();
1620 }
1621
1622 try {
1623 save();
1624 } catch (IOException e) {
1625 LOGGER.log(Level.SEVERE, "Failed to save build record",e);
1626 }
1627 }
1628
1629 try {
1630 getParent().logRotate();
1631 } catch (IOException e) {
1632 LOGGER.log(Level.SEVERE, "Failed to rotate log",e);
1633 } catch (InterruptedException e) {
1634 LOGGER.log(Level.SEVERE, "Failed to rotate log",e);
1635 }
1636 } finally {
1637 onEndBuilding();
1638 }
1639 }
1640
1641 /**
1642 * Handles a fatal build problem (exception) that occurred during the build.
1643 */
1644 private void handleFatalBuildProblem(BuildListener listener, Throwable e) {
1645 if(listener!=null) {
1646 LOGGER.log(FINE, getDisplayName()+" failed to build",e);
1647
1648 if(e instanceof IOException)
1649 Util.displayIOException((IOException)e,listener);
1650
1651 Writer w = listener.fatalError(e.getMessage());
1652 if(w!=null) {
1653 try {
1654 e.printStackTrace(new PrintWriter(w));
1655 w.close();
1656 } catch (IOException e1) {
1657 // ignore
1658 }
1659 }
1660 } else {
1661 LOGGER.log(SEVERE, getDisplayName()+" failed to build and we don't even have a listener",e);
1662 }
1663 }
1664
1665 /**
1666 * Called when a job started building.
1667 */
1668 protected void onStartBuilding() {
1669 state = State.BUILDING;
1670 startTime = System.currentTimeMillis();
1671 if (runner!=null)
1672 RunnerStack.INSTANCE.push(runner);
1673 }
1674
1675 /**
1676 * Called when a job finished building normally or abnormally.
1677 */
1678 protected void onEndBuilding() {
1679 // signal that we've finished building.
1680 if (runner!=null) {
1681 // MavenBuilds may be created without their corresponding runners.
1682 state = State.COMPLETED;
1683 runner.checkpoints.allDone();
1684 runner = null;
1685 RunnerStack.INSTANCE.pop();
1686 } else {
1687 state = State.COMPLETED;
1688 }
1689 if (result == null) {
1690 result = Result.FAILURE;
1691 LOGGER.warning(toString() + ": No build result is set, so marking as failure. This shouldn't happen.");
1692 }
1693
1694 RunListener.fireFinalized(this);
1695 }
1696
1697 /**
1698 * Save the settings to a file.
1699 */
1700 public synchronized void save() throws IOException {
1701 if(BulkChange.contains(this)) return;
1702 getDataFile().write(this);
1703 SaveableListener.fireOnChange(this, getDataFile());
1704 }
1705
1706 private XmlFile getDataFile() {
1707 return new XmlFile(XSTREAM,new File(getRootDir(),"build.xml"));
1708 }
1709
1710 /**
1711 * Gets the log of the build as a string.
1712 *
1713 * @deprecated since 2007-11-11.
1714 * Use {@link #getLog(int)} instead as it avoids loading
1715 * the whole log into memory unnecessarily.
1716 */
1717 @Deprecated
1718 public String getLog() throws IOException {
1719 return Util.loadFile(getLogFile(),getCharset());
1720 }
1721
1722 /**
1723 * Gets the log of the build as a list of strings (one per log line).
1724 * The number of lines returned is constrained by the maxLines parameter.
1725 *
1726 * @param maxLines The maximum number of log lines to return. If the log
1727 * is bigger than this, only the most recent lines are returned.
1728 * @return A list of log lines. Will have no more than maxLines elements.
1729 * @throws IOException If there is a problem reading the log file.
1730 */
1731 public List<String> getLog(int maxLines) throws IOException {
1732 int lineCount = 0;
1733 List<String> logLines = new LinkedList<String>();
1734 BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(getLogFile()),getCharset()));
1735 try {
1736 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1737 logLines.add(line);
1738 ++lineCount;
1739 // If we have too many lines, remove the oldest line. This way we
1740 // never have to hold the full contents of a huge log file in memory.
1741 // Adding to and removing from the ends of a linked list are cheap
1742 // operations.
1743 if (lineCount > maxLines)
1744 logLines.remove(0);
1745 }
1746 } finally {
1747 reader.close();
1748 }
1749
1750 // If the log has been truncated, include that information.
1751 // Use set (replaces the first element) rather than add so that
1752 // the list doesn't grow beyond the specified maximum number of lines.
1753 if (lineCount > maxLines)
1754 logLines.set(0, "[...truncated " + (lineCount - (maxLines - 1)) + " lines...]");
1755
1756 return ConsoleNote.removeNotes(logLines);
1757 }
1758
1759 public void doBuildStatus( StaplerRequest req, StaplerResponse rsp ) throws IOException {
1760 rsp.sendRedirect2(req.getContextPath()+"/images/48x48/"+getBuildStatusUrl());
1761 }
1762
1763 public String getBuildStatusUrl() {
1764 return getIconColor().getImage();
1765 }
1766
1767 public static class Summary {
1768 /**
1769 * Is this build worse or better, compared to the previous build?
1770 */
1771 public boolean isWorse;
1772 public String message;
1773
1774 public Summary(boolean worse, String message) {
1775 this.isWorse = worse;
1776 this.message = message;
1777 }
1778 }
1779
1780 /**
1781 * Gets an object which represents the single line summary of the status of this build
1782 * (especially in comparison with the previous build.)
1783 */
1784 public Summary getBuildStatusSummary() {
1785 if (isBuilding()) {
1786 return new Summary(false, Messages.Run_Summary_Unknown());
1787 }
1788
1789 ResultTrend trend = ResultTrend.getResultTrend(this);
1790
1791 switch (trend) {
1792 case ABORTED : return new Summary(false, Messages.Run_Summary_Aborted());
1793
1794 case NOT_BUILT : return new Summary(false, Messages.Run_Summary_NotBuilt());
1795
1796 case FAILURE : return new Summary(true, Messages.Run_Summary_BrokenSinceThisBuild());
1797
1798 case STILL_FAILING :
1799 RunT since = getPreviousNotFailedBuild();
1800 if(since==null)
1801 return new Summary(false, Messages.Run_Summary_BrokenForALongTime());
1802 RunT failedBuild = since.getNextBuild();
1803 return new Summary(false, Messages.Run_Summary_BrokenSince(failedBuild.getDisplayName()));
1804
1805 case NOW_UNSTABLE:
1806 return determineDetailedUnstableSummary(Boolean.FALSE);
1807 case UNSTABLE :
1808 return determineDetailedUnstableSummary(Boolean.TRUE);
1809 case STILL_UNSTABLE :
1810 return determineDetailedUnstableSummary(null);
1811
1812 case SUCCESS :
1813 return new Summary(false, Messages.Run_Summary_Stable());
1814
1815 case FIXED :
1816 return new Summary(false, Messages.Run_Summary_BackToNormal());
1817
1818 }
1819
1820 return new Summary(false, Messages.Run_Summary_Unknown());
1821 }
1822
1823 /**
1824 * @param worseOverride override the 'worse' parameter to this value.
1825 * May be null in which case 'worse' is calculated based on the number of failed tests.
1826 */
1827 private Summary determineDetailedUnstableSummary(Boolean worseOverride) {
1828 if(((Run)this) instanceof AbstractBuild) {
1829 AbstractTestResultAction trN = ((AbstractBuild)(Run)this).getTestResultAction();
1830 Run prev = getPreviousBuild();
1831 AbstractTestResultAction trP = prev==null ? null : ((AbstractBuild) prev).getTestResultAction();
1832 if(trP==null) {
1833 if(trN!=null && trN.getFailCount()>0)
1834 return new Summary(worseOverride != null ? worseOverride : true,
1835 Messages.Run_Summary_TestFailures(trN.getFailCount()));
1836 } else {
1837 if(trN!=null && trN.getFailCount()!= 0) {
1838 if(trP.getFailCount()==0)
1839 return new Summary(worseOverride != null ? worseOverride : true,
1840 Messages.Run_Summary_TestsStartedToFail(trN.getFailCount()));
1841 if(trP.getFailCount() < trN.getFailCount())
1842 return new Summary(worseOverride != null ? worseOverride : true,
1843 Messages.Run_Summary_MoreTestsFailing(trN.getFailCount()-trP.getFailCount(), trN.getFailCount()));
1844 if(trP.getFailCount() > trN.getFailCount())
1845 return new Summary(worseOverride != null ? worseOverride : false,
1846 Messages.Run_Summary_LessTestsFailing(trP.getFailCount()-trN.getFailCount(), trN.getFailCount()));
1847
1848 return new Summary(worseOverride != null ? worseOverride : false,
1849 Messages.Run_Summary_TestsStillFailing(trN.getFailCount()));
1850 }
1851 }
1852 }
1853
1854 return new Summary(worseOverride != null ? worseOverride : false,
1855 Messages.Run_Summary_Unstable());
1856 }
1857
1858 /**
1859 * Serves the artifacts.
1860 */
1861 public DirectoryBrowserSupport doArtifact() {
1862 if(Functions.isArtifactsPermissionEnabled()) {
1863 checkPermission(ARTIFACTS);
1864 }
1865 return new DirectoryBrowserSupport(this,new FilePath(getArtifactsDir()), project.getDisplayName()+' '+getDisplayName(), "package.png", true);
1866 }
1867
1868 /**
1869 * Returns the build number in the body.
1870 */
1871 public void doBuildNumber(StaplerResponse rsp) throws IOException {
1872 rsp.setContentType("text/plain");
1873 rsp.setCharacterEncoding("US-ASCII");
1874 rsp.setStatus(HttpServletResponse.SC_OK);
1875 rsp.getWriter().print(number);
1876 }
1877
1878 /**
1879 * Returns the build time stamp in the body.
1880 */
1881 public void doBuildTimestamp( StaplerRequest req, StaplerResponse rsp, @QueryParameter String format) throws IOException {
1882 rsp.setContentType("text/plain");
1883 rsp.setCharacterEncoding("US-ASCII");
1884 rsp.setStatus(HttpServletResponse.SC_OK);
1885 DateFormat df = format==null ?
1886 DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT, Locale.ENGLISH) :
1887 new SimpleDateFormat(format,req.getLocale());
1888 rsp.getWriter().print(df.format(getTime()));
1889 }
1890
1891 /**
1892 * Sends out the raw console output.
1893 */
1894 public void doConsoleText(StaplerRequest req, StaplerResponse rsp) throws IOException {
1895 rsp.setContentType("text/plain;charset=UTF-8");
1896 // Prevent jelly from flushing stream so Content-Length header can be added afterwards
1897 FlushProofOutputStream out = new FlushProofOutputStream(rsp.getCompressedOutputStream(req));
1898 try{
1899 getLogText().writeLogTo(0,out);
1900 } catch (IOException e) {
1901 // see comment in writeLogTo() method
1902 InputStream input = getLogInputStream();
1903 try {
1904 IOUtils.copy(input, out);
1905 } finally {
1906 IOUtils.closeQuietly(input);
1907 }
1908 }
1909 out.close();
1910 }
1911
1912 /**
1913 * Handles incremental log output.
1914 * @deprecated as of 1.352
1915 * Use {@code getLogText().doProgressiveText(req,rsp)}
1916 */
1917 public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
1918 getLogText().doProgressText(req,rsp);
1919 }
1920
1921 public void doToggleLogKeep( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1922 keepLog(!keepLog);
1923 rsp.forwardToPreviousPage(req);
1924 }
1925
1926 /**
1927 * Marks this build to keep the log.
1928 */
1929 @CLIMethod(name="keep-build")
1930 public final void keepLog() throws IOException {
1931 keepLog(true);
1932 }
1933
1934 public void keepLog(boolean newValue) throws IOException {
1935 checkPermission(UPDATE);
1936 keepLog = newValue;
1937 save();
1938 }
1939
1940 /**
1941 * Deletes the build when the button is pressed.
1942 */
1943 @RequirePOST
1944 public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1945 checkPermission(DELETE);
1946
1947 // We should not simply delete the build if it has been explicitly
1948 // marked to be preserved, or if the build should not be deleted
1949 // due to dependencies!
1950 String why = getWhyKeepLog();
1951 if (why!=null) {
1952 sendError(Messages.Run_UnableToDelete(toString(),why),req,rsp);
1953 return;
1954 }
1955
1956 delete();
1957 rsp.sendRedirect2(req.getContextPath()+'/' + getParent().getUrl());
1958 }
1959
1960 public void setDescription(String description) throws IOException {
1961 checkPermission(UPDATE);
1962 this.description = description;
1963 save();
1964 }
1965
1966 /**
1967 * Accepts the new description.
1968 */
1969 public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1970 setDescription(req.getParameter("description"));
1971 rsp.sendRedirect("."); // go to the top page
1972 }
1973
1974 /**
1975 * @deprecated as of 1.292
1976 * Use {@link #getEnvironment()} instead.
1977 */
1978 public Map<String,String> getEnvVars() {
1979 try {
1980 return getEnvironment();
1981 } catch (IOException e) {
1982 return new EnvVars();
1983 } catch (InterruptedException e) {
1984 return new EnvVars();
1985 }
1986 }
1987
1988 /**
1989 * @deprecated as of 1.305 use {@link #getEnvironment(TaskListener)}
1990 */
1991 public EnvVars getEnvironment() throws IOException, InterruptedException {
1992 return getEnvironment(new LogTaskListener(LOGGER, Level.INFO));
1993 }
1994
1995 /**
1996 * Returns the map that contains environmental variables to be used for launching
1997 * processes for this build.
1998 *
1999 * <p>
2000 * {@link BuildStep}s that invoke external processes should use this.
2001 * This allows {@link BuildWrapper}s and other project configurations (such as JDK selection)
2002 * to take effect.
2003 *
2004 * <p>
2005 * Unlike earlier {@link #getEnvVars()}, this map contains the whole environment,
2006 * not just the overrides, so one can introspect values to change its behavior.
2007 *
2008 * @return the map with the environmental variables. Never <code>null</code>.
2009 * @since 1.305
2010 */
2011 public EnvVars getEnvironment(TaskListener listener) throws IOException, InterruptedException {
2012 Computer c = Computer.currentComputer();
2013 Node n = c==null ? null : c.getNode();
2014
2015 EnvVars env = getParent().getEnvironment(n,listener);
2016 env.putAll(getCharacteristicEnvVars());
2017
2018 // apply them in a reverse order so that higher ordinal ones can modify values added by lower ordinal ones
2019 for (EnvironmentContributor ec : EnvironmentContributor.all().reverseView())
2020 ec.buildEnvironmentFor(this,env,listener);
2021
2022 return env;
2023 }
2024
2025 /**
2026 * Builds up the environment variable map that's sufficient to identify a process
2027 * as ours. This is used to kill run-away processes via {@link ProcessTree#killAll(Map)}.
2028 */
2029 public final EnvVars getCharacteristicEnvVars() {
2030 EnvVars env = getParent().getCharacteristicEnvVars();
2031 env.put("BUILD_NUMBER",String.valueOf(number));
2032 env.put("BUILD_ID",getId());
2033 env.put("BUILD_TAG","jenkins-"+getParent().getFullName().replace('/', '-')+"-"+number);
2034 return env;
2035 }
2036
2037 public String getExternalizableId() {
2038 return project.getFullName() + "#" + getNumber();
2039 }
2040
2041 public static Run<?,?> fromExternalizableId(String id) {
2042 int hash = id.lastIndexOf('#');
2043 if (hash <= 0) {
2044 throw new IllegalArgumentException("Invalid id");
2045 }
2046 String jobName = id.substring(0, hash);
2047 int number = Integer.parseInt(id.substring(hash + 1));
2048
2049 Job<?,?> job = Jenkins.getInstance().getItemByFullName(jobName, Job.class);
2050 if (job == null) {
2051 throw new IllegalArgumentException("no such job " + jobName);
2052 }
2053 return job.getBuildByNumber(number);
2054 }
2055
2056 /**
2057 * Returns the estimated duration for this run if it is currently running.
2058 * Default to {@link Job#getEstimatedDuration()}, may be overridden in subclasses
2059 * if duration may depend on run specific parameters (like incremental Maven builds).
2060 *
2061 * @return the estimated duration in milliseconds
2062 * @since 1.383
2063 */
2064 @Exported
2065 public long getEstimatedDuration() {
2066 return project.getEstimatedDuration();
2067 }
2068
2069 @RequirePOST
2070 public HttpResponse doConfigSubmit( StaplerRequest req ) throws IOException, ServletException, FormException {
2071 checkPermission(UPDATE);
2072 BulkChange bc = new BulkChange(this);
2073 try {
2074 JSONObject json = req.getSubmittedForm();
2075 submit(json);
2076 bc.commit();
2077 } finally {
2078 bc.abort();
2079 }
2080 return FormApply.success(".");
2081 }
2082
2083 protected void submit(JSONObject json) throws IOException {
2084 setDisplayName(Util.fixEmptyAndTrim(json.getString("displayName")));
2085 setDescription(json.getString("description"));
2086 }
2087
2088 public static final XStream XSTREAM = new XStream2();
2089
2090 /**
2091 * Alias to {@link #XSTREAM} so that one can access additional methods on {@link XStream2} more easily.
2092 */
2093 public static final XStream2 XSTREAM2 = (XStream2)XSTREAM;
2094
2095 static {
2096 XSTREAM.alias("build",FreeStyleBuild.class);
2097 XSTREAM.alias("matrix-build",MatrixBuild.class);
2098 XSTREAM.alias("matrix-run",MatrixRun.class);
2099 XSTREAM.registerConverter(Result.conv);
2100 }
2101
2102 private static final Logger LOGGER = Logger.getLogger(Run.class.getName());
2103
2104 /**
2105 * Sort by date. Newer ones first.
2106 */
2107 public static final Comparator<Run> ORDER_BY_DATE = new Comparator<Run>() {
2108 public int compare(Run lhs, Run rhs) {
2109 long lt = lhs.getTimeInMillis();
2110 long rt = rhs.getTimeInMillis();
2111 if(lt>rt) return -1;
2112 if(lt<rt) return 1;
2113 return 0;
2114 }
2115 };
2116
2117 /**
2118 * {@link FeedAdapter} to produce feed from the summary of this build.
2119 */
2120 public static final FeedAdapter<Run> FEED_ADAPTER = new DefaultFeedAdapter();
2121
2122 /**
2123 * {@link FeedAdapter} to produce feeds to show one build per project.
2124 */
2125 public static final FeedAdapter<Run> FEED_ADAPTER_LATEST = new DefaultFeedAdapter() {
2126 /**
2127 * The entry unique ID needs to be tied to a project, so that
2128 * new builds will replace the old result.
2129 */
2130 @Override
2131 public String getEntryID(Run e) {
2132 // can't use a meaningful year field unless we remember when the job was created.
2133 return "tag:hudson.dev.java.net,2008:"+e.getParent().getAbsoluteUrl();
2134 }
2135 };
2136
2137 /**
2138 * {@link BuildBadgeAction} that shows the logs are being kept.
2139 */
2140 public final class KeepLogBuildBadge implements BuildBadgeAction {
2141 public String getIconFileName() { return null; }
2142 public String getDisplayName() { return null; }
2143 public String getUrlName() { return null; }
2144 public String getWhyKeepLog() { return Run.this.getWhyKeepLog(); }
2145 }
2146
2147 public static final PermissionGroup PERMISSIONS = new PermissionGroup(Run.class,Messages._Run_Permissions_Title());
2148 public static final Permission DELETE = new Permission(PERMISSIONS,"Delete",Messages._Run_DeletePermission_Description(),Permission.DELETE, PermissionScope.RUN);
2149 public static final Permission UPDATE = new Permission(PERMISSIONS,"Update",Messages._Run_UpdatePermission_Description(),Permission.UPDATE, PermissionScope.RUN);
2150 /** See {@link hudson.Functions#isArtifactsPermissionEnabled} */
2151 public static final Permission ARTIFACTS = new Permission(PERMISSIONS,"Artifacts",Messages._Run_ArtifactsPermission_Description(), null,
2152 Functions.isArtifactsPermissionEnabled(), new PermissionScope[]{PermissionScope.RUN});
2153
2154 private static class DefaultFeedAdapter implements FeedAdapter<Run> {
2155 public String getEntryTitle(Run entry) {
2156 return entry+" ("+entry.getBuildStatusSummary().message+")";
2157 }
2158
2159 public String getEntryUrl(Run entry) {
2160 return entry.getUrl();
2161 }
2162
2163 public String getEntryID(Run entry) {
2164 return "tag:" + "hudson.dev.java.net,"
2165 + entry.getTimestamp().get(Calendar.YEAR) + ":"
2166 + entry.getParent().getName()+':'+entry.getId();
2167 }
2168
2169 public String getEntryDescription(Run entry) {
2170 return entry.getDescription();
2171 }
2172
2173 public Calendar getEntryTimestamp(Run entry) {
2174 return entry.getTimestamp();
2175 }
2176
2177 public String getEntryAuthor(Run entry) {
2178 return JenkinsLocationConfiguration.get().getAdminAddress();
2179 }
2180 }
2181
2182 @Override
2183 public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
2184 Object result = super.getDynamic(token, req, rsp);
2185 if (result == null){
2186 //check transient actions too
2187 for(Action action: getTransientActions()){
2188 if(action.getUrlName().equals(token))
2189 return action;
2190 }
2191 // Next/Previous Build links on an action page (like /job/Abc/123/testReport)
2192 // will also point to same action (/job/Abc/124/testReport), but other builds
2193 // may not have the action.. tell browsers to redirect up to the build page.
2194 result = new RedirectUp();
2195 }
2196 return result;
2197 }
2198
2199 public static class RedirectUp {
2200 public void doDynamic(StaplerResponse rsp) throws IOException {
2201 // Compromise to handle both browsers (auto-redirect) and programmatic access
2202 // (want accurate 404 response).. send 404 with javscript to redirect browsers.
2203 rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
2204 rsp.setContentType("text/html;charset=UTF-8");
2205 PrintWriter out = rsp.getWriter();
2206 out.println("<html><head>" +
2207 "<meta http-equiv='refresh' content='1;url=..'/>" +
2208 "<script>window.location.replace('..');</script>" +
2209 "</head>" +
2210 "<body style='background-color:white; color:white;'>" +
2211 "Not found</body></html>");
2212 out.flush();
2213 }
2214 }
2215 }
2216