The spring data rest's behavior looks weird - java

I have a project with spring data rest that when I run it does not have some expected features. When I go to http://localhost:8080/rest-api/profile, the following links appear, which the repository related to each of them has extended CrudRepository.
{
"_links": {
"self": {
"href": "http://localhost:8080/rest-api/profile"
},
"roles": {
"href": "http://localhost:8080/rest-api/profile/roles"
},
"users": {
"href": "http://localhost:8080/rest-api/profile/users"
},
"offers": {
"href": "http://localhost:8080/rest-api/profile/offers"
},
"businesses": {
"href": "http://localhost:8080/rest-api/profile/businesses"
},
"apps": {
"href": "http://localhost:8080/rest-api/profile/apps"
},
"addresses": {
"href": "http://localhost:8080/rest-api/profile/addresses"
},
"contacts": {
"href": "http://localhost:8080/rest-api/profile/contacts"
}
}
}
Now, when I go to the http://localhost:8080/rest-api/users/search, the following page opens:
{
"_links": {
"getByEmail": {
"href": "http://localhost:8080/rest-api/users/search/getByEmail{?email}",
"templated": true
},
"getByRoleName": {
"href": "http://localhost:8080/rest-api/users/search/getByRoleName{?role}",
"templated": true
},
"existsByEmail": {
"href": "http://localhost:8080/rest-api/users/search/existsByEmail{?email}",
"templated": true
},
"self": {
"href": "http://localhost:8080/rest-api/users/search"
}
}
}
First question:
1-Why does not the spring data rest expose method for CrudRepository's specific methods? like findAll ro findById ?Are they not the search method?
Second question:
When I open http://localhost:8080/rest-api/apps/search it gives a 404 not found error.
Why does it give an error when I go to the search link related to apps? Don't apps have search methods? In addition, when I go to the mentioned link, the following line is logged in the program console:
2022-10-24 11:02:14.990 WARN 7560 --- [nio-8080-exec-8] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.data.rest.webmvc.ResourceNotFoundException: EntityRepresentationModel not found!]
I would be really appreciated if you would answer me.

Related

Make Spring Boot 3 Actuator aware of X-Forwarded-Prefix header?

This question is similar to Make Spring Boot 2.7.x Actuator aware of X-Forwarded-Prefix header?, but for Spring Boot 3.
We're using Spring Boot 3 with spring-boot-starter-actuator that we're exposing on port 8081 under the /management context path. The proxy sets several X-Forwarded-* headers, including the X-Forwarded-Prefix header that is set to /service. But when navigating to https://www.company.com/management this is what is returned:
{
"_links": {
"self": {
"href": "https://www.company.com/management",
"templated": false
},
"beans": {
"href": "https://www.company.com/management/beans",
"templated": false
},
"caches-cache": {
"href": "https://www.company.com/management/caches/{cache}",
"templated": true
},
"caches": {
"href": "https://www.company.com/management/caches",
"templated": false
},
"health": {
"href": "https://www.company.com/management/health",
"templated": false
},
"health-path": {
"href": "https://www.company.com/management/health/{*path}",
"templated": true
},
"info": {
"href": "https://www.company.com/management/info",
"templated": false
},
"conditions": {
"href": "https://www.company.com/management/conditions",
"templated": false
},
"configprops": {
"href": "https://www.company.com/management/configprops",
"templated": false
},
"configprops-prefix": {
"href": "https://www.company.com/management/configprops/{prefix}",
"templated": true
},
"env": {
"href": "https://www.company.com/management/env",
"templated": false
},
"env-toMatch": {
"href": "https://www.company.com/management/env/{toMatch}",
"templated": true
},
"integrationgraph": {
"href": "https://www.company.com/management/integrationgraph",
"templated": false
},
"loggers": {
"href": "https://www.company.com/management/loggers",
"templated": false
},
"loggers-name": {
"href": "https://www.company.com/management/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "https://www.company.com/management/heapdump",
"templated": false
},
"threaddump": {
"href": "https://www.company.com/management/threaddump",
"templated": false
},
"metrics-requiredMetricName": {
"href": "https://www.company.com/management/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "https://www.company.com/management/metrics",
"templated": false
},
"scheduledtasks": {
"href": "https://www.company.com/management/scheduledtasks",
"templated": false
},
"sessions-sessionId": {
"href": "https://www.company.com/management/sessions/{sessionId}",
"templated": true
},
"sessions": {
"href": "https://www.company.com/management/sessions",
"templated": false
},
"mappings": {
"href": "https://www.company.com/management/mappings",
"templated": false
},
"refresh": {
"href": "https://www.company.com/management/refresh",
"templated": false
},
"features": {
"href": "https://www.company.com/management/features",
"templated": false
},
"traces": {
"href": "https://www.company.com/management/traces",
"templated": false
}
}
}
I want the href's in the response to start with https://www.company.com/service due to the supplied X-Forwarded-Prefix header. It won't work by simply adding a ForwardedHeaderFilter:
#Bean
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilterFilterRegistrationBean() {
ForwardedHeaderFilter forwardedHeaderFilter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>(forwardedHeaderFilter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
I added an issue at Spring Boot GitHub, and they acknowledged that the behavior is a bit confusing (since it works in webflux). However, in Spring Boot 2.7, this workaround could be applied to make it work:
#Component
#ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
public class ManagementContextFactoryBeanPostProcessor
implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ManagementContextFactory managementContextFactory) {
return (ManagementContextFactory) (parent, configurationClasses) -> {
var context = managementContextFactory.createManagementContext(parent, configurationClasses);
if (context instanceof GenericWebApplicationContext genericWebApplicationContext) {
genericWebApplicationContext.registerBean(ForwardedHeaderFilterRegistrationBean.class);
}
return context;
};
}
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
public static class ForwardedHeaderFilterRegistrationBean
extends FilterRegistrationBean<ForwardedHeaderFilter> {
public ForwardedHeaderFilterRegistrationBean() {
setFilter(new ForwardedHeaderFilter());
setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
}
But this no longer works in Spring Boot 3. The ManagementContextFactory is no longer an interface but rather a final class, and the signature of the createManagementContext method has changed.
So my question is, how can I make actuator take the X-Forwarded-Prefix header into account when generating the links to the endpoints behind a proxy in Spring Boot 3?

HAL-based REST service client using Traverson

I am trying to write a Java program to consume the output from a REST-based web service I wrote utilizing the tutorials at spring.io. When I run SpringBoot bootRun the JSON output is built nicely with embedded data and links, and looks like this:
{
"_embedded": {
"invoiceList": [
{
"id": 4,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 1000,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/4"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
},
{
"id": 5,
"seqNum": 1,
"fileId": null,
"fileName": null,
"invoiceNumber": "10080",
"invoiceDate": "2018-06-18T05:00:00.000+0000",
"invoiceTotal": 500,
"sourceLastModified": "2018-11-30T16:22:23.000+0000",
"lastModified": "2018-11-30T16:22:23.000+0000",
"validFrom": "2018-11-30T16:22:23.000+0000",
"validTo": "9999-12-31T06:00:00.000+0000",
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/5"
},
"invoices": {
"href": "http://localhost:8086/vadir/dental/invoices"
},
"claims": {
"href": "http://localhost:8086/vadir/dental/claims?invoice=10080"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8086/vadir/dental/invoices/last"
}
}
}
I have found the documentation for Traverson and the SO question at Consuming HAL-based REST. However, when I use
ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> parameterizedTypeReference =
new ParameterizedTypeReference<Resources<Resource<DentalInvoice>>> () {
};
Traverson traverson =
new Traverson (new URI ("http://localhost:8086/vadir/dental/invoices/last"), MediaTypes.HAL_JSON);
Resources<Resource<DentalInvoice>> invoiceResources = traverson
//.follow ((String) null)
.follow ("$._embedded.invoiceList")
.toObject (parameterizedTypeReference);
I get an error: Illegal character in scheme name at index 0: [{"id":4,...
It looks like Traverson is expecting the content of the follow reference to be a link rather than an embedded object. How can I get Traverson to parse the embedded object into a list of Resources of DentalInvoices?
Do I have to create a controller method that only outputs the links to the possible actions just so Traverson has a link to follow?

Empty collection returns single object in spring data rest instead of empty array

I am using spring data rest for my application.
I have disabled to hal json type by using the following:-
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.useHalAsDefaultJsonMediaType(false);
}
My response looks like the following for empty object:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"relTargetType": "com.sample.User",
"collectionValue": true,
"value": [ ]
}
]
}
When I have data for user, It looks like the following:-
links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"id": 2,
"key": "wire",
"name": "Wireless",
"content": [ ],
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users/2"
},
{
"rel": "user",
"href": "http://localhost:8081/users/2"
}
]
}..
My concern is to have empty content collection like as below:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": []
}
How can I handle empty collection in spring data rest ? Kindly help me

Parsing _embedded items from JSON HAL response

We are trying to parse the below JSON to get a list of people.
JSON Response:
{
"_embedded": {
"people": [
{
"id": 35356,
"name": "Jon",
"description": "Test",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/35356"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=35356"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
},
{
"id": 46363,
"name": "Kevin",
"description": "",
"type": "person",
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people/46363"
},
"items": {
"href": "http://localhost/api/v1/50452/items?person_id=46363"
},
"enabled_services": [
{
"title": "Water Company",
"href": "http://localhost/api/v1/50452/services/103890"
}
]
}
}
]
},
"_links": {
"self": {
"href": "http://localhost/api/v1/50452/people"
}
}
}
Our code:
ParameterizedTypeReference<Resources<Person>> resource = new ParameterizedTypeReference<Resources<Person>>() {};
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/people"), MediaType.APPLICATION_JSON_UTF8);
// Create our own LinkDiscoverer as our service returns application/json instead of application/json+hal
List<LinkDiscoverer> linkDiscoverers = new ArrayList<>();
linkDiscoverers.add(new JsonPathLinkDiscoverer("$._links..['%s']..href", MediaType.APPLICATION_JSON_UTF8));
traverson.setLinkDiscoverers(linkDiscoverers);
HttpHeaders headers = new HttpHeaders();
headers.add("App-Key", Globals.Appkey);
headers.add("App-Id", Globals.AppId);
Resources<Person> personResources = traverson.follow("people").withHeaders(headers).toObject(resource);
However we are getting the following error:
java.lang.IllegalStateException: Expected to find link with rel 'people' in response {"_embedded":{"people":[{"id":31350,"name":"Jon Blue","description":"Developer","type":"person","deleted":false,"disabled":false,"company_id":50452,"order":31350,"phone_prefix":"44","_links":{"self":{"href":"http://localhost/api/v1/50452/people/31350"},"items":{"href":"http://localhost/api/v1/50452/items?person_id=31350"},"enabled_services":[{"title":"Water ...
Based on the (very limited) client docs this seems to be the correct way to do things. Does anyone know what we might be missing here?
Thanks
The Traverson is meant to be used to follow links:
Component to ease traversing hypermedia APIs by following links with
relation types. Highly inspired by the equally named JavaScript
library.
There is no link with rel people in your response, so it cannot be followed.
I could imagine you wanted to call the top-level resource that has a link to people:
Traverson traverson = new Traverson(new URI("http://localhost/api/v1/"), MediaType.APPLICATION_JSON_UTF8);
Otherwise I would suggest to use a RestTemplate to get the Resource:
restTemplate.exchange(
URI.create("http://localhost/api/v1/people"),
HttpMethod.GET,
new HttpEntity<Void>(headers),
resource).getBody();

REST hypermedia/links to collections

Part of REST best practice is to make use of links in the responses to allow clients to navigate from one entity to another.
For example if I had a customer object type which has child account. If I was to request a customer using /customers/1 then I might provide the following response
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"accounts": [
{
"self": "http://localhost:43002/rest/v1/accounts/1",
"id": 1,
"name": "Main Account",
"number": "000001",
"currency": "GBP",
"fromDate": "2013-01-01",
"toDate": "9999-01-01",
"createdDttm": "2013-01-01T00:00:00.000"
}
]
}
Note the self property holds the links.
However let's say I didn't want to return the accounts in the customer query, perhaps the number of accounts might be very large so I don't want to return them by default.
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button"
}
A resource URL for a customer's accounts could be /customers/1/accounts
However with the customer response above the client would be unable to discover the /customers/1/accounts link.
Is there a best practice for providing hyperlinks in a response that point to "child" collections of the returned resource?
One practice is to use a links element like this:
{
"self": "http://localhost:43002/rest/v1/customers/1",
"id": 1,
"name": "Isabella Button",
"number": "000001",
"forename": "Isabella",
"surname": "Button",
"links" : [
{
"rel" : "http://www.yourapi.com/rels/accounts",
"href" : "http://localhost:43002/rest/v1/customers/1/accounts"
},
{
"rel" : "http://www.yourapi.com/rels/someOtherCollection",
"href" : "http://localhost:43002/rest/v1/customers/1/someOtherCollection",
}
]
}
Or, if you find easier to construct/read the response, you can put the same links as Link http headers.
provide a links attribute like in this example http://caines.ca/blog/programming/json-is-under-defined-for-rest/
{
"links": {
"self" : { "href": "{id}" },
"up" : { "href": "{upId}" },
"children" : { "href": "{id}/children" }
}
}

Categories