Merge branch 2.0.0-m3 into feature/global_config_v2_endpoint

This commit is contained in:
Michael Behlendorf
2018-07-13 11:13:43 +02:00
71 changed files with 1990 additions and 955 deletions

View File

@@ -0,0 +1,68 @@
package sonia.scm;
import com.github.sdorra.ssp.PermissionCheck;
import sonia.scm.util.AssertUtil;
import java.util.function.Function;
import java.util.function.Supplier;
public class ManagerDaoAdapter<T extends ModelObject, E extends Exception> {
private final GenericDAO<T> dao;
private final Function<T, E> notFoundException;
private final Function<T, E> alreadyExistsException;
public ManagerDaoAdapter(GenericDAO<T> dao, Function<T, E> notFoundException, Function<T, E> alreadyExistsException) {
this.dao = dao;
this.notFoundException = notFoundException;
this.alreadyExistsException = alreadyExistsException;
}
public void modify(T object, Function<T, PermissionCheck> permissionCheck, AroundHandler<T, E> beforeUpdate, AroundHandler<T, E> afterUpdate) throws E {
T notModified = dao.get(object.getId());
if (notModified != null) {
permissionCheck.apply(notModified).check();
AssertUtil.assertIsValid(object);
beforeUpdate.handle(notModified);
object.setLastModified(System.currentTimeMillis());
object.setCreationDate(notModified.getCreationDate());
dao.modify(object);
afterUpdate.handle(notModified);
} else {
throw notFoundException.apply(object);
}
}
public T create(T newObject, Supplier<PermissionCheck> permissionCheck, AroundHandler<T, E> beforeCreate, AroundHandler<T, E> afterCreate) throws E {
permissionCheck.get().check();
AssertUtil.assertIsValid(newObject);
if (dao.contains(newObject)) {
throw alreadyExistsException.apply(newObject);
}
newObject.setCreationDate(System.currentTimeMillis());
beforeCreate.handle(newObject);
dao.add(newObject);
afterCreate.handle(newObject);
return newObject;
}
public void delete(T toDelete, Supplier<PermissionCheck> permissionCheck, AroundHandler<T, E> beforeDelete, AroundHandler<T, E> afterDelete) throws E {
permissionCheck.get().check();
if (dao.contains(toDelete)) {
beforeDelete.handle(toDelete);
dao.delete(toDelete);
afterDelete.handle(toDelete);
} else {
throw notFoundException.apply(toDelete);
}
}
@FunctionalInterface
public interface AroundHandler<T extends ModelObject, E extends Exception> {
void handle(T notModified) throws E;
}
}

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.rest;
import sonia.scm.repository.RepositoryAlreadyExistsException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class RepositoryAlreadyExistsExceptionMapper implements ExceptionMapper<RepositoryAlreadyExistsException> {
@Override
public Response toResponse(RepositoryAlreadyExistsException exception) {
return Response.status(Status.CONFLICT).build();
}
}

View File

@@ -48,13 +48,13 @@ import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
@@ -76,17 +76,15 @@ public abstract class AbstractManagerResource<T extends ModelObject,
private static final Logger logger =
LoggerFactory.getLogger(AbstractManagerResource.class);
//~--- constructors ---------------------------------------------------------
protected final Manager<T, E> manager;
private final Class<T> type;
/**
* Constructs ...
*
*
* @param manager
*/
public AbstractManagerResource(Manager<T, E> manager)
{
protected int cacheMaxAge = 0;
protected boolean disableCache = false;
public AbstractManagerResource(Manager<T, E> manager, Class<T> type) {
this.manager = manager;
this.type = type;
}
//~--- methods --------------------------------------------------------------
@@ -526,45 +524,25 @@ public abstract class AbstractManagerResource<T extends ModelObject,
return builder.build();
}
/**
* Method description
*
*
* @param sortby
* @param desc
*
* @return
*/
@SuppressWarnings("unchecked")
private Comparator<T> createComparator(String sortby, boolean desc)
private Comparator<T> createComparator(String sortBy, boolean desc)
{
checkSortByField(sortBy);
Comparator comparator;
if (desc)
{
comparator = new BeanReverseComparator(sortby);
comparator = new BeanReverseComparator(sortBy);
}
else
{
comparator = new BeanComparator(sortby);
comparator = new BeanComparator(sortBy);
}
return comparator;
}
/**
* Method description
*
*
*
* @param sortby
* @param desc
* @param start
* @param limit
*
* @return
*/
private Collection<T> fetchItems(String sortby, boolean desc, int start,
private Collection<T> fetchItems(String sortBy, boolean desc, int start,
int limit)
{
AssertUtil.assertPositive(start);
@@ -573,18 +551,18 @@ public abstract class AbstractManagerResource<T extends ModelObject,
if (limit > 0)
{
if (Util.isEmpty(sortby))
if (Util.isEmpty(sortBy))
{
// replace with something useful
sortby = "id";
sortBy = "id";
}
items = manager.getAll(createComparator(sortby, desc), start, limit);
items = manager.getAll(createComparator(sortBy, desc), start, limit);
}
else if (Util.isNotEmpty(sortby))
else if (Util.isNotEmpty(sortBy))
{
items = manager.getAll(createComparator(sortby, desc));
items = manager.getAll(createComparator(sortBy, desc));
}
else
{
@@ -594,17 +572,32 @@ public abstract class AbstractManagerResource<T extends ModelObject,
return items;
}
protected PageResult<T> fetchPage(String sortby, boolean desc, int pageNumber,
// We have to handle IntrospectionException here, because it's a checked exception
// It shouldn't occur really - so creating a new unchecked exception would be over-engineered here
@SuppressWarnings("squid:S00112")
private void checkSortByField(String sortBy) {
try {
BeanInfo info = Introspector.getBeanInfo(type);
PropertyDescriptor[] pds = info.getPropertyDescriptors();
if (Arrays.stream(pds).noneMatch(p -> p.getName().equals(sortBy))) {
throw new IllegalArgumentException("sortBy");
}
} catch (IntrospectionException e) {
throw new RuntimeException("error introspecting model type " + type.getName(), e);
}
}
protected PageResult<T> fetchPage(String sortBy, boolean desc, int pageNumber,
int pageSize) {
AssertUtil.assertPositive(pageNumber);
AssertUtil.assertPositive(pageSize);
if (Util.isEmpty(sortby)) {
if (Util.isEmpty(sortBy)) {
// replace with something useful
sortby = "id";
sortBy = "id";
}
return manager.getPage(createComparator(sortby, desc), pageNumber, pageSize);
return manager.getPage(createComparator(sortBy, desc), pageNumber, pageSize);
}
//~--- get methods ----------------------------------------------------------
@@ -676,16 +669,4 @@ public abstract class AbstractManagerResource<T extends ModelObject,
return super.compare(o1, o2) * -1;
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
protected int cacheMaxAge = 0;
/** Field description */
protected boolean disableCache = false;
/** Field description */
protected Manager<T, E> manager;
}

View File

@@ -41,34 +41,17 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import sonia.scm.group.Group;
import sonia.scm.group.GroupException;
import sonia.scm.group.GroupManager;
import sonia.scm.security.Role;
//~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
//~--- JDK imports ------------------------------------------------------------
/**
* RESTful Web Service Resource to manage groups and their members.
@@ -97,7 +80,7 @@ public class GroupResource
@Inject
public GroupResource(GroupManager groupManager)
{
super(groupManager);
super(groupManager, Group.class);
}
//~--- methods --------------------------------------------------------------

View File

@@ -40,24 +40,17 @@ import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.inject.Inject;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatuerException;
import sonia.scm.Type;
import sonia.scm.api.rest.RestActionUploadResult;
import sonia.scm.repository.AdvancedImportHandler;
import sonia.scm.repository.ImportHandler;
import sonia.scm.repository.ImportResult;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryAlreadyExistsException;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryHandler;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryType;
import sonia.scm.repository.*;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
@@ -65,45 +58,24 @@ import sonia.scm.repository.api.UnbundleCommandBuilder;
import sonia.scm.security.Role;
import sonia.scm.util.IOUtil;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.DefaultValue;
import javax.ws.rs.FormParam;
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.QueryParam;
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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
//~--- JDK imports ------------------------------------------------------------
/**
* Rest resource for importing repositories.
@@ -564,10 +536,6 @@ public class RepositoryImportResource
{
handleGenericCreationFailure(ex, type, name);
}
catch (IOException ex)
{
handleGenericCreationFailure(ex, type, name);
}
return repository;
}
@@ -716,10 +684,6 @@ public class RepositoryImportResource
{
manager.delete(repository);
}
catch (IOException e)
{
logger.error("can not delete repository", e);
}
catch (RepositoryException e)
{
logger.error("can not delete repository", e);

View File

@@ -46,52 +46,16 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.BlameResult;
import sonia.scm.repository.Branches;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HealthChecker;
import sonia.scm.repository.Permission;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.Tags;
import sonia.scm.repository.api.BlameCommandBuilder;
import sonia.scm.repository.api.BrowseCommandBuilder;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.CommandNotSupportedException;
import sonia.scm.repository.api.DiffCommandBuilder;
import sonia.scm.repository.api.DiffFormat;
import sonia.scm.repository.api.LogCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.*;
import sonia.scm.repository.api.*;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -120,19 +84,15 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
/**
* Constructs ...
*
*
* @param configuration
* @param repositoryManager
* @param repositoryManager
* @param servicefactory
* @param healthChecker
*/
@Inject
public RepositoryResource(ScmConfiguration configuration,
RepositoryManager repositoryManager,
public RepositoryResource(RepositoryManager repositoryManager,
RepositoryServiceFactory servicefactory, HealthChecker healthChecker)
{
super(repositoryManager);
this.configuration = configuration;
super(repositoryManager, Repository.class);
this.repositoryManager = repositoryManager;
this.servicefactory = servicefactory;
this.healthChecker = healthChecker;
@@ -210,9 +170,9 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
logger.warn("delete not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (RepositoryException | IOException ex)
catch (RepositoryException ex)
{
logger.error("error during create", ex);
logger.error("error during delete", ex);
response = createErrorResponse(ex);
}
}
@@ -1103,9 +1063,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final HealthChecker healthChecker;

View File

@@ -41,10 +41,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.security.Role;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
@@ -52,26 +50,11 @@ import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
//~--- JDK imports ------------------------------------------------------------
/**
* RESTful Web Service Resource to manage users.
@@ -101,7 +84,7 @@ public class UserResource extends AbstractManagerResource<User, UserException>
@Inject
public UserResource(UserManager userManager, PasswordService passwordService)
{
super(userManager);
super(userManager, User.class);
this.passwordService = passwordService;
}

View File

@@ -9,7 +9,7 @@ import java.time.Instant;
abstract class BaseMapper<T extends ModelObject, D extends HalRepresentation> {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract D map(T user);
public abstract D map(T modelObject);
Instant mapTime(Long epochMilli) {
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class BranchCollectionResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
public class BranchRootResource {
private final Provider<BranchCollectionResource> branchCollectionResource;
@Inject
public BranchRootResource(Provider<BranchCollectionResource> branchCollectionResource) {
this.branchCollectionResource = branchCollectionResource;
}
@Path("")
public BranchCollectionResource getBranchCollectionResource() {
return branchCollectionResource.get();
}
}

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class ChangesetCollectionResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
public class ChangesetRootResource {
private final Provider<ChangesetCollectionResource> changesetCollectionResource;
@Inject
public ChangesetRootResource(Provider<ChangesetCollectionResource> changesetCollectionResource) {
this.changesetCollectionResource = changesetCollectionResource;
}
@Path("")
public ChangesetCollectionResource getChangesetCollectionResource() {
return changesetCollectionResource.get();
}
}

View File

@@ -8,7 +8,6 @@ import sonia.scm.api.rest.resources.AbstractManagerResource;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.function.Function;
@@ -17,50 +16,23 @@ import java.util.function.Supplier;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
/**
* Adapter from resource http endpoints to managers.
* Adapter from resource http endpoints to managers, for Collection resources (e.g. {@code /users}).
*
* Provides common CRUD operations and DTO to Model Object mapping to keep Resources more DRY.
*
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*
* @see SingleResourceManagerAdapter
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
ResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager) {
super(manager);
}
/**
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
MODEL_OBJECT modelObject = manager.get(id);
if (modelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
DTO dto = mapToDto.apply(modelObject);
return Response.ok(dto).build();
}
/**
* Update the model object for the given id according to the given function and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
MODEL_OBJECT existingModelObject = manager.get(id);
if (existingModelObject == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject);
if (!id.equals(changedModelObject.getId())) {
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
}
return update(id, changedModelObject);
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
super(manager, type);
}
/**
@@ -76,13 +48,13 @@ class ResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
* Creates a model object for the given dto and returns a corresponding http response.
* This handles all corner cases, eg. no conflicts or missing privileges.
*/
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws EXCEPTION {
if (dto == null) {
return Response.status(BAD_REQUEST).build();
}
MODEL_OBJECT modelObject = modelObjectSupplier.get();
manager.create(modelObject);
return Response.created(URI.create(uriCreator.apply(modelObject))).build();
MODEL_OBJECT created = manager.create(modelObject);
return Response.created(URI.create(uriCreator.apply(created))).build();
}
@Override

View File

@@ -19,23 +19,24 @@ public class GroupCollectionResource {
private final GroupCollectionToDtoMapper groupCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final ResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
@Inject
public GroupCollectionResource(GroupManager manager, GroupDtoToGroupMapper dtoToGroupMapper, GroupCollectionToDtoMapper groupCollectionToDtoMapper, ResourceLinks resourceLinks) {
this.dtoToGroupMapper = dtoToGroupMapper;
this.groupCollectionToDtoMapper = groupCollectionToDtoMapper;
this.resourceLinks = resourceLinks;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager, Group.class);
}
/**
* Returns all groups for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
*
*
* <strong>Note:</strong> This method requires "group" privilege.
* @param page the number of the requested page
*
* @param page the number of the requested page
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
* @param sortBy sort parameter
* @param sortBy sort parameter (if empty - undefined sorting)
* @param desc sort direction desc or aesc
*/
@GET
@@ -44,6 +45,7 @@ public class GroupCollectionResource {
@TypeHint(GroupDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "\"sortBy\" field unknown"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")

View File

@@ -9,27 +9,21 @@ import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class GroupResource {
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
private final GroupDtoToGroupMapper dtoToGroupMapper;
private final ResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
private final IdResourceManagerAdapter<Group, GroupDto, GroupException> adapter;
@Inject
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
GroupDtoToGroupMapper groupDtoToGroupMapper) {
this.groupToGroupDtoMapper = groupToGroupDtoMapper;
this.dtoToGroupMapper = groupDtoToGroupMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager, Group.class);
}
/**

View File

@@ -0,0 +1,11 @@
package sonia.scm.api.v2.resources;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class HealthCheckFailureDto {
private String description;
private String summary;
private String url;
}

View File

@@ -0,0 +1,66 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Facade for {@link SingleResourceManagerAdapter} and {@link CollectionResourceManagerAdapter}
* for model objects handled by a single id.
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> {
private final Manager<MODEL_OBJECT, EXCEPTION> manager;
private final SingleResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> singleAdapter;
private final CollectionResourceManagerAdapter<MODEL_OBJECT, DTO, EXCEPTION> collectionAdapter;
IdResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
this.manager = manager;
singleAdapter = new SingleResourceManagerAdapter<>(manager, type);
collectionAdapter = new CollectionResourceManagerAdapter<>(manager, type);
}
Response get(String id, Function<MODEL_OBJECT, DTO> mapToDto) {
return singleAdapter.get(loadBy(id), mapToDto);
}
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) {
return singleAdapter.update(
loadBy(id),
applyChanges,
idStaysTheSame(id)
);
}
public Response getAll(int page, int pageSize, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
}
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) throws IOException, EXCEPTION {
return collectionAdapter.create(dto, modelObjectSupplier, uriCreator);
}
public Response delete(String id) {
return singleAdapter.delete(id);
}
private Supplier<Optional<MODEL_OBJECT>> loadBy(String id) {
return () -> Optional.ofNullable(manager.get(id));
}
private Predicate<MODEL_OBJECT> idStaysTheSame(String id) {
return changed -> changed.getId().equals(id);
}
}

View File

@@ -18,6 +18,9 @@ public class MapperModule extends AbstractModule {
bind(ScmConfigurationToGlobalConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToGlobalConfigDtoMapper.class).getClass());
bind(GlobalConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(GlobalConfigDtoToScmConfigurationMapper.class).getClass());
bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass());
bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass());
bind(UriInfoStore.class).in(ServletScopes.REQUEST);
}
}

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class PermissionCollectionResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
public class PermissionRootResource {
private final Provider<PermissionCollectionResource> permissionCollectionResource;
@Inject
public PermissionRootResource(Provider<PermissionCollectionResource> permissionCollectionResource) {
this.permissionCollectionResource = permissionCollectionResource;
}
@Path("")
public PermissionCollectionResource getPermissionCollectionResource() {
return permissionCollectionResource.get();
}
}

View File

@@ -0,0 +1,84 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.*;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class RepositoryCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10;
private final CollectionResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
private final RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper;
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
private final ResourceLinks resourceLinks;
@Inject
public RepositoryCollectionResource(RepositoryManager manager, RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper, RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, ResourceLinks resourceLinks) {
this.adapter = new CollectionResourceManagerAdapter<>(manager, Repository.class);
this.repositoryCollectionToDtoMapper = repositoryCollectionToDtoMapper;
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.resourceLinks = resourceLinks;
}
/**
* Returns all repositories for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
*
* <strong>Note:</strong> This method requires "repository" privilege.
*
* @param page the number of the requested page
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
* @param sortBy sort parameter (if empty - undefined sorting)
* @param desc sort direction desc or asc
*/
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY_COLLECTION)
@TypeHint(RepositoryDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
return adapter.getAll(page, pageSize, sortBy, desc,
pageResult -> repositoryCollectionToDtoMapper.map(page, pageSize, pageResult));
}
/**
* Creates a new repository.
*
* <strong>Note:</strong> This method requires "repository" privilege. The namespace of the given repository will
* be ignored and set by the configured namespace strategy.
*
* @param repositoryDto The repository to be created.
* @return A response with the link to the new repository (if created successfully).
*/
@POST
@Path("")
@Consumes(VndMediaType.REPOSITORY)
@StatusCodes({
@ResponseCode(code = 201, condition = "create success"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
@ResponseCode(code = 409, condition = "conflict, a repository with this name already exists"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
public Response create(RepositoryDto repositoryDto) throws RepositoryException {
return adapter.create(repositoryDto,
() -> dtoToRepositoryMapper.map(repositoryDto, null),
repository -> resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
}
}

View File

@@ -0,0 +1,34 @@
package sonia.scm.api.v2.resources;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import javax.inject.Inject;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
public class RepositoryCollectionToDtoMapper extends BasicCollectionToDtoMapper<Repository, RepositoryDto> {
private final ResourceLinks resourceLinks;
@Inject
public RepositoryCollectionToDtoMapper(RepositoryToRepositoryDtoMapper repositoryToDtoMapper, ResourceLinks resourceLinks) {
super("repositories", repositoryToDtoMapper);
this.resourceLinks = resourceLinks;
}
@Override
String createCreateLink() {
return resourceLinks.repositoryCollection().create();
}
@Override
String createSelfLink() {
return resourceLinks.repositoryCollection().self();
}
@Override
boolean isCreatePermitted() {
return RepositoryPermissions.create().isPermitted();
}
}

View File

@@ -0,0 +1,33 @@
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@Getter @Setter
public class RepositoryDto extends HalRepresentation {
private String contact;
private Instant creationDate;
private String description;
private List<HealthCheckFailureDto> healthCheckFailures;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
private String namespace;
private String name;
private boolean archived = false;
private String type;
protected Map<String, String> properties;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
protected HalRepresentation add(Links links) {
return super.add(links);
}
}

View File

@@ -0,0 +1,21 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.*;
import sonia.scm.repository.Repository;
@Mapper
public abstract class RepositoryDtoToRepositoryMapper {
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "lastModified", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "publicReadable", ignore = true)
@Mapping(target = "healthCheckFailures", ignore = true)
@Mapping(target = "permissions", ignore = true)
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
@AfterMapping
void updateId(@MappingTarget Repository repository, @Context String id) {
repository.setId(id);
}
}

View File

@@ -0,0 +1,158 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class RepositoryResource {
private final RepositoryToRepositoryDtoMapper repositoryToDtoMapper;
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
private final RepositoryManager manager;
private final SingleResourceManagerAdapter<Repository, RepositoryDto, RepositoryException> adapter;
private final Provider<TagRootResource> tagRootResource;
private final Provider<BranchRootResource> branchRootResource;
private final Provider<ChangesetRootResource> changesetRootResource;
private final Provider<SourceRootResource> sourceRootResource;
private final Provider<PermissionRootResource> permissionRootResource;
@Inject
public RepositoryResource(
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
Provider<TagRootResource> tagRootResource,
Provider<BranchRootResource> branchRootResource,
Provider<ChangesetRootResource> changesetRootResource,
Provider<SourceRootResource> sourceRootResource, Provider<PermissionRootResource> permissionRootResource) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
this.tagRootResource = tagRootResource;
this.branchRootResource = branchRootResource;
this.changesetRootResource = changesetRootResource;
this.sourceRootResource = sourceRootResource;
this.permissionRootResource = permissionRootResource;
}
/**
* Returns a repository.
*
* <strong>Note:</strong> This method requires "repository" privilege.
*
* @param namespace the namespace of the repository
* @param name the name of the repository
*
*/
@GET
@Path("")
@Produces(VndMediaType.REPOSITORY)
@TypeHint(RepositoryDto.class)
@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) {
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
}
/**
* Deletes a repository.
*
* <strong>Note:</strong> This method requires "repository" privilege.
*
* @param namespace the namespace of the repository to delete
* @param name the name of the repository to delete
*
*/
@DELETE
@Path("")
@StatusCodes({
@ResponseCode(code = 204, condition = "delete success or nothing to delete"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response delete(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return adapter.delete(loadBy(namespace, name));
}
/**
* Modifies the given repository.
*
* <strong>Note:</strong> This method requires "repository" privilege.
*
* @param namespace the namespace of the repository to be modified
* @param name the name of the repository to be modified
* @param repositoryDto repository object to modify
*/
@PUT
@Path("")
@Consumes(VndMediaType.REPOSITORY)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of namespace or name"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository\" privilege"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified namespace and name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@PathParam("namespace") String namespace, @PathParam("name") String name, RepositoryDto repositoryDto) {
return adapter.update(
loadBy(namespace, name),
existing -> dtoToRepositoryMapper.map(repositoryDto, existing.getId()),
nameAndNamespaceStaysTheSame(namespace, name)
);
}
@Path("tags/")
public TagRootResource tags() {
return tagRootResource.get();
}
@Path("branches/")
public BranchRootResource branches() {
return branchRootResource.get();
}
@Path("changesets/")
public ChangesetRootResource changesets() {
return changesetRootResource.get();
}
@Path("sources/")
public SourceRootResource sources() {
return sourceRootResource.get();
}
@Path("permissions/")
public PermissionRootResource permissions() {
return permissionRootResource.get();
}
private Supplier<Optional<Repository>> loadBy(String namespace, String name) {
return () -> manager.getByNamespace(namespace, name);
}
private Predicate<Repository> nameAndNamespaceStaysTheSame(String namespace, String name) {
return changed -> changed.getName().equals(name) && changed.getNamespace().equals(namespace);
}
}

View File

@@ -0,0 +1,32 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
/**
* RESTful Web Service Resource to manage repositories.
*/
@Path(RepositoryRootResource.REPOSITORIES_PATH_V2)
public class RepositoryRootResource {
static final String REPOSITORIES_PATH_V2 = "v2/repositories/";
private final Provider<RepositoryResource> repositoryResource;
private final Provider<RepositoryCollectionResource> repositoryCollectionResource;
@Inject
public RepositoryRootResource(Provider<RepositoryResource> repositoryResource, Provider<RepositoryCollectionResource> repositoryCollectionResource) {
this.repositoryResource = repositoryResource;
this.repositoryCollectionResource = repositoryCollectionResource;
}
@Path("{namespace}/{name}")
public RepositoryResource getRepositoryResource() {
return repositoryResource.get();
}
@Path("")
public RepositoryCollectionResource getRepositoryCollectionResource() {
return repositoryCollectionResource.get();
}
}

View File

@@ -0,0 +1,41 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Repository, RepositoryDto> {
@Inject
private ResourceLinks resourceLinks;
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
@AfterMapping
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName()));
if (RepositoryPermissions.delete(repository).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
}
if (RepositoryPermissions.modify(repository).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
linksBuilder.single(link("permissions", resourceLinks.permissionCollection().self(target.getNamespace(), target.getName())));
}
linksBuilder.single(link("tags", resourceLinks.tagCollection().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("changesets", resourceLinks.changesetCollection().self(target.getNamespace(), target.getName())));
linksBuilder.single(link("sources", resourceLinks.sourceCollection().self(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}
}

View File

@@ -20,7 +20,7 @@ class ResourceLinks {
static class GroupLinks {
private final LinkBuilder groupLinkBuilder;
private GroupLinks(UriInfo uriInfo) {
GroupLinks(UriInfo uriInfo) {
groupLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupResource.class);
}
@@ -44,7 +44,7 @@ class ResourceLinks {
static class GroupCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
private GroupCollectionLinks(UriInfo uriInfo) {
GroupCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupCollectionResource.class);
}
@@ -64,7 +64,7 @@ class ResourceLinks {
static class UserLinks {
private final LinkBuilder userLinkBuilder;
private UserLinks(UriInfo uriInfo) {
UserLinks(UriInfo uriInfo) {
userLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserResource.class);
}
@@ -88,7 +88,7 @@ class ResourceLinks {
static class UserCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
private UserCollectionLinks(UriInfo uriInfo) {
UserCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserCollectionResource.class);
}
@@ -108,7 +108,7 @@ class ResourceLinks {
static class GlobalConfigLinks {
private final LinkBuilder globalConfigLinkBuilder;
private GlobalConfigLinks(UriInfo uriInfo) {
GlobalConfigLinks(UriInfo uriInfo) {
globalConfigLinkBuilder = new LinkBuilder(uriInfo, GlobalConfigResource.class);
}
@@ -120,4 +120,128 @@ class ResourceLinks {
return globalConfigLinkBuilder.method("update").parameters().href();
}
}
public RepositoryLinks repository() {
return new RepositoryLinks(uriInfoStore.get());
}
static class RepositoryLinks {
private final LinkBuilder repositoryLinkBuilder;
RepositoryLinks(UriInfo uriInfo) {
repositoryLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class);
}
String self(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("get").parameters().href();
}
String delete(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("delete").parameters().href();
}
String update(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("update").parameters().href();
}
}
RepositoryCollectionLinks repositoryCollection() {
return new RepositoryCollectionLinks(uriInfoStore.get());
}
static class RepositoryCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
RepositoryCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryCollectionResource.class);
}
String self() {
return collectionLinkBuilder.method("getRepositoryCollectionResource").parameters().method("getAll").parameters().href();
}
String create() {
return collectionLinkBuilder.method("getRepositoryCollectionResource").parameters().method("create").parameters().href();
}
}
public TagCollectionLinks tagCollection() {
return new TagCollectionLinks(uriInfoStore.get());
}
static class TagCollectionLinks {
private final LinkBuilder tagLinkBuilder;
TagCollectionLinks(UriInfo uriInfo) {
tagLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, TagRootResource.class, TagCollectionResource.class);
}
String self(String namespace, String name) {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("getTagCollectionResource").parameters().method("getAll").parameters().href();
}
}
public BranchCollectionLinks branchCollection() {
return new BranchCollectionLinks(uriInfoStore.get());
}
static class BranchCollectionLinks {
private final LinkBuilder branchLinkBuilder;
BranchCollectionLinks(UriInfo uriInfo) {
branchLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, BranchRootResource.class, BranchCollectionResource.class);
}
String self(String namespace, String name) {
return branchLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("branches").parameters().method("getBranchCollectionResource").parameters().method("getAll").parameters().href();
}
}
public ChangesetCollectionLinks changesetCollection() {
return new ChangesetCollectionLinks(uriInfoStore.get());
}
static class ChangesetCollectionLinks {
private final LinkBuilder changesetLinkBuilder;
ChangesetCollectionLinks(UriInfo uriInfo) {
changesetLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, ChangesetRootResource.class, ChangesetCollectionResource.class);
}
String self(String namespace, String name) {
return changesetLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("changesets").parameters().method("getChangesetCollectionResource").parameters().method("getAll").parameters().href();
}
}
public SourceCollectionLinks sourceCollection() {
return new SourceCollectionLinks(uriInfoStore.get());
}
static class SourceCollectionLinks {
private final LinkBuilder sourceLinkBuilder;
SourceCollectionLinks(UriInfo uriInfo) {
sourceLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, SourceRootResource.class, SourceCollectionResource.class);
}
String self(String namespace, String name) {
return sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("sources").parameters().method("getSourceCollectionResource").parameters().method("getAll").parameters().href();
}
}
public PermissionCollectionLinks permissionCollection() {
return new PermissionCollectionLinks(uriInfoStore.get());
}
static class PermissionCollectionLinks {
private final LinkBuilder permissionLinkBuilder;
PermissionCollectionLinks(UriInfo uriInfo) {
permissionLinkBuilder = new LinkBuilder(uriInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class, PermissionCollectionResource.class);
}
String self(String namespace, String name) {
return permissionLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("permissions").parameters().method("getPermissionCollectionResource").parameters().method("getAll").parameters().href();
}
}
}

View File

@@ -0,0 +1,87 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.api.rest.resources.AbstractManagerResource;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
/**
* Adapter from resource http endpoints to managers, for Single resources (e.g. {@code /user/name}).
*
* Provides common CRUD operations and DTO to Model Object mapping to keep Resources more DRY.
*
* @param <MODEL_OBJECT> The type of the model object, eg. {@link sonia.scm.user.User}.
* @param <DTO> The corresponding transport object, eg. {@link UserDto}.
* @param <EXCEPTION> The exception type for the model object, eg. {@link sonia.scm.user.UserException}.
*
* @see CollectionResourceManagerAdapter
*/
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
DTO extends HalRepresentation,
EXCEPTION extends Exception> extends AbstractManagerResource<MODEL_OBJECT, EXCEPTION> {
SingleResourceManagerAdapter(Manager<MODEL_OBJECT, EXCEPTION> manager, Class<MODEL_OBJECT> type) {
super(manager, type);
}
/**
* Reads the model object for the given id, transforms it to a dto and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
Response get(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, DTO> mapToDto) {
return reader.get()
.map(mapToDto)
.map(Response::ok)
.map(Response.ResponseBuilder::build)
.orElse(Response.status(Response.Status.NOT_FOUND).build());
}
/**
* Update the model object for the given id according to the given function and returns a corresponding http response.
* This handles all corner cases, eg. no matching object for the id or missing privileges.
*/
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey) {
Optional<MODEL_OBJECT> existingModelObject = reader.get();
if (!existingModelObject.isPresent()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
MODEL_OBJECT changedModelObject = applyChanges.apply(existingModelObject.get());
if (!hasSameKey.test(changedModelObject)) {
return Response.status(BAD_REQUEST).entity("illegal change of id").build();
}
return update(getId(existingModelObject.get()), changedModelObject);
}
public Response delete(Supplier<Optional<MODEL_OBJECT>> reader) {
return reader.get()
.map(MODEL_OBJECT::getId)
.map(this::delete)
.orElse(null);
}
@Override
protected GenericEntity<Collection<MODEL_OBJECT>> createGenericEntity(Collection<MODEL_OBJECT> modelObjects) {
throw new UnsupportedOperationException();
}
@Override
protected String getId(MODEL_OBJECT item) {
return item.getId();
}
@Override
protected String getPathPart() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class SourceCollectionResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
public class SourceRootResource {
private final Provider<SourceCollectionResource> sourceCollectionResource;
@Inject
public SourceRootResource(Provider<SourceCollectionResource> sourceCollectionResource) {
this.sourceCollectionResource = sourceCollectionResource;
}
@Path("")
public SourceCollectionResource getSourceCollectionResource() {
return sourceCollectionResource.get();
}
}

View File

@@ -0,0 +1,18 @@
package sonia.scm.api.v2.resources;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class TagCollectionResource {
@GET
@Path("")
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("10") @QueryParam("pageSize") int pageSize,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.Path;
public class TagRootResource {
private final Provider<TagCollectionResource> tagCollectionResource;
@Inject
public TagRootResource(Provider<TagCollectionResource> tagCollectionResource) {
this.tagCollectionResource = tagCollectionResource;
}
@Path("")
public TagCollectionResource getTagCollectionResource() {
return tagCollectionResource.get();
}
}

View File

@@ -18,14 +18,14 @@ public class UserCollectionResource {
private final UserCollectionToDtoMapper userCollectionToDtoMapper;
private final ResourceLinks resourceLinks;
private final ResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
UserCollectionToDtoMapper userCollectionToDtoMapper, ResourceLinks resourceLinks) {
this.dtoToUserMapper = dtoToUserMapper;
this.userCollectionToDtoMapper = userCollectionToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
this.resourceLinks = resourceLinks;
}
@@ -33,9 +33,10 @@ public class UserCollectionResource {
* Returns all users for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}).
*
* <strong>Note:</strong> This method requires "user" privilege.
* @param page the number of the requested page
*
* @param page the number of the requested page
* @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE})
* @param sortBy sort parameter
* @param sortBy sort parameter (if empty - undefined sorting)
* @param desc sort direction desc or asc
*/
@GET
@@ -44,6 +45,7 @@ public class UserCollectionResource {
@TypeHint(UserDto[].class)
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 400, condition = "\"sortBy\" field unknown"),
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
@ResponseCode(code = 500, condition = "internal server error")

View File

@@ -9,13 +9,7 @@ import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
public class UserResource {
@@ -23,13 +17,13 @@ public class UserResource {
private final UserDtoToUserMapper dtoToUserMapper;
private final UserToUserDtoMapper userToDtoMapper;
private final ResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager) {
this.dtoToUserMapper = dtoToUserMapper;
this.userToDtoMapper = userToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
}
/**

View File

@@ -43,6 +43,7 @@ import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.SCMContextProvider;
import sonia.scm.TransformFilter;
import sonia.scm.search.SearchRequest;
@@ -79,6 +80,10 @@ public class DefaultGroupManager extends AbstractGroupManager
public DefaultGroupManager(GroupDAO groupDAO)
{
this.groupDAO = groupDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(
groupDAO,
GroupNotFoundException::new,
GroupAlreadyExistsException::new);
}
//~--- methods --------------------------------------------------------------
@@ -96,78 +101,34 @@ public class DefaultGroupManager extends AbstractGroupManager
// do nothing
}
/**
* Method description
*
*
* @param group
*
* @throws GroupException
* @throws IOException
*/
@Override
public void create(Group group) throws GroupException, IOException
{
public Group create(Group group) throws GroupException {
String type = group.getType();
if (Util.isEmpty(type))
{
if (Util.isEmpty(type)) {
group.setType(groupDAO.getType());
}
String name = group.getName();
if (logger.isInfoEnabled())
{
logger.info("create group {} of type {}", name,
group.getType());
}
GroupPermissions.create().check();
if (groupDAO.contains(name))
{
throw new GroupAlreadyExistsException(name.concat(" group already exists"));
}
logger.info("create group {} of type {}", group.getName(), group.getType());
removeDuplicateMembers(group);
group.setCreationDate(System.currentTimeMillis());
fireEvent(HandlerEventType.BEFORE_CREATE, group);
groupDAO.add(group);
fireEvent(HandlerEventType.CREATE, group);
return managerDaoAdapter.create(
group,
GroupPermissions::create,
newGroup -> fireEvent(HandlerEventType.BEFORE_CREATE, newGroup),
newGroup -> fireEvent(HandlerEventType.CREATE, newGroup)
);
}
/**
* Method description
*
*
* @param group
*
* @throws GroupException
* @throws IOException
*/
@Override
public void delete(Group group) throws GroupException, IOException
{
if (logger.isInfoEnabled())
{
logger.info("delete group {} of type {}", group.getName(),
group.getType());
}
String name = group.getName();
GroupPermissions.delete().check(name);
if (groupDAO.contains(name))
{
fireEvent(HandlerEventType.BEFORE_DELETE, group);
groupDAO.delete(group);
fireEvent(HandlerEventType.DELETE, group);
}
else
{
throw new GroupNotFoundException("user does not exists");
}
public void delete(Group group) throws GroupException {
logger.info("delete group {} of type {}", group.getName(), group.getType());
managerDaoAdapter.delete(
group,
() -> GroupPermissions.delete(group.getName()),
toDelete -> fireEvent(HandlerEventType.BEFORE_DELETE, toDelete),
toDelete -> fireEvent(HandlerEventType.DELETE, toDelete)
);
}
/**
@@ -189,31 +150,18 @@ public class DefaultGroupManager extends AbstractGroupManager
* @throws IOException
*/
@Override
public void modify(Group group) throws GroupException, IOException
{
if (logger.isInfoEnabled())
{
logger.info("modify group {} of type {}", group.getName(),
group.getType());
}
public void modify(Group group) throws GroupException {
logger.info("modify group {} of type {}", group.getName(), group.getType());
String name = group.getName();
GroupPermissions.modify().check(name);
Group notModified = groupDAO.get(name);
if (notModified != null)
{
removeDuplicateMembers(group);
fireEvent(HandlerEventType.BEFORE_MODIFY, group, notModified);
group.setLastModified(System.currentTimeMillis());
group.setCreationDate(notModified.getCreationDate());
groupDAO.modify(group);
fireEvent(HandlerEventType.MODIFY, group, notModified);
}
else
{
throw new GroupNotFoundException("group does not exists");
}
managerDaoAdapter.modify(
group,
GroupPermissions::modify,
notModified -> {
removeDuplicateMembers(group);
fireEvent(HandlerEventType.BEFORE_MODIFY, group, notModified);
},
notModified -> fireEvent(HandlerEventType.MODIFY, group, notModified)
);
}
/**
@@ -226,7 +174,7 @@ public class DefaultGroupManager extends AbstractGroupManager
* @throws IOException
*/
@Override
public void refresh(Group group) throws GroupException, IOException
public void refresh(Group group) throws GroupException
{
String name = group.getName();
if (logger.isInfoEnabled())
@@ -239,7 +187,7 @@ public class DefaultGroupManager extends AbstractGroupManager
if (fresh == null)
{
throw new GroupNotFoundException("group does not exists");
throw new GroupNotFoundException(group);
}
fresh.copyProperties(group);
@@ -452,4 +400,5 @@ public class DefaultGroupManager extends AbstractGroupManager
/** Field description */
private GroupDAO groupDAO;
private final ManagerDaoAdapter<Group, GroupException> managerDaoAdapter;
}

View File

@@ -42,29 +42,14 @@ import com.google.inject.Singleton;
import org.apache.shiro.concurrent.SubjectAwareExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ArgumentIsInvalidException;
import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.*;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.KeyGenerator;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import sonia.scm.util.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -90,6 +75,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final Set<Type> types;
private RepositoryMatcher repositoryMatcher;
private NamespaceStrategy namespaceStrategy;
private final ManagerDaoAdapter<Repository, RepositoryException> managerDaoAdapter;
@Inject
@@ -116,6 +102,10 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
for (RepositoryHandler handler : handlerSet) {
addHandler(contextProvider, handler);
}
managerDaoAdapter = new ManagerDaoAdapter<>(
repositoryDAO,
RepositoryNotFoundException::new,
RepositoryAlreadyExistsException::create);
}
@@ -128,89 +118,47 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
}
/**
* Method description
*
*
* @param repository
* @param initRepository
*
* @throws IOException
* @throws RepositoryException
*/
public void create(Repository repository, boolean initRepository)
throws RepositoryException, IOException {
logger.info("create repository {} of type {}", repository.getName(),
repository.getType());
RepositoryPermissions.create().check();
AssertUtil.assertIsValid(repository);
if (repositoryDAO.contains(repository)) {
throw RepositoryAlreadyExistsException.create(repository);
}
@Override
public Repository create(Repository repository) throws RepositoryException {
return create(repository, true);
}
public Repository create(Repository repository, boolean initRepository) throws RepositoryException {
repository.setId(keyGenerator.createKey());
repository.setCreationDate(System.currentTimeMillis());
repository.setNamespace(namespaceStrategy.getNamespace());
if (initRepository) {
getHandler(repository).create(repository);
}
logger.info("create repository {} of type {} in namespace {}", repository.getName(), repository.getType(), repository.getNamespace());
fireEvent(HandlerEventType.BEFORE_CREATE, repository);
repositoryDAO.add(repository);
fireEvent(HandlerEventType.CREATE, repository);
return managerDaoAdapter.create(
repository,
RepositoryPermissions::create,
newRepository -> {
if (initRepository) {
getHandler(newRepository).create(newRepository);
}
fireEvent(HandlerEventType.BEFORE_CREATE, newRepository);
},
newRepository -> fireEvent(HandlerEventType.CREATE, newRepository)
);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void create(Repository repository)
throws RepositoryException, IOException {
create(repository, true);
public void delete(Repository repository) throws RepositoryException {
logger.info("delete repository {} of type {}", repository.getName(), repository.getType());
managerDaoAdapter.delete(
repository,
() -> RepositoryPermissions.delete(repository),
this::preDelete,
toDelete -> fireEvent(HandlerEventType.DELETE, toDelete)
);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void delete(Repository repository)
throws RepositoryException, IOException {
if (logger.isInfoEnabled()) {
logger.info("delete repository {} of type {}", repository.getName(),
repository.getType());
}
RepositoryPermissions.delete(repository).check();
if (configuration.isEnableRepositoryArchive() && !repository.isArchived()) {
throw new RepositoryIsNotArchivedException(
"Repository could not deleted, because it is not archived.");
}
if (repositoryDAO.contains(repository)) {
fireEvent(HandlerEventType.BEFORE_DELETE, repository);
getHandler(repository).delete(repository);
repositoryDAO.delete(repository);
fireEvent(HandlerEventType.DELETE, repository);
} else {
throw new RepositoryNotFoundException(
"repository ".concat(repository.getName()).concat(" not found"));
private void preDelete(Repository toDelete) throws RepositoryException {
if (configuration.isEnableRepositoryArchive() && !toDelete.isArchived()) {
throw new RepositoryIsNotArchivedException("Repository could not deleted, because it is not archived.");
}
fireEvent(HandlerEventType.BEFORE_DELETE, toDelete);
getHandler(toDelete).delete(toDelete);
}
/**
@@ -248,29 +196,18 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
* @throws RepositoryException
*/
@Override
public void modify(Repository repository)
throws RepositoryException, IOException {
if (logger.isInfoEnabled()) {
logger.info("modify repository {} of type {}", repository.getName(),
repository.getType());
}
public void modify(Repository repository) throws RepositoryException {
logger.info("modify repository {} of type {}", repository.getName(), repository.getType());
AssertUtil.assertIsValid(repository);
Repository oldRepository = repositoryDAO.get(repository.getType(),
repository.getName());
if (oldRepository != null) {
RepositoryPermissions.modify(oldRepository).check();
fireEvent(HandlerEventType.BEFORE_MODIFY, repository, oldRepository);
repository.setLastModified(System.currentTimeMillis());
getHandler(repository).modify(repository);
repositoryDAO.modify(repository);
fireEvent(HandlerEventType.MODIFY, repository, oldRepository);
} else {
throw new RepositoryNotFoundException(
"repository ".concat(repository.getName()).concat(" not found"));
}
managerDaoAdapter.modify(
repository,
RepositoryPermissions::modify,
notModified -> {
fireEvent(HandlerEventType.BEFORE_MODIFY, repository, notModified);
getHandler(repository).modify(repository);
},
notModified -> fireEvent(HandlerEventType.MODIFY, repository, notModified)
);
}
/**
@@ -284,7 +221,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
*/
@Override
public void refresh(Repository repository)
throws RepositoryException, IOException {
throws RepositoryException {
AssertUtil.assertIsNotNull(repository);
RepositoryPermissions.read(repository).check();
@@ -294,8 +231,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
if (fresh != null) {
fresh.copyProperties(repository);
} else {
throw new RepositoryNotFoundException(
"repository ".concat(repository.getName()).concat(" not found"));
throw new RepositoryNotFoundException(repository);
}
}
@@ -636,5 +572,4 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return handler;
}
}

View File

@@ -35,10 +35,8 @@ package sonia.scm.repository;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.plugin.Extension;
import sonia.scm.web.security.AdministrationContext;
@@ -46,8 +44,6 @@ import sonia.scm.web.security.PrivilegedAction;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
/**
*
* @author Sebastian Sdorra
@@ -154,7 +150,7 @@ public final class LastModifiedUpdateListener
{
repositoryManager.modify(dbr);
}
catch (RepositoryException | IOException ex)
catch (RepositoryException ex)
{
logger.error("could not modify repository", ex);
}

View File

@@ -41,11 +41,11 @@ import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
import sonia.scm.SCMContextProvider;
import sonia.scm.TransformFilter;
import sonia.scm.search.SearchRequest;
import sonia.scm.search.SearchUtil;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.CollectionAppender;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
@@ -55,11 +55,7 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.*;
//~--- JDK imports ------------------------------------------------------------
@@ -96,6 +92,10 @@ public class DefaultUserManager extends AbstractUserManager
public DefaultUserManager(UserDAO userDAO)
{
this.userDAO = userDAO;
this.managerDaoAdapter = new ManagerDaoAdapter<>(
userDAO,
UserNotFoundException::new,
UserAlreadyExistsException::new);
}
//~--- methods --------------------------------------------------------------
@@ -137,64 +137,31 @@ public class DefaultUserManager extends AbstractUserManager
* @throws UserException
*/
@Override
public void create(User user) throws UserException, IOException
{
public User create(User user) throws UserException {
String type = user.getType();
if (Util.isEmpty(type))
{
if (Util.isEmpty(type)) {
user.setType(userDAO.getType());
}
if (logger.isInfoEnabled())
{
logger.info("create user {} of type {}", user.getName(), user.getType());
}
logger.info("create user {} of type {}", user.getName(), user.getType());
UserPermissions.create().check();
if (userDAO.contains(user.getName()))
{
throw new UserAlreadyExistsException(user.getName().concat(" user already exists"));
}
AssertUtil.assertIsValid(user);
user.setCreationDate(System.currentTimeMillis());
fireEvent(HandlerEventType.BEFORE_CREATE, user);
userDAO.add(user);
fireEvent(HandlerEventType.CREATE, user);
return managerDaoAdapter.create(
user,
UserPermissions::create,
newUser -> fireEvent(HandlerEventType.BEFORE_CREATE, newUser),
newUser -> fireEvent(HandlerEventType.CREATE, newUser)
);
}
/**
* Method description
*
*
* @param user
*
* @throws IOException
* @throws UserException
*/
@Override
public void delete(User user) throws UserException, IOException
{
if (logger.isInfoEnabled())
{
logger.info("delete user {} of type {}", user.getName(), user.getType());
}
String name = user.getName();
UserPermissions.delete(name).check();
if (userDAO.contains(name))
{
fireEvent(HandlerEventType.BEFORE_DELETE, user);
userDAO.delete(user);
fireEvent(HandlerEventType.DELETE, user);
}
else
{
throw new UserNotFoundException("user does not exists");
}
public void delete(User user) throws UserException {
logger.info("delete user {} of type {}", user.getName(), user.getType());
managerDaoAdapter.delete(
user,
() -> UserPermissions.delete(user.getName()),
toDelete -> fireEvent(HandlerEventType.BEFORE_DELETE, toDelete),
toDelete -> fireEvent(HandlerEventType.DELETE, toDelete)
);
}
/**
@@ -224,29 +191,15 @@ public class DefaultUserManager extends AbstractUserManager
* @throws UserException
*/
@Override
public void modify(User user) throws UserException, IOException
public void modify(User user) throws UserException
{
String name = user.getName();
if (logger.isInfoEnabled())
{
logger.info("modify user {} of type {}", user.getName(), user.getType());
}
UserPermissions.modify(user).check();
User notModified = userDAO.get(name);
if (notModified != null)
{
AssertUtil.assertIsValid(user);
fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified);
user.setLastModified(System.currentTimeMillis());
user.setCreationDate(notModified.getCreationDate());
userDAO.modify(user);
fireEvent(HandlerEventType.MODIFY, user, notModified);
}
else
{
throw new UserNotFoundException("user does not exists");
}
logger.info("modify user {} of type {}", user.getName(), user.getType());
managerDaoAdapter.modify(
user,
UserPermissions::modify,
notModified -> fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified),
notModified -> fireEvent(HandlerEventType.MODIFY, user, notModified));
}
/**
@@ -259,7 +212,7 @@ public class DefaultUserManager extends AbstractUserManager
* @throws UserException
*/
@Override
public void refresh(User user) throws UserException, IOException
public void refresh(User user) throws UserException
{
if (logger.isInfoEnabled())
{
@@ -271,7 +224,7 @@ public class DefaultUserManager extends AbstractUserManager
if (fresh == null)
{
throw new UserNotFoundException("user does not exists");
throw new UserNotFoundException(user);
}
fresh.copyProperties(user);
@@ -496,6 +449,6 @@ public class DefaultUserManager extends AbstractUserManager
//~--- fields ---------------------------------------------------------------
/** Field description */
private final UserDAO userDAO;
private final ManagerDaoAdapter<User, UserException> managerDaoAdapter;
}