From a35e839688ba6d222088fcaad94797026ffa0871 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 26 Nov 2014 13:56:27 +0100 Subject: [PATCH] improve import rest api to handle imports of external repositories via url --- .../resources/RepositoryImportResource.java | 320 +++++++++++++++++- 1 file changed, 315 insertions(+), 5 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index ffa1087f45..f3b470409c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -35,8 +35,12 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Objects; +import com.google.common.base.Strings; import com.google.inject.Inject; +import org.apache.shiro.SecurityUtils; + import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; @@ -46,28 +50,46 @@ import org.slf4j.LoggerFactory; import sonia.scm.NotSupportedFeatuerException; import sonia.scm.Type; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryAllreadyExistExeption; import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryHandler; import sonia.scm.repository.RepositoryManager; -import sonia.scm.util.SecurityUtil; +import sonia.scm.repository.RepositoryType; +import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.RepositoryService; +import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.security.Role; +import sonia.scm.util.IOUtil; + +import static com.google.common.base.Preconditions.*; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.net.URI; + import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; /** * Rest resource for importing repositories. @@ -91,15 +113,90 @@ public class RepositoryImportResource * Constructs a new repository import resource. * * @param manager repository manager + * @param serviceFactory */ @Inject - public RepositoryImportResource(RepositoryManager manager) + public RepositoryImportResource(RepositoryManager manager, + RepositoryServiceFactory serviceFactory) { this.manager = manager; + this.serviceFactory = serviceFactory; } //~--- methods -------------------------------------------------------------- + /** + * Imports a external repository which is accessible via url. The method can + * only be used, if the repository type supports the {@link Command#PULL}. The + * method will return a location header with the url to the imported + * repository. + * + * Status codes: + * + * + * @param uriInfo uri info + * @param type repository type + * @param request request object + * + * @return empty response with location header which points to the imported + * repository + */ + @POST + @Path("{type}/url") + @TypeHint(Repository.class) + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response importFromUrl(@Context UriInfo uriInfo, + @PathParam("type") String type, UrlImportRequest request) + { + SecurityUtils.getSubject().checkRole(Role.ADMIN); + checkNotNull(request, "request is required"); + checkArgument(!Strings.isNullOrEmpty(request.getName()), + "request does not contain name of the repository"); + checkArgument(!Strings.isNullOrEmpty(request.getUrl()), + "request does not contain url of the remote repository"); + + RepositoryHandler handler = manager.getHandler(type); + + if (handler == null) + { + logger.warn("no handler for type {} found", type); + + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + + Type t = handler.getType(); + + checkSupport(t, Command.PULL, request); + + Repository repository = create(type, request.getName()); + RepositoryService service = null; + + try + { + service = serviceFactory.create(repository); + service.getPullCommand().pull(request.getUrl()); + } + catch (RepositoryException ex) + { + handleImportFailure(ex, repository); + } + catch (IOException ex) + { + handleImportFailure(ex, repository); + } + finally + { + IOUtil.close(service); + } + + return buildResponse(uriInfo, repository); + } + /** * Imports repositories of the given type from the configured repository * directory. This method requires admin privileges.
@@ -122,7 +219,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response importRepositories(@PathParam("type") String type) { - SecurityUtil.assertIsAdmin(); + SecurityUtils.getSubject().checkRole(Role.ADMIN); List repositories = new ArrayList(); RepositoryHandler handler = manager.getHandler(type); @@ -180,7 +277,9 @@ public class RepositoryImportResource //~--- get methods ---------------------------------------------------------- /** - * Returns a list of repository types, which support the import feature. + * Returns a list of repository types, which support the directory import + * feature. + * * This method requires admin privileges.
*
* Status codes: @@ -188,6 +287,7 @@ public class RepositoryImportResource *
  • 200 ok, successful
  • *
  • 400 bad request, the import feature is not * supported by this type of repositories.
  • + *
  • 409 conflict, a repository with the name already exists.
  • *
  • 500 internal server error
  • * * @@ -198,7 +298,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getImportableTypes() { - SecurityUtil.assertIsAdmin(); + SecurityUtils.getSubject().checkRole(Role.ADMIN); List types = new ArrayList(); Collection handlerTypes = manager.getTypes(); @@ -242,8 +342,218 @@ public class RepositoryImportResource //J+ } + //~--- methods -------------------------------------------------------------- + + /** + * Build rest response for repository. + * + * + * @param uriInfo uri info + * @param repository imported repository + * + * @return rest response + */ + private Response buildResponse(UriInfo uriInfo, Repository repository) + { + URI location = uriInfo.getBaseUriBuilder().path( + RepositoryResource.class).path(repository.getId()).build(); + + return Response.created(location).build(); + } + + /** + * Check repository type for support for the given command. + * + * + * @param type repository type + * @param cmd command + * @param request request object + */ + private void checkSupport(Type type, Command cmd, Object request) + { + if (!(type instanceof RepositoryType)) + { + logger.warn("type {} is not a repository type", type.getName()); + + throw new WebApplicationException(Response.Status.BAD_REQUEST); + } + + Set cmds = ((RepositoryType) type).getSupportedCommands(); + + if (!cmds.contains(cmd)) + { + logger.warn("type {} does not support this type of import: {}", + type.getName(), request); + + throw new WebApplicationException(Response.Status.BAD_REQUEST); + } + } + + /** + * Creates a new repository with the given name and type. + * + * + * @param type repository type + * @param name repository name + * + * @return newly created repository + */ + private Repository create(String type, String name) + { + Repository repository = null; + + try + { + repository = new Repository(null, type, name); + manager.create(repository); + } + catch (RepositoryAllreadyExistExeption ex) + { + logger.warn("a {} repository with the name {} already exists", ex); + + throw new WebApplicationException(Response.Status.CONFLICT); + } + catch (RepositoryException ex) + { + handleGenericCreationFailure(ex, type, name); + } + catch (IOException ex) + { + handleGenericCreationFailure(ex, type, name); + } + + return repository; + } + + /** + * Handle creation failures. + * + * + * @param ex exception + * @param type repository type + * @param name name of the repository + */ + private void handleGenericCreationFailure(Exception ex, String type, + String name) + { + logger.error(String.format("could not create repository {} with type {}", + type, name), ex); + + throw new WebApplicationException(ex); + } + + /** + * Handle import failures. + * + * + * @param ex exception + * @param repository repository + */ + private void handleImportFailure(Exception ex, Repository repository) + { + logger.error("import for repository failed, delete repository", ex); + + try + { + manager.delete(repository); + } + catch (IOException e) + { + logger.error("can not delete repository", e); + } + catch (RepositoryException e) + { + logger.error("can not delete repository", e); + } + + throw new WebApplicationException(ex, + Response.Status.INTERNAL_SERVER_ERROR); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Request for importing external repositories which are accessible via url. + */ + @XmlRootElement(name = "import") + @XmlAccessorType(XmlAccessType.FIELD) + public static class UrlImportRequest + { + + /** + * Constructs ... + * + */ + public UrlImportRequest() {} + + /** + * Constructs a new {@link UrlImportRequest} + * + * + * @param name name of the repository + * @param url external url of the repository + */ + public UrlImportRequest(String name, String url) + { + this.name = name; + this.url = url; + } + + //~--- methods ------------------------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("name", name) + .add("url", url) + .toString(); + //J+ + } + + //~--- get methods -------------------------------------------------------- + + /** + * Returns name of the repository. + * + * + * @return name of the repository + */ + public String getName() + { + return name; + } + + /** + * Returns external url of the repository. + * + * + * @return external url of the repository + */ + public String getUrl() + { + return url; + } + + //~--- fields ------------------------------------------------------------- + + /** name of the repository */ + private String name; + + /** external url of the repository */ + private String url; + } + + //~--- fields --------------------------------------------------------------- /** repository manager */ private final RepositoryManager manager; + + /** repository service factory */ + private final RepositoryServiceFactory serviceFactory; }