de.otto.synapse.edison.state.StateRepositoryRestController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of synapse-edison Show documentation
Show all versions of synapse-edison Show documentation
A library used at otto.de to integrate synapse-core with edison-microservice.
package de.otto.synapse.edison.state;
import com.damnhandy.uri.template.UriTemplate;
import com.google.common.collect.ImmutableMap;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import de.otto.edison.hal.paging.PagingRel;
import de.otto.synapse.journal.Journal;
import de.otto.synapse.journal.JournalRegistry;
import de.otto.synapse.state.StateRepository;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.damnhandy.uri.template.UriTemplate.fromTemplate;
import static com.google.common.collect.Maps.uniqueIndex;
import static de.otto.edison.hal.Link.*;
import static de.otto.edison.hal.Links.emptyLinks;
import static de.otto.edison.hal.Links.linkingTo;
import static de.otto.edison.hal.paging.NumberedPaging.zeroBasedNumberedPaging;
import static java.util.EnumSet.allOf;
import static java.util.stream.Collectors.toList;
import static org.springframework.http.HttpStatus.NOT_FOUND;
@Controller
@ConditionalOnBean(
StateRepository.class)
@ConditionalOnProperty(
prefix = "synapse.edison.state.ui",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
public class StateRepositoryRestController {
private final JournalRegistry journals;
private final ImmutableMap> stateRepositories;
private final String managementBasePath;
public StateRepositoryRestController(final List> stateRepositories,
final JournalRegistry journals,
final EdisonStateRepositoryUiProperties properties,
final @Value("${edison.application.management.base-path:internal}") String managementBasePath) {
this.stateRepositories = uniqueIndex(stateRepositories
.stream()
.filter(repo -> !properties.getExcluded().contains(repo.getName()))
.collect(Collectors.toSet()), StateRepository::getName);
this.journals = journals;
this.managementBasePath = managementBasePath;
}
/**
* Returns an application/hal+json representation of the container-resource containing all staterepositories that
* are not {@link EdisonStateRepositoryUiProperties#getExcluded() excluded} from the state-repository UI.
*
* @param request current HttpServletRequest
* @return HalRepresentation of the collection of state repositories
*/
@GetMapping(
path = "${edison.application.management.base-path:internal}/staterepositories",
produces = {"application/hal+json", "application/json"}
)
@ResponseBody
public HalRepresentation getStateRepositories(final HttpServletRequest request) {
final String selfHref = request.getRequestURL().toString();
final List itemLinks = itemLinks(selfHref);
return new HalRepresentation(
linkingTo()
.self(selfHref)
.array(itemLinks)
.build()
);
}
/**
* Returns an application/hal+json representation of a {@link StateRepository}, containing a pageable collection
* resource with links to the event-sourced entities stored in the repository.
*
* @param repositoryName the name of the StateRepository
* @param page the zero-based page number
* @param pageSize the number of entities to return
* @param uriComponentsBuilder builder used to create hrefs
*
* @return HalRepresentation of the paged collection resource
*/
@GetMapping(
path = "${edison.application.management.base-path:internal}/staterepositories/{repositoryName}",
produces = {"application/hal+json", "application/json"}
)
@ResponseBody
public HalRepresentation getStateRepository(final @PathVariable String repositoryName,
final @RequestParam(defaultValue = "0") int page,
final @RequestParam(defaultValue = "100") int pageSize,
final UriComponentsBuilder uriComponentsBuilder) {
if (stateRepositories.containsKey(repositoryName)) {
final UriComponentsBuilder baseUriBuilder = uriComponentsBuilder
.pathSegment(managementBasePath)
.path("/staterepositories");
final UriTemplate repositoriesUri = fromTemplate(baseUriBuilder.toUriString());
final UriTemplate repositoryUri = fromTemplate(baseUriBuilder.toUriString() + "/" + repositoryName + "{?page,pageSize}");
final UriTemplate entityUri = fromTemplate(baseUriBuilder.toUriString() + "/" + repositoryName + "/{entityId}");
final StateRepository> stateRepository = stateRepositories
.get(repositoryName);
final Set allEntityIds = stateRepository.keySet();
final List entityPageIds = allEntityIds
.stream()
.skip(page * pageSize)
.limit(pageSize)
.collect(toList());
final Links pagingLinks = pageSize > 0
? zeroBasedNumberedPaging(page, pageSize, (int)stateRepository.size()).links(repositoryUri, allOf(PagingRel.class))
: emptyLinks();
final List itemLinks = entityItemLinks(entityUri, entityPageIds);
return new HalRepresentation(
linkingTo()
.with(pagingLinks)
.single(collection(repositoriesUri.expand()))
.array(itemLinks).build()
);
} else {
throw new ResponseStatusException(NOT_FOUND, "No such StateRepository " + repositoryName);
}
}
/**
* Returns an application/hal+json representation of a single event-sourced entity stored in the
* {@link StateRepository} with the specified {@code repositoryName}.
*
* If the state repository has a {@link Journal}, the returned
* entity representation will contain a link to the messages that where leading to the current state of the
* entity.
*
* @param repositoryName the name of the {@code StateRepository}
* @param entityId the id of the requested entity
* @param uriComponentsBuilder builder used to create hrefs
*
* @return HalRepresentation the representation of the entity
*/
@GetMapping(
path = "${edison.application.management.base-path:internal}/staterepositories/{repositoryName}/{entityId}",
produces = {"application/hal+json", "application/json"}
)
@ResponseBody
public HalRepresentation getEntityJson(final @PathVariable String repositoryName,
final @PathVariable String entityId,
final UriComponentsBuilder uriComponentsBuilder) {
final String baseUri = uriComponentsBuilder.pathSegment(managementBasePath).toUriString();
if (stateRepositories.containsKey(repositoryName)) {
final StateRepository> stateRepository = stateRepositories.get(repositoryName);
final Links.Builder links = linkingTo()
.self(
baseUri + "/" + repositoryName + "/staterepositories/" + entityId)
.single(
collection(baseUri + "/staterepositories/" + repositoryName + "{?page,pageSize}"));
if (journals.hasJournal(repositoryName)) {
links.single(
link("working-copy-of", baseUri + "/journals/" + repositoryName + "/" + entityId));
}
return new EntityHalRepresentation(
links.build(),
stateRepository.get(entityId));
} else {
throw new ResponseStatusException(NOT_FOUND, "No such StateRepository " + repositoryName);
}
}
private List entityItemLinks(final UriTemplate uriTemplate,
final List entityIds) {
return entityIds
.stream()
.map(key -> item(uriTemplate.set("entityId", key).expand()))
.collect(toList());
}
private List itemLinks(final String baseHref) {
return this.stateRepositories
.keySet()
.stream()
.sorted()
.map(itemId -> item(itemHref(baseHref, itemId)))
.collect(toList());
}
private String itemHref(final String baseHref,
final String itemId) {
return baseHref.endsWith("/")
? baseHref + itemId
: baseHref + "/" + itemId;
}
}