mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 11:35:57 +01:00
merge with 2.0.0-m3
This commit is contained in:
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> extends HalAppenderMapper implements InstantAttributeMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract D map(T modelObject);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
|
||||
*/
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
public ChangesetDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation withEmbedded(String rel, List<? extends HalRepresentation> halRepresentations) {
|
||||
return super.withEmbedded(rel, halRepresentations);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapping;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
public interface ChangesetToChangesetDtoMapper {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
|
||||
/**
|
||||
* The {@link LinkAppender} can be used within an {@link LinkEnricher} to append hateoas links to a json response.
|
||||
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface LinkAppender {
|
||||
public interface HalAppender {
|
||||
|
||||
/**
|
||||
* Appends one link to the json response.
|
||||
@@ -14,7 +16,7 @@ public interface LinkAppender {
|
||||
* @param rel name of relation
|
||||
* @param href link uri
|
||||
*/
|
||||
void appendOne(String rel, String href);
|
||||
void appendLink(String rel, String href);
|
||||
|
||||
/**
|
||||
* Returns a builder which is able to append an array of links to the resource.
|
||||
@@ -22,8 +24,15 @@ public interface LinkAppender {
|
||||
* @param rel name of link relation
|
||||
* @return multi link builder
|
||||
*/
|
||||
LinkArrayBuilder arrayBuilder(String rel);
|
||||
LinkArrayBuilder linkArrayBuilder(String rel);
|
||||
|
||||
/**
|
||||
* Appends one embedded to the json response.
|
||||
*
|
||||
* @param rel name of relation
|
||||
* @param embeddedItem embedded object
|
||||
*/
|
||||
void appendEmbedded(String rel, HalRepresentation embeddedItem);
|
||||
|
||||
/**
|
||||
* Builder for link arrays.
|
||||
@@ -4,17 +4,17 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LinkAppenderMapper {
|
||||
public class HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@VisibleForTesting
|
||||
void setRegistry(LinkEnricherRegistry registry) {
|
||||
void setRegistry(HalEnricherRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
protected void appendLinks(LinkAppender appender, Object source, Object... contextEntries) {
|
||||
protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) {
|
||||
// null check is only their to not break existing tests
|
||||
if (registry != null) {
|
||||
|
||||
@@ -24,10 +24,10 @@ public class LinkAppenderMapper {
|
||||
ctx[i + 1] = contextEntries[i];
|
||||
}
|
||||
|
||||
LinkEnricherContext context = LinkEnricherContext.of(ctx);
|
||||
HalEnricherContext context = HalEnricherContext.of(ctx);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (LinkEnricher enricher : enrichers) {
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(source.getClass());
|
||||
for (HalEnricher enricher : enrichers) {
|
||||
enricher.enrich(context, appender);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package sonia.scm.api.v2.resources;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A {@link LinkEnricher} can be used to append hateoas links to a specific json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link LinkEnricherRegistry} which is available
|
||||
* A {@link HalEnricher} can be used to append hal specific attributes, such as links, to the json response.
|
||||
* To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available
|
||||
* via injection.
|
||||
*
|
||||
* <b>Warning:</b> enrichers are always registered as singletons.
|
||||
@@ -14,13 +14,13 @@ import sonia.scm.plugin.ExtensionPoint;
|
||||
*/
|
||||
@ExtensionPoint
|
||||
@FunctionalInterface
|
||||
public interface LinkEnricher {
|
||||
public interface HalEnricher {
|
||||
|
||||
/**
|
||||
* Enriches the response with hateoas links.
|
||||
* Enriches the response with hal specific attributes.
|
||||
*
|
||||
* @param context contains the source for the json mapping and related objects
|
||||
* @param appender can be used to append links to the json response
|
||||
* @param appender can be used to append links or embedded objects to the json response
|
||||
*/
|
||||
void enrich(LinkEnricherContext context, LinkAppender appender);
|
||||
void enrich(HalEnricherContext context, HalAppender appender);
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Context object for the {@link LinkEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the link creation.
|
||||
* Context object for the {@link HalEnricher}. The context holds the source object for the json and all related
|
||||
* objects, which can be useful for the enrichment.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class LinkEnricherContext {
|
||||
public final class HalEnricherContext {
|
||||
|
||||
private final Map<Class, Object> instanceMap;
|
||||
|
||||
private LinkEnricherContext(Map<Class,Object> instanceMap) {
|
||||
private HalEnricherContext(Map<Class,Object> instanceMap) {
|
||||
this.instanceMap = instanceMap;
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ public final class LinkEnricherContext {
|
||||
*
|
||||
* @return context of given entries
|
||||
*/
|
||||
public static LinkEnricherContext of(Object... instances) {
|
||||
public static HalEnricherContext of(Object... instances) {
|
||||
ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder();
|
||||
for (Object instance : instances) {
|
||||
builder.put(instance.getClass(), instance);
|
||||
}
|
||||
return new LinkEnricherContext(builder.build());
|
||||
return new HalEnricherContext(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7,34 +7,34 @@ import sonia.scm.plugin.Extension;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* The {@link LinkEnricherRegistry} is responsible for binding {@link LinkEnricher} instances to their source types.
|
||||
* The {@link HalEnricherRegistry} is responsible for binding {@link HalEnricher} instances to their source types.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Extension
|
||||
@Singleton
|
||||
public final class LinkEnricherRegistry {
|
||||
public final class HalEnricherRegistry {
|
||||
|
||||
private final Multimap<Class, LinkEnricher> enrichers = HashMultimap.create();
|
||||
private final Multimap<Class, HalEnricher> enrichers = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Registers a new {@link LinkEnricher} for the given source type.
|
||||
* Registers a new {@link HalEnricher} for the given source type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @param enricher link enricher instance
|
||||
*/
|
||||
public void register(Class sourceType, LinkEnricher enricher) {
|
||||
public void register(Class sourceType, HalEnricher enricher) {
|
||||
enrichers.put(sourceType, enricher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered {@link LinkEnricher} for the given type.
|
||||
* Returns all registered {@link HalEnricher} for the given type.
|
||||
*
|
||||
* @param sourceType type of json mapping source
|
||||
* @return all registered enrichers
|
||||
*/
|
||||
public Iterable<LinkEnricher> allByType(Class sourceType) {
|
||||
public Iterable<HalEnricher> allByType(Class sourceType) {
|
||||
return enrichers.get(sourceType);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Index} object can be used to register a {@link LinkEnricher} for the index resource.
|
||||
* The {@link Index} object can be used to register a {@link HalEnricher} for the index resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
/**
|
||||
* The {@link Me} object can be used to register a {@link LinkEnricher} for the me resource.
|
||||
* The {@link Me} object can be used to register a {@link HalEnricher} for the me resource.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
|
||||
@@ -11,51 +11,51 @@ import java.util.Optional;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LinkAppenderMapperTest {
|
||||
class HalAppenderMapperTest {
|
||||
|
||||
@Mock
|
||||
private LinkAppender appender;
|
||||
private HalAppender appender;
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private LinkAppenderMapper mapper;
|
||||
private HalEnricherRegistry registry;
|
||||
private HalAppenderMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
mapper = new LinkAppenderMapper();
|
||||
registry = new HalEnricherRegistry();
|
||||
mapper = new HalAppenderMapper();
|
||||
mapper.setRegistry(registry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendSimpleLink() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallMultipleEnrichers() {
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendOne("21", "https://scm.hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("42", "https://hitchhiker.com"));
|
||||
registry.register(String.class, (ctx, appender) -> appender.appendLink("21", "https://scm.hitchhiker.com"));
|
||||
|
||||
mapper.appendLinks(appender, "hello");
|
||||
mapper.applyEnrichers(appender, "hello");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendOne("21", "https://scm.hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("21", "https://scm.hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendLinkByUsingSourceFromContext() {
|
||||
registry.register(String.class, (ctx, appender) -> {
|
||||
Optional<String> rel = ctx.oneByType(String.class);
|
||||
appender.appendOne(rel.get(), "https://hitchhiker.com");
|
||||
appender.appendLink(rel.get(), "https://hitchhiker.com");
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, "42");
|
||||
mapper.applyEnrichers(appender, "42");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,12 +63,12 @@ class LinkAppenderMapperTest {
|
||||
registry.register(Integer.class, (ctx, appender) -> {
|
||||
Optional<Integer> rel = ctx.oneByType(Integer.class);
|
||||
Optional<String> href = ctx.oneByType(String.class);
|
||||
appender.appendOne(String.valueOf(rel.get()), href.get());
|
||||
appender.appendLink(String.valueOf(rel.get()), href.get());
|
||||
});
|
||||
|
||||
mapper.appendLinks(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
mapper.applyEnrichers(appender, Integer.valueOf(42), "https://hitchhiker.com");
|
||||
|
||||
verify(appender).appendOne("42", "https://hitchhiker.com");
|
||||
verify(appender).appendLink("42", "https://hitchhiker.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
class LinkEnricherContextTest {
|
||||
class HalEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromSingleObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateContextFromMultipleObjects() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
HalEnricherContext context = HalEnricherContext.of("hello", Integer.valueOf(42), Long.valueOf(21L));
|
||||
assertThat(context.oneByType(String.class)).contains("hello");
|
||||
assertThat(context.oneByType(Integer.class)).contains(42);
|
||||
assertThat(context.oneByType(Long.class)).contains(21L);
|
||||
@@ -25,19 +25,19 @@ class LinkEnricherContextTest {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyOptionalForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThat(context.oneByType(String.class)).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnRequiredObject() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of("hello");
|
||||
HalEnricherContext context = HalEnricherContext.of("hello");
|
||||
assertThat(context.oneRequireByType(String.class)).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowAnNoSuchElementExceptionForUnknownTypes() {
|
||||
LinkEnricherContext context = LinkEnricherContext.of();
|
||||
HalEnricherContext context = HalEnricherContext.of();
|
||||
assertThrows(NoSuchElementException.class, () -> context.oneRequireByType(String.class));
|
||||
}
|
||||
|
||||
@@ -5,54 +5,54 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LinkEnricherRegistryTest {
|
||||
class HalEnricherRegistryTest {
|
||||
|
||||
private LinkEnricherRegistry registry;
|
||||
private HalEnricherRegistry registry;
|
||||
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
registry = new LinkEnricherRegistry();
|
||||
registry = new HalEnricherRegistry();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterTheEnricher() {
|
||||
SampleLinkEnricher enricher = new SampleLinkEnricher();
|
||||
SampleHalEnricher enricher = new SampleHalEnricher();
|
||||
registry.register(String.class, enricher);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(enricher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterMultipleEnrichers() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(String.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one, two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterEnrichersForDifferentTypes() {
|
||||
SampleLinkEnricher one = new SampleLinkEnricher();
|
||||
SampleHalEnricher one = new SampleHalEnricher();
|
||||
registry.register(String.class, one);
|
||||
|
||||
SampleLinkEnricher two = new SampleLinkEnricher();
|
||||
SampleHalEnricher two = new SampleHalEnricher();
|
||||
registry.register(Integer.class, two);
|
||||
|
||||
Iterable<LinkEnricher> enrichers = registry.allByType(String.class);
|
||||
Iterable<HalEnricher> enrichers = registry.allByType(String.class);
|
||||
assertThat(enrichers).containsOnly(one);
|
||||
|
||||
enrichers = registry.allByType(Integer.class);
|
||||
assertThat(enrichers).containsOnly(two);
|
||||
}
|
||||
|
||||
private static class SampleLinkEnricher implements LinkEnricher {
|
||||
private static class SampleHalEnricher implements HalEnricher {
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,55 @@
|
||||
{
|
||||
"scm-git-plugin": {
|
||||
"information": {
|
||||
"clone" : "Repository Klonen",
|
||||
"create" : "Neue Repository erstellen",
|
||||
"replace" : "Eine existierende Repository aktualisieren"
|
||||
"clone" : "Repository klonen",
|
||||
"create" : "Neues Repository erstellen",
|
||||
"replace" : "Ein bestehendes Repository aktualisieren",
|
||||
"merge": {
|
||||
"heading": "Merge des Source Branch in den Target Branch",
|
||||
"checkout": "1. Sicherstellen, dass der Workspace aufgeräumt ist und der Target Branch ausgecheckt wurde.",
|
||||
"update": "2. Update Workspace",
|
||||
"merge": "3. Merge Source Branch",
|
||||
"resolve": "4. Merge Konflikte auflösen und korrigierte Dateien dem Index hinzufügen.",
|
||||
"commit": "5. Commit",
|
||||
"push": "6. Push des Merge"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"link": "Git",
|
||||
"title": "Git Konfiguration",
|
||||
"gcExpression": "GC Cron Ausdruck",
|
||||
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
||||
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
||||
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
||||
"submit": "Speichern"
|
||||
},
|
||||
"repo-config": {
|
||||
"link": "Konfiguration",
|
||||
"default-branch": "Standard Branch",
|
||||
"submit": "Speichern",
|
||||
"error": {
|
||||
"title": "Fehler",
|
||||
"subtitle": "Ein Fehler ist aufgetreten."
|
||||
},
|
||||
"success": "Der standard Branch wurde geändert!"
|
||||
}
|
||||
},
|
||||
"permissions" : {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"git": {
|
||||
"displayName": "Git Konfiguration lesen",
|
||||
"description": "Darf die git Konfiguration lesen."
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"git": {
|
||||
"displayName": "Git Konfiguration schreiben",
|
||||
"description": "Darf die git Konfiguration verändern."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,48 @@
|
||||
{
|
||||
"scm-hg-plugin": {
|
||||
"information": {
|
||||
"clone" : "Repository Klonen",
|
||||
"create" : "Neue Repository erstellen",
|
||||
"replace" : "Eine existierende Repository aktualisieren"
|
||||
"clone" : "Repository klonen",
|
||||
"create" : "Neues Repository erstellen",
|
||||
"replace" : "Ein bestehendes Repository aktualisieren"
|
||||
},
|
||||
"config": {
|
||||
"link": "Mercurial",
|
||||
"title": "Mercurial Konfiguration",
|
||||
"hgBinary": "HG Binary",
|
||||
"hgBinaryHelpText": "Pfad des Mercurial Binary.",
|
||||
"pythonBinary": "Python Binary",
|
||||
"pythonBinaryHelpText": "Pfad des Python binary.",
|
||||
"pythonPath": "Python Module Such Pfad",
|
||||
"pythonPathHelpText": "Python Module Such Pfad (PYTHONPATH).",
|
||||
"encoding": "Encoding",
|
||||
"encodingHelpText": "Repository Encoding.",
|
||||
"useOptimizedBytecode": "Optimized Bytecode (.pyo)",
|
||||
"useOptimizedBytecodeHelpText": "Verwende den Python '-O' Switch.",
|
||||
"showRevisionInId": "Revision anzeigen",
|
||||
"showRevisionInIdHelpText": "Die Revision als Teil der Node ID anzeigen.",
|
||||
"enableHttpPostArgs": "HttpPostArgs Protocol aktivieren",
|
||||
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
|
||||
"disableHookSSLValidation": "SSL Validierung für Hooks deaktivieren",
|
||||
"disableHookSSLValidationHelpText": "Deaktiviert die Validierung von SSL Zertifikaten für den Mercurial Hook, der die Repositoryänderungen wieder zurück an den SCM-Manager leitet. Diese Option sollte nur benutzt werden, wenn der SCM-Manager ein selbstsigniertes Zertifikat verwendet.",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.",
|
||||
"required": "Dieser Konfigurationswert wird benötigt"
|
||||
}
|
||||
},
|
||||
"permissions" : {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"hg": {
|
||||
"displayName": "Mercurial Konfiguration lesen",
|
||||
"description": "Darf die Mercurial Konfiguration lesen"
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"hg": {
|
||||
"displayName": "Mercurial Konfiguration schreiben",
|
||||
"description": "Darf die Mercurial Konfiguration verändern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
"showRevisionInId": "Show Revision",
|
||||
"showRevisionInIdHelpText": "Show revision as part of the node id.",
|
||||
"enableHttpPostArgs": "Enable HttpPostArgs Protocol",
|
||||
"enableHttpPostArgsHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.",
|
||||
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||
"disableHookSSLValidation": "Disable SSL Validation on Hooks",
|
||||
"disableHookSSLValidationHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
|
||||
"disableHookSSLValidationHelpText": "Disables the validation of ssl certificates for the mercurial hook, which forwards the repository changes back to scm-manager. This option should only be used, if SCM-Manager uses a self signed certificate.",
|
||||
"disabled": "Disabled",
|
||||
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
||||
"required": "This configuration value is required"
|
||||
|
||||
@@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.tmatesoft.svn.core.SVNException;
|
||||
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||
import org.tmatesoft.svn.core.wc.admin.SVNLookClient;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -19,23 +21,45 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
super(context, repository);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Modifications getModifications(String revision) {
|
||||
Modifications modifications = null;
|
||||
log.debug("get modifications {}", revision);
|
||||
public Modifications getModifications(String revisionOrTransactionId) {
|
||||
Modifications modifications;
|
||||
try {
|
||||
long revisionNumber = SvnUtil.parseRevision(revision, repository);
|
||||
if (SvnUtil.isTransactionEntryId(revisionOrTransactionId)) {
|
||||
modifications = getModificationsFromTransaction(SvnUtil.getTransactionId(revisionOrTransactionId));
|
||||
} else {
|
||||
modifications = getModificationFromRevision(revisionOrTransactionId);
|
||||
}
|
||||
return modifications;
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(
|
||||
repository,
|
||||
"failed to get svn modifications for " + revisionOrTransactionId,
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Modifications getModificationFromRevision(String revision) throws SVNException {
|
||||
log.debug("get svn modifications from revision: {}", revision);
|
||||
long revisionNumber = SvnUtil.getRevisionNumber(revision, repository);
|
||||
SVNRepository repo = open();
|
||||
Collection<SVNLogEntry> entries = repo.log(null, null, revisionNumber,
|
||||
revisionNumber, true, true);
|
||||
if (Util.isNotEmpty(entries)) {
|
||||
modifications = SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
return SvnUtil.createModifications(entries.iterator().next(), revision);
|
||||
}
|
||||
} catch (SVNException ex) {
|
||||
throw new InternalRepositoryException(repository, "could not open repository", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
private Modifications getModificationsFromTransaction(String transaction) throws SVNException {
|
||||
log.debug("get svn modifications from transaction: {}", transaction);
|
||||
final Modifications modifications = new Modifications();
|
||||
SVNLookClient client = SVNClientManager.newInstance().getLookClient();
|
||||
client.doGetChanged(context.getDirectory(), transaction,
|
||||
e -> SvnUtil.appendModification(modifications, e.getType(), e.getPath()), true);
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
@@ -44,5 +68,4 @@ public class SvnModificationsCommand extends AbstractSvnCommand implements Modif
|
||||
return getModifications(request.getRevision());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,42 @@
|
||||
{
|
||||
"scm-svn-plugin": {
|
||||
"information": {
|
||||
"checkout" : "Repository auschecken"
|
||||
"checkout": "Repository auschecken"
|
||||
},
|
||||
"config": {
|
||||
"link": "Subversion",
|
||||
"title": "Subversion Konfiguration",
|
||||
"compatibility": "Version Kompatibilität",
|
||||
"compatibilityHelpText": "Gibt an, mit welcher Subversion Version die Repositories kompatibel sind.",
|
||||
"compatibility-values": {
|
||||
"none": "Keine Kompatibilität",
|
||||
"pre14": "Vor 1.4 kompatibel",
|
||||
"pre15": "Vor 1.5 kompatibel",
|
||||
"pre16": "Vor 1.6 kompatibel",
|
||||
"pre17": "Vor 1.7 kompatibel",
|
||||
"with17": "Mit 1.7 kompatibel"
|
||||
},
|
||||
"enabledGZip": "GZip Compression aktivieren",
|
||||
"enabledGZipHelpText": "Aktiviert GZip Kompression für SVN Responses",
|
||||
"disabled": "Deaktiviert",
|
||||
"disabledHelpText": "Aktiviert oder deaktiviert das SVN Plugin",
|
||||
"required": "Dieser Konfigurationswert wird benötigt"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"configuration": {
|
||||
"read": {
|
||||
"svn": {
|
||||
"displayName": "Subversion Konfiguration lesen",
|
||||
"description": "Darf die Subversion Konfiguration lesen"
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"svn": {
|
||||
"displayName": "Subversion Konfiguration schreiben",
|
||||
"description": "Darf die Subversion Konfiguration verändern"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"link": "Subversion",
|
||||
"title": "Subversion Configuration",
|
||||
"compatibility": "Version Compatibility",
|
||||
"compatibilityHelpText": "Specifies with which subversion version repositories are compatible.",
|
||||
"compatibilityHelpText": "Specifies with which Subversion version repositories are compatible.",
|
||||
"compatibility-values": {
|
||||
"none": "No compatibility",
|
||||
"pre14": "Pre 1.4 Compatible",
|
||||
@@ -17,9 +17,9 @@
|
||||
"with17": "With 1.7 Compatible"
|
||||
},
|
||||
"enabledGZip": "Enable GZip Compression",
|
||||
"enabledGZipHelpText": "Enable GZip compression for svn responses.",
|
||||
"enabledGZipHelpText": "Enable GZip compression for SVN responses.",
|
||||
"disabled": "Disabled",
|
||||
"disabledHelpText": "Enable or disable the Git plugin",
|
||||
"disabledHelpText": "Enable or disable the SVN plugin",
|
||||
"required": "This configuration value is required"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -36,9 +36,9 @@ class ConfigurationBinder {
|
||||
binder.bind("config.navigation", ConfigNavLink, configPredicate);
|
||||
|
||||
// route for global configuration, passes the link from the index resource to component
|
||||
const ConfigRoute = ({ url, links }) => {
|
||||
const ConfigRoute = ({ url, links, ...additionalProps }) => {
|
||||
const link = links[linkName].href;
|
||||
return this.route(url + to, <ConfigurationComponent link={link}/>);
|
||||
return this.route(url + to, <ConfigurationComponent link={link} {...additionalProps} />);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
@@ -63,9 +63,9 @@ class ConfigurationBinder {
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository}) => {
|
||||
const link = repository._links[linkName].href
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>);
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
|
||||
@@ -48,7 +48,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
<AddButton
|
||||
label={buttonLabel}
|
||||
action={this.addButtonClicked}
|
||||
disabled={disabled}
|
||||
disabled={disabled || this.state.entryToAdd ===""}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -50,8 +50,19 @@ class PrimaryNavigation extends React.Component<Props> {
|
||||
|
||||
createNavigationItems = () => {
|
||||
const navigationItems = [];
|
||||
const { t, links } = this.props;
|
||||
|
||||
const props = {
|
||||
links,
|
||||
label: t("primary-navigation.first-menu")
|
||||
};
|
||||
|
||||
const append = this.createNavigationAppender(navigationItems);
|
||||
if (binder.hasExtension("primary-navigation.first-menu", props)) {
|
||||
navigationItems.push(
|
||||
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
|
||||
);
|
||||
}
|
||||
append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
||||
append("/users", "/(user|users)", "primary-navigation.users", "users");
|
||||
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");
|
||||
|
||||
@@ -10,7 +10,7 @@ import Login from "../containers/Login";
|
||||
import Logout from "../containers/Logout";
|
||||
|
||||
import { ProtectedRoute } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import {binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
|
||||
import AddUser from "../users/containers/AddUser";
|
||||
import SingleUser from "../users/containers/SingleUser";
|
||||
@@ -32,10 +32,15 @@ type Props = {
|
||||
class Main extends React.Component<Props> {
|
||||
render() {
|
||||
const { authenticated, links } = this.props;
|
||||
const redirectUrlFactory = binder.getExtension("main.redirect", this.props);
|
||||
let url ="/repos";
|
||||
if (redirectUrlFactory){
|
||||
url = redirectUrlFactory(this.props);
|
||||
}
|
||||
return (
|
||||
<div className="main">
|
||||
<Switch>
|
||||
<Redirect exact path="/" to="/repos" />
|
||||
<Redirect exact path="/" to={url}/>
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route path="/logout" component={Logout} />
|
||||
<ProtectedRoute
|
||||
|
||||
@@ -21,7 +21,7 @@ import ChangesetView from "./ChangesetView";
|
||||
import PermissionsNavLink from "../components/PermissionsNavLink";
|
||||
import Sources from "../sources/containers/Sources";
|
||||
import RepositoryNavLink from "../components/RepositoryNavLink";
|
||||
import {getRepositoriesLink} from "../../modules/indexResource";
|
||||
import {getLinks, getRepositoriesLink} from "../../modules/indexResource";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = {
|
||||
@@ -31,6 +31,7 @@ type Props = {
|
||||
loading: boolean,
|
||||
error: Error,
|
||||
repoLink: string,
|
||||
indexLinks: Object,
|
||||
|
||||
// dispatch functions
|
||||
fetchRepoByName: (link: string, namespace: string, name: string) => void,
|
||||
@@ -75,7 +76,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, error, repository, t } = this.props;
|
||||
const { loading, error, indexLinks, repository, t } = this.props;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@@ -95,7 +96,8 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
|
||||
const extensionProps = {
|
||||
repository,
|
||||
url
|
||||
url,
|
||||
indexLinks
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -216,13 +218,15 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const loading = isFetchRepoPending(state, namespace, name);
|
||||
const error = getFetchRepoFailure(state, namespace, name);
|
||||
const repoLink = getRepositoriesLink(state);
|
||||
const indexLinks = getLinks(state);
|
||||
return {
|
||||
namespace,
|
||||
name,
|
||||
repository,
|
||||
loading,
|
||||
error,
|
||||
repoLink
|
||||
repoLink,
|
||||
indexLinks
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -12,8 +13,7 @@ public class BranchDto extends HalRepresentation {
|
||||
private String name;
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
BranchDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
|
||||
@@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.linkBuilder;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper {
|
||||
public abstract class BranchToBranchDtoMapper extends HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -23,16 +23,17 @@ public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper {
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Branch source, @MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) {
|
||||
@ObjectFactory
|
||||
BranchDto createDto(@Context NamespaceAndName namespaceAndName, Branch branch) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
|
||||
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
|
||||
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
|
||||
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
|
||||
.self(resourceLinks.branch().self(namespaceAndName, branch.getName()))
|
||||
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, branch.getName())).build())
|
||||
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build())
|
||||
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build());
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), source, namespaceAndName);
|
||||
Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), branch, namespaceAndName);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new BranchDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ public class ConfigDto extends HalRepresentation {
|
||||
private boolean disableGroupingGrid;
|
||||
private String dateFormat;
|
||||
private boolean anonymousAccessEnabled;
|
||||
@NoBlankStrings
|
||||
private Set<String> adminGroups;
|
||||
@NoBlankStrings
|
||||
private Set<String> adminUsers;
|
||||
private String baseUrl;
|
||||
private boolean forceBaseUrl;
|
||||
|
||||
@@ -9,6 +9,7 @@ import sonia.scm.util.ScmConfigurationUtil;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
@@ -71,7 +72,7 @@ public class ConfigResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(ConfigDto configDto) {
|
||||
public Response update(@Valid ConfigDto configDto) {
|
||||
|
||||
// This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes.
|
||||
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -19,11 +19,12 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper{
|
||||
|
||||
@Inject
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@@ -31,7 +32,6 @@ public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper i
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
|
||||
@Inject
|
||||
private BranchCollectionToDtoMapper branchCollectionToDtoMapper;
|
||||
|
||||
@@ -46,31 +46,35 @@ public abstract class ChangesetToChangesetDtoMapper extends LinkAppenderMapper i
|
||||
public abstract ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Changeset source, @MappingTarget ChangesetDto target, @Context Repository repository) {
|
||||
@ObjectFactory
|
||||
ChangesetDto createDto(@Context Repository repository, Changeset source) {
|
||||
String namespace = repository.getNamespace();
|
||||
String name = repository.getName();
|
||||
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
|
||||
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
||||
if (repositoryService.isSupported(Command.TAGS)) {
|
||||
target.withEmbedded("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
|
||||
embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
|
||||
getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId()))));
|
||||
}
|
||||
if (repositoryService.isSupported(Command.BRANCHES)) {
|
||||
target.withEmbedded("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
|
||||
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
|
||||
getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId()))));
|
||||
}
|
||||
}
|
||||
target.withEmbedded("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository)));
|
||||
embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository)));
|
||||
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), target.getId()))
|
||||
.single(link("diff", resourceLinks.diff().self(namespace, name, target.getId())))
|
||||
.single(link("modifications", resourceLinks.modifications().self(namespace, name, target.getId())));
|
||||
.self(resourceLinks.changeset().self(repository.getNamespace(), repository.getName(), source.getId()))
|
||||
.single(link("diff", resourceLinks.diff().self(namespace, name, source.getId())))
|
||||
.single(link("sources", resourceLinks.source().self(namespace, name, source.getId())))
|
||||
.single(link("modifications", resourceLinks.modifications().self(namespace, name, source.getId())));
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), source, repository);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), source, repository);
|
||||
|
||||
return new ChangesetDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private <T> List<T> getListOfObjects(List<String> list, Function<String, T> mapFunction) {
|
||||
@@ -1,27 +1,36 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class EdisonLinkAppender implements LinkAppender {
|
||||
class EdisonHalAppender implements HalAppender {
|
||||
|
||||
private final Links.Builder builder;
|
||||
private final Links.Builder linkBuilder;
|
||||
private final Embedded.Builder embeddedBuilder;
|
||||
|
||||
EdisonLinkAppender(Links.Builder builder) {
|
||||
this.builder = builder;
|
||||
EdisonHalAppender(Links.Builder linkBuilder, Embedded.Builder embeddedBuilder) {
|
||||
this.linkBuilder = linkBuilder;
|
||||
this.embeddedBuilder = embeddedBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendOne(String rel, String href) {
|
||||
builder.single(Link.link(rel, href));
|
||||
public void appendLink(String rel, String href) {
|
||||
linkBuilder.single(Link.link(rel, href));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkArrayBuilder arrayBuilder(String rel) {
|
||||
return new EdisonLinkArrayBuilder(builder, rel);
|
||||
public LinkArrayBuilder linkArrayBuilder(String rel) {
|
||||
return new EdisonLinkArrayBuilder(linkBuilder, rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendEmbedded(String rel, HalRepresentation embedded) {
|
||||
embeddedBuilder.with(rel, embedded);
|
||||
}
|
||||
|
||||
private static class EdisonLinkArrayBuilder implements LinkArrayBuilder {
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -27,10 +28,8 @@ public class FileObjectDto extends HalRepresentation {
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
public FileObjectDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
public void setChildren(List<FileObjectDto> children) {
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
|
||||
@Mapper
|
||||
public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper implements InstantAttributeMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -25,20 +26,21 @@ public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper
|
||||
|
||||
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
|
||||
|
||||
@AfterMapping
|
||||
void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) {
|
||||
@ObjectFactory
|
||||
FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context String revision, FileObject fileObject) {
|
||||
String path = removeFirstSlash(fileObject.getPath());
|
||||
Links.Builder links = Links.linkingTo();
|
||||
if (dto.isDirectory()) {
|
||||
if (fileObject.isDirectory()) {
|
||||
links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
|
||||
} else {
|
||||
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
|
||||
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(links), fileObject, namespaceAndName, revision);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, revision);
|
||||
|
||||
dto.add(links.build());
|
||||
return new FileObjectDto(links.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private String removeFirstSlash(String source) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -27,13 +28,7 @@ public class GroupDto extends HalRepresentation {
|
||||
private Map<String, String> properties;
|
||||
private List<String> members;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
|
||||
public HalRepresentation withMembers(List<MemberDto> members) {
|
||||
return super.withEmbedded("members", members);
|
||||
GroupDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupPermissions;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
@@ -12,6 +12,7 @@ import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@@ -23,28 +24,26 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Group group, @MappingTarget GroupDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(target.getName()));
|
||||
@ObjectFactory
|
||||
GroupDto createDto(Group group) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(group.getName()));
|
||||
if (GroupPermissions.delete(group).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.group().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.group().delete(group.getName())));
|
||||
}
|
||||
if (GroupPermissions.modify(group).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.group().update(group.getName())));
|
||||
}
|
||||
if (PermissionPermissions.read().isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(group.getName())));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), group);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void mapMembers(Group group, @MappingTarget GroupDto target) {
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
List<MemberDto> memberDtos = group.getMembers().stream().map(this::createMember).collect(Collectors.toList());
|
||||
target.withMembers(memberDtos);
|
||||
embeddedBuilder.with("members", memberDtos);
|
||||
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), group);
|
||||
|
||||
return new GroupDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private MemberDto createMember(String name) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -9,8 +10,8 @@ public class IndexDto extends HalRepresentation {
|
||||
|
||||
private final String version;
|
||||
|
||||
IndexDto(String version, Links links) {
|
||||
super(links);
|
||||
IndexDto(Links links, Embedded embedded, String version) {
|
||||
super(links, embedded);
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -13,9 +14,10 @@ import sonia.scm.user.UserPermissions;
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
|
||||
public class IndexDtoGenerator extends LinkAppenderMapper {
|
||||
public class IndexDtoGenerator extends HalAppenderMapper {
|
||||
|
||||
private final ResourceLinks resourceLinks;
|
||||
private final SCMContextProvider scmContextProvider;
|
||||
@@ -61,8 +63,9 @@ public class IndexDtoGenerator extends LinkAppenderMapper {
|
||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(builder), new Index());
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(builder, embeddedBuilder), new Index());
|
||||
|
||||
return new IndexDto(scmContextProvider.getVersion(), builder.build());
|
||||
return new IndexDto(builder.build(), embeddedBuilder.build(), scmContextProvider.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,30 @@ import javax.servlet.ServletContextListener;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Registers every {@link LinkEnricher} which is annotated with an {@link Enrich} annotation.
|
||||
* Registers every {@link HalEnricher} which is annotated with an {@link Enrich} annotation.
|
||||
*/
|
||||
@Extension
|
||||
public class LinkEnricherAutoRegistration implements ServletContextListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LinkEnricherAutoRegistration.class);
|
||||
|
||||
private final LinkEnricherRegistry registry;
|
||||
private final Set<LinkEnricher> enrichers;
|
||||
private final HalEnricherRegistry registry;
|
||||
private final Set<HalEnricher> enrichers;
|
||||
|
||||
@Inject
|
||||
public LinkEnricherAutoRegistration(LinkEnricherRegistry registry, Set<LinkEnricher> enrichers) {
|
||||
public LinkEnricherAutoRegistration(HalEnricherRegistry registry, Set<HalEnricher> enrichers) {
|
||||
this.registry = registry;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
for (LinkEnricher enricher : enrichers) {
|
||||
for (HalEnricher enricher : enrichers) {
|
||||
Enrich annotation = enricher.getClass().getAnnotation(Enrich.class);
|
||||
if (annotation != null) {
|
||||
registry.register(annotation.value(), enricher);
|
||||
} else {
|
||||
LOG.warn("found LinkEnricher extension {} without Enrich annotation", enricher.getClass());
|
||||
LOG.warn("found HalEnricher extension {} without Enrich annotation", enricher.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class MapperModule extends AbstractModule {
|
||||
bind(RepositoryPermissionDtoToRepositoryPermissionMapper.class).to(Mappers.getMapper(RepositoryPermissionDtoToRepositoryPermissionMapper.class).getClass());
|
||||
bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass());
|
||||
|
||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass());
|
||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(DefaultChangesetToChangesetDtoMapper.class).getClass());
|
||||
bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass());
|
||||
|
||||
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -18,9 +19,7 @@ public class MeDto extends HalRepresentation {
|
||||
private String mail;
|
||||
private List<String> groups;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
MeDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
@@ -13,10 +14,11 @@ import sonia.scm.user.UserPermissions;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
public class MeDtoFactory extends LinkAppenderMapper {
|
||||
public class MeDtoFactory extends HalAppenderMapper {
|
||||
|
||||
private final ResourceLinks resourceLinks;
|
||||
private final UserManager userManager;
|
||||
@@ -29,15 +31,11 @@ public class MeDtoFactory extends LinkAppenderMapper {
|
||||
|
||||
public MeDto create() {
|
||||
PrincipalCollection principals = getPrincipalCollection();
|
||||
|
||||
MeDto dto = new MeDto();
|
||||
|
||||
User user = principals.oneByType(User.class);
|
||||
|
||||
MeDto dto = createDto(user);
|
||||
mapUserProperties(user, dto);
|
||||
mapGroups(principals, dto);
|
||||
|
||||
appendLinks(user, dto);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -61,21 +59,22 @@ public class MeDtoFactory extends LinkAppenderMapper {
|
||||
}
|
||||
|
||||
|
||||
private void appendLinks(User user, MeDto target) {
|
||||
private MeDto createDto(User user) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName())));
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.me().update(user.getName())));
|
||||
}
|
||||
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
|
||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), new Me(), user);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new MeDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Constraint(validatedBy = NoBlankStringsValidator.class)
|
||||
@Documented
|
||||
public @interface NoBlankStrings {
|
||||
|
||||
String message() default "collection must not contain empty strings";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Collection;
|
||||
|
||||
public class NoBlankStringsValidator implements ConstraintValidator<NoBlankStrings, Collection> {
|
||||
|
||||
@Override
|
||||
public void initialize(NoBlankStrings constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Collection object, ConstraintValidatorContext constraintContext) {
|
||||
if ( object == null || object.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return object.stream()
|
||||
.map(x -> x.toString())
|
||||
.map(s -> ((String) s).trim())
|
||||
.noneMatch(s -> ((String) s).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
@@ -13,7 +15,7 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter @Setter
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class RepositoryDto extends HalRepresentation {
|
||||
|
||||
@Email
|
||||
@@ -31,9 +33,7 @@ public class RepositoryDto extends HalRepresentation {
|
||||
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);
|
||||
RepositoryDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.HealthCheckFailure;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -17,6 +17,7 @@ import sonia.scm.repository.api.ScmProtocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@@ -33,17 +34,17 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
|
||||
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()));
|
||||
@ObjectFactory
|
||||
RepositoryDto createDto(Repository repository) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
|
||||
if (RepositoryPermissions.delete(repository).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.repository().delete(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (RepositoryPermissions.modify(repository).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (RepositoryPermissions.permissionRead(repository).isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
||||
if (RepositoryPermissions.pull(repository).isPermitted()) {
|
||||
@@ -53,26 +54,27 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
linksBuilder.array(protocolLinks);
|
||||
}
|
||||
if (repositoryService.isSupported(Command.TAGS)) {
|
||||
linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("tags", resourceLinks.tag().all(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Command.BRANCHES)) {
|
||||
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Feature.INCOMING_REVISION)) {
|
||||
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Command.MERGE)) {
|
||||
linksBuilder.single(link("merge", resourceLinks.merge().merge(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("merge", resourceLinks.merge().merge(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
}
|
||||
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("changesets", resourceLinks.changeset().all(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(repository.getNamespace(), repository.getName())));
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), repository);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repository);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private Link createProtocolLink(ScmProtocol protocol) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -15,10 +16,8 @@ public class TagDto extends HalRepresentation {
|
||||
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
TagDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Tag;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class TagToTagDtoMapper extends LinkAppenderMapper {
|
||||
public abstract class TagToTagDtoMapper extends HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -23,15 +24,16 @@ public abstract class TagToTagDtoMapper extends LinkAppenderMapper {
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Tag tag, @MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) {
|
||||
@ObjectFactory
|
||||
TagDto createDto(@Context NamespaceAndName namespaceAndName, Tag tag) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()))
|
||||
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
|
||||
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
|
||||
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName()))
|
||||
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())))
|
||||
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())));
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), tag, namespaceAndName);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), tag, namespaceAndName);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new TagDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -33,9 +34,7 @@ public class UserDto extends HalRepresentation {
|
||||
private String type;
|
||||
private 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);
|
||||
UserDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
@@ -12,6 +12,7 @@ import sonia.scm.user.UserPermissions;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@@ -31,25 +32,26 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@AfterMapping
|
||||
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
|
||||
@ObjectFactory
|
||||
UserDto createDto(User user) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(user.getName()));
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.user().delete(user.getName())));
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
|
||||
}
|
||||
}
|
||||
if (PermissionPermissions.read().isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName())));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), user);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), user);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new UserDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,8 +78,9 @@ public class I18nServlet extends HttpServlet {
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse response) {
|
||||
try (PrintWriter out = response.getWriter()) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
try (PrintWriter out = response.getWriter()) {
|
||||
String path = req.getServletPath();
|
||||
Function<String, Optional<JsonNode>> jsonFileProvider = usedPath -> Optional.empty();
|
||||
BiConsumer<String, JsonNode> createdJsonFileConsumer = (usedPath, jsonNode) -> log.debug("A json File is created from the path {}", usedPath);
|
||||
|
||||
81
scm-webapp/src/main/resources/locales/de/plugins.json
Normal file
81
scm-webapp/src/main/resources/locales/de/plugins.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"permissions": {
|
||||
"repository": {
|
||||
"read,pull": {
|
||||
"*": {
|
||||
"displayName": "Alle Repositories lesen",
|
||||
"description": "Darf alle Repositories lesen und klonen."
|
||||
}
|
||||
},
|
||||
"read,pull,push": {
|
||||
"*": {
|
||||
"displayName": "Alle Repositories schreiben",
|
||||
"description": "Darf alle Repositories lesen, klonen und schreiben."
|
||||
}
|
||||
},
|
||||
"*": {
|
||||
"*": {
|
||||
"displayName": "Alle Repositories besitzen (Owner)",
|
||||
"description": "Darf alle Repositories lesen, klonen, schreiben, konfigurieren und löschen."
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"displayName": "Repositories erstellen",
|
||||
"description": "Darf Repositories erstellen."
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"*": {
|
||||
"displayName": "Benutzer administrieren",
|
||||
"description": "Darf Benutzer administrieren."
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"*": {
|
||||
"displayName": "Gruppen administrieren",
|
||||
"description": "Darf Gruppen administrieren."
|
||||
}
|
||||
},
|
||||
"unknown": "Unbekannte Berechtigung"
|
||||
},
|
||||
"verbs": {
|
||||
"repository": {
|
||||
"read": {
|
||||
"displayName": "Lesen",
|
||||
"description": "Darf das Repository im SCM-Manager sehen."
|
||||
},
|
||||
"modify": {
|
||||
"displayName": "Modifizieren",
|
||||
"description": "Darf die Eigenschaften des Repository verändern."
|
||||
},
|
||||
"delete": {
|
||||
"displayName": "Löschen",
|
||||
"description": "Darf das Repository löschen."
|
||||
},
|
||||
"pull": {
|
||||
"displayName": "Pull/Checkout",
|
||||
"description": "Darf pull/checkout auf das Repository ausführen."
|
||||
},
|
||||
"push": {
|
||||
"displayName": "Push/Commit",
|
||||
"description": "Darf push/commit auf das Repository ausführen und damit den Inhalt verändern."
|
||||
},
|
||||
"permissionRead": {
|
||||
"displayName": "Berechtigungen lesen",
|
||||
"description": "Darf die Berechtigungen des Repository sehen."
|
||||
},
|
||||
"permissionWrite": {
|
||||
"displayName": "Berechtigungen modifizieren",
|
||||
"description": "Darf die Berechtigungen des Repository bearbeiten."
|
||||
},
|
||||
"healthCheck": {
|
||||
"displayName": "Health Check",
|
||||
"description": "Darf den Repository Health Check ausführen."
|
||||
},
|
||||
"*": {
|
||||
"displayName": "Alle Repository Rechte",
|
||||
"description": "Darf im Repository Kontext alles ausführen. Dies beinhaltet alle Repository Berechtigungen."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
@@ -24,12 +24,12 @@ class BranchToBranchDtoMapperTest {
|
||||
|
||||
@Test
|
||||
void shouldAppendLinks() {
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(Branch.class, (ctx, appender) -> {
|
||||
NamespaceAndName namespaceAndName = ctx.oneRequireByType(NamespaceAndName.class);
|
||||
Branch branch = ctx.oneRequireByType(Branch.class);
|
||||
|
||||
appender.appendOne("ka", "http://" + namespaceAndName.logString() + "/" + branch.getName());
|
||||
appender.appendLink("ka", "http://" + namespaceAndName.logString() + "/" + branch.getName());
|
||||
});
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ChangesetCollectionToDtoMapperTest {
|
||||
|
||||
public static final Repository REPOSITORY = new Repository("", "git", "space", "name");
|
||||
public static final Changeset CHANGESET = new Changeset();
|
||||
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper = mock(ChangesetToChangesetDtoMapper.class);
|
||||
private final DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper = mock(DefaultChangesetToChangesetDtoMapperImpl.class);
|
||||
|
||||
private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, ResourceLinksMock.createMock(URI.create("/")));
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
||||
|
||||
@InjectMocks
|
||||
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private ChangesetRootResource changesetRootResource;
|
||||
|
||||
|
||||
@@ -93,11 +93,7 @@ public class ConfigResourceTest {
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldUpdateConfig() throws URISyntaxException, IOException {
|
||||
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
.content(configJson);
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
@@ -114,11 +110,7 @@ public class ConfigResourceTest {
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
.content(configJson);
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:global]");
|
||||
@@ -126,6 +118,36 @@ public class ConfigResourceTest {
|
||||
dispatcher.invoke(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldFailForEmptyAdminUsers() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-user.json");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldFailForEmptyAdminGroups() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-empty-admin-group.json");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||
}
|
||||
|
||||
private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException {
|
||||
URL url = Resources.getResource(resourceName);
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
return MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
.content(configJson);
|
||||
}
|
||||
|
||||
private static ScmConfiguration createConfiguration() {
|
||||
ScmConfiguration scmConfiguration = new ScmConfiguration();
|
||||
scmConfiguration.setProxyPassword("heartOfGold");
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class EdisonHalAppenderTest {
|
||||
|
||||
private Links.Builder linksBuilder;
|
||||
private Embedded.Builder embeddedBuilder;
|
||||
private EdisonHalAppender appender;
|
||||
|
||||
@BeforeEach
|
||||
void prepare() {
|
||||
linksBuilder = linkingTo();
|
||||
embeddedBuilder = embeddedBuilder();
|
||||
appender = new EdisonHalAppender(linksBuilder, embeddedBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendOneLink() {
|
||||
appender.appendLink("self", "https://scm.hitchhiker.com");
|
||||
|
||||
Links links = linksBuilder.build();
|
||||
assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMultipleLinks() {
|
||||
appender.linkArrayBuilder("items")
|
||||
.append("one", "http://one")
|
||||
.append("two", "http://two")
|
||||
.build();
|
||||
|
||||
List<Link> items = linksBuilder.build().getLinksBy("items");
|
||||
assertThat(items).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendEmbedded() {
|
||||
HalRepresentation one = new HalRepresentation();
|
||||
appender.appendEmbedded("one", one);
|
||||
|
||||
HalRepresentation two = new HalRepresentation();
|
||||
appender.appendEmbedded("two", new HalRepresentation());
|
||||
|
||||
Embedded embedded = embeddedBuilder.build();
|
||||
assertThat(embedded.getItemsBy("one")).containsOnly(one);
|
||||
assertThat(embedded.getItemsBy("two")).containsOnly(two);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class EdisonLinkAppenderTest {
|
||||
|
||||
private Links.Builder builder;
|
||||
private EdisonLinkAppender appender;
|
||||
|
||||
@BeforeEach
|
||||
void prepare() {
|
||||
builder = linkingTo();
|
||||
appender = new EdisonLinkAppender(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendOneLink() {
|
||||
appender.appendOne("self", "https://scm.hitchhiker.com");
|
||||
|
||||
Links links = builder.build();
|
||||
assertThat(links.getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMultipleLinks() {
|
||||
appender.arrayBuilder("items")
|
||||
.append("one", "http://one")
|
||||
.append("two", "http://two")
|
||||
.build();
|
||||
|
||||
List<Link> items = builder.build().getLinksBy("items");
|
||||
assertThat(items).hasSize(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
|
||||
private FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper;
|
||||
|
||||
@InjectMocks
|
||||
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private FileHistoryRootResource fileHistoryRootResource;
|
||||
|
||||
|
||||
@@ -73,13 +73,13 @@ public class FileObjectToFileObjectDtoMapperTest {
|
||||
|
||||
@Test
|
||||
public void shouldAppendLinks() {
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(FileObject.class, (ctx, appender) -> {
|
||||
NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class);
|
||||
FileObject fo = ctx.oneRequireByType(FileObject.class);
|
||||
String rev = ctx.oneRequireByType(String.class);
|
||||
|
||||
appender.appendOne("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev);
|
||||
appender.appendLink("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev);
|
||||
});
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ public class GroupToGroupDtoMapperTest {
|
||||
|
||||
@Test
|
||||
public void shouldAppendLinks() {
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(Group.class, (ctx, appender) -> {
|
||||
Group group = ctx.oneRequireByType(Group.class);
|
||||
appender.appendOne("some", "http://" + group.getName());
|
||||
appender.appendLink("some", "http://" + group.getName());
|
||||
});
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
|
||||
class HalEnricherAutoRegistrationTest {
|
||||
|
||||
@Test
|
||||
void shouldRegisterAllAvailableLinkEnrichers() {
|
||||
HalEnricher one = new One();
|
||||
HalEnricher two = new Two();
|
||||
HalEnricher three = new Three();
|
||||
HalEnricher four = new Four();
|
||||
Set<HalEnricher> enrichers = ImmutableSet.of(one, two, three, four);
|
||||
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
|
||||
LinkEnricherAutoRegistration autoRegistration = new LinkEnricherAutoRegistration(registry, enrichers);
|
||||
autoRegistration.contextInitialized(null);
|
||||
|
||||
assertThat(registry.allByType(String.class)).containsOnly(one, two);
|
||||
assertThat(registry.allByType(Integer.class)).containsOnly(three);
|
||||
}
|
||||
|
||||
@Enrich(String.class)
|
||||
public static class One implements HalEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Enrich(String.class)
|
||||
public static class Two implements HalEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Enrich(Integer.class)
|
||||
public static class Three implements HalEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class Four implements HalEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(HalEnricherContext context, HalAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
|
||||
private IncomingChangesetCollectionToDtoMapper incomingChangesetCollectionToDtoMapper;
|
||||
|
||||
@InjectMocks
|
||||
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
private IncomingRootResource incomingRootResource;
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
|
||||
class LinkEnricherAutoRegistrationTest {
|
||||
|
||||
@Test
|
||||
void shouldRegisterAllAvailableLinkEnrichers() {
|
||||
LinkEnricher one = new One();
|
||||
LinkEnricher two = new Two();
|
||||
LinkEnricher three = new Three();
|
||||
LinkEnricher four = new Four();
|
||||
Set<LinkEnricher> enrichers = ImmutableSet.of(one, two, three, four);
|
||||
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
|
||||
LinkEnricherAutoRegistration autoRegistration = new LinkEnricherAutoRegistration(registry, enrichers);
|
||||
autoRegistration.contextInitialized(null);
|
||||
|
||||
assertThat(registry.allByType(String.class)).containsOnly(one, two);
|
||||
assertThat(registry.allByType(Integer.class)).containsOnly(three);
|
||||
}
|
||||
|
||||
@Enrich(String.class)
|
||||
public static class One implements LinkEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Enrich(String.class)
|
||||
public static class Two implements LinkEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Enrich(Integer.class)
|
||||
public static class Three implements LinkEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class Four implements LinkEnricher {
|
||||
|
||||
@Override
|
||||
public void enrich(LinkEnricherContext context, LinkAppender appender) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -169,12 +169,12 @@ class MeDtoFactoryTest {
|
||||
void shouldAppendLinks() {
|
||||
prepareSubject(UserTestData.createTrillian());
|
||||
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
meDtoFactory.setRegistry(registry);
|
||||
|
||||
registry.register(Me.class, (ctx, appender) -> {
|
||||
User user = ctx.oneRequireByType(User.class);
|
||||
appender.appendOne("profile", "http://hitchhiker.com/users/" + user.getName());
|
||||
appender.appendLink("profile", "http://hitchhiker.com/users/" + user.getName());
|
||||
});
|
||||
|
||||
MeDto dto = meDtoFactory.create();
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class NoBlankStringsValidatorTest {
|
||||
|
||||
@Test
|
||||
void shouldAcceptNonEmptyElements() {
|
||||
assertTrue(new NoBlankStringsValidator().isValid(Arrays.asList("not", "empty"), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailForEmptyElements() {
|
||||
assertFalse(new NoBlankStringsValidator().isValid(Arrays.asList("one", "", "three"), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAcceptEmptyList() {
|
||||
assertTrue(new NoBlankStringsValidator().isValid(emptySet(), null));
|
||||
}
|
||||
}
|
||||
@@ -211,10 +211,10 @@ public class RepositoryToRepositoryDtoMapperTest {
|
||||
|
||||
@Test
|
||||
public void shouldAppendLinks() {
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(Repository.class, (ctx, appender) -> {
|
||||
Repository repository = ctx.oneRequireByType(Repository.class);
|
||||
appender.appendOne("id", "http://" + repository.getId());
|
||||
appender.appendLink("id", "http://" + repository.getId());
|
||||
});
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ class TagToTagDtoMapperTest {
|
||||
|
||||
@Test
|
||||
void shouldAppendLinks() {
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(Tag.class, (ctx, appender) -> {
|
||||
NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class);
|
||||
Tag tag = ctx.oneRequireByType(Tag.class);
|
||||
appender.appendOne("yo", "http://" + repository.logString() + "/" + tag.getName());
|
||||
appender.appendLink("yo", "http://" + repository.logString() + "/" + tag.getName());
|
||||
});
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
|
||||
@@ -155,8 +155,8 @@ public class UserToUserDtoMapperTest {
|
||||
public void shouldAppendLink() {
|
||||
User trillian = UserTestData.createTrillian();
|
||||
|
||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||
registry.register(User.class, (ctx, appender) -> appender.appendOne("sample", "http://" + ctx.oneByType(User.class).get().getName()));
|
||||
HalEnricherRegistry registry = new HalEnricherRegistry();
|
||||
registry.register(User.class, (ctx, appender) -> appender.appendLink("sample", "http://" + ctx.oneByType(User.class).get().getName()));
|
||||
mapper.setRegistry(registry);
|
||||
|
||||
UserDto userDto = mapper.map(trillian);
|
||||
|
||||
@@ -136,10 +136,13 @@ public class GitLfsITCase {
|
||||
}
|
||||
|
||||
private void createUser(User user) {
|
||||
UserDto dto = new UserToUserDtoMapperImpl(){
|
||||
@Override
|
||||
protected void appendLinks(User user, UserDto target) {}
|
||||
}.map(user);
|
||||
UserDto dto = new UserDto();
|
||||
dto.setName(user.getName());
|
||||
dto.setMail(user.getMail());
|
||||
dto.setDisplayName(user.getDisplayName());
|
||||
dto.setType(user.getType());
|
||||
dto.setActive(user.isActive());
|
||||
dto.setAdmin(user.isAdmin());
|
||||
dto.setPassword(user.getPassword());
|
||||
createResource(adminClient, "users")
|
||||
.accept("*/*")
|
||||
|
||||
@@ -2,8 +2,6 @@ package sonia.scm.web.i18n;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -40,12 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
|
||||
public class I18nServletTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private static final String GIT_PLUGIN_JSON = json(
|
||||
"{",
|
||||
"'scm-git-plugin': {",
|
||||
@@ -86,15 +80,15 @@ public class I18nServletTest {
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Mock
|
||||
PluginLoader pluginLoader;
|
||||
private PluginLoader pluginLoader;
|
||||
|
||||
@Mock
|
||||
CacheManager cacheManager;
|
||||
private CacheManager cacheManager;
|
||||
|
||||
@Mock
|
||||
ClassLoader classLoader;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
I18nServlet servlet;
|
||||
private I18nServlet servlet;
|
||||
|
||||
@Mock
|
||||
private Cache cache;
|
||||
@@ -104,9 +98,9 @@ public class I18nServletTest {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void init() throws IOException {
|
||||
resources = Collections.enumeration(Lists.newArrayList(
|
||||
createFileFromString(SVN_PLUGIN_JSON).toURL(),
|
||||
createFileFromString(GIT_PLUGIN_JSON).toURL(),
|
||||
createFileFromString(HG_PLUGIN_JSON).toURL()
|
||||
createFileFromString(SVN_PLUGIN_JSON).toURI().toURL(),
|
||||
createFileFromString(GIT_PLUGIN_JSON).toURI().toURL(),
|
||||
createFileFromString(HG_PLUGIN_JSON).toURI().toURL()
|
||||
));
|
||||
when(pluginLoader.getUberClassLoader()).thenReturn(classLoader);
|
||||
when(cacheManager.getCache(I18nServlet.CACHE_NAME)).thenReturn(cache);
|
||||
@@ -192,6 +186,8 @@ public class I18nServletTest {
|
||||
assertJson(json);
|
||||
verify(cache).get(path);
|
||||
verify(cache).put(eq(path), any());
|
||||
|
||||
verifyHeaders(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -219,6 +215,8 @@ public class I18nServletTest {
|
||||
verify(cache, never()).put(eq(path), any());
|
||||
verify(cache).get(path);
|
||||
assertJson(json);
|
||||
|
||||
verifyHeaders(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,11 +230,16 @@ public class I18nServletTest {
|
||||
assertJson(jsonNodeOptional.orElse(null));
|
||||
}
|
||||
|
||||
private void verifyHeaders(HttpServletResponse response) {
|
||||
verify(response).setCharacterEncoding("UTF-8");
|
||||
verify(response).setContentType("application/json");
|
||||
}
|
||||
|
||||
public void assertJson(JsonNode actual) throws IOException {
|
||||
assertJson(actual.toString());
|
||||
}
|
||||
|
||||
public void assertJson(String actual) throws IOException {
|
||||
private void assertJson(String actual) throws IOException {
|
||||
assertThat(actual)
|
||||
.isNotEmpty()
|
||||
.contains(StringUtils.deleteWhitespace(GIT_PLUGIN_JSON.substring(1, GIT_PLUGIN_JSON.length() - 1)))
|
||||
@@ -244,7 +247,7 @@ public class I18nServletTest {
|
||||
.contains(StringUtils.deleteWhitespace(SVN_PLUGIN_JSON.substring(1, SVN_PLUGIN_JSON.length() - 1)));
|
||||
}
|
||||
|
||||
public File createFileFromString(String json) throws IOException {
|
||||
private File createFileFromString(String json) throws IOException {
|
||||
File file = temporaryFolder.newFile();
|
||||
Files.write(json.getBytes(Charsets.UTF_8), file);
|
||||
return file;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"adminGroups": [""]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"adminUsers": [""]
|
||||
}
|
||||
Reference in New Issue
Block a user