merge with 2.0.0-m3

This commit is contained in:
Sebastian Sdorra
2019-02-05 16:50:14 +01:00
73 changed files with 835 additions and 429 deletions

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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());
}
/**

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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));
}

View File

@@ -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) {
}
}

View File

@@ -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."
}
}
}
}
}

View File

@@ -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"
}
}
}
}
}

View File

@@ -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"

View File

@@ -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());
}
}

View File

@@ -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"
}
}
}
}
}

View File

@@ -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"
}
},

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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");

View File

@@ -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

View File

@@ -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
};
};

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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 {};
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);

View 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."
}
}
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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("/")));

View File

@@ -65,7 +65,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
@InjectMocks
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private ChangesetRootResource changesetRootResource;

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -66,7 +66,7 @@ public class FileHistoryResourceTest extends RepositoryTestBase {
private FileHistoryCollectionToDtoMapper fileHistoryCollectionToDtoMapper;
@InjectMocks
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private FileHistoryRootResource fileHistoryRootResource;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {
}
}
}

View File

@@ -74,7 +74,7 @@ public class IncomingRootResourceTest extends RepositoryTestBase {
private IncomingChangesetCollectionToDtoMapper incomingChangesetCollectionToDtoMapper;
@InjectMocks
private ChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
private IncomingRootResource incomingRootResource;

View File

@@ -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) {
}
}
}

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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("*/*")

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
{
"adminGroups": [""]
}

View File

@@ -0,0 +1,3 @@
{
"adminUsers": [""]
}