diff --git a/pom.xml b/pom.xml
index 2416371d48..2f872b60b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -492,6 +492,7 @@
1.19.4
2.8.6
4.0
+ 3.1.3.Final
1.2.0
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java
similarity index 100%
rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java
rename to scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java
similarity index 100%
rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java
rename to scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java
diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
index fb80450bc8..c7b8d369bd 100644
--- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
+++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
@@ -9,8 +9,8 @@ public class VndMediaType {
private static final String VERSION = "2";
private static final String TYPE = "application";
private static final String SUBTYPE_PREFIX = "vnd.scmm-";
- private static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX;
- private static final String SUFFIX = "+json;v=" + VERSION;
+ public static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX;
+ public static final String SUFFIX = "+json;v=" + VERSION;
public static final String USER = PREFIX + "user" + SUFFIX;
public static final String GROUP = PREFIX + "group" + SUFFIX;
diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml
index 5b61882815..168c5910e4 100644
--- a/scm-plugins/pom.xml
+++ b/scm-plugins/pom.xml
@@ -56,6 +56,79 @@
test
+
+
+
+ com.webcohesion.enunciate
+ enunciate-core-annotations
+ ${enunciate.version}
+
+
+
+ org.mapstruct
+ mapstruct-jdk8
+ ${org.mapstruct.version}
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+ provided
+
+
+
+ de.otto.edison
+ edison-hal
+ 2.0.1
+ compile
+
+
+
+ org.projectlombok
+ lombok
+ 1.16.18
+ provided
+
+
+
+
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ ${resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-jaxb-provider
+ ${resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-jackson2-provider
+ ${resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-multipart-provider
+ ${resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-guice
+ ${resteasy.version}
+
+
+
+ org.jboss.resteasy
+ resteasy-servlet-initializer
+ ${resteasy.version}
+
+
@@ -112,6 +185,10 @@
+
+ 2.9.1
+
+
release
diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml
index 7013c237cb..cdf1775bba 100644
--- a/scm-plugins/scm-git-plugin/pom.xml
+++ b/scm-plugins/scm-git-plugin/pom.xml
@@ -59,6 +59,20 @@
test
+
+ com.sun.jersey
+ jersey-client
+ ${jersey-client.version}
+ test
+
+
+
+ com.sun.jersey.contribs
+ jersey-apache-client
+ ${jersey-client.version}
+ test
+
+
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java
new file mode 100644
index 0000000000..2bd2cf755c
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java
@@ -0,0 +1,23 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.HalRepresentation;
+import de.otto.edison.hal.Links;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+
+@NoArgsConstructor
+@Getter
+@Setter
+public class GitConfigDto extends SimpleRepositoryConfigDto {
+
+ @XmlElement(name = "gc-expression")
+ private String gcExpression;
+
+ @Override
+ protected HalRepresentation add(Links links) {
+ return super.add(links);
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java
new file mode 100644
index 0000000000..74ba684a24
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java
@@ -0,0 +1,11 @@
+package sonia.scm.api.v2.resources;
+
+import org.mapstruct.Mapper;
+import sonia.scm.repository.GitConfig;
+
+// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
+@SuppressWarnings("squid:S3306")
+@Mapper
+public abstract class GitConfigDtoToGitConfigMapper {
+ public abstract GitConfig map(GitConfigDto dto);
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java
new file mode 100644
index 0000000000..a0079464cc
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java
@@ -0,0 +1,93 @@
+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 org.apache.shiro.SecurityUtils;
+import sonia.scm.repository.GitConfig;
+import sonia.scm.repository.GitRepositoryHandler;
+import sonia.scm.security.Role;
+import sonia.scm.web.GitVndMediaType;
+
+import javax.inject.Inject;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+@Path(GitConfigResource.GIT_CONFIG_PATH_V2)
+public class GitConfigResource {
+
+ static final String GIT_CONFIG_PATH_V2 = "v2/config/repository/git";
+ private final GitConfigDtoToGitConfigMapper dtoToConfigMapper;
+ private final GitConfigToGitConfigDtoMapper configToDtoMapper;
+ private final GitRepositoryHandler repositoryHandler;
+
+ @Inject
+ public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, GitRepositoryHandler repositoryHandler) {
+ this.dtoToConfigMapper = dtoToConfigMapper;
+ this.configToDtoMapper = configToDtoMapper;
+ this.repositoryHandler = repositoryHandler;
+ }
+
+ /**
+ * Returns the git config.
+ */
+ @GET
+ @Path("")
+ @Produces(GitVndMediaType.GIT_CONFIG)
+ @TypeHint(GitConfigDto.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 git config"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ public Response get() {
+ Response response;
+
+ if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
+ GitConfig config = repositoryHandler.getConfig();
+
+ if (config == null) {
+ config = new GitConfig();
+ repositoryHandler.setConfig(config);
+ }
+
+ response = Response.ok(configToDtoMapper.map(config)).build();
+ } else {
+ response = Response.status(Response.Status.FORBIDDEN).build();
+ }
+
+ return response;
+ }
+
+ /**
+ * Modifies the git config.
+ *
+ * @param configDto new git configuration as DTO
+ */
+ @PUT
+ @Path("")
+ @Consumes(GitVndMediaType.GIT_CONFIG)
+ @StatusCodes({
+ @ResponseCode(code = 201, condition = "update success"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the git config"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ @TypeHint(TypeHint.NO_CONTENT.class)
+ public Response update(@Context UriInfo uriInfo, GitConfigDto configDto) {
+ Response response;
+
+ if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
+ repositoryHandler.setConfig(dtoToConfigMapper.map(configDto));
+ repositoryHandler.storeConfig();
+ response = Response.created(uriInfo.getRequestUri()).build();
+ } else {
+ response = Response.status(Response.Status.FORBIDDEN).build();
+ }
+
+ return response;
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java
new file mode 100644
index 0000000000..ceab24d6f7
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java
@@ -0,0 +1,45 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.Links;
+import org.apache.shiro.SecurityUtils;
+import org.mapstruct.AfterMapping;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+import sonia.scm.repository.GitConfig;
+import sonia.scm.security.Role;
+
+import javax.inject.Inject;
+
+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 GitConfigToGitConfigDtoMapper {
+
+ @Inject
+ private UriInfoStore uriInfoStore;
+
+ public abstract GitConfigDto map(GitConfig config);
+
+ @AfterMapping
+ void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) {
+ Links.Builder linksBuilder = linkingTo().self(self());
+ // TODO: ConfigPermissions?
+ if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) {
+ linksBuilder.single(link("update", update()));
+ }
+ target.add(linksBuilder.build());
+ }
+
+ private String self() {
+ LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class);
+ return linkBuilder.method("get").parameters().href();
+ }
+
+ private String update() {
+ LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class);
+ return linkBuilder.method("update").parameters().href();
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java
new file mode 100644
index 0000000000..267ecd201e
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java
@@ -0,0 +1,16 @@
+package sonia.scm.api.v2.resources;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.servlet.ServletScopes;
+import org.mapstruct.factory.Mappers;
+
+public class MapperModule extends AbstractModule {
+ @Override
+ protected void configure() {
+
+ bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
+ bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
+
+ bind(UriInfoStore.class).in(ServletScopes.REQUEST);
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java
new file mode 100644
index 0000000000..45cb0bd199
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java
@@ -0,0 +1,17 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.HalRepresentation;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.xml.bind.annotation.XmlElement;
+import java.io.File;
+
+@Getter
+@Setter
+public abstract class SimpleRepositoryConfigDto extends HalRepresentation {
+
+ private boolean disabled = false;
+ @XmlElement(name = "repository-directory")
+ private File repositoryDirectory;
+}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
index 80fe8907ac..77352c6ca0 100644
--- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java
@@ -55,5 +55,8 @@ public class GitConfig extends SimpleRepositoryConfig {
{
return gcExpression;
}
-
+
+ public void setGcExpression(String gcExpression) {
+ this.gcExpression = gcExpression;
+ }
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java
new file mode 100644
index 0000000000..f099faf098
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java
@@ -0,0 +1,5 @@
+package sonia.scm.web;
+
+public class GitVndMediaType {
+ public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
+}
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java
new file mode 100644
index 0000000000..3b09dff51f
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java
@@ -0,0 +1,35 @@
+package sonia.scm.api.v2.resources;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.repository.GitConfig;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitConfigDtoToGitConfigMapperTest {
+
+ @InjectMocks
+ private GitConfigDtoToGitConfigMapperImpl mapper;
+
+ @Test
+ public void shouldMapFields() {
+ GitConfigDto dto = createDefaultDto();
+ GitConfig config = mapper.map(dto);
+ assertEquals("express", config.getGcExpression());
+ assertEquals("repository/directory", config.getRepositoryDirectory().getPath());
+ assertEquals(false, config.isDisabled());
+ }
+
+ private GitConfigDto createDefaultDto() {
+ GitConfigDto gitConfigDto = new GitConfigDto();
+ gitConfigDto.setGcExpression("express");
+ gitConfigDto.setDisabled(false);
+ gitConfigDto.setRepositoryDirectory(new File("repository/directory"));
+ return gitConfigDto;
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java
new file mode 100644
index 0000000000..59f7cc4799
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java
@@ -0,0 +1,77 @@
+package sonia.scm.api.v2.resources;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectThreadState;
+import org.apache.shiro.util.ThreadContext;
+import org.apache.shiro.util.ThreadState;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import sonia.scm.repository.GitConfig;
+import sonia.scm.security.Role;
+
+import java.io.File;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitConfigToGitConfigDtoMapperTest {
+
+ private URI baseUri = URI.create("http://example.com/base/");
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private UriInfoStore uriInfoStore;
+
+ @InjectMocks
+ private GitConfigToGitConfigDtoMapperImpl mapper;
+
+ private final Subject subject = mock(Subject.class);
+ private final ThreadState subjectThreadState = new SubjectThreadState(subject);
+
+ private URI expectedBaseUri;
+
+ @Before
+ public void init() {
+ when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
+ expectedBaseUri = baseUri.resolve(GitConfigResource.GIT_CONFIG_PATH_V2);
+ subjectThreadState.bind();
+ ThreadContext.bind(subject);
+ }
+
+ @After
+ public void unbindSubject() {
+ ThreadContext.unbindSubject();
+ }
+
+ @Test
+ public void shouldMapFields() {
+ GitConfig config = createConfiguration();
+
+ when(subject.hasRole(Role.ADMIN)).thenReturn(true);
+ GitConfigDto dto = mapper.map(config);
+
+ assertEquals("express", dto.getGcExpression());
+ assertFalse(dto.isDisabled());
+ assertEquals("repository/directory", dto.getRepositoryDirectory().getPath());
+ assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
+ assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
+ }
+
+ private GitConfig createConfiguration() {
+ GitConfig config = new GitConfig();
+ config.setDisabled(false);
+ config.setRepositoryDirectory(new File("repository/directory"));
+ config.setGcExpression("express");
+ return config;
+ }
+
+}
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 98c70ff82a..8d91e21025 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -561,7 +561,6 @@
2.9.1
1.0
0.8.17
- 3.1.3.Final
Tomcat
e1
javascript:S3827