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.FileNotFoundException;
13  import java.io.IOException;
14  import java.nio.ByteBuffer;
15  import java.nio.channels.Channels;
16  import java.nio.channels.ReadableByteChannel;
17  import java.nio.channels.WritableByteChannel;
18  import java.nio.file.Path;
19  import java.util.logging.Level;
20  import java.util.logging.Logger;
21  
22  import javax.servlet.AsyncContext;
23  import javax.servlet.ReadListener;
24  import javax.servlet.ServletInputStream;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.http.HttpStatus;
29  import org.eclipse.jgit.lfs.errors.CorruptLongObjectException;
30  import org.eclipse.jgit.lfs.internal.AtomicObjectOutputStream;
31  import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
32  import org.eclipse.jgit.lfs.lib.Constants;
33  
34  /**
35   * Handle asynchronous object upload.
36   *
37   * @since 4.6
38   */
39  public class ObjectUploadListener implements ReadListener {
40  
41  	private static final Logger LOG = Logger
42  			.getLogger(ObjectUploadListener.class.getName());
43  
44  	private final AsyncContext context;
45  
46  	private final HttpServletResponse response;
47  
48  	private final ServletInputStream in;
49  
50  	private final ReadableByteChannel inChannel;
51  
52  	private final AtomicObjectOutputStream out;
53  
54  	private WritableByteChannel channel;
55  
56  	private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
57  
58  	private final Path path;
59  
60  	private long uploaded;
61  
62  	private Callback callback;
63  
64  	/**
65  	 * Callback invoked after object upload completed.
66  	 *
67  	 * @since 5.1.7
68  	 */
69  	public interface Callback {
70  		/**
71  		 * Notified after object upload completed.
72  		 *
73  		 * @param path
74  		 *            path to the object on the backend
75  		 * @param size
76  		 *            uploaded size in bytes
77  		 */
78  		void uploadCompleted(String path, long size);
79  	}
80  
81  	/**
82  	 * Constructor for ObjectUploadListener.
83  	 *
84  	 * @param repository
85  	 *            the repository storing large objects
86  	 * @param context
87  	 *            a {@link javax.servlet.AsyncContext} object.
88  	 * @param request
89  	 *            a {@link javax.servlet.http.HttpServletRequest} object.
90  	 * @param response
91  	 *            a {@link javax.servlet.http.HttpServletResponse} object.
92  	 * @param id
93  	 *            a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object.
94  	 * @throws java.io.FileNotFoundException
95  	 * @throws java.io.IOException
96  	 */
97  	public ObjectUploadListener(FileLfsRepository repository,
98  			AsyncContext context, HttpServletRequest request,
99  			HttpServletResponse response, AnyLongObjectId id)
100 					throws FileNotFoundException, IOException {
101 		this.context = context;
102 		this.response = response;
103 		this.in = request.getInputStream();
104 		this.inChannel = Channels.newChannel(in);
105 		this.out = repository.getOutputStream(id);
106 		this.channel = Channels.newChannel(out);
107 		this.path = repository.getPath(id);
108 		this.uploaded = 0L;
109 		response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON);
110 	}
111 
112 	/**
113 	 * Set the callback to invoke after upload completed.
114 	 *
115 	 * @param callback
116 	 *            the callback
117 	 * @return {@code this}.
118 	 * @since 5.1.7
119 	 */
120 	public ObjectUploadListener setCallback(Callback callback) {
121 		this.callback = callback;
122 		return this;
123 	}
124 
125 	/**
126 	 * {@inheritDoc}
127 	 *
128 	 * Writes all the received data to the output channel
129 	 */
130 	@Override
131 	public void onDataAvailable() throws IOException {
132 		while (in.isReady()) {
133 			if (inChannel.read(buffer) > 0) {
134 				buffer.flip();
135 				uploaded += Integer.valueOf(channel.write(buffer)).longValue();
136 				buffer.compact();
137 			} else {
138 				buffer.flip();
139 				while (buffer.hasRemaining()) {
140 					uploaded += Integer.valueOf(channel.write(buffer))
141 							.longValue();
142 				}
143 				close();
144 				return;
145 			}
146 		}
147 	}
148 
149 	/** {@inheritDoc} */
150 	@Override
151 	public void onAllDataRead() throws IOException {
152 		close();
153 	}
154 
155 	/**
156 	 * Close resources held by this listener
157 	 *
158 	 * @throws java.io.IOException
159 	 */
160 	protected void close() throws IOException {
161 		try {
162 			inChannel.close();
163 			channel.close();
164 			// TODO check if status 200 is ok for PUT request, HTTP foresees 204
165 			// for successful PUT without response body
166 			if (!response.isCommitted()) {
167 				response.setStatus(HttpServletResponse.SC_OK);
168 			}
169 			if (callback != null) {
170 				callback.uploadCompleted(path.toString(), uploaded);
171 			}
172 		} finally {
173 			context.complete();
174 		}
175 	}
176 
177 	/** {@inheritDoc} */
178 	@Override
179 	public void onError(Throwable e) {
180 		try {
181 			out.abort();
182 			inChannel.close();
183 			channel.close();
184 			int status;
185 			if (e instanceof CorruptLongObjectException) {
186 				status = HttpStatus.SC_BAD_REQUEST;
187 				LOG.log(Level.WARNING, e.getMessage(), e);
188 			} else {
189 				status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
190 				LOG.log(Level.SEVERE, e.getMessage(), e);
191 			}
192 			FileLfsServlet.sendError(response, status, e.getMessage());
193 		} catch (IOException ex) {
194 			LOG.log(Level.SEVERE, ex.getMessage(), ex);
195 		}
196 	}
197 }