Merged heads

This commit is contained in:
Philipp Czora
2018-07-24 13:42:25 +02:00
299 changed files with 4288 additions and 23099 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

@@ -39,14 +39,10 @@ import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import com.google.inject.Module;
import org.apache.shiro.guice.web.ShiroWebModule;
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
import sonia.scm.api.v2.resources.MapperModule;
import sonia.scm.cache.CacheManager;
import sonia.scm.debug.DebugModule;
@@ -61,14 +57,13 @@ import sonia.scm.upgrade.UpgradeManager;
import sonia.scm.user.UserManager;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
import java.util.Set;
import java.util.Collections;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import java.util.Collections;
import java.util.List;
import java.util.Set;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -140,7 +135,7 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
moduleList.add(new EagerSingletonModule());
moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(new WebElementModule(pluginLoader));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides, pluginLoader.getExtensionProcessor()));
moduleList.add(
new ScmSecurityModule(context, pluginLoader.getExtensionProcessor())
);

View File

@@ -38,14 +38,11 @@ package sonia.scm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.cache.CacheManager;
import sonia.scm.cache.GuavaCacheManager;
@@ -58,18 +55,10 @@ import sonia.scm.group.GroupManagerProvider;
import sonia.scm.group.xml.XmlGroupDAO;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem;
import sonia.scm.plugin.DefaultPluginLoader;
import sonia.scm.plugin.DefaultPluginManager;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager;
import sonia.scm.repository.DefaultRepositoryManager;
import sonia.scm.repository.DefaultRepositoryProvider;
import sonia.scm.repository.HealthCheckContextListener;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryManagerProvider;
import sonia.scm.repository.RepositoryProvider;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.ahc.*;
import sonia.scm.plugin.*;
import sonia.scm.repository.*;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.spi.HookEventFacade;
@@ -78,28 +67,14 @@ import sonia.scm.resources.DefaultResourceManager;
import sonia.scm.resources.DevelopmentResourceManager;
import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ScriptResourceServlet;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.SecuritySystem;
import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.*;
import sonia.scm.store.*;
import sonia.scm.template.MustacheTemplateEngine;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import sonia.scm.template.TemplateServlet;
import sonia.scm.url.RestJsonUrlProvider;
import sonia.scm.url.RestXmlUrlProvider;
import sonia.scm.url.UrlProvider;
import sonia.scm.url.UrlProviderFactory;
import sonia.scm.url.WebUIUrlProvider;
import sonia.scm.user.DefaultUserManager;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserManager;
@@ -107,31 +82,17 @@ import sonia.scm.user.UserManagerProvider;
import sonia.scm.user.xml.XmlUserDAO;
import sonia.scm.util.DebugServlet;
import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.UserAgentParser;
import sonia.scm.web.cgi.CGIExecutorFactory;
import sonia.scm.web.cgi.DefaultCGIExecutorFactory;
import sonia.scm.web.filter.LoggingFilter;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.DefaultAdministrationContext;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.ServletContext;
import sonia.scm.store.ConfigurationStoreFactory;
import javax.net.ssl.SSLContext;
import sonia.scm.net.SSLContextProvider;
import sonia.scm.net.ahc.AdvancedHttpClient;
import sonia.scm.net.ahc.ContentTransformer;
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
import sonia.scm.net.ahc.JsonContentTransformer;
import sonia.scm.net.ahc.XmlContentTransformer;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.web.UserAgentParser;
import javax.servlet.ServletContext;
//~--- JDK imports ------------------------------------------------------------
/**
*
@@ -202,17 +163,18 @@ public class ScmServletModule extends ServletModule
* Constructs ...
*
*
*
* @param servletContext
* @param pluginLoader
* @param overrides
* @param extensionProcessor
*/
ScmServletModule(ServletContext servletContext,
DefaultPluginLoader pluginLoader, ClassOverrides overrides)
DefaultPluginLoader pluginLoader, ClassOverrides overrides, ExtensionProcessor extensionProcessor)
{
this.servletContext = servletContext;
this.pluginLoader = pluginLoader;
this.overrides = overrides;
this.extensionProcessor = extensionProcessor;
}
//~--- methods --------------------------------------------------------------
@@ -232,7 +194,9 @@ public class ScmServletModule extends ServletModule
ScmConfiguration config = getScmConfiguration();
CipherUtil cu = CipherUtil.getInstance();
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
// bind repository provider
ThrowingProviderBinder.create(binder()).bind(
RepositoryProvider.class, Repository.class).to(
@@ -295,7 +259,6 @@ public class ScmServletModule extends ServletModule
// bind sslcontext provider
bind(SSLContext.class).toProvider(SSLContextProvider.class);
// bind ahc
Multibinder<ContentTransformer> transformers =
Multibinder.newSetBinder(binder(), ContentTransformer.class);
@@ -313,17 +276,6 @@ public class ScmServletModule extends ServletModule
bind(ResourceManager.class, DefaultResourceManager.class);
}
// bind url provider staff
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_RESTAPI_JSON)).toProvider(
RestJsonUrlProvider.class);
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_RESTAPI_XML)).toProvider(
RestXmlUrlProvider.class);
bind(UrlProvider.class).annotatedWith(
Names.named(UrlProviderFactory.TYPE_WUI)).toProvider(
WebUIUrlProvider.class);
// bind repository service factory
bind(RepositoryServiceFactory.class);
@@ -360,8 +312,11 @@ public class ScmServletModule extends ServletModule
// bind events
// bind(LastModifiedUpdateListener.class);
}
/**
* Method description
*
@@ -476,4 +431,6 @@ public class ScmServletModule extends ServletModule
/** Field description */
private final ServletContext servletContext;
private final ExtensionProcessor extensionProcessor;
}

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

@@ -55,6 +55,11 @@ import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
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 +81,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 --------------------------------------------------------------
@@ -159,7 +162,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (Exception ex)
{
logger.error("error during create", ex);
response = createErrorResonse(ex);
response = createErrorResponse(ex);
}
return response;
@@ -195,7 +198,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
catch (Exception ex)
{
logger.error("error during delete", ex);
response = createErrorResonse(ex);
response = createErrorResponse(ex);
}
}
@@ -227,13 +230,13 @@ public abstract class AbstractManagerResource<T extends ModelObject,
}
catch (AuthorizationException ex)
{
logger.warn("update not allowd", ex);
logger.warn("update not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build();
}
catch (Exception ex)
{
logger.error("error during update", ex);
response = createErrorResonse(ex);
response = createErrorResponse(ex);
}
return response;
@@ -370,9 +373,9 @@ public abstract class AbstractManagerResource<T extends ModelObject,
*
* @return
*/
protected Response createErrorResonse(Throwable throwable)
protected Response createErrorResponse(Throwable throwable)
{
return createErrorResonse(Status.INTERNAL_SERVER_ERROR,
return createErrorResponse(Status.INTERNAL_SERVER_ERROR,
throwable.getMessage(), throwable);
}
@@ -385,9 +388,9 @@ public abstract class AbstractManagerResource<T extends ModelObject,
*
* @return
*/
protected Response createErrorResonse(Status status, Throwable throwable)
protected Response createErrorResponse(Status status, Throwable throwable)
{
return createErrorResonse(status, throwable.getMessage(), throwable);
return createErrorResponse(status, throwable.getMessage(), throwable);
}
/**
@@ -400,7 +403,7 @@ public abstract class AbstractManagerResource<T extends ModelObject,
*
* @return
*/
protected Response createErrorResonse(Status status, String message,
protected Response createErrorResponse(Status status, String message,
Throwable throwable)
{
return Response.status(status).entity(new RestExceptionResult(message,
@@ -526,45 +529,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 +556,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 +577,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 +674,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,18 +41,12 @@ 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 java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
@@ -69,6 +63,9 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------
/**
* RESTful Web Service Resource to manage groups and their members.
@@ -97,7 +94,7 @@ public class GroupResource
@Inject
public GroupResource(GroupManager groupManager)
{
super(groupManager);
super(groupManager, Group.class);
}
//~--- methods --------------------------------------------------------------

View File

@@ -40,12 +40,13 @@ 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;
@@ -65,26 +66,6 @@ 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 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;
@@ -100,10 +81,22 @@ 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;
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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
//~--- JDK imports ------------------------------------------------------------
/**
* Rest resource for importing repositories.
@@ -550,7 +543,8 @@ public class RepositoryImportResource
try
{
repository = new Repository(null, type, name);
// TODO #8783
// repository = new Repository(null, type, name);
manager.create(repository);
}
catch (RepositoryAlreadyExistsException ex)
@@ -564,10 +558,6 @@ public class RepositoryImportResource
{
handleGenericCreationFailure(ex, type, name);
}
catch (IOException ex)
{
handleGenericCreationFailure(ex, type, name);
}
return repository;
}
@@ -716,10 +706,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);
@@ -753,7 +739,8 @@ public class RepositoryImportResource
{
for (String repositoryName : repositoryNames)
{
Repository repository = manager.get(type, repositoryName);
// TODO #8783
/*Repository repository = null; //manager.get(type, repositoryName);
if (repository != null)
{
@@ -763,7 +750,7 @@ public class RepositoryImportResource
{
logger.warn("could not find imported repository {}",
repositoryName);
}
}*/
}
}
}

View File

@@ -42,13 +42,10 @@ 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.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;
@@ -76,13 +73,6 @@ import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
@@ -101,7 +91,11 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import org.apache.shiro.authz.AuthorizationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------
/**
* Repository related RESTful Web Service Endpoint.
@@ -125,19 +119,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;
@@ -215,10 +205,10 @@ 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);
response = createErrorResonse(ex);
logger.error("error during delete", ex);
response = createErrorResponse(ex);
}
}
else
@@ -566,42 +556,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
return response;
}
/**
* Returns the {@link Repository} with the specified type and name.
*
* @param type the type of the repository
* @param name the name of the repository
*
* @return the {@link Repository} with the specified type and name
*/
@GET
@Path("{type: [a-z]+}/{name: .*}")
@StatusCodes({
@ResponseCode(code = 200, condition = "success"),
@ResponseCode(code = 404, condition = "not found, no repository with the specified type and name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(Repository.class)
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getByTypeAndName(@PathParam("type") String type,
@PathParam("name") String name)
{
Response response;
Repository repository = repositoryManager.get(type, name);
if (repository != null)
{
prepareForReturn(repository);
response = Response.ok(repository).build();
}
else
{
response = Response.status(Response.Status.NOT_FOUND).build();
}
return response;
}
/**
* Returns the {@link Changeset} from the given repository
* with the specified revision.
@@ -823,7 +777,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
catch (Exception ex)
{
logger.error("could not retrive content", ex);
response = createErrorResonse(ex);
response = createErrorResponse(ex);
}
return response;
@@ -908,7 +862,7 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
catch (Exception ex)
{
logger.error("could not create diff", ex);
response = createErrorResonse(ex);
response = createErrorResponse(ex);
}
return response;
@@ -1108,9 +1062,6 @@ public class RepositoryResource extends AbstractManagerResource<Repository, Repo
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final HealthChecker healthChecker;

View File

@@ -44,8 +44,6 @@ import com.google.inject.Inject;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTypePredicate;
import sonia.scm.url.UrlProvider;
import sonia.scm.url.UrlProviderFactory;
import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
@@ -106,19 +104,15 @@ public class RepositoryRootResource
*/
@GET
@Produces(MediaType.TEXT_HTML)
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request,
@PathParam("type") final String type)
throws IOException
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request, @PathParam("type") final String type)
{
String baseUrl = HttpUtil.getCompleteUrl(request);
UrlProvider uiUrlProvider = UrlProviderFactory.createUrlProvider(baseUrl,
UrlProviderFactory.TYPE_WUI);
//J-
Collection<RepositoryTemplateElement> unsortedRepositories =
Collections2.transform(
Collections2.filter(
repositoryManager.getAll(), new RepositoryTypePredicate(type))
, new RepositoryTransformFunction(uiUrlProvider, baseUrl)
, new RepositoryTransformFunction(baseUrl)
);
List<RepositoryTemplateElement> repositories = Ordering.from(
@@ -149,43 +143,16 @@ public class RepositoryRootResource
*
*
* @param repository
* @param uiUrlProvider
* @param baseUrl
*/
public RepositoryTemplateElement(Repository repository,
UrlProvider uiUrlProvider, String baseUrl)
public RepositoryTemplateElement(Repository repository, String baseUrl)
{
this.repository = repository;
this.urlProvider = uiUrlProvider;
this.baseUrl = baseUrl;
}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getCommitUrl()
{
return urlProvider.getRepositoryUrlProvider().getChangesetUrl(
repository.getId(), 0, 20);
}
/**
* Method description
*
*
* @return
*/
public String getDetailUrl()
{
return urlProvider.getRepositoryUrlProvider().getDetailUrl(
repository.getId());
}
/**
* Method description
*
@@ -208,18 +175,6 @@ public class RepositoryRootResource
return repository;
}
/**
* Method description
*
*
* @return
*/
public String getSourceUrl()
{
return urlProvider.getRepositoryUrlProvider().getBrowseUrl(
repository.getId(), null, null);
}
/**
* Method description
*
@@ -239,8 +194,6 @@ public class RepositoryRootResource
/** Field description */
private Repository repository;
/** Field description */
private UrlProvider urlProvider;
}
@@ -284,20 +237,8 @@ public class RepositoryRootResource
implements Function<Repository, RepositoryTemplateElement>
{
/**
* Constructs ...
*
*
*
*
* @param request
* @param repositoryManager
* @param urlProvider
* @param baseUrl
*/
public RepositoryTransformFunction(UrlProvider urlProvider, String baseUrl)
public RepositoryTransformFunction(String baseUrl)
{
this.urlProvider = urlProvider;
this.baseUrl = baseUrl;
}
@@ -314,15 +255,12 @@ public class RepositoryRootResource
@Override
public RepositoryTemplateElement apply(Repository repository)
{
return new RepositoryTemplateElement(repository, urlProvider, baseUrl);
return new RepositoryTemplateElement(repository, baseUrl);
}
//~--- fields -------------------------------------------------------------
/** Field description */
private String baseUrl;
/** Field description */
private UrlProvider urlProvider;
}
}

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,11 +50,6 @@ import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
@@ -72,6 +65,9 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;
//~--- JDK imports ------------------------------------------------------------
/**
* RESTful Web Service Resource to manage users.
@@ -101,7 +97,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

@@ -3,25 +3,15 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import org.mapstruct.Mapping;
import sonia.scm.ModelObject;
import sonia.scm.util.AssertUtil;
import java.time.Instant;
import java.util.Optional;
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) {
AssertUtil.assertIsNotNull(epochMilli);
return Instant.ofEpochMilli(epochMilli);
}
Optional<Instant> mapOptionalTime(Long epochMilli) {
return Optional
.ofNullable(epochMilli)
.map(Instant::ofEpochMilli);
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

@@ -18,38 +18,35 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import static sonia.scm.api.v2.resources.ResourceLinks.group;
public class GroupCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10;
private final GroupDtoToGroupMapper dtoToGroupMapper;
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) {
public GroupCollectionResource(GroupManager manager, GroupDtoToGroupMapper dtoToGroupMapper, GroupCollectionToDtoMapper groupCollectionToDtoMapper, ResourceLinks resourceLinks) {
this.dtoToGroupMapper = dtoToGroupMapper;
this.groupCollectionToDtoMapper = groupCollectionToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.resourceLinks = resourceLinks;
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 request the current request
* @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
@@ -58,14 +55,14 @@ 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")
})
public Response getAll(@Context Request request,
@DefaultValue("0") @QueryParam("page") int page,
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortby") String sortBy,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false")
@QueryParam("desc") boolean desc) {
return adapter.getAll(page, pageSize, sortBy, desc,
@@ -89,9 +86,9 @@ public class GroupCollectionResource {
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created group"))
public Response create(@Context UriInfo uriInfo, GroupDto groupDto) throws IOException, GroupException {
public Response create(GroupDto groupDto) throws IOException, GroupException {
return adapter.create(groupDto,
() -> dtoToGroupMapper.map(groupDto),
group -> group(uriInfo).self(group.getName()));
group -> resourceLinks.group().self(group.getName()));
}
}

View File

@@ -5,26 +5,24 @@ import sonia.scm.group.GroupPermissions;
import javax.inject.Inject;
import static sonia.scm.api.v2.resources.ResourceLinks.groupCollection;
public class GroupCollectionToDtoMapper extends BasicCollectionToDtoMapper<Group, GroupDto> {
private final UriInfoStore uriInfoStore;
private final ResourceLinks resourceLinks;
@Inject
public GroupCollectionToDtoMapper(GroupToGroupDtoMapper groupToDtoMapper, UriInfoStore uriInfoStore) {
public GroupCollectionToDtoMapper(GroupToGroupDtoMapper groupToDtoMapper, ResourceLinks resourceLinks) {
super("groups", groupToDtoMapper);
this.uriInfoStore = uriInfoStore;
this.resourceLinks = resourceLinks;
}
@Override
String createCreateLink() {
return groupCollection(uriInfoStore.get()).create();
return resourceLinks.groupCollection().create();
}
@Override
String createSelfLink() {
return groupCollection(uriInfoStore.get()).self();
return resourceLinks.groupCollection().self();
}
@Override

View File

@@ -10,15 +10,14 @@ import lombok.Setter;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Getter @Setter @NoArgsConstructor
public class GroupDto extends HalRepresentation {
private Instant creationDate;
private String description;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Optional<Instant> lastModified;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
private String name;
private String type;
private Map<String, String> properties;

View File

@@ -9,24 +9,27 @@ import sonia.scm.group.GroupManager;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
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.core.Response;
import javax.ws.rs.core.UriInfo;
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);
}
/**
@@ -34,7 +37,6 @@ public class GroupResource {
*
* <strong>Note:</strong> This method requires "group" privilege.
*
* @param request the current request
* @param id the id/name of the group
*
*/
@@ -49,7 +51,7 @@ public class GroupResource {
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@Context Request request, @Context UriInfo uriInfo, @PathParam("id") String id) {
public Response get(@PathParam("id") String id) {
return adapter.get(id, groupToGroupDtoMapper::map);
}
@@ -87,13 +89,14 @@ public class GroupResource {
@Consumes(VndMediaType.GROUP)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of id/group name"),
@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 = 404, condition = "not found, no group with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, GroupDto groupDto) {
public Response update(@PathParam("id") String name, GroupDto groupDto) {
return adapter.update(name, existing -> dtoToGroupMapper.map(groupDto));
}
}

View File

@@ -13,8 +13,6 @@ import java.util.stream.Collectors;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static sonia.scm.api.v2.resources.ResourceLinks.group;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@@ -22,16 +20,16 @@ import static sonia.scm.api.v2.resources.ResourceLinks.user;
public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto> {
@Inject
private UriInfoStore uriInfoStore;
private ResourceLinks resourceLinks;
@AfterMapping
void appendLinks(Group group, @MappingTarget GroupDto target) {
Links.Builder linksBuilder = linkingTo().self(group(uriInfoStore.get()).self(target.getName()));
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(target.getName()));
if (GroupPermissions.delete(group).isPermitted()) {
linksBuilder.single(link("delete", group(uriInfoStore.get()).delete(target.getName())));
linksBuilder.single(link("delete", resourceLinks.group().delete(target.getName())));
}
if (GroupPermissions.modify(group).isPermitted()) {
linksBuilder.single(link("update", group(uriInfoStore.get()).update(target.getName())));
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
}
target.add(linksBuilder.build());
}
@@ -43,7 +41,7 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
}
private MemberDto createMember(String name) {
Links.Builder linksBuilder = linkingTo().self(user(uriInfoStore.get()).self(name));
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(name));
MemberDto memberDto = new MemberDto(name);
memberDto.add(linksBuilder.build());
return memberDto;

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

@@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule {
bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass());
bind(GroupCollectionToDtoMapper.class);
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

@@ -25,12 +25,11 @@ public class MeResource {
private final UserToUserDtoMapper userToDtoMapper;
private final ResourceManagerAdapter<User, UserDto, UserException> adapter;
private final IdResourceManagerAdapter<User, UserDto, UserException> adapter;
@Inject
public MeResource(UserToUserDtoMapper userToDtoMapper, UserManager manager) {
this.userToDtoMapper = userToDtoMapper;
this.adapter = new ResourceManagerAdapter<>(manager);
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
}
/**

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,94 @@
package sonia.scm.api.v2.resources;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
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.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
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,25 @@
package sonia.scm.api.v2.resources;
import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
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,164 @@
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.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.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

@@ -1,20 +1,26 @@
package sonia.scm.api.v2.resources;
import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
class ResourceLinks {
private ResourceLinks() {
private final UriInfoStore uriInfoStore;
@Inject
ResourceLinks(UriInfoStore uriInfoStore) {
this.uriInfoStore = uriInfoStore;
}
static GroupLinks group(UriInfo uriInfo) {
return new GroupLinks(uriInfo);
GroupLinks group() {
return new GroupLinks(uriInfoStore.get());
}
static class GroupLinks {
private final LinkBuilder groupLinkBuilder;
private GroupLinks(UriInfo uriInfo) {
GroupLinks(UriInfo uriInfo) {
groupLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupResource.class);
}
@@ -31,14 +37,14 @@ class ResourceLinks {
}
}
static GroupCollectionLinks groupCollection(UriInfo uriInfo) {
return new GroupCollectionLinks(uriInfo);
GroupCollectionLinks groupCollection() {
return new GroupCollectionLinks(uriInfoStore.get());
}
static class GroupCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
private GroupCollectionLinks(UriInfo uriInfo) {
GroupCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, GroupRootResource.class, GroupCollectionResource.class);
}
@@ -51,14 +57,14 @@ class ResourceLinks {
}
}
static UserLinks user(UriInfo uriInfo) {
return new UserLinks(uriInfo);
UserLinks user() {
return new UserLinks(uriInfoStore.get());
}
static class UserLinks {
private final LinkBuilder userLinkBuilder;
private UserLinks(UriInfo uriInfo) {
UserLinks(UriInfo uriInfo) {
userLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserResource.class);
}
@@ -75,14 +81,14 @@ class ResourceLinks {
}
}
static UserCollectionLinks userCollection(UriInfo uriInfo) {
return new UserCollectionLinks(uriInfo);
UserCollectionLinks userCollection() {
return new UserCollectionLinks(uriInfoStore.get());
}
static class UserCollectionLinks {
private final LinkBuilder collectionLinkBuilder;
private UserCollectionLinks(UriInfo uriInfo) {
UserCollectionLinks(UriInfo uriInfo) {
collectionLinkBuilder = new LinkBuilder(uriInfo, UserRootResource.class, UserCollectionResource.class);
}
@@ -94,4 +100,128 @@ class ResourceLinks {
return collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").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,28 +18,25 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
public class UserCollectionResource {
private static final int DEFAULT_PAGE_SIZE = 10;
private final UserDtoToUserMapper dtoToUserMapper;
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) {
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;
}
/**
@@ -47,10 +44,9 @@ public class UserCollectionResource {
*
* <strong>Note:</strong> This method requires "user" privilege.
*
* @param request the current request
* @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
@@ -59,14 +55,14 @@ 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")
})
public Response getAll(@Context Request request,
@DefaultValue("0") @QueryParam("page") int page,
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
@QueryParam("sortby") String sortBy,
@QueryParam("sortBy") String sortBy,
@DefaultValue("false") @QueryParam("desc") boolean desc) {
return adapter.getAll(page, pageSize, sortBy, desc,
pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
@@ -92,9 +88,9 @@ public class UserCollectionResource {
})
@TypeHint(TypeHint.NO_CONTENT.class)
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
public Response create(@Context UriInfo uriInfo, UserDto userDto) throws IOException, UserException {
public Response create(UserDto userDto) throws IOException, UserException {
return adapter.create(userDto,
() -> dtoToUserMapper.map(userDto, ""),
user -> user(uriInfo).self(user.getName()));
user -> resourceLinks.user().self(user.getName()));
}
}

View File

@@ -5,28 +5,26 @@ import sonia.scm.user.UserPermissions;
import javax.inject.Inject;
import static sonia.scm.api.v2.resources.ResourceLinks.userCollection;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
public class UserCollectionToDtoMapper extends BasicCollectionToDtoMapper<User, UserDto> {
private final UriInfoStore uriInfoStore;
private final ResourceLinks resourceLinks;
@Inject
public UserCollectionToDtoMapper(UserToUserDtoMapper userToDtoMapper, UriInfoStore uriInfoStore) {
public UserCollectionToDtoMapper(UserToUserDtoMapper userToDtoMapper, ResourceLinks resourceLinks) {
super("users", userToDtoMapper);
this.uriInfoStore = uriInfoStore;
this.resourceLinks = resourceLinks;
}
@Override
String createCreateLink() {
return userCollection(uriInfoStore.get()).create();
return resourceLinks.userCollection().create();
}
@Override
String createSelfLink() {
return userCollection(uriInfoStore.get()).self();
return resourceLinks.userCollection().self();
}
@Override

View File

@@ -9,7 +9,6 @@ import lombok.Setter;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
@NoArgsConstructor @Getter @Setter
public class UserDto extends HalRepresentation {
@@ -17,8 +16,8 @@ public class UserDto extends HalRepresentation {
private boolean admin;
private Instant creationDate;
private String displayName;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Optional<Instant> lastModified;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
private String mail;
private String name;
private String password;

View File

@@ -16,23 +16,20 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
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);
}
/**
@@ -40,7 +37,6 @@ public class UserResource {
*
* <strong>Note:</strong> This method requires "user" privilege.
*
* @param request the current request
* @param id the id/name of the user
*
*/
@@ -55,7 +51,7 @@ public class UserResource {
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
public Response get(@Context Request request, @Context UriInfo uriInfo, @PathParam("id") String id) {
public Response get(@PathParam("id") String id) {
return adapter.get(id, userToDtoMapper::map);
}
@@ -93,13 +89,14 @@ public class UserResource {
@Consumes(VndMediaType.USER)
@StatusCodes({
@ResponseCode(code = 204, condition = "update success"),
@ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of id/user name"),
@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 = 404, condition = "not found, no user with the specified id/name available"),
@ResponseCode(code = 500, condition = "internal server error")
})
@TypeHint(TypeHint.NO_CONTENT.class)
public Response update(@Context UriInfo uriInfo, @PathParam("id") String name, UserDto userDto) {
public Response update(@PathParam("id") String name, UserDto userDto) {
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
}
}

View File

@@ -12,7 +12,6 @@ import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static sonia.scm.api.v2.resources.ResourceLinks.user;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@@ -20,7 +19,7 @@ import static sonia.scm.api.v2.resources.ResourceLinks.user;
public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
@Inject
private UriInfoStore uriInfoStore;
private ResourceLinks resourceLinks;
@AfterMapping
void removePassword(@MappingTarget UserDto target) {
@@ -29,13 +28,14 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
@AfterMapping
void appendLinks(User user, @MappingTarget UserDto target) {
Links.Builder linksBuilder = linkingTo().self(user(uriInfoStore.get()).self(target.getName()));
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", user(uriInfoStore.get()).delete(target.getName())));
linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName())));
}
if (UserPermissions.modify(user).isPermitted()) {
linksBuilder.single(link("update", user(uriInfoStore.get()).update(target.getName())));
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
}
target.add(linksBuilder.build());
}
}

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;
@@ -51,7 +52,12 @@ import sonia.scm.util.CollectionAppender;
import sonia.scm.util.Util;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
//~--- JDK imports ------------------------------------------------------------
@@ -79,6 +85,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 +106,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 +155,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 +179,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 +192,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 +405,5 @@ public class DefaultGroupManager extends AbstractGroupManager
/** Field description */
private GroupDAO groupDAO;
private final ManagerDaoAdapter<Group, GroupException> managerDaoAdapter;
}

View File

@@ -0,0 +1,17 @@
package sonia.scm.repository;
import org.apache.shiro.SecurityUtils;
import sonia.scm.plugin.Extension;
/**
* The DefaultNamespaceStrategy returns the username of the currently logged in user as namespace.
* @since 2.0.0
*/
@Extension
public class DefaultNamespaceStrategy implements NamespaceStrategy {
@Override
public String getNamespace() {
return SecurityUtils.getSubject().getPrincipal().toString();
}
}

View File

@@ -1,19 +1,19 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
@@ -24,13 +24,11 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* <p>
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
@@ -41,15 +39,13 @@ import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
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.ManagerDaoAdapter;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.config.ScmConfiguration;
@@ -60,10 +56,8 @@ import sonia.scm.util.HttpUtil;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -76,7 +70,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javax.servlet.http.HttpServletRequest;
//~--- JDK imports ------------------------------------------------------------
/**
* Default implementation of {@link RepositoryManager}.
@@ -84,310 +78,170 @@ import javax.servlet.http.HttpServletRequest;
* @author Sebastian Sdorra
*/
@Singleton
public class DefaultRepositoryManager extends AbstractRepositoryManager
{
public class DefaultRepositoryManager extends AbstractRepositoryManager {
/** Field description */
private static final String THREAD_NAME = "Hook-%s";
/** Field description */
private static final Logger logger =
LoggerFactory.getLogger(DefaultRepositoryManager.class);
private final ScmConfiguration configuration;
private final ExecutorService executorService;
private final Map<String, RepositoryHandler> handlerMap;
private final KeyGenerator keyGenerator;
private final RepositoryDAO repositoryDAO;
private final Set<Type> types;
private RepositoryMatcher repositoryMatcher;
private NamespaceStrategy namespaceStrategy;
private final ManagerDaoAdapter<Repository, RepositoryException> managerDaoAdapter;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param configuration
* @param contextProvider
* @param keyGenerator
* @param repositoryDAO
* @param handlerSet
* @param repositoryMatcher
*/
@Inject
public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
RepositoryMatcher repositoryMatcher)
{
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
RepositoryMatcher repositoryMatcher,
NamespaceStrategy namespaceStrategy) {
this.configuration = configuration;
this.keyGenerator = keyGenerator;
this.repositoryDAO = repositoryDAO;
this.repositoryMatcher = repositoryMatcher;
this.namespaceStrategy = namespaceStrategy;
//J-
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat(THREAD_NAME).build();
this.executorService = new SubjectAwareExecutorService(
Executors.newCachedThreadPool(factory)
);
//J+
handlerMap = new HashMap<>();
types = new HashSet<>();
for (RepositoryHandler handler : handlerSet)
{
for (RepositoryHandler handler : handlerSet) {
addHandler(contextProvider, handler);
}
managerDaoAdapter = new ManagerDaoAdapter<>(
repositoryDAO,
RepositoryNotFoundException::new,
RepositoryAlreadyExistsException::create);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
public void close() {
executorService.shutdown();
for (RepositoryHandler handler : handlerMap.values())
{
for (RepositoryHandler handler : handlerMap.values()) {
IOUtil.close(handler);
}
}
/**
* 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);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void importRepository(Repository repository)
throws RepositoryException, IOException
{
throws RepositoryException, IOException {
create(repository, false);
}
/**
* Method description
*
*
* @param context
*/
@Override
public void init(SCMContextProvider context) {}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void modify(Repository repository)
throws RepositoryException, IOException
{
if (logger.isInfoEnabled())
{
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"));
}
public void init(SCMContextProvider context) {
}
@Override
public void modify(Repository repository) throws RepositoryException {
logger.info("modify repository {} of type {}", repository.getName(), repository.getType());
managerDaoAdapter.modify(
repository,
RepositoryPermissions::modify,
notModified -> {
fireEvent(HandlerEventType.BEFORE_MODIFY, repository, notModified);
getHandler(repository).modify(repository);
},
notModified -> fireEvent(HandlerEventType.MODIFY, repository, notModified)
);
}
/**
* Method description
*
*
* @param repository
*
* @throws IOException
* @throws RepositoryException
*/
@Override
public void refresh(Repository repository)
throws RepositoryException, IOException
{
throws RepositoryException {
AssertUtil.assertIsNotNull(repository);
RepositoryPermissions.read(repository).check();
Repository fresh = repositoryDAO.get(repository.getType(),
repository.getName());
Repository fresh = repositoryDAO.get(repository.getNamespaceAndName());
if (fresh != null)
{
if (fresh != null) {
fresh.copyProperties(repository);
}
else
{
throw new RepositoryNotFoundException(
"repository ".concat(repository.getName()).concat(" not found"));
} else {
throw new RepositoryNotFoundException(repository);
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param id
*
* @return
*/
@Override
public Repository get(String id)
{
public Repository get(String id) {
AssertUtil.assertIsNotEmpty(id);
RepositoryPermissions.read(id).check();
Repository repository = repositoryDAO.get(id);
if (repository != null)
{
if (repository != null) {
repository = repository.clone();
}
return repository;
}
/**
* Method description
*
*
* @param type
* @param name
*
* @return
*/
@Override
public Repository get(String type, String name)
{
AssertUtil.assertIsNotEmpty(type);
AssertUtil.assertIsNotEmpty(name);
public Repository get(NamespaceAndName namespaceAndName) {
AssertUtil.assertIsNotNull(namespaceAndName);
AssertUtil.assertIsNotEmpty(namespaceAndName.getNamespace());
AssertUtil.assertIsNotEmpty(namespaceAndName.getName());
Repository repository = repositoryDAO.get(type, name);
Repository repository = repositoryDAO.get(namespaceAndName);
if (repository != null)
{
if (repository != null) {
RepositoryPermissions.read(repository).check();
repository = repository.clone();
}
@@ -395,114 +249,62 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
return repository;
}
/**
* Method description
*
*
*
* @param comparator
* @return
*/
@Override
public Collection<Repository> getAll(Comparator<Repository> comparator)
{
public Collection<Repository> getAll(Comparator<Repository> comparator) {
List<Repository> repositories = Lists.newArrayList();
PermissionActionCheck<Repository> check = RepositoryPermissions.read();
for (Repository repository : repositoryDAO.getAll())
{
for (Repository repository : repositoryDAO.getAll()) {
if (handlerMap.containsKey(repository.getType())
&& check.isPermitted(repository))
{
&& check.isPermitted(repository)) {
Repository r = repository.clone();
repositories.add(r);
}
}
if (comparator != null)
{
if (comparator != null) {
Collections.sort(repositories, comparator);
}
return repositories;
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Repository> getAll()
{
public Collection<Repository> getAll() {
return getAll(null);
}
/**
* Method description
*
*
*
* @param comparator
* @param start
* @param limit
*
* @return
*/
@Override
public Collection<Repository> getAll(Comparator<Repository> comparator,
int start, int limit)
{
int start, int limit) {
final PermissionActionCheck<Repository> check =
RepositoryPermissions.read();
return Util.createSubCollection(repositoryDAO.getAll(), comparator,
new CollectionAppender<Repository>()
{
@Override
public void append(Collection<Repository> collection, Repository item)
{
if (check.isPermitted(item))
{
collection.add(item.clone());
new CollectionAppender<Repository>() {
@Override
public void append(Collection<Repository> collection, Repository item) {
if (check.isPermitted(item)) {
collection.add(item.clone());
}
}
}
}, start, limit);
}, start, limit);
}
/**
* Method description
*
*
* @param start
* @param limit
*
* @return
*/
@Override
public Collection<Repository> getAll(int start, int limit)
{
public Collection<Repository> getAll(int start, int limit) {
return getAll(null, start, limit);
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Type> getConfiguredTypes()
{
public Collection<Type> getConfiguredTypes() {
List<Type> validTypes = Lists.newArrayList();
for (RepositoryHandler handler : handlerMap.values())
{
if (handler.isConfigured())
{
for (RepositoryHandler handler : handlerMap.values()) {
if (handler.isConfigured()) {
validTypes.add(handler.getType());
}
}
@@ -510,41 +312,40 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
return validTypes;
}
/**
* Method description
*
*
* @param request
*
* @return
*/
@Override
public Repository getFromRequest(HttpServletRequest request)
{
public Repository getFromRequest(HttpServletRequest request) {
AssertUtil.assertIsNotNull(request);
return getFromUri(HttpUtil.getStrippedURI(request));
}
/**
* Method description
*
*
* @param type
* @param uri
*
* @return
*/
@Override
public Repository getFromTypeAndUri(String type, String uri)
{
if (Strings.isNullOrEmpty(type))
{
public Repository getFromUri(String uri) {
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) {
uri = uri.substring(1);
}
int typeSeparator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
Repository repository = null;
if (typeSeparator > 0) {
String type = uri.substring(0, typeSeparator);
uri = uri.substring(typeSeparator + 1);
repository = getFromTypeAndUri(type, uri);
}
return repository;
}
private Repository getFromTypeAndUri(String type, String uri) {
if (Strings.isNullOrEmpty(type)) {
throw new ArgumentIsInvalidException("argument type is required");
}
if (Strings.isNullOrEmpty(uri))
{
if (Strings.isNullOrEmpty(uri)) {
throw new ArgumentIsInvalidException("argument uri is required");
}
@@ -553,16 +354,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
Repository repository = null;
if (handlerMap.containsKey(type))
{
if (handlerMap.containsKey(type)) {
Collection<Repository> repositories = repositoryDAO.getAll();
PermissionActionCheck<Repository> check = RepositoryPermissions.read();
for (Repository r : repositories)
{
if (repositoryMatcher.matches(r, type, uri))
{
for (Repository r : repositories) {
if (repositoryMatcher.matches(r, type, uri)) {
check.check(r);
repository = r.clone();
@@ -571,8 +369,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
}
}
if ((repository == null) && logger.isDebugEnabled())
{
if ((repository == null) && logger.isDebugEnabled()) {
logger.debug("could not find repository with type {} and uri {}", type,
uri);
}
@@ -580,103 +377,35 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
return repository;
}
/**
* Method description
*
*
* @param uri
*
* @return
*/
@Override
public Repository getFromUri(String uri)
{
AssertUtil.assertIsNotEmpty(uri);
if (uri.startsWith(HttpUtil.SEPARATOR_PATH))
{
uri = uri.substring(1);
}
int typeSeperator = uri.indexOf(HttpUtil.SEPARATOR_PATH);
Repository repository = null;
if (typeSeperator > 0)
{
String type = uri.substring(0, typeSeperator);
uri = uri.substring(typeSeperator + 1);
repository = getFromTypeAndUri(type, uri);
}
return repository;
}
/**
* Method description
*
*
* @param type
*
* @return
*/
@Override
public RepositoryHandler getHandler(String type)
{
public RepositoryHandler getHandler(String type) {
return handlerMap.get(type);
}
/**
* Method description
*
*
* @return
*/
@Override
public Long getLastModified()
{
public Long getLastModified() {
return repositoryDAO.getLastModified();
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Type> getTypes()
{
public Collection<Type> getTypes() {
return types;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
*
* @param contextProvider
* @param handler
*/
private void addHandler(SCMContextProvider contextProvider,
RepositoryHandler handler)
{
RepositoryHandler handler) {
AssertUtil.assertIsNotNull(handler);
Type type = handler.getType();
AssertUtil.assertIsNotNull(type);
if (handlerMap.containsKey(type.getName()))
{
if (handlerMap.containsKey(type.getName())) {
throw new ConfigurationException(
type.getName().concat("allready registered"));
}
if (logger.isInfoEnabled())
{
if (logger.isInfoEnabled()) {
logger.info("added RepositoryHandler {} for type {}", handler.getClass(),
type);
}
@@ -686,58 +415,18 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager
types.add(type);
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param repository
*
* @return
*
*
* @throws RepositoryException
*/
private RepositoryHandler getHandler(Repository repository)
throws RepositoryException
{
throws RepositoryException {
String type = repository.getType();
RepositoryHandler handler = handlerMap.get(type);
if (handler == null)
{
if (handler == null) {
throw new RepositoryHandlerNotFoundException(
"could not find handler for ".concat(type));
}
else if (!handler.isConfigured())
{
} else if (!handler.isConfigured()) {
throw new RepositoryException("handler is not configured");
}
return handler;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final ExecutorService executorService;
/** Field description */
private final Map<String, RepositoryHandler> handlerMap;
/** Field description */
private final KeyGenerator keyGenerator;
/** Field description */
private final RepositoryDAO repositoryDAO;
/** Field description */
private final Set<Type> types;
/** Field description */
private RepositoryMatcher repositoryMatcher;
}

View File

@@ -33,12 +33,10 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.eventbus.Subscribe;
import com.github.legman.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

@@ -32,14 +32,15 @@
package sonia.scm.repository;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.inject.Inject;
import java.util.Map;
import java.util.Set;
/**
* RepositoryMatcher is able to check if a repository matches the requested path.
@@ -83,9 +84,24 @@ public final class RepositoryMatcher {
}
private boolean isPathMatching(Repository repository, String path) {
return getPathMatcherForType(repository.getType()).isPathMatching(repository, path);
String namespace = extractNamespace(path);
String remainingPath = path.substring(namespace.length() + 1);
return getPathMatcherForType(repository.getType()).isPathMatching(repository, remainingPath);
}
private String extractNamespace(String path) {
if (path.startsWith(HttpUtil.SEPARATOR_PATH)) {
path = path.substring(1);
}
int namespaceSeparator = path.indexOf(HttpUtil.SEPARATOR_PATH);
if (namespaceSeparator > 0) {
return path.substring(0, namespaceSeparator);
}
throw new IllegalArgumentException("no namespace in path " + path);
}
private RepositoryPathMatcher getPathMatcherForType(String type) {
RepositoryPathMatcher pathMatcher = pathMatchers.get(type);
if (pathMatcher == null) {

View File

@@ -30,8 +30,8 @@
*/
package sonia.scm.security;
import com.github.legman.Subscribe;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class RestJsonUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public RestJsonUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_RESTAPI_JSON);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class RestXmlUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public RestXmlUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_RESTAPI_XML);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2010, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.url;
//~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Inject;
import com.google.inject.Provider;
import sonia.scm.config.ScmConfiguration;
/**
*
* @author Sebastian Sdorra
*/
public class WebUIUrlProvider implements Provider<UrlProvider>
{
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public WebUIUrlProvider(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public UrlProvider get()
{
return UrlProviderFactory.createUrlProvider(configuration.getBaseUrl(),
UrlProviderFactory.TYPE_WUI);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ScmConfiguration configuration;
}

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;
@@ -96,6 +96,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 +141,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 +195,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 +216,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 +228,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 +453,6 @@ public class DefaultUserManager extends AbstractUserManager
//~--- fields ---------------------------------------------------------------
/** Field description */
private final UserDAO userDAO;
private final ManagerDaoAdapter<User, UserException> managerDaoAdapter;
}