Skip to content

Commit

Permalink
Merge pull request #299 from xenit-eu/ACC-1761-profile
Browse files Browse the repository at this point in the history
[ACC-1761] Update HAL-FORMS entity profile endpoint with entity information
  • Loading branch information
NielsCW authored Jan 7, 2025
2 parents eee5080 + 9b905db commit 60334b4
Show file tree
Hide file tree
Showing 43 changed files with 1,422 additions and 37 deletions.
1 change: 1 addition & 0 deletions contentgrid-spring-data-rest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {

implementation project(':contentgrid-spring-querydsl')
implementation project(':contentgrid-spring-data-pagination')
implementation project(':contentgrid-spring-data-support')

testAnnotationProcessor project(':contentgrid-spring-boot-starter-annotations')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class CollectionFilterImpl<T> implements CollectionFilter<T> {
@Getter
private final String filterName;

@Getter
private final String filterType;

@Getter
private final Path<T> path;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private Stream<CollectionFilter<?>> createFilter(Property property, CollectionFi
return boundPaths.stream()
.map(boundPath -> new CollectionFilterImpl<>(
prefix+getName(property, filterParam),
predicateFactory.getFilterType(),
boundPath,
filterParam.documented(),
propertyPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ public CollectionFilters forDomainType(Class<?> domainType) {
}

@Override
public Optional<CollectionFilter<?>> forProperty(Class<?> domainType, String... properties) {
public CollectionFilters forProperty(Class<?> domainType, String... properties) {
var persistentEntity = repositories.getPersistentEntity(domainType);

var pathNavigator = createEntityPathNavigatorFor(persistentEntity);
for (String propertyName : properties) {
pathNavigator = pathNavigator.get(propertyName);
}

return forPersistentEntity(persistentEntity).forPath(pathNavigator.getPath()).filters().findFirst();
return forPersistentEntity(persistentEntity).forPath(pathNavigator.getPath());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ protected Optional<Predicate> bindCoerced(Path<Object> path, Collection<?> value
return DEFAULT.bind(path, values);
}

@Override
public String getFilterType() {
return "exact-match";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface Property {

boolean isIgnored();
boolean isRequired();
boolean isUnique();
boolean isReadOnly();

Optional<Container> nestedContainer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public boolean isRequired() {
return delegate.isRequired();
}

@Override
public boolean isUnique() {
return delegate.isUnique();
}

@Override
public boolean isReadOnly() {
return delegate.isReadOnly() || delegate.findAnnotation(JsonProperty.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public boolean isRequired() {
.anyMatch(Predicate.isEqual(Boolean.FALSE));
}

@Override
public boolean isUnique() {
return findAnnotation(Column.class).map(Column::unique).orElse(false);
}

@Override
public Optional<Container> nestedContainer() {
if(findAnnotation(Embedded.class).isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public boolean isRequired() {
return delegate.isRequired();
}

@Override
public boolean isUnique() {
return delegate.isUnique();
}

@Override
public boolean isReadOnly() {
return delegate.isReadOnly();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public boolean isRequired() {
return delegate.isRequired();
}

@Override
public boolean isUnique() {
return delegate.isUnique();
}

@Override
public boolean isReadOnly() {
return delegate.isReadOnly();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public boolean isRequired() {
return false;
}

@Override
public boolean isUnique() {
return false;
}

@Override
public Optional<Container> nestedContainer() {
if(findAnnotation(Embedded.class).isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.io.File;
import java.time.Instant;
import java.time.LocalDate;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.hateoas.mediatype.InputTypeFactory;
import org.springframework.hateoas.mediatype.html.HtmlInputType;
Expand All @@ -15,6 +15,7 @@
* <li>{@link Boolean} maps to {@code checkbox}</li>
* <li>{@link Instant} maps to {@code datetime}</li>
* <li>Content maps to {@code file}</li>
* <li>{@link UUID} maps to {@code text}</li>
* </ul>
*/
@Slf4j
Expand All @@ -26,7 +27,7 @@ public String getInputType(Class<?> type) {

HtmlInputType inputType = HtmlInputType.from(type);

if (Boolean.class.equals(type)) {
if (Boolean.class.equals(type) || boolean.class.equals(type)) {
inputType = HtmlInputType.CHECKBOX;
}

Expand All @@ -35,7 +36,11 @@ public String getInputType(Class<?> type) {
}

if (File.class.equals(type)) {
return "file";
inputType = HtmlInputType.FILE;
}

if (UUID.class.equals(type)) {
inputType = HtmlInputType.TEXT;
}

if (inputType == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.contentgrid.spring.data.rest.webmvc;

import com.contentgrid.spring.data.rest.mapping.ContentGridDomainTypeMappingConfiguration;
import com.contentgrid.spring.data.rest.webmvc.blueprint.ContentGridSpringBlueprintConfiguration;
import com.contentgrid.spring.data.rest.webmvc.blueprint.EntityRepresentationModelAssembler;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
Expand All @@ -11,19 +13,23 @@
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;

@Configuration(proxyBeanMethods = false)
@Import(ContentGridDomainTypeMappingConfiguration.class)
@Import({
ContentGridDomainTypeMappingConfiguration.class,
ContentGridSpringBlueprintConfiguration.class
})
public class ContentGridSpringDataRestProfileConfiguration {

@Bean
HalFormsProfileController halFormsProfileController(
RepositoryRestConfiguration repositoryRestConfiguration,
EntityLinks entityLinks,
DomainTypeToHalFormsPayloadMetadataConverter domainTypeToHalFormsPayloadMetadataConverter,
@Qualifier("halFormsJacksonHttpMessageConverter") TypeConstrainedMappingJackson2HttpMessageConverter messageConverter
@Qualifier("halFormsJacksonHttpMessageConverter") TypeConstrainedMappingJackson2HttpMessageConverter messageConverter,
EntityRepresentationModelAssembler entityRepresentationModelAssembler
) {
var objectMapper = messageConverter.getObjectMapper().copy();
return new HalFormsProfileController(repositoryRestConfiguration, entityLinks,
domainTypeToHalFormsPayloadMetadataConverter, objectMapper);
domainTypeToHalFormsPayloadMetadataConverter, objectMapper, entityRepresentationModelAssembler);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.contentgrid.spring.data.rest.webmvc;

import com.contentgrid.spring.data.rest.webmvc.blueprint.EntityRepresentationModelAssembler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
Expand All @@ -13,12 +14,10 @@
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Objects;
import java.util.Random;
import jakarta.servlet.http.HttpServletResponse;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.BasePathAwareController;
Expand All @@ -27,7 +26,6 @@
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.TemplateVariable.VariableType;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.UriTemplate;
Expand All @@ -37,11 +35,9 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@RequiredArgsConstructor
@BasePathAwareController
Expand All @@ -51,6 +47,7 @@ public class HalFormsProfileController implements InitializingBean {
private final EntityLinks entityLinks;
private final DomainTypeToHalFormsPayloadMetadataConverter toHalFormsPayloadMetadataConverter;
private final ObjectMapper objectMapper;
private final EntityRepresentationModelAssembler entityAssembler;

private static final Class<?> HAL_FORMS_TEMPLATE_CLASS;

Expand All @@ -76,7 +73,7 @@ HttpEntity<?> halFormsOptions() {
})
void halFormsProfile(RootResourceInformation information, HttpServletResponse response)
throws IOException {
var model = new RepresentationModel<>();
var model = entityAssembler.toModel(information);

model.add(Link.of(ProfileController.getPath(configuration, information.getResourceMetadata())));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.contentgrid.spring.data.rest.webmvc.blueprint;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
import lombok.Builder;
import lombok.Value;
import org.springframework.hateoas.server.core.Relation;

public sealed interface AttributeConstraintRepresentationModel {

@JsonProperty
String getType();

static AllowedValuesConstraintRepresentationModel allowedValues(List<String> values) {
return AllowedValuesConstraintRepresentationModel.builder()
.values(values)
.build();
}

static RequiredConstraintRepresentationModel required() {
return new RequiredConstraintRepresentationModel();
}

static UniqueConstraintRepresentationModel unique() {
return new UniqueConstraintRepresentationModel();
}

@Builder
@Value
@Relation(BlueprintLinkRelations.CONSTRAINT_STRING)
class AllowedValuesConstraintRepresentationModel implements AttributeConstraintRepresentationModel {

@Builder.Default
List<String> values = new ArrayList<>();

@Override
public String getType() {
return "allowed-values";
}
}

@Relation(BlueprintLinkRelations.CONSTRAINT_STRING)
final class RequiredConstraintRepresentationModel implements AttributeConstraintRepresentationModel {

@Override
public String getType() {
return "required";
}
}

@Relation(BlueprintLinkRelations.CONSTRAINT_STRING)
final class UniqueConstraintRepresentationModel implements AttributeConstraintRepresentationModel {

@Override
public String getType() {
return "unique";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.contentgrid.spring.data.rest.webmvc.blueprint;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import java.util.Collection;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.core.EmbeddedWrapper;
import org.springframework.hateoas.server.core.EmbeddedWrappers;
import org.springframework.hateoas.server.core.Relation;

@Builder
@Getter
@AllArgsConstructor
@Relation(BlueprintLinkRelations.ATTRIBUTE_STRING)
public class AttributeRepresentationModel extends RepresentationModel<AttributeRepresentationModel> {

@NonNull
private final String name;
@JsonInclude(Include.NON_EMPTY)
private final String title;

private final String type;

private final String description;

private final boolean readOnly;

private final boolean required;

@JsonIgnore
@Builder.Default
private final Collection<AttributeConstraintRepresentationModel> constraints = List.of();

@JsonIgnore
@Builder.Default
private final Collection<SearchParamRepresentationModel> searchParams = List.of();

@JsonIgnore
@Builder.Default
private final Collection<AttributeRepresentationModel> attributes = List.of();

@JsonProperty
@JsonUnwrapped
public CollectionModel<EmbeddedWrapper> getEmbeddeds() {
var embeddedWrappers = new EmbeddedWrappers(true);

return CollectionModel.of(List.of(
embeddedWrappers.wrap(constraints, BlueprintLinkRelations.CONSTRAINT),
embeddedWrappers.wrap(searchParams, BlueprintLinkRelations.SEARCH_PARAM),
embeddedWrappers.wrap(attributes, BlueprintLinkRelations.ATTRIBUTE)
));
}

@Builder
@Getter
@AllArgsConstructor
@Relation(BlueprintLinkRelations.SEARCH_PARAM_STRING)
public static class SearchParamRepresentationModel {

String name;
@JsonInclude(Include.NON_EMPTY)
String title;
String type;

}
}
Loading

0 comments on commit 60334b4

Please sign in to comment.