PageRenderTime 31ms CodeModel.GetById 3ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/model/LargeText.java

http://github.com/hudson/hudson
Java | 372 lines | 232 code | 55 blank | 85 comment | 15 complexity | 2bebbda87a1f25d155daae34b09a0c4d MD5 | raw file
  1/*
  2 * The MIT License
  3 * 
  4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
  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 */
 24package hudson.model;
 25
 26import hudson.util.ByteBuffer;
 27import hudson.util.CharSpool;
 28import hudson.util.LineEndNormalizingWriter;
 29import org.kohsuke.stapler.StaplerRequest;
 30import org.kohsuke.stapler.StaplerResponse;
 31import org.kohsuke.stapler.framework.io.WriterOutputStream;
 32import org.apache.commons.io.output.CountingOutputStream;
 33
 34import javax.servlet.http.HttpServletResponse;
 35import java.io.File;
 36import java.io.IOException;
 37import java.io.InputStream;
 38import java.io.OutputStream;
 39import java.io.RandomAccessFile;
 40import java.io.Writer;
 41import java.io.Reader;
 42import java.io.InputStreamReader;
 43
 44/**
 45 * Represents a large text data.
 46 *
 47 * <p>
 48 * This class defines methods for handling progressive text update.
 49 *
 50 * @author Kohsuke Kawaguchi
 51 * @deprecated moved to stapler, as of Hudson 1.220
 52 */
 53public class LargeText {
 54    /**
 55     * Represents the data source of this text.
 56     */
 57    private interface Source {
 58        Session open() throws IOException;
 59        long length();
 60        boolean exists();
 61    }
 62    private final Source source;
 63
 64    private volatile boolean completed;
 65
 66    public LargeText(final File file, boolean completed) {
 67        this.source = new Source() {
 68            public Session open() throws IOException {
 69                return new FileSession(file);
 70            }
 71
 72            public long length() {
 73                return file.length();
 74            }
 75
 76            public boolean exists() {
 77                return file.exists();
 78            }
 79        };
 80        this.completed = completed;
 81    }
 82
 83    public LargeText(final ByteBuffer memory, boolean completed) {
 84        this.source = new Source() {
 85            public Session open() throws IOException {
 86                return new BufferSession(memory);
 87            }
 88
 89            public long length() {
 90                return memory.length();
 91            }
 92
 93            public boolean exists() {
 94                return true;
 95            }
 96        };
 97        this.completed = completed;
 98    }
 99
100    public void markAsComplete() {
101        completed = true;
102    }
103
104    public boolean isComplete() {
105        return completed;
106    }
107
108    /**
109     * Returns {@link Reader} for reading the raw bytes.
110     */
111    public Reader readAll() throws IOException {
112        return new InputStreamReader(new InputStream() {
113            final Session session = source.open();
114            public int read() throws IOException {
115                byte[] buf = new byte[1];
116                int n = session.read(buf);
117                if(n==1)    return buf[0];
118                else        return -1; // EOF
119            }
120
121            public int read(byte[] buf, int off, int len) throws IOException {
122                return session.read(buf,off,len);
123            }
124
125            public void close() throws IOException {
126                session.close();
127            }
128        });
129    }
130
131    /**
132     * Writes the tail portion of the file to the {@link Writer}.
133     *
134     * <p>
135     * The text file is assumed to be in the system default encoding.
136     *
137     * @param start
138     *      The byte offset in the input file where the write operation starts.
139     *
140     * @return
141     *      if the file is still being written, this method writes the file
142     *      until the last newline character and returns the offset to start
143     *      the next write operation.
144     */
145    public long writeLogTo(long start, Writer w) throws IOException {
146        CountingOutputStream os = new CountingOutputStream(new WriterOutputStream(w));
147
148        Session f = source.open();
149        f.skip(start);
150
151        if(completed) {
152            // write everything till EOF
153            byte[] buf = new byte[1024];
154            int sz;
155            while((sz=f.read(buf))>=0)
156                os.write(buf,0,sz);
157        } else {
158            ByteBuf buf = new ByteBuf(null,f);
159            HeadMark head = new HeadMark(buf);
160            TailMark tail = new TailMark(buf);
161
162            while(tail.moveToNextLine(f)) {
163                head.moveTo(tail,os);
164            }
165            head.finish(os);
166        }
167
168        f.close();
169        os.flush();
170
171        return os.getCount()+start;
172    }
173
174    /**
175     * Implements the progressive text handling.
176     * This method is used as a "web method" with progressiveText.jelly.
177     */
178    public void doProgressText(StaplerRequest req, StaplerResponse rsp) throws IOException {
179        rsp.setContentType("text/plain");
180        rsp.setStatus(HttpServletResponse.SC_OK);
181
182        if(!source.exists()) {
183            // file doesn't exist yet
184            rsp.addHeader("X-Text-Size","0");
185            rsp.addHeader("X-More-Data","true");
186            return;
187        }
188
189        long start = 0;
190        String s = req.getParameter("start");
191        if(s!=null)
192            start = Long.parseLong(s);
193
194        if(source.length() < start )
195            start = 0;  // text rolled over
196
197        CharSpool spool = new CharSpool();
198        long r = writeLogTo(start,spool);
199
200        rsp.addHeader("X-Text-Size",String.valueOf(r));
201        if(!completed)
202            rsp.addHeader("X-More-Data","true");
203
204        // when sending big text, try compression. don't bother if it's small
205        Writer w;
206        if(r-start>4096)
207            w = rsp.getCompressedWriter(req);
208        else
209            w = rsp.getWriter();
210        spool.writeTo(new LineEndNormalizingWriter(w));
211        w.close();
212
213    }
214
215    /**
216     * Points to a byte in the buffer.
217     */
218    private static class Mark {
219        protected ByteBuf buf;
220        protected int pos;
221
222        public Mark(ByteBuf buf) {
223            this.buf = buf;
224        }
225    }
226
227    /**
228     * Points to the start of the region that's not committed
229     * to the output yet.
230     */
231    private static final class HeadMark extends Mark {
232        public HeadMark(ByteBuf buf) {
233            super(buf);
234        }
235
236        /**
237         * Moves this mark to 'that' mark, and writes the data
238         * to {@link OutputStream} if necessary.
239         */
240        void moveTo(Mark that, OutputStream os) throws IOException {
241            while(this.buf!=that.buf) {
242                os.write(buf.buf,0,buf.size);
243                buf = buf.next;
244                pos = 0;
245            }
246
247            this.pos = that.pos;
248        }
249
250        void finish(OutputStream os) throws IOException {
251            os.write(buf.buf,0,pos);
252        }
253    }
254
255    /**
256     * Points to the end of the region.
257     */
258    private static final class TailMark extends Mark {
259        public TailMark(ByteBuf buf) {
260            super(buf);
261        }
262
263        boolean moveToNextLine(Session f) throws IOException {
264            while(true) {
265                while(pos==buf.size) {
266                    if(!buf.isFull()) {
267                        // read until EOF
268                        return false;
269                    } else {
270                        // read into the next buffer
271                        buf = new ByteBuf(buf,f);
272                        pos = 0;
273                    }
274                }
275                byte b = buf.buf[pos++];
276                if(b=='\r' || b=='\n')
277                    return true;
278            }
279        }
280    }
281
282    private static final class ByteBuf {
283        private final byte[] buf = new byte[1024];
284        private int size = 0;
285        private ByteBuf next;
286
287        public ByteBuf(ByteBuf previous, Session f) throws IOException {
288            if(previous!=null) {
289                assert previous.next==null;
290                previous.next = this;
291            }
292
293            while(!this.isFull()) {
294                int chunk = f.read(buf, size, buf.length - size);
295                if(chunk==-1)
296                    return;
297                size+= chunk;
298            }
299        }
300
301        public boolean isFull() {
302            return buf.length==size;
303        }
304    }
305
306    /**
307     * Represents the read session of the {@link Source}.
308     * Methods generally follow the contracts of {@link InputStream}.
309     */
310    private interface Session {
311        void close() throws IOException;
312        void skip(long start) throws IOException;
313        int read(byte[] buf) throws IOException;
314        int read(byte[] buf, int offset, int length) throws IOException;
315    }
316
317    /**
318     * {@link Session} implementation over {@link RandomAccessFile}.
319     */
320    private static final class FileSession implements Session {
321        private final RandomAccessFile file;
322
323        public FileSession(File file) throws IOException {
324            this.file = new RandomAccessFile(file,"r");
325        }
326
327        public void close() throws IOException {
328            file.close();
329        }
330
331        public void skip(long start) throws IOException {
332            file.seek(file.getFilePointer()+start);
333        }
334
335        public int read(byte[] buf) throws IOException {
336            return file.read(buf);
337        }
338
339        public int read(byte[] buf, int offset, int length) throws IOException {
340            return file.read(buf,offset,length);
341        }
342    }
343
344    /**
345     * {@link Session} implementation over {@link ByteBuffer}.
346     */
347    private static final class BufferSession implements Session {
348        private final InputStream in;
349
350        public BufferSession(ByteBuffer buf) {
351            this.in = buf.newInputStream();
352        }
353
354
355        public void close() throws IOException {
356            in.close();
357        }
358
359        public void skip(long n) throws IOException {
360            while(n>0)
361                n -= in.skip(n);
362        }
363
364        public int read(byte[] buf) throws IOException {
365            return in.read(buf);
366        }
367
368        public int read(byte[] buf, int offset, int length) throws IOException {
369            return in.read(buf,offset,length);
370        }
371    }
372}