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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.console;
25
26 import hudson.ExtensionPoint;
27 import hudson.Functions;
28 import hudson.MarkupText;
29 import hudson.model.Describable;
30 import jenkins.model.Jenkins;
31 import hudson.model.Run;
32 import hudson.remoting.ObjectInputStreamEx;
33 import hudson.util.IOException2;
34 import hudson.util.IOUtils;
35 import hudson.util.UnbufferedBase64InputStream;
36 import org.apache.commons.codec.binary.Base64OutputStream;
37 import org.apache.commons.io.output.ByteArrayOutputStream;
38 import org.apache.tools.ant.BuildListener;
39
40 import java.io.ByteArrayInputStream;
41 import java.io.DataInputStream;
42 import java.io.DataOutputStream;
43 import java.io.IOException;
44 import java.io.ObjectInputStream;
45 import java.io.ObjectOutputStream;
46 import java.io.OutputStream;
47 import java.io.Serializable;
48 import java.io.Writer;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.List;
53 import java.util.zip.GZIPInputStream;
54 import java.util.zip.GZIPOutputStream;
55
56 /**
57 * Data that hangs off from a console output.
58 *
59 * <p>
60 * A {@link ConsoleNote} can be put into a console output while it's being written, and it represents
61 * a machine readable information about a particular position of the console output.
62 *
63 * <p>
64 * When Hudson is reading back a console output for display, a {@link ConsoleNote} is used
65 * to trigger {@link ConsoleAnnotator}, which in turn uses the information in the note to
66 * generate markup. In this way, we can overlay richer information on top of the console output.
67 *
68 * <h2>Comparison with {@link ConsoleAnnotatorFactory}</h2>
69 * <p>
70 * Compared to {@link ConsoleAnnotatorFactory}, the main advantage of {@link ConsoleNote} is that
71 * it can be emitted into the output by the producer of the output (or by a filter), which can
72 * have a much better knowledge about the context of what's being executed.
73 *
74 * <ol>
75 * <li>
76 * For example, when your plugin is about to report an error message, you can emit a {@link ConsoleNote}
77 * that indicates an error, instead of printing an error message as plain text. The {@link #annotate(Object, MarkupText, int)}
78 * method will then generate the proper error message, with all the HTML markup that makes error message
79 * more user friendly.
80 *
81 * <li>
82 * Or consider annotating output from Ant. A modified {@link BuildListener} can place a {@link ConsoleNote}
83 * every time a new target execution starts. These notes can be then later used to build the outline
84 * that shows what targets are executed, hyperlinked to their corresponding locations in the build output.
85 * </ol>
86 *
87 * <p>
88 * Doing these things by {@link ConsoleAnnotatorFactory} would be a lot harder, as they can only rely
89 * on the pattern matching of the output.
90 *
91 * <h2>Persistence</h2>
92 * <p>
93 * {@link ConsoleNote}s are serialized and gzip compressed into a byte sequence and then embedded into the
94 * console output text file, with a bit of preamble/postamble to allow tools to ignore them. In this way
95 * {@link ConsoleNote} always sticks to a particular point in the console output.
96 *
97 * <p>
98 * This design allows descendant processes of Hudson to emit {@link ConsoleNote}s. For example, Ant forked
99 * by a shell forked by Hudson can put an encoded note in its stdout, and Hudson will correctly understands that.
100 * The preamble and postamble includes a certain ANSI escape sequence designed in such a way to minimize garbage
101 * if this output is observed by a human being directly.
102 *
103 * <p>
104 * Because of this persistence mechanism, {@link ConsoleNote}s need to be serializable, and care should be taken
105 * to reduce footprint of the notes, if you are putting a lot of notes. Serialization format compatibility
106 * is also important, although {@link ConsoleNote}s that failed to deserialize will be simply ignored, so the
107 * worst thing that can happen is that you just lose some notes.
108 *
109 * <h2>Behaviour, JavaScript, and CSS</h2>
110 * <p>
111 * {@link ConsoleNote} can have associated <tt>script.js</tt> and <tt>style.css</tt> (put them
112 * in the same resource directory that you normally put Jelly scripts), which will be loaded into
113 * the HTML page whenever the console notes are used. This allows you to use minimal markup in
114 * code generation, and do the styling in CSS and perform the rest of the interesting work as a CSS behaviour/JavaScript.
115 *
116 * @param <T>
117 * Contextual model object that this console is associated with, such as {@link Run}.
118 *
119 * @author Kohsuke Kawaguchi
120 * @see ConsoleAnnotationDescriptor
121 * @see Functions#generateConsoleAnnotationScriptAndStylesheet()
122 * @since 1.349
123 */
124 public abstract class ConsoleNote<T> implements Serializable, Describable<ConsoleNote<?>>, ExtensionPoint {
125 /**
126 * When the line of a console output that this annotation is attached is read by someone,
127 * a new {@link ConsoleNote} is de-serialized and this method is invoked to annotate that line.
128 *
129 * @param context
130 * The object that owns the console output in question.
131 * @param text
132 * Represents a line of the console output being annotated.
133 * @param charPos
134 * The character position in 'text' where this annotation is attached.
135 *
136 * @return
137 * if non-null value is returned, this annotator will handle the next line.
138 * this mechanism can be used to annotate multiple lines starting at the annotated position.
139 */
140 public abstract ConsoleAnnotator annotate(T context, MarkupText text, int charPos);
141
142 public ConsoleAnnotationDescriptor getDescriptor() {
143 return (ConsoleAnnotationDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass());
144 }
145
146 /**
147 * Prints this note into a stream.
148 *
149 * <p>
150 * The most typical use of this is {@code n.encodedTo(System.out)} where stdout is connected to Hudson.
151 * The encoded form doesn't include any new line character to work better in the line-oriented nature
152 * of {@link ConsoleAnnotator}.
153 */
154 public void encodeTo(OutputStream out) throws IOException {
155 // atomically write to the final output, to minimize the chance of something else getting in between the output.
156 // even with this, it is still technically possible to get such a mix-up to occur (for example,
157 // if Java program is reading stdout/stderr separately and copying them into the same final stream.)
158 out.write(encodeToBytes().toByteArray());
159 }
160
161 /**
162 * Prints this note into a writer.
163 *
164 * <p>
165 * Technically, this method only works if the {@link Writer} to {@link OutputStream}
166 * encoding is ASCII compatible.
167 */
168 public void encodeTo(Writer out) throws IOException {
169 out.write(encodeToBytes().toString());
170 }
171
172 private ByteArrayOutputStream encodeToBytes() throws IOException {
173 ByteArrayOutputStream buf = new ByteArrayOutputStream();
174 ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(buf));
175 oos.writeObject(this);
176 oos.close();
177
178 ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
179
180 DataOutputStream dos = new DataOutputStream(new Base64OutputStream(buf2,true,-1,null));
181 buf2.write(PREAMBLE);
182 dos.writeInt(buf.size());
183 buf.writeTo(dos);
184 dos.close();
185 buf2.write(POSTAMBLE);
186 return buf2;
187 }
188
189 /**
190 * Works like {@link #encodeTo(Writer)} but obtain the result as a string.
191 */
192 public String encode() throws IOException {
193 return encodeToBytes().toString();
194 }
195
196 /**
197 * Reads a note back from {@linkplain #encodeTo(OutputStream) its encoded form}.
198 *
199 * @param in
200 * Must point to the beginning of a preamble.
201 *
202 * @return null if the encoded form is malformed.
203 */
204 public static ConsoleNote readFrom(DataInputStream in) throws IOException, ClassNotFoundException {
205 try {
206 byte[] preamble = new byte[PREAMBLE.length];
207 in.readFully(preamble);
208 if (!Arrays.equals(preamble,PREAMBLE))
209 return null; // not a valid preamble
210
211 DataInputStream decoded = new DataInputStream(new UnbufferedBase64InputStream(in));
212 int sz = decoded.readInt();
213 byte[] buf = new byte[sz];
214 decoded.readFully(buf);
215
216 byte[] postamble = new byte[POSTAMBLE.length];
217 in.readFully(postamble);
218 if (!Arrays.equals(postamble,POSTAMBLE))
219 return null; // not a valid postamble
220
221 ObjectInputStream ois = new ObjectInputStreamEx(
222 new GZIPInputStream(new ByteArrayInputStream(buf)), Jenkins.getInstance().pluginManager.uberClassLoader);
223 try {
224 return (ConsoleNote) ois.readObject();
225 } finally {
226 ois.close();
227 }
228 } catch (Error e) {
229 // for example, bogus 'sz' can result in OutOfMemoryError.
230 // package that up as IOException so that the caller won't fatally die.
231 throw new IOException2(e);
232 }
233 }
234
235 /**
236 * Skips the encoded console note.
237 */
238 public static void skip(DataInputStream in) throws IOException {
239 byte[] preamble = new byte[PREAMBLE.length];
240 in.readFully(preamble);
241 if (!Arrays.equals(preamble,PREAMBLE))
242 return; // not a valid preamble
243
244 DataInputStream decoded = new DataInputStream(new UnbufferedBase64InputStream(in));
245 int sz = decoded.readInt();
246 IOUtils.skip(decoded,sz);
247
248 byte[] postamble = new byte[POSTAMBLE.length];
249 in.readFully(postamble);
250 }
251
252 private static final long serialVersionUID = 1L;
253
254 public static final String PREAMBLE_STR = "\u001B[8mha:";
255 public static final String POSTAMBLE_STR = "\u001B[0m";
256
257 /**
258 * Preamble of the encoded form. ANSI escape sequence to stop echo back
259 * plus a few magic characters.
260 */
261 public static final byte[] PREAMBLE = PREAMBLE_STR.getBytes();
262 /**
263 * Post amble is the ANSI escape sequence that brings back the echo.
264 */
265 public static final byte[] POSTAMBLE = POSTAMBLE_STR.getBytes();
266
267 /**
268 * Locates the preamble in the given buffer.
269 */
270 public static int findPreamble(byte[] buf, int start, int len) {
271 int e = start + len - PREAMBLE.length + 1;
272
273 OUTER:
274 for (int i=start; i<e; i++) {
275 if (buf[i]==PREAMBLE[0]) {
276 // check for the rest of the match
277 for (int j=1; j<PREAMBLE.length; j++) {
278 if (buf[i+j]!=PREAMBLE[j])
279 continue OUTER;
280 }
281 return i; // found it
282 }
283 }
284 return -1; // not found
285 }
286
287 /**
288 * Removes the embedded console notes in the given log lines.
289 *
290 * @since 1.350
291 */
292 public static List<String> removeNotes(Collection<String> logLines) {
293 List<String> r = new ArrayList<String>(logLines.size());
294 for (String l : logLines)
295 r.add(removeNotes(l));
296 return r;
297 }
298
299 /**
300 * Removes the embedded console notes in the given log line.
301 *
302 * @since 1.350
303 */
304 public static String removeNotes(String line) {
305 while (true) {
306 int idx = line.indexOf(PREAMBLE_STR);
307 if (idx<0) return line;
308 int e = line.indexOf(POSTAMBLE_STR,idx);
309 if (e<0) return line;
310 line = line.substring(0,idx)+line.substring(e+POSTAMBLE_STR.length());
311 }
312 }
313 }
314