View Javadoc
1   /*
2    * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.lfs.server.fs;
11  
12  import java.io.IOException;
13  import java.nio.ByteBuffer;
14  import java.nio.channels.Channels;
15  import java.nio.channels.ReadableByteChannel;
16  import java.nio.channels.WritableByteChannel;
17  import java.util.logging.Level;
18  import java.util.logging.Logger;
19  
20  import javax.servlet.AsyncContext;
21  import javax.servlet.ServletOutputStream;
22  import javax.servlet.WriteListener;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.apache.http.HttpStatus;
26  import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
27  import org.eclipse.jgit.lfs.lib.Constants;
28  import org.eclipse.jgit.util.HttpSupport;
29  
30  /**
31   * Handle asynchronous large object download.
32   *
33   * @since 4.7
34   */
35  public class ObjectDownloadListener implements WriteListener {
36  
37  	private static final Logger LOG = Logger
38  			.getLogger(ObjectDownloadListener.class.getName());
39  
40  	private final AsyncContext context;
41  
42  	private final HttpServletResponse response;
43  
44  	private final ServletOutputStream out;
45  
46  	private final ReadableByteChannel in;
47  
48  	private final WritableByteChannel outChannel;
49  
50  	private ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
51  
52  	/**
53  	 * <p>Constructor for ObjectDownloadListener.</p>
54  	 *
55  	 * @param repository
56  	 *            the repository storing large objects
57  	 * @param context
58  	 *            the servlet asynchronous context
59  	 * @param response
60  	 *            the servlet response
61  	 * @param id
62  	 *            id of the object to be downloaded
63  	 * @throws java.io.IOException
64  	 */
65  	public ObjectDownloadListener(FileLfsRepository repository,
66  			AsyncContext context, HttpServletResponse response,
67  			AnyLongObjectId id) throws IOException {
68  		this.context = context;
69  		this.response = response;
70  		this.in = repository.getReadChannel(id);
71  		this.out = response.getOutputStream();
72  		this.outChannel = Channels.newChannel(out);
73  
74  		response.addHeader(HttpSupport.HDR_CONTENT_LENGTH,
75  				String.valueOf(repository.getSize(id)));
76  		response.setContentType(Constants.HDR_APPLICATION_OCTET_STREAM);
77  	}
78  
79  	/**
80  	 * {@inheritDoc}
81  	 *
82  	 * Write file content
83  	 */
84  	@SuppressWarnings("Finally")
85  	@Override
86  	public void onWritePossible() throws IOException {
87  		while (out.isReady()) {
88  			try {
89  				buffer.clear();
90  				if (in.read(buffer) < 0) {
91  					buffer = null;
92  				} else {
93  					buffer.flip();
94  				}
95  			} catch (Throwable t) {
96  				LOG.log(Level.SEVERE, t.getMessage(), t);
97  				buffer = null;
98  			} finally {
99  				if (buffer != null) {
100 					outChannel.write(buffer);
101 				} else {
102 					try {
103 						in.close();
104 					} catch (IOException e) {
105 						LOG.log(Level.SEVERE, e.getMessage(), e);
106 					}
107 					try {
108 						out.close();
109 					} finally {
110 						context.complete();
111 					}
112 					// This is need to avoid endless loop in recent Jetty versions.
113 					// That's because out.isReady() is returning true for already
114 					// closed streams and because out.close() doesn't throw any
115 					// exception any more when trying to close already closed stream.
116 					return;
117 				}
118 			}
119 		}
120 	}
121 
122 	/**
123 	 * {@inheritDoc}
124 	 *
125 	 * Handle errors
126 	 */
127 	@Override
128 	public void onError(Throwable e) {
129 		try {
130 			FileLfsServlet.sendError(response,
131 					HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage());
132 			context.complete();
133 			in.close();
134 		} catch (IOException ex) {
135 			LOG.log(Level.SEVERE, ex.getMessage(), ex);
136 		}
137 	}
138 }