aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server')
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java89
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java48
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java126
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java82
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java168
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/AtomicObjectOutputStream.java121
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java185
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java167
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java151
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java176
-rw-r--r--org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java65
11 files changed, 1378 insertions, 0 deletions
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java
new file mode 100644
index 0000000000..3bdf8d08b2
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LargeFileRepository.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+
+/**
+ * Abstraction of a repository for storing large objects
+ *
+ * @since 4.3
+ */
+public interface LargeFileRepository {
+
+ /**
+ * @param id
+ * id of the object to download
+ * @return Action for downloading the object
+ */
+ public Response.Action getDownloadAction(AnyLongObjectId id);
+
+ /**
+ * @param id
+ * id of the object to upload
+ * @param size
+ * size of the object to be uploaded
+ * @return Action for uploading the object
+ */
+ public Response.Action getUploadAction(AnyLongObjectId id, long size);
+
+ /**
+ * @param id
+ * id of the object to be verified
+ * @return Action for verifying the object, or {@code null} if the server
+ * doesn't support or require verification
+ */
+ public @Nullable Response.Action getVerifyAction(AnyLongObjectId id);
+
+ /**
+ * @param id
+ * id of the object
+ * @return length of the object content in bytes, -1 if the object doesn't
+ * exist
+ * @throws IOException
+ */
+ public long getSize(AnyLongObjectId id) throws IOException;
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java
new file mode 100644
index 0000000000..30ba22e885
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsObject.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server;
+
+class LfsObject {
+ String oid;
+ long size;
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
new file mode 100644
index 0000000000..394137cf68
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * LFS protocol handler implementing the LFS batch API [1]
+ *
+ * [1] https://github.com/github/git-lfs/blob/master/docs/api/http-v1-batch.md
+ *
+ * @since 4.3
+ */
+public abstract class LfsProtocolServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String CONTENTTYPE_VND_GIT_LFS_JSON = "application/vnd.git-lfs+json"; //$NON-NLS-1$
+
+ private Gson gson = createGson();
+
+ /**
+ * Get the large file repository
+ *
+ * @return the large file repository storing large files
+ */
+ protected abstract LargeFileRepository getLargeFileRepository();
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse res)
+ throws ServletException, IOException {
+ res.setStatus(SC_OK);
+ res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
+
+ Writer w = new BufferedWriter(
+ new OutputStreamWriter(res.getOutputStream(), UTF_8));
+
+ Reader r = new BufferedReader(new InputStreamReader(req.getInputStream(), UTF_8));
+ LfsRequest request = gson.fromJson(r, LfsRequest.class);
+
+ LargeFileRepository repo = getLargeFileRepository();
+ if (repo == null) {
+ res.setStatus(SC_SERVICE_UNAVAILABLE);
+ return;
+ }
+
+ TransferHandler handler = TransferHandler
+ .forOperation(request.operation, repo, request.objects);
+ gson.toJson(handler.process(), w);
+ w.flush();
+ }
+
+ private static class LfsRequest {
+ String operation;
+
+ List<LfsObject> objects;
+ }
+
+ private static Gson createGson() {
+ GsonBuilder gb = new GsonBuilder()
+ .setFieldNamingPolicy(
+ FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .setPrettyPrinting().disableHtmlEscaping();
+ return gb.create();
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java
new file mode 100644
index 0000000000..dc972e04ee
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/Response.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * POJOs for Gson serialization/deserialization
+ *
+ * See
+ * {@link <a href="https://github.com/github/git-lfs/tree/master/docs/api">LFS
+ * API specification</a>}
+ *
+ * @since 4.3
+ */
+public interface Response {
+ /** Describes an action the client can execute on a single object */
+ class Action {
+ public String href;
+ public Map<String, String> header;
+ }
+
+ /** Describes an error to be returned by the LFS batch API */
+ class Error {
+ public int code;
+ public String message;
+ }
+
+ /** Describes the actions the LFS server offers for a single object */
+ class ObjectInfo {
+ public String oid;
+ public long size;
+ public Map<String, Action> actions;
+ public Error error;
+ }
+
+ /** Describes the body of a LFS batch API response */
+ class Body {
+ public List<ObjectInfo> objects;
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
new file mode 100644
index 0000000000..bf5b61cc6e
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lfs.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.eclipse.jgit.lfs.server.Response.Action;
+import org.eclipse.jgit.lfs.server.Response.Body;
+import org.eclipse.jgit.lfs.server.internal.LfsServerText;
+
+abstract class TransferHandler {
+
+ private static final String DOWNLOAD = "download"; //$NON-NLS-1$
+ private static final String UPLOAD = "upload"; //$NON-NLS-1$
+ private static final String VERIFY = "verify"; //$NON-NLS-1$
+
+ static TransferHandler forOperation(String operation,
+ LargeFileRepository repository, List<LfsObject> objects) {
+ switch (operation) {
+ case TransferHandler.UPLOAD:
+ return new Upload(repository, objects);
+ case TransferHandler.DOWNLOAD:
+ return new Download(repository, objects);
+ case TransferHandler.VERIFY:
+ default:
+ throw new UnsupportedOperationException(MessageFormat.format(
+ LfsServerText.get().unsupportedOperation, operation));
+ }
+ }
+
+ private static class Upload extends TransferHandler {
+ Upload(LargeFileRepository repository,
+ List<LfsObject> objects) {
+ super(repository, objects);
+ }
+
+ @Override
+ Body process() throws IOException {
+ Response.Body body = new Response.Body();
+ if (objects.size() > 0) {
+ body.objects = new ArrayList<>();
+ for (LfsObject o : objects) {
+ addObjectInfo(body, o);
+ }
+ }
+ return body;
+ }
+
+ private void addObjectInfo(Response.Body body, LfsObject o)
+ throws IOException {
+ Response.ObjectInfo info = new Response.ObjectInfo();
+ body.objects.add(info);
+ info.oid = o.oid;
+ info.size = o.size;
+
+ LongObjectId oid = LongObjectId.fromString(o.oid);
+ if (repository.getSize(oid) == -1) {
+ info.actions = new HashMap<>();
+ info.actions.put(UPLOAD,
+ repository.getUploadAction(oid, o.size));
+ Action verify = repository.getVerifyAction(oid);
+ if (verify != null) {
+ info.actions.put(VERIFY, verify);
+ }
+ }
+ }
+ }
+
+ private static class Download extends TransferHandler {
+ Download(LargeFileRepository repository,
+ List<LfsObject> objects) {
+ super(repository, objects);
+ }
+
+ @Override
+ Body process() throws IOException {
+ Response.Body body = new Response.Body();
+ if (objects.size() > 0) {
+ body.objects = new ArrayList<>();
+ for (LfsObject o : objects) {
+ addObjectInfo(body, o);
+ }
+ }
+ return body;
+ }
+
+ private void addObjectInfo(Response.Body body, LfsObject o)
+ throws IOException {
+ Response.ObjectInfo info = new Response.ObjectInfo();
+ body.objects.add(info);
+ info.oid = o.oid;
+ info.size = o.size;
+
+ LongObjectId oid = LongObjectId.fromString(o.oid);
+ if (repository.getSize(oid) >= 0) {
+ info.actions = new HashMap<>();
+ info.actions.put(DOWNLOAD,
+ repository.getDownloadAction(oid));
+ } else {
+ info.error = new Response.Error();
+ info.error.code = SC_NOT_FOUND;
+ info.error.message = MessageFormat.format(
+ LfsServerText.get().objectNotFound,
+ oid.getName());
+ }
+ }
+ }
+
+ final LargeFileRepository repository;
+
+ final List<LfsObject> objects;
+
+ TransferHandler(LargeFileRepository repository,
+ List<LfsObject> objects) {
+ this.repository = repository;
+ this.objects = objects;
+ }
+
+ abstract Response.Body process() throws IOException;
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/AtomicObjectOutputStream.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/AtomicObjectOutputStream.java
new file mode 100644
index 0000000000..ecc7c1f36c
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/AtomicObjectOutputStream.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.security.DigestOutputStream;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.storage.file.LockFile;
+import org.eclipse.jgit.lfs.errors.CorruptLongObjectException;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.eclipse.jgit.lfs.server.internal.LfsServerText;
+
+/**
+ * Output stream writing content to a {@link LockFile} which is committed on
+ * close(). The stream checks if the hash of the stream content matches the
+ * id.
+ */
+class AtomicObjectOutputStream extends OutputStream {
+
+ private LockFile locked;
+
+ private DigestOutputStream out;
+
+ private boolean aborted;
+
+ private AnyLongObjectId id;
+
+ AtomicObjectOutputStream(Path path, AnyLongObjectId id)
+ throws IOException {
+ locked = new LockFile(path.toFile());
+ locked.lock();
+ this.id = id;
+ out = new DigestOutputStream(locked.getOutputStream(),
+ Constants.newMessageDigest());
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ out.close();
+ if (!aborted) {
+ verifyHash();
+ locked.commit();
+ }
+ }
+
+ private void verifyHash() {
+ AnyLongObjectId contentHash = LongObjectId
+ .fromRaw(out.getMessageDigest().digest());
+ if (!contentHash.equals(id)) {
+ abort();
+ throw new CorruptLongObjectException(id, contentHash,
+ MessageFormat.format(LfsServerText.get().corruptLongObject,
+ contentHash, id));
+ }
+ }
+
+ void abort() {
+ locked.unlock();
+ aborted = true;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java
new file mode 100644
index 0000000000..12da271b37
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsRepository.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
+
+import java.io.IOException;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lfs.server.LargeFileRepository;
+import org.eclipse.jgit.lfs.server.Response;
+import org.eclipse.jgit.lfs.server.Response.Action;
+
+/**
+ * Repository storing large objects in the file system
+ *
+ * @since 4.3
+ */
+public class FileLfsRepository implements LargeFileRepository {
+
+ private final String url;
+ private final Path dir;
+ private AtomicObjectOutputStream out;
+
+ /**
+ * @param url
+ * external URL of this repository
+ * @param dir
+ * storage directory
+ * @throws IOException
+ */
+ public FileLfsRepository(String url, Path dir) throws IOException {
+ this.url = url;
+ this.dir = dir;
+ Files.createDirectories(dir);
+ }
+
+ @Override
+ public Response.Action getDownloadAction(AnyLongObjectId id) {
+ return getAction(id);
+ }
+
+ @Override
+ public Action getUploadAction(AnyLongObjectId id, long size) {
+ return getAction(id);
+ }
+
+ @Override
+ public @Nullable Action getVerifyAction(AnyLongObjectId id) {
+ return null;
+ }
+
+ @Override
+ public long getSize(AnyLongObjectId id) throws IOException {
+ Path p = getPath(id);
+ if (Files.exists(p)) {
+ return Files.size(p);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Get the storage directory
+ *
+ * @return the path of the storage directory
+ */
+ public Path getDir() {
+ return dir;
+ }
+
+ /**
+ * Get the path where the given object is stored
+ *
+ * @param id
+ * id of a large object
+ * @return path the object's storage path
+ */
+ protected Path getPath(AnyLongObjectId id) {
+ StringBuilder s = new StringBuilder(
+ Constants.LONG_OBJECT_ID_STRING_LENGTH + 6);
+ s.append(toHexCharArray(id.getFirstByte())).append('/');
+ s.append(toHexCharArray(id.getSecondByte())).append('/');
+ s.append(id.name());
+ return dir.resolve(s.toString());
+ }
+
+ private Response.Action getAction(AnyLongObjectId id) {
+ Response.Action a = new Response.Action();
+ a.href = url + id.getName();
+ a.header = Collections.singletonMap(HDR_AUTHORIZATION, "not:required"); //$NON-NLS-1$
+ return a;
+ }
+
+ ReadableByteChannel getReadChannel(AnyLongObjectId id)
+ throws IOException {
+ return FileChannel.open(getPath(id), StandardOpenOption.READ);
+ }
+
+ WritableByteChannel getWriteChannel(AnyLongObjectId id)
+ throws IOException {
+ Path path = getPath(id);
+ Files.createDirectories(path.getParent());
+ out = new AtomicObjectOutputStream(path, id);
+ return Channels.newChannel(out);
+ }
+
+ /**
+ * Abort the output stream
+ */
+ void abortWrite() {
+ if (out != null) {
+ out.abort();
+ }
+ }
+
+ private static char[] toHexCharArray(int b) {
+ final char[] dst = new char[2];
+ formatHexChar(dst, 0, b);
+ return dst;
+ }
+
+ private static final char[] hexchar = { '0', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ private static void formatHexChar(final char[] dst, final int p, int b) {
+ int o = p + 1;
+ while (o >= p && b != 0) {
+ dst[o--] = hexchar[b & 0xf];
+ b >>>= 4;
+ }
+ while (o >= p)
+ dst[o--] = '0';
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
new file mode 100644
index 0000000000..8864af87ab
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lfs.lib.LongObjectId;
+import org.eclipse.jgit.lfs.server.internal.LfsServerText;
+
+/**
+ * Servlet supporting upload and download of large objects as defined by the
+ * GitHub Large File Storage extension API extending git to allow separate
+ * storage of large files
+ * (https://github.com/github/git-lfs/tree/master/docs/api).
+ *
+ * @since 4.3
+ */
+@WebServlet(asyncSupported = true)
+public class FileLfsServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private final FileLfsRepository repository;
+
+ private final long timeout;
+
+ /**
+ * @param repository
+ * the repository storing the large objects
+ * @param timeout
+ * timeout for object upload / download in milliseconds
+ */
+ public FileLfsServlet(FileLfsRepository repository, long timeout) {
+ this.repository = repository;
+ this.timeout = timeout;
+ }
+
+ /**
+ * Handles object downloads
+ *
+ * @param req
+ * servlet request
+ * @param rsp
+ * servlet response
+ * @throws ServletException
+ * if a servlet-specific error occurs
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ @Override
+ protected void doGet(HttpServletRequest req,
+ HttpServletResponse rsp) throws ServletException, IOException {
+ AnyLongObjectId obj = getObjectToTransfer(req, rsp);
+ if (obj != null) {
+ if (repository.getSize(obj) == -1) {
+ sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat
+ .format(LfsServerText.get().objectNotFound, obj));
+ return;
+ }
+ AsyncContext context = req.startAsync();
+ context.setTimeout(timeout);
+ rsp.getOutputStream()
+ .setWriteListener(new ObjectDownloadListener(repository,
+ context, rsp, obj));
+ }
+ }
+
+ private AnyLongObjectId getObjectToTransfer(HttpServletRequest req,
+ HttpServletResponse rsp) throws IOException {
+ String info = req.getPathInfo();
+ if (info.length() != 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH) {
+ sendError(rsp, HttpStatus.SC_BAD_REQUEST, MessageFormat
+ .format(LfsServerText.get().invalidPathInfo, info));
+ return null;
+ }
+ try {
+ return LongObjectId.fromString(info.substring(1, 65));
+ } catch (InvalidLongObjectIdException e) {
+ sendError(rsp, HttpStatus.SC_BAD_REQUEST, e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Handle object uploads
+ *
+ * @param req
+ * servlet request
+ * @param rsp
+ * servlet response
+ * @throws ServletException
+ * if a servlet-specific error occurs
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ @Override
+ protected void doPut(HttpServletRequest req,
+ HttpServletResponse rsp) throws ServletException, IOException {
+ AnyLongObjectId id = getObjectToTransfer(req, rsp);
+ if (id != null) {
+ AsyncContext context = req.startAsync();
+ context.setTimeout(timeout);
+ req.getInputStream().setReadListener(new ObjectUploadListener(
+ repository, context, req, rsp, id));
+ }
+ }
+
+ static void sendError(HttpServletResponse rsp, int status, String message)
+ throws IOException {
+ rsp.setStatus(status);
+ // TODO return message in response body in json format as specified in
+ // https://github.com/github/git-lfs/blob/master/docs/api/http-v1-batch.md
+ rsp.flushBuffer();
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
new file mode 100644
index 0000000000..bfdea4fb1d
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectDownloadListener.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.util.HttpSupport;
+
+/**
+ * Handle asynchronous large object download
+ */
+class ObjectDownloadListener implements WriteListener {
+
+ private static Logger LOG = Logger
+ .getLogger(ObjectDownloadListener.class.getName());
+
+ private final AsyncContext context;
+
+ private final HttpServletResponse response;
+
+ private final ServletOutputStream out;
+
+ private final ReadableByteChannel in;
+
+ private final WritableByteChannel outChannel;
+
+ private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+
+ /**
+ * @param repository
+ * the repository storing large objects
+ * @param context
+ * the servlet asynchronous context
+ * @param response
+ * the servlet response
+ * @param id
+ * id of the object to be downloaded
+ * @throws IOException
+ */
+ public ObjectDownloadListener(FileLfsRepository repository,
+ AsyncContext context, HttpServletResponse response,
+ AnyLongObjectId id) throws IOException {
+ this.context = context;
+ this.response = response;
+ this.in = repository.getReadChannel(id);
+ this.out = response.getOutputStream();
+ this.outChannel = Channels.newChannel(out);
+
+ response.addHeader(HttpSupport.HDR_CONTENT_LENGTH,
+ String.valueOf(repository.getSize(id)));
+ response.setContentType(Constants.HDR_APPLICATION_OCTET_STREAM);
+ }
+
+ /**
+ * Write file content
+ *
+ * @throws IOException
+ */
+ @Override
+ public void onWritePossible() throws IOException {
+ while (out.isReady()) {
+ if (in.read(buffer) != -1) {
+ buffer.flip();
+ outChannel.write(buffer);
+ buffer.compact();
+ } else {
+ in.close();
+ buffer.flip();
+ while (out.isReady()) {
+ if (buffer.hasRemaining()) {
+ outChannel.write(buffer);
+ } else {
+ context.complete();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle errors
+ *
+ * @param e
+ * the cause
+ */
+ @Override
+ public void onError(Throwable e) {
+ try {
+ FileLfsServlet.sendError(response,
+ HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+ context.complete();
+ in.close();
+ } catch (IOException ex) {
+ LOG.log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
new file mode 100644
index 0000000000..05970ad1f2
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/ObjectUploadListener.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohnk@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lfs.errors.CorruptLongObjectException;
+import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
+import org.eclipse.jgit.lfs.lib.Constants;
+
+/**
+ * Handle asynchronous object upload
+ */
+class ObjectUploadListener implements ReadListener {
+
+ private static Logger LOG = Logger
+ .getLogger(ObjectUploadListener.class.getName());
+
+ private final AsyncContext context;
+
+ private final HttpServletResponse response;
+
+ private FileLfsRepository repository;
+
+ private final ServletInputStream in;
+
+ private final ReadableByteChannel inChannel;
+
+ private WritableByteChannel out;
+
+ private final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+
+ /**
+ * @param repository
+ * the repository storing large objects
+ * @param context
+ * @param request
+ * @param response
+ * @param id
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public ObjectUploadListener(FileLfsRepository repository,
+ AsyncContext context, HttpServletRequest request,
+ HttpServletResponse response, AnyLongObjectId id)
+ throws FileNotFoundException, IOException {
+ this.repository = repository;
+ this.context = context;
+ this.response = response;
+ this.in = request.getInputStream();
+ this.inChannel = Channels.newChannel(in);
+ this.out = repository.getWriteChannel(id);
+ response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON);
+ }
+
+ /**
+ * Writes all the received data to the output channel
+ *
+ * @throws IOException
+ */
+ @Override
+ public void onDataAvailable() throws IOException {
+ while (in.isReady()) {
+ if (inChannel.read(buffer) > 0) {
+ buffer.flip();
+ out.write(buffer);
+ buffer.compact();
+ } else {
+ buffer.flip();
+ while (buffer.hasRemaining()) {
+ out.write(buffer);
+ }
+ close();
+ return;
+ }
+ }
+ }
+
+ /**
+ * @throws IOException
+ */
+ @Override
+ public void onAllDataRead() throws IOException {
+ close();
+ }
+
+ protected void close() throws IOException {
+ try {
+ inChannel.close();
+ out.close();
+ // TODO check if status 200 is ok for PUT request, HTTP foresees 204
+ // for successful PUT without response body
+ response.setStatus(HttpServletResponse.SC_OK);
+ } finally {
+ context.complete();
+ }
+ }
+
+ /**
+ * @param e
+ * the exception that caused the problem
+ */
+ @Override
+ public void onError(Throwable e) {
+ try {
+ repository.abortWrite();
+ inChannel.close();
+ out.close();
+ int status;
+ if (e instanceof CorruptLongObjectException) {
+ status = HttpStatus.SC_BAD_REQUEST;
+ LOG.log(Level.WARNING, e.getMessage(), e);
+ } else {
+ status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
+ LOG.log(Level.SEVERE, e.getMessage(), e);
+ }
+ FileLfsServlet.sendError(response, status, e.getMessage());
+ } catch (IOException ex) {
+ LOG.log(Level.SEVERE, ex.getMessage(), ex);
+ }
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java
new file mode 100644
index 0000000000..4e1c4c639e
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/internal/LfsServerText.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.internal;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+/**
+ * Translation bundle for JGit LFS server
+ */
+public class LfsServerText extends TranslationBundle {
+
+ /**
+ * @return an instance of this translation bundle
+ */
+ public static LfsServerText get() {
+ return NLS.getBundleFor(LfsServerText.class);
+ }
+
+ // @formatter:off
+ /***/ public String corruptLongObject;
+ /***/ public String invalidPathInfo;
+ /***/ public String objectNotFound;
+ /***/ public String unsupportedOperation;
+}