Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Dec 04 10:12
    reda-alaoui synchronize #1312
  • Nov 16 19:36

    odrotbohm on 2.0.0

    (compare)

  • Nov 16 19:36

    odrotbohm on main

    #1881 - Release version 2.0.0. #1881 - Prepare next developmen… (compare)

  • Nov 16 19:28

    odrotbohm on release

    #1881 - Release version 2.0.0. (compare)

  • Nov 16 19:26

    odrotbohm on main

    #1882 - Fix settings.xml to use… (compare)

  • Nov 16 19:19

    odrotbohm on release

    #1881 - Release version 2.0.0. (compare)

  • Nov 16 19:19

    odrotbohm on release

    (compare)

  • Nov 16 19:17

    odrotbohm on main

    #1881 - Configure Maven Release… (compare)

  • Nov 16 17:53

    odrotbohm on main

    #1881 - Fix flaky test. (compare)

  • Nov 16 17:49

    odrotbohm on main

    #1881 - Fix flaky test. (compare)

  • Nov 16 17:29

    odrotbohm on main

    Debugging CI failures. (compare)

  • Nov 16 17:23

    odrotbohm on main

    Debugging CI failures. (compare)

  • Nov 16 16:53

    odrotbohm on main

    Debugging CI failures. (compare)

  • Nov 16 16:46

    odrotbohm on main

    Debugging CI failures. (compare)

  • Nov 16 16:35

    odrotbohm on main

    #1882 - Switching to Github act… (compare)

  • Nov 16 16:32

    odrotbohm on main

    #1882 - Switching to Github act… (compare)

  • Nov 16 11:53

    odrotbohm on release-2.0

    #1868 - Continue development on… #1863 - Upgrade to Jackson 2.14. #1873 - Upgrade to Mockk 1.13.2. and 12 more (compare)

  • Nov 16 11:48

    odrotbohm on main

    #1881 - Update changelog. #1881 - Remove obsolete snapsho… (compare)

  • Nov 16 11:43

    odrotbohm on main

    #1880 - Upgrade to Spring Plugi… #1877 - Upgrade to Spring Frame… #1881 - Polish POM. (compare)

  • Nov 15 22:31

    odrotbohm on main

    #1873 - Upgrade to Mockk 1.13.2. #1874 - Upgrade to Kotlin 1.7.2… #1875 - Upgrade to Servlet API … and 4 more (compare)

Knut Schleßelmann
@kschlesselmann
How do you test HAL responses using Spring HATEOAS? The HalResponseModel (which could take the _embedded) is not public and all other representation classes simply do not use all HAL properties. What am I supposed to do with my WebTestClient if I want to check for all links, embeds, … in my response?
Ingo Griebsch
@ingogriebsch
@kschlesselmann I simply put a json file that contains the expected result on the classpath, read that file and compare the content of the file with the response generated through the test. :)
43 replies
Knut Schleßelmann
@kschlesselmann
@gregturn Since we're already in the flow – we're using the reactive API of Spring HATEOAS. Is there something like ResourceAssemblerSupport planned for the reactive stack as well? Right now we try to stick to the passtern and simply create our own @Components for such purposes.
Greg L. Turnquist
@gregturn
ReactiveRepresentationModelAssembler and SimpleReactiveRepresentationModelAssembler are the two we have for WebFlux.
8 replies
So...nothing directly tied to a WebFlux controller class. But I think they get you a lot of the way.
Greg L. Turnquist
@gregturn
This is among our test cases...
    class ResourceAssemblerWithCustomLinkSimple implements SimpleReactiveRepresentationModelAssembler<Employee> {

        @Override
        public EntityModel<Employee> addLinks(EntityModel<Employee> resource,
                ServerWebExchange exchange) {
            return resource.add(Link.of("/employees").withRel("employees"));
        }

        @Override
        public CollectionModel<EntityModel<Employee>> addLinks(
                CollectionModel<EntityModel<Employee>> resources, ServerWebExchange exchange) {
            return resources.add(Link.of("/").withRel("root"));
        }
    }
Knut Schleßelmann
@kschlesselmann

@gregturn Yeah … right now we do something along

    val model = mock<DraftResponseModel>()
    fun links(): Mono<List<Link>> = TODO()
    fun previewForSomething(): Mono<Tuple2<Link, DraftResponseModel>> = TODO()

    model.toMono()
            .zipWith(links()) { model, links ->
                HalModelBuilder.halModelOf(model)
                        .links(links)
            }
            .zipWith(previewForSomething()) { builder, (link, preview) -> 
                builder.preview(preview)
                        .forLink(link)
            }
            .map { it.build() }

what do you think?

Greg L. Turnquist
@gregturn
Nice! (My Kotlin is a bit rusty.)
Greg L. Turnquist
@gregturn
Heads up, I updated Spring HATEOAS Examples to use Spring Boot 2.3.4.RELEASE. Replaced some of the deprecated APIs with new stuff. Enjoy!
Ingo Griebsch
@ingogriebsch
I created a repository that contains examples for Spring HATEOAS Siren to showcase how to use the features provided by the library.
https://github.com/ingogriebsch/spring-hateoas-siren-samples
Some examples are already available, more will follow soon.
Every feedback is very welcome! 🙂
Kai Toedter
@toedter
If a HATEOAS user wants to add things to the ObjectMapper used for a specific MediaType configuration, what would be the best practice for doing that? For instance, adding thinks like
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Kai Toedter
@toedter

One way wuld be something like

    @Configuration
    @WebAppConfiguration
    @EnableWebMvc
    @EnableHypermediaSupport(type = HAL)
    static class TestConfig {
        @Bean
        JsonApiMediaTypeConfiguration jsonApiMediaTypeConfiguration(ObjectProvider<JsonApiConfiguration> configuration,
                                                                    AutowireCapableBeanFactory beanFactory) {
            return new JsonApiMediaTypeConfiguration(configuration, beanFactory) {
                @Override
                ObjectMapper configureObjectMapper(ObjectMapper mapper, JsonApiConfiguration configuration) {
                    ObjectMapper objectMapper = super.configureObjectMapper(mapper, configuration);

                    objectMapper.registerModule(new JavaTimeModule());
                    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
                    return objectMapper;
                }
            };
        }
    }

But may there there is a better/easier way.

Greg L. Turnquist
@gregturn
@toedter I think we honor Spring Boot's contracts for applying changes to ALL ObjectMappers. Don't think there's really an API to get apply customizations for a single one based on media type.
The thing we DO have is HalConfig and HalFormsConfig, which provides some user-facing APIs to apply tweaks deep inside the relevant serializers.
But nothing as direct as ObjectMapper settings.
If we were to add a hook, I'd imagine it being there.
To me, a ObjectMapperCustomizer sort of lambda function added to HalConfig would be the simplest way. But I'd also question granting users that much access to the internals.
Kai Toedter
@toedter
Thx Greg, I think that many users want to use additional Jackson modules, like the JavaTimeModule. I would prefer the possibility that a user could add some configuration, like how to serialize LocalDateTime or Instant on a Spring HATEOAS level, and then apply this to all media types.
Greg L. Turnquist
@gregturn

Something like...

public interface ObjectMapperCustomizer extends Consumer<ObjectMapper> {
}

You provide new HalConfig().customizeObjectMapper(objectMapper -> { /* insert customizations here */}), and the serializer, after getting/creating the ObjectMapper, could then invoke halConfig.objectMapperCustomizer.accept(theObjectMapper).

Yeah?

I could see this applied to the others as well.

I've opened spring-projects/spring-hateoas#1382 to track this discussion.
Greg L. Turnquist
@gregturn
Greg L. Turnquist
@gregturn
Woot!
Patrick Hahn
@xorph
Has someone an example how to build page links for PagedModel with a pageable in a reactive application using the WebFluxLinkBuilder? The resulting link does not contain paging parameters.
@GetMapping(produces = [MediaTypes.HAL_JSON_VALUE])
fun findAllPaged(
        @PageableDefault(
                size = DEFAULT_PAGE_SIZE,
                page = FIRST_PAGE,
                sort = [DEFAULT_SORT_BY],
                direction = Sort.Direction.ASC
        )
        pageable: Pageable
    ): Mono<PagedModel<Foo>>
linkTo(methodOn(FooController::class.java).findAllPaged(pageable))
                        .withSelfRel()
                        .toMono()
1 reply
Greg L. Turnquist
@gregturn
To back up a bit, "pageable" is still a fuzzy concept in reactive. If you pass in a Pageable to a reactive repostiory, you get back a Flux, not a page. So essentially, you have to build it up yourself.
We need to hammer out with the Reactor team on what makes for good, reactive "paged" systems.
Patrick Hahn
@xorph

Right now we added the paging query params via a kotlin extension on Link. So we can do something like this:

linkTo(methodOn(FooController::class.java).findAllPaged(pageable))
                        .withSelfRel()
                        .toMono()
                        .map { it.toPagedLink(pageable) }

Under the hood we had to transform the link to URI, extend with paging query params and create the link with the rel again. Is there a way to directly add query params to the Link?

Greg L. Turnquist
@gregturn
It's not the most common use case, but you can do Link.of(UriTemplate, LinkRelation). UriTemplate has helper methods to build up all kinds of stuff.
Patrick Hahn
@xorph
I will have a look at it. Thanks!
DonHamzovic
@DonHamzovic
This message was deleted

I'm developing a microservice using SPRING DATA REST where I need to sub-resources to be accessible only thru main resource like the following structure :

http://localhost:8080/posts
http://localhost:8080/posts/1/comments

and block direct access to sub-resource directly like this http://localhost:8080/comments/*.

where comments must be accessible only thru related poste , I did the following in my repository :

@RepositoryRestResource(collectionResourceRel = "posts", path = "posts")
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {
   ...
}

for comments :

 @RepositoryRestResource(collectionResourceRel = "comments", path = "comments")
    public interface CommentRepository extends PagingAndSortingRepository<Comment, Long> {
       ...
    }

now by default SPRING DATA REST returns the following results when i goto to : http://localhost:8080

{
  "id": 1,
  "content": "some post text .........................",
  "author":"abc",

  "_links": {
    "self": {
      "href": "http://localhost:8080/posts/1"
    },
    "attributes": {
      "href": "http://localhost:8080/posts/1/comments"
    }
  }
}

now if i want to post a comment i need to do the following :

http://{server:port}/comment METHOD:POST

```
{"author":"abc","content":"PQROHSFHFSHOFSHOSF", "post":"http://{server:port}/post/1"}

```
but what i need to achienve is to POST to an url like this http://{server:port}/posts/1/comment where the POST is the root resource NOT like the previous path http://{server:port}/comment

i now that this is possible if a create a custom comment @controller but i want to use the already building features of SPRING DATA REST and Hateoas support .

Greg L. Turnquist
@gregturn
Why do you need the CommentRepository? SDR, by default, tries to create root aggregates for every repository you define. If, as you are saying, don't need Comments to be a root aggregate, why not ditch it?
DonHamzovic
@DonHamzovic
no i need it if i don't add CommentRepository SPRING DATA REST will not create the link for the sub-resource
Greg L. Turnquist
@gregturn
@DonHamzovic I'm not sure how this fits, but have you tried applying @RestResource(exported = false) on CommenRepository? That should stop exporting it as an aggregate root. But I don't know if that ALSO stops making it available as a subresource.
1 reply
Greg L. Turnquist
@gregturn
@xorph Have you seen @toedter 's deck? It shows up the UriTemplate APIs => https://speakerdeck.com/toedter/hello-rest-api-what-can-i-do-next-c47a5e7c-4a1f-491a-8337-bdc28c382f0d?slide=28
Ingo Griebsch
@ingogriebsch
A collection of examples which showcase how to use the features of Spring HATEOAS Siren is available now! Included are topics like setup, serialization, deserialization and internationalization.
Every feedback is very welcome! :)
https://github.com/ingogriebsch/spring-hateoas-siren-samples
Ingo Griebsch
@ingogriebsch
I’m pleased to announce the release of Spring HATEOAS Siren 1.0.0-M3! The library is accessible through Maven Central.
Source: https://github.com/ingogriebsch/spring-hateoas-siren
Documentation: https://ingogriebsch.github.io/spring-hateoas-siren
You can expect the release of the first version soon! :)
Ingo Griebsch
@ingogriebsch
I’m really happy to announce the first final release of Spring HATEOAS Siren!
Check it out, version 1.0.0 is accessible through Maven Central!
Source: https://github.com/ingogriebsch/spring-hateoas-siren
Documentation: https://ingogriebsch.github.io/spring-hateoas-siren
Examples: https://github.com/ingogriebsch/spring-hateoas-siren-samples
Every feedback is very welcome! :)
ctwoolsey
@ctwoolsey

I'm struggling to read data from a webClient query.
I've configured SpringConfig:

@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
class SpringConfig {
@Bean
WebClientCustomizer webClientCustomizer(HypermediaWebClientConfigurer configurer) {
return {webClientBuilder ->
configurer.registerHypermediaTypes(webClientBuilder)
}
}
}

Then in my repository I have:

@Repository ('PersonRepository')
class PersonRepository {
private final WebClient webClient

PersonRepository(final WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.build()
}

List<person> getPeople() {
CollectionModel<person> personCollectionModel = this.webClient.get().uri('localhost:8080/api/people')
.retrieve()
.bodyToMono(new TypeReferences.CollectionModelType<person>())
.block()

return personCollectionModel.content
}

The problem is that "personCollectionModel" is empty. It is like the HAL format is unable to be read into the structure.

This is the data that is returned from localhost:8080/api/people

{
"_embedded": {
"people": [
{
"id": 2,
"firstName": "Mark",
"lastName": "Hamil"
}
]
}
}

Any light you can shine on why this isn't returning data as expected would be appreciated. Thank you.

ctwoolsey
@ctwoolsey
@gregturn In your article: https://spring.io/blog/2020/04/22/spring-hateoas-brings-you-new-ways-to-configure-clients, do these new ways to configure allow for reading HAL json with "_embedded" tags?
Greg L. Turnquist
@gregturn
They should.
        client.get().uri("http://localhost/employees").accept(HAL_JSON) //
                .exchange() //
                .expectStatus().isOk() //
                .expectBody(new TypeReferences.CollectionModelType<EntityModel<Employee>>() {}) // <3>
                .consumeWith(result -> {
                    CollectionModel<EntityModel<Employee>> model = result.getResponseBody(); // <4>

                    // Assert against the hypermedia model.
                    assertThat(model.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(Link.of("http://localhost/employees"));
                    assertThat(model.getContent()).hasSize(2);
                });
Greg L. Turnquist
@gregturn
The inner type needs to be EntityModel<Employee> and you also need the {} as well.
ctwoolsey
@ctwoolsey

@gregturn Thank you for the pointer. I am receiving a status of 406 Not accepted when I use accept(HAL_JSON). Here is the modified HAL output:

    "_embedded": {
        "people": [
            {
                "id": 2,
                "firstName": "Mark",
                "lastName": "Hamil",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/api/people/2"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/api/people"
        }
    }
}

And here is my code to receive it:

Mono<CollectionModel<EntityModel<Person>>> personCollectionModel = this.webClient.get().uri('localhost:8080/api/people')
                  .accept(MediaTypes.HAL_JSON)
                  .exchange()
                  .flatMap { response ->
                       if (response.statusCode().isError()) {
                         System.out.println('Error---------------------------------')
                       } else {
                            response.bodyToMono(new ParameterizedTypeReference<CollectionModel<EntityModel<Person>>>() {})
                       }
                  }

I'm not sure why the HAL format is not being recognized as valid HAL.

ctwoolsey
@ctwoolsey
Thank you for your help. I figured it out, this is now working, I needed to set produces = "application/hal+json"
Greg L. Turnquist
@gregturn
If the Content-Type header is something else, you can always drop the Accept request header. If you are getting HAL, you should be able to consume it. The rest of simply HTTP negotation.
It's possible, if you're using Boot, that you're getting served application/json. Don't remember it NOT serving up application/hal+json when requested, though.
Ravish Rathod
@ravishrathod
Hello, I recently upgrade my app from a very old version of spring-boot (and hateoas) to the latest boot and hateoas v1.1.2.RELEASE. After this I am seeing a spike in memory usage. Heap dump points to WebHandler.AFFORDANCES_CACHE consuming most of memory. Looking at WebHandler it seems that the cache is never cleared and is unbounded... has anyone faced this issue ? Thanks
Greg L. Turnquist
@gregturn
@ravishrathod The last fix that involved that cache was in 1.0. If you'll open a new ticket and provide reproducible details, we can certainly investigate.
Ravish Rathod
@ravishrathod
Thanks @gregturn , I will add a link to git project
Here is the link to a simple project which exposes a /users/{id}. If we call this api with enough distinct id values, it causes AFFORDANCES_CACHE to fill up. https://github.com/ravishrathod/spring-hateoas-memory