Merge 2.0.0-m3

This commit is contained in:
Philipp Czora
2018-08-16 13:35:50 +02:00
21 changed files with 677 additions and 369 deletions

View File

@@ -35,26 +35,20 @@ package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.io.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.OutputStream;
import sonia.scm.util.IOUtil;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import sonia.scm.util.IOUtil;
import java.io.IOException;
import java.io.OutputStream;
/**
*

View File

@@ -0,0 +1,154 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.spotter.ContentType;
import com.github.sdorra.spotter.ContentTypes;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.RevisionNotFoundException;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public class ContentResource {
private static final Logger LOG = LoggerFactory.getLogger(ContentResource.class);
private static final int HEAD_BUFFER_SIZE = 1024;
private final RepositoryServiceFactory serviceFactory;
@Inject
public ContentResource(RepositoryServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}
/**
* Returns the content of a file for the given revision in the repository. The content type depends on the file
* content and can be discovered calling <code>HEAD</code> on the same URL. If a programming languge could be
* recognized, this will be given in the header <code>Language</code>.
*
* @param namespace the namespace of the repository
* @param name the name of the repository
* @param revision the revision
* @param path The path of the file
*
*/
@GET
@Path("{revision}/{path: .*}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
StreamingOutput stream = createStreamingOutput(namespace, name, revision, path, repositoryService);
Response.ResponseBuilder responseBuilder = Response.ok(stream);
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
} catch (RepositoryNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
return Response.status(Status.NOT_FOUND).build();
}
}
private StreamingOutput createStreamingOutput(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path, RepositoryService repositoryService) {
return os -> {
try {
repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path);
os.close();
} catch (PathNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
throw new WebApplicationException(Status.NOT_FOUND);
} catch (RepositoryException e) {
LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e);
throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
}
};
}
/**
* Returns the content type and the programming language (if it can be detected) of a file for the given revision in
* the repository. The programming language will be given in the header <code>Language</code>.
*
* @param namespace the namespace of the repository
* @param name the name of the repository
* @param revision the revision
* @param path The path of the file
*
*/
@HEAD
@Path("{revision}/{path: .*}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified name available in the namespace"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response metadata(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
Response.ResponseBuilder responseBuilder = Response.ok();
return createContentHeader(namespace, name, revision, path, repositoryService, responseBuilder);
} catch (RepositoryNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
return Response.status(Status.NOT_FOUND).build();
}
}
private Response createContentHeader(String namespace, String name, String revision, String path, RepositoryService repositoryService, Response.ResponseBuilder responseBuilder) {
try {
appendContentHeader(path, getHead(revision, path, repositoryService), responseBuilder);
} catch (PathNotFoundException e) {
LOG.debug("path '{}' not found in repository {}/{}", path, namespace, name, e);
return Response.status(Status.NOT_FOUND).build();
} catch (RevisionNotFoundException e) {
LOG.debug("revision '{}' not found in repository {}/{}", revision, namespace, name, e);
return Response.status(Status.NOT_FOUND).build();
} catch (IOException | RepositoryException e) {
LOG.info("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
return responseBuilder.build();
}
private void appendContentHeader(String path, byte[] head, Response.ResponseBuilder responseBuilder) {
ContentType contentType = ContentTypes.detect(path, head);
responseBuilder.header("Content-Type", contentType.getRaw());
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
}
private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, RepositoryException {
InputStream stream = repositoryService.getCatCommand().setRevision(revision).getStream(path);
try {
byte[] buffer = new byte[HEAD_BUFFER_SIZE];
int length = stream.read(buffer);
if (length < buffer.length) {
return Arrays.copyOf(buffer, length);
} else {
return buffer;
}
} finally {
IOUtil.close(stream);
}
}
}

View File

@@ -35,6 +35,7 @@ public class RepositoryResource {
private final Provider<BranchRootResource> branchRootResource;
private final Provider<ChangesetRootResource> changesetRootResource;
private final Provider<SourceRootResource> sourceRootResource;
private final Provider<ContentResource> contentResource;
private final Provider<PermissionRootResource> permissionRootResource;
@Inject
@@ -44,7 +45,7 @@ public class RepositoryResource {
Provider<TagRootResource> tagRootResource,
Provider<BranchRootResource> branchRootResource,
Provider<ChangesetRootResource> changesetRootResource,
Provider<SourceRootResource> sourceRootResource, Provider<PermissionRootResource> permissionRootResource) {
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource, Provider<PermissionRootResource> permissionRootResource) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
@@ -53,6 +54,7 @@ public class RepositoryResource {
this.branchRootResource = branchRootResource;
this.changesetRootResource = changesetRootResource;
this.sourceRootResource = sourceRootResource;
this.contentResource = contentResource;
this.permissionRootResource = permissionRootResource;
}
@@ -151,6 +153,11 @@ public class RepositoryResource {
return sourceRootResource.get();
}
@Path("content/")
public ContentResource content() {
return contentResource.get();
}
@Path("permissions/")
public PermissionRootResource permissions() {
return permissionRootResource.get();