Package hudson.console

Source Code of hudson.console.ConsoleNote

/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.console;

import hudson.Functions;
import hudson.MarkupText;
import hudson.model.Describable;
import hudson.model.Hudson;
import hudson.model.Run;
import hudson.remoting.ObjectInputStreamEx;
import hudson.util.IOException2;
import hudson.util.IOUtils;
import hudson.util.UnbufferedBase64InputStream;
import org.apache.commons.codec.binary.Base64OutputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.tools.ant.BuildListener;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
* Data that hangs off from a console output.
*
* <p>
* A {@link ConsoleNote} can be put into a console output while it's being written, and it represents
* a machine readable information about a particular position of the console output.
*
* <p>
* When Hudson is reading back a console output for display, a {@link ConsoleNote} is used
* to trigger {@link ConsoleAnnotator}, which in turn uses the information in the note to
* generate markup. In this way, we can overlay richer information on top of the console output.
*
* <h2>Comparison with {@link ConsoleAnnotatorFactory}</h2>
* <p>
* Compared to {@link ConsoleAnnotatorFactory}, the main advantage of {@link ConsoleNote} is that
* it can be emitted into the output by the producer of the output (or by a filter), which can
* have a much better knowledge about the context of what's being executed.
*
* <ol>
* <li>
* For example, when your plugin is about to report an error message, you can emit a {@link ConsoleNote}
* that indicates an error, instead of printing an error message as plain text. The {@link #annotate(Object, MarkupText, int)}
* method will then generate the proper error message, with all the HTML markup that makes error message
* more user friendly.
*
* <li>
* Or consider annotating output from Ant. A modified {@link BuildListener} can place a {@link ConsoleNote}
* every time a new target execution starts. These notes can be then later used to build the outline
* that shows what targets are executed, hyperlinked to their corresponding locations in the build output.
* </ol>
*
* <p>
* Doing these things by {@link ConsoleAnnotatorFactory} would be a lot harder, as they can only rely
* on the pattern matching of the output.
*
* <h2>Persistence</h2>
* <p>
* {@link ConsoleNote}s are serialized and gzip compressed into a byte sequence and then embedded into the
* console output text file, with a bit of preamble/postamble to allow tools to ignore them. In this way
* {@link ConsoleNote} always sticks to a particular point in the console output.
*
* <p>
* This design allows descendant processes of Hudson to emit {@link ConsoleNote}s. For example, Ant forked
* by a shell forked by Hudson can put an encoded note in its stdout, and Hudson will correctly understands that.
* The preamble and postamble includes a certain ANSI escape sequence designed in such a way to minimize garbage
* if this output is observed by a human being directly.
*
* <p>
* Because of this persistence mechanism, {@link ConsoleNote}s need to be serializable, and care should be taken
* to reduce footprint of the notes, if you are putting a lot of notes. Serialization format compatibility
* is also important, although {@link ConsoleNote}s that failed to deserialize will be simply ignored, so the
* worst thing that can happen is that you just lose some notes.
*
* <h2>Behaviour, JavaScript, and CSS</h2>
* <p>
* {@link ConsoleNote} can have associated <tt>script.js</tt> and <tt>style.css</tt> (put them
* in the same resource directory that you normally put Jelly scripts), which will be loaded into
* the HTML page whenever the console notes are used. This allows you to use minimal markup in
* code generation, and do the styling in CSS and perform the rest of the interesting work as a CSS behaviour/JavaScript.
*
* @param <T>
*      Contextual model object that this console is associated with, such as {@link Run}.
*
* @author Kohsuke Kawaguchi
* @see ConsoleAnnotationDescriptor
* @see Functions#generateConsoleAnnotationScriptAndStylesheet()
* @since 1.349
*/
public abstract class ConsoleNote<T> implements Serializable, Describable<ConsoleNote<?>> {
    /**
     * When the line of a console output that this annotation is attached is read by someone,
     * a new {@link ConsoleNote} is de-serialized and this method is invoked to annotate that line.
     *
     * @param context
     *      The object that owns the console output in question.
     * @param text
     *      Represents a line of the console output being annotated.
     * @param charPos
     *      The character position in 'text' where this annotation is attached.
     *
     * @return
     *      if non-null value is returned, this annotator will handle the next line.
     *      this mechanism can be used to annotate multiple lines starting at the annotated position.
     */
    public abstract ConsoleAnnotator annotate(T context, MarkupText text, int charPos);

    public ConsoleAnnotationDescriptor getDescriptor() {
        return (ConsoleAnnotationDescriptor)Hudson.getInstance().getDescriptorOrDie(getClass());
    }

    /**
     * Prints this note into a stream.
     *
     * <p>
     * The most typical use of this is {@code n.encodedTo(System.out)} where stdout is connected to Hudson.
     * The encoded form doesn't include any new line character to work better in the line-oriented nature
     * of {@link ConsoleAnnotator}.
     */
    public void encodeTo(OutputStream out) throws IOException {
        // atomically write to the final output, to minimize the chance of something else getting in between the output.
        // even with this, it is still technically possible to get such a mix-up to occur (for example,
        // if Java program is reading stdout/stderr separately and copying them into the same final stream.)
        out.write(encodeToBytes().toByteArray());
    }

    /**
     * Prints this note into a writer.
     *
     * <p>
     * Technically, this method only works if the {@link Writer} to {@link OutputStream}
     * encoding is ASCII compatible.
     */
    public void encodeTo(Writer out) throws IOException {
        out.write(encodeToBytes().toString());
    }

    private ByteArrayOutputStream encodeToBytes() throws IOException {
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(buf));
        oos.writeObject(this);
        oos.close();

        ByteArrayOutputStream buf2 = new ByteArrayOutputStream();

        DataOutputStream dos = new DataOutputStream(new Base64OutputStream(buf2,true,-1,null));
        buf2.write(PREAMBLE);
        dos.writeInt(buf.size());
        buf.writeTo(dos);
        dos.close();
        buf2.write(POSTAMBLE);
        return buf2;
    }

    /**
     * Works like {@link #encodeTo(Writer)} but obtain the result as a string.
     */
    public String encode() throws IOException {
        StringWriter sw = new StringWriter();
        encodeTo(sw);
        return sw.toString();
    }

    /**
     * Reads a note back from {@linkplain #encodeTo(OutputStream) its encoded form}.
     *
     * @param in
     *      Must point to the beginning of a preamble.
     *
     * @return null if the encoded form is malformed.
     */
    public static ConsoleNote readFrom(DataInputStream in) throws IOException, ClassNotFoundException {
        try {
            byte[] preamble = new byte[PREAMBLE.length];
            in.readFully(preamble);
            if (!Arrays.equals(preamble,PREAMBLE))
                return null;    // not a valid preamble

            DataInputStream decoded = new DataInputStream(new UnbufferedBase64InputStream(in));
            int sz = decoded.readInt();
            //Size should be greater than Zero. See http://issues.hudson-ci.org/browse/HUDSON-6558
            if (sz < 0) {
                return null;
            }
            byte[] buf = new byte[sz];
            decoded.readFully(buf);

            byte[] postamble = new byte[POSTAMBLE.length];
            in.readFully(postamble);
            if (!Arrays.equals(postamble,POSTAMBLE))
                return null;    // not a valid postamble

            ObjectInputStream ois = new ObjectInputStreamEx(
                    new GZIPInputStream(new ByteArrayInputStream(buf)), Hudson.getInstance().pluginManager.uberClassLoader);
            return (ConsoleNote) ois.readObject();
        } catch (Error e) {
            // for example, bogus 'sz' can result in OutOfMemoryError.
            // package that up as IOException so that the caller won't fatally die.
            throw new IOException2(e);
        }
    }

    /**
     * Skips the encoded console note.
     */
    public static void skip(DataInputStream in) throws IOException {
        byte[] preamble = new byte[PREAMBLE.length];
        in.readFully(preamble);
        if (!Arrays.equals(preamble,PREAMBLE))
            return;    // not a valid preamble

        DataInputStream decoded = new DataInputStream(new UnbufferedBase64InputStream(in));
        int sz = decoded.readInt();
        IOUtils.skip(decoded,sz);

        byte[] postamble = new byte[POSTAMBLE.length];
        in.readFully(postamble);
    }

    private static final long serialVersionUID = 1L;

    public static final String PREAMBLE_STR = "\u001B[8mha:";
    public static final String POSTAMBLE_STR = "\u001B[0m";

    /**
     * Preamble of the encoded form. ANSI escape sequence to stop echo back
     * plus a few magic characters.
     */
    public static final byte[] PREAMBLE = PREAMBLE_STR.getBytes();
    /**
     * Post amble is the ANSI escape sequence that brings back the echo.
     */
    public static final byte[] POSTAMBLE = POSTAMBLE_STR.getBytes();

    /**
     * Locates the preamble in the given buffer.
     */
    public static int findPreamble(byte[] buf, int start, int len) {
        int e = start + len - PREAMBLE.length + 1;

        OUTER:
        for (int i=start; i<e; i++) {
            if (buf[i]==PREAMBLE[0]) {
                // check for the rest of the match
                for (int j=1; j<PREAMBLE.length; j++) {
                    if (buf[i+j]!=PREAMBLE[j])
                        continue OUTER;
                }
                return i; // found it
            }
        }
        return -1; // not found
    }

    /**
     * Removes the embedded console notes in the given log lines.
     *
     * @since 1.350
     */
    public static List<String> removeNotes(Collection<String> logLines) {
        List<String> r = new ArrayList<String>(logLines.size());
        for (String l : logLines)
            r.add(removeNotes(l));
        return r;
    }

    /**
     * Removes the embedded console notes in the given log line.
     *
     * @since 1.350
     */
    public static String removeNotes(String line) {
        while (true) {
            int idx = line.indexOf(PREAMBLE_STR);
            if (idx<0return line;
            int e = line.indexOf(POSTAMBLE_STR,idx);
            if (e<0)    return line;
            line = line.substring(0,idx)+line.substring(e+POSTAMBLE_STR.length());
        }
    }
}
TOP

Related Classes of hudson.console.ConsoleNote

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.