I'm trying to contract test a Flux of events emitted from an endpoint using Spring Cloud Stream and Spring Cloud Contract. However, I'm getting an IllegalArgumentException with detailMessage Message must not be null when running my test. Or in other words I'm not receiving any messages on my message receiver. My spring cloud version is Hoxton. I've added the following dependencies in my pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<!--TEST -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
I've configured a new binding following the samples here. Is working properly when I run the app and I send a curl -X POST localhost:8080 request.
application.properties
stubrunner.stream.enabled=true
spring.cloud.stream.bindings.uppercase-in-0.destination=test
spring.cloud.stream.bindings.uppercase-in-0.group=testGroup
spring.cloud.stream.bindings.uppercase-out-0.destination=hood
spring.cloud.stream.bindings.consume-in-0.destination=hood
spring.cloud.stream.bindings.consume-out-0.destination=downtown
spring.cloud.stream.bindings.supplier-out-0.destination=test
spring.cloud.function.definition=uppercase;consume;supplier
Controller
#PostMapping(value = "/")
public void handlePost() {
Faker faker = new Faker();
Message<String> message = MessageBuilder.withPayload(faker.chuckNorris().fact()).build();
// supplier.get();
EventSupplier.processor.onNext(message);
}
Bean definition
#Configuration
public class EventSupplier {
public static final EmitterProcessor<Message<String>> processor = EmitterProcessor.create();
#Bean
// public Supplier<String> supplier() {
public Supplier<Flux<Message<String>>> supplier() {
// return () -> "";
return () -> processor;
}
}
Contracts Base test
#ActiveProfiles("test")
#SpringBootTest
#RunWith(SpringRunner.class)
#AutoConfigureMessageVerifier
#EnableBinding(KafkaStreamerApplication.class)
public abstract class KafkaStreamerApplicationTests {
#Autowired
private DummyController dummyController;
public void supply() {
dummyController.handlePost();
}
}
Contract
org.springframework.cloud.contract.spec.Contract.make {
label 'some_label'
input {
triggeredBy("supply()")
}
outputMessage {
sentTo('supplier-out-0')
body(
anyNonBlankString()
)
headers {
messagingContentType(applicationJson())
}
}
}
I've tried creating contracts for function and consumer and both worked. I've also tried using a supplier of type String instead of Flux<Message<String>> and worked too. You can look at the commented lines at the code above.
While debugging I've seen that in StreamMessageCollectorMessageReceiver I'm getting a DirectWithAttributesChannel instance when retrieving the MessageChannel bean at receive("supplier-out-0", 5, "SECONDS") method. Also while looking into the documentation I've found a MessageChannel implementation that, to me, looks like a candidate to be the one should have been instantiated FluxMessageChannel.
My guess is that spring cloud contract is waiting for a message to be sent into the queue directly(DirectWithAttributesChannel) but since the flux is already in the queue and I'm just updating the flux content, it means that not message is pushed into the queue(FluxMessageChannel?) so no message is received(null) and that's why I'm getting an IllegalArgumentException with details Message must not be null error when running my tests.
Is there something missing in my configuration? Can this scenario be tested by contracts? Is a bad approach to test this by using contracts?
I've written a simple webService in Java 8, on Eclipse Photon, using RestTemplate to post (using postForObject) an object (called patentListWrapper) that wraps a List of objects (called PatentDetails ). I post from a Java client (called MainWsClient ) , then set a value in patentDetails on the server side and read the patentListWrapper object back at the client. It works fine when the server side (program SpringWebServiceHello) uses old Spring MVC 4 technology with only 1 jar file (Spring-web.5.07.RELEASE.jar) following this - serverSideExample ie a web.xml and rest-servlet.xml files controlling the access point. I then wrote another server side program (PndGuidRequestWs) using SpringBoot 2.03 with Spring 5.07 jars, and Maven ,with an identicle #RequestMapping method but no web.xml file and the access point defined in the application.properties file:
server.port=8082
server.servlet.path=/
#spring.mvc.servlet.path=/
#server.servlet.contextPath=/
When I call this new server program using this client - ARC it also works fine
but when I call it using the same java client and exactly the same request (accept for a different url obviously). I get a 400 error:
2018-12-18 16:56:53,861 [main] INFO - Running MainWsClient with name = DS fileType = post3
2018-12-18 16:56:54,101 [main] DEBUG - Created POST request for "http://localhost:8082/guidRequest/xmlList"
2018-12-18 16:56:54,145 [main] DEBUG - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
2018-12-18 16:56:54,152 [main] DEBUG - Writing [com.springservice.client.PatentListWrapper#4ba2ca36] using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter#3444d69d]
2018-12-18 16:56:54,384 [main] DEBUG - POST request for "http://localhost:8082/guidRequest/xmlList" resulted in 400 (null); invoking error handler
2018-12-18 16:56:54,387 [main] ERROR - DS1B org.springframework.web.client.HttpClientErrorException: 400 null
The non working ,PndGuidRequestWs, server side has:
#RestController
public class PndController {
#RequestMapping(value = "/guidRequest/xmlList", method = RequestMethod.POST, produces = { "application/xml" } )
public PatentListWrapper guidSearchList(#RequestBody PatentListWrapper patentListWrapper) {
for (PatentDetails pd : patentListWrapper.getPatentList())
{
pd.setGuid("guidSetOnServer3");
}
return patentListWrapper;
}
}
The working (SpringWebServiceHello) server side is identicle except for :
value = "/service/greeting/xml/post2"
The Java client has:
public void runCode(String name , String fileType)
{
String url;
if (fileType.equalsIgnoreCase("post2")) {
url = "http://localhost:8080/SpringWebServiceHello/service/greeting/xml/post2";
// This method is identicle to postToPndGuidRequestWs() but this method works fine.
postToSpringWebServiceHello(url);
}else if (fileType.equalsIgnoreCase("post3")) {
url = "http://localhost:8082/guidRequest/xmlList";
// This method gives 404 error
postToPndGuidRequestWs(url);
}
}
private void postToPndGuidRequestWs(String url)
{
PatentListWrapper patentListWrapper = new PatentListWrapper();
PatentDetails pd = new PatentDetails("CN","108552082","A","00000000",12345,"guidIn");
List<PatentDetails> patentList = new ArrayList<PatentDetails>();
patentList.add(pd);
patentListWrapper.setPatentList(patentList);
RestTemplate restTemplate = new RestTemplate();
/* HttpHeaders headers = new HttpHeaders();
headers.add("header_name", "header_value");
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<PatentListWrapper> request = new HttpEntity<PatentListWrapper>(patentListWrapper, headers); */
/*List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
Jaxb2RootElementHttpMessageConverter jaxbMessageConverter = new Jaxb2RootElementHttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.APPLICATION_XML);
jaxbMessageConverter.setSupportedMediaTypes(mediaTypes);
messageConverters.add(jaxbMessageConverter);
restTemplate.setMessageConverters(messageConverters);*/
/* headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);*/
try {
patentListWrapper = restTemplate.postForObject(
url,
patentListWrapper,
PatentListWrapper.class);
logger.debug("DS1A employee obj returned. guid = " + patentListWrapper.getPatentList().get(0).getGuid());
}catch(Exception e) {
logger.error("DS1B " + e);
}
}
}
ie fileType="post2" calls SpringWebServiceHello, fileType="post3" calls PndGuidRequestWs. As you can see i've tried several commented out solutions but nothing works. Since the only real difference between the 2 server side programs is that none working one uses Spring boot and the working one doesn't the problem must be in the SpringBoot setup ie directory structure, application.properties or pom.xml. My pom.xml has:
<?xml version="1.0" encoding="UTF-8"?>
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<groupId>com.clarivate</groupId>
<artifactId>pndguidrequestws</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>pndGuidRequestWs</name>
<description>Guid request webService</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<start-class>com.clarivate.pndguidrequestws.PndGuidRequestWsApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.1.0</version>
<!-- <scope>provided</scope> --> <!-- DS insert for unix -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Implementing XML Representation for Spring Boot Services -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- httpcomponents jars are Required by PndGuidGenerator -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
<build>
<finalName>PndGuidRequestWs</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>
The PatentListWrapper class is:
package com.clarivate.pndguidrequestws.model;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class PatentListWrapper {
private List<PatentDetails> patentList;
public PatentListWrapper() {}
public List<PatentDetails> getPatentList() {
return patentList;
}
public void setPatentList(List<PatentDetails> patentList) {
this.patentList = patentList;
}
}
Any suggestions most welcome.
EDIT:
To simplify the object I created PatentListWrapper2 with just 1 string member:
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class PatentListWrapper2 {
private String name;
public PatentListWrapper2() {}
public PatentListWrapper2(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I can again successfully send this xml using the ARC client:
<patentListWrapper2>
<name>DSDS</name>
</patentListWrapper2>
with contentType="application/xml"
but when I try to send patentListWrapper2 from java I get an unmarshalling error:
2018-12-20 09:17:13,931 [main] INFO - Running MainWsClient with name = DS fileType = post4
2018-12-20 09:17:14,166 [main] DEBUG - Created POST request for "http://localhost:8082/guidRequest/xmlList2"
2018-12-20 09:17:14,200 [main] DEBUG - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
2018-12-20 09:17:14,206 [main] DEBUG - Writing [com.springservice.client.PatentListWrapper2#517cd4b] using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter#6cc7b4de]
2018-12-20 09:17:14,246 [main] DEBUG - POST request for "http://localhost:8082/guidRequest/xmlList2" resulted in 200 (null)
2018-12-20 09:17:14,248 [main] DEBUG - Reading [com.springservice.client.PatentListWrapper2] as "application/xml;charset=UTF-8" using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter#6cc7b4de]
2018-12-20 09:17:14,255 [main] ERROR - DS2B org.springframework.web.client.RestClientException: Error while extracting response for type [class com.springservice.client.PatentListWrapper2] and content type [application/xml;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class com.springservice.client.PatentListWrapper2]: unexpected element (uri:"", local:"PatentListWrapper2"). Expected elements are <{}patentListWrapper2>; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"PatentListWrapper2"). Expected elements are <{}patentListWrapper2>
EDIT2 I ran pndGuidRequestWs on Eclipse Tomcat , instead of - Run As -> Spring Boot App. The server log is below:
2018-12-20 11:15:45.655 WARN 236 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.clarivate.pndguidrequestws.model.PatentDetails` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CN'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.clarivate.pndguidrequestws.model.PatentDetails` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CN') at [Source: (PushbackInputStream); line: 1, column: 98] (through reference chain: com.clarivate.pndguidrequestws.model.PatentListWrapper["patentList"]->java.util.ArrayList[0])
Can you test with :
try {
HttpHeaders headers = new HttpHeaders();
//headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
//headers.setContentType((MediaType.APPLICATION_JSON));
// I comment the code abouve because you did not specify a consumes whitch defines the media types that the methods of
//a resource class or MessageBodyReader can accept. If not specified, a container will assume that any media type is acceptable.
HttpEntity<PatentListWrapper> request = new HttpEntity<>(patentListWrapper, headers);
PatentListWrapper patentListWrapperResult = = restTemplate.exchange(url, HttpMethod.POST, request,PatentListWrapper.class);
logger.debug("DS1A employee obj returned. guid = " + patentListWrapper.getPatentList().get(0).getGuid());
}catch(Exception e) {
logger.error("DS1B " + e);
}
PatentListWrapper is a complex object, not a piece of xml , so the answer is to remove all references to xml ie
Remove #XmlRootElement(name="PatentListWrapper") from PatentListWrapper.
Add jackson-*.jar(s) into the classpath to do the message converting
Change the server side #RequestMapping from :
#RequestMapping(value = "/xmlList", method = RequestMethod.POST , consumes = { "application/xml" }, produces = { "application/xml" })
to
#RequestMapping(value = "/xmlList", method = RequestMethod.POST )
This means that the ARC client now returns JSON (as it's the default return type), even when I send xml, but that's not important as it's just a test tool.
So, when posting objects with RestTemplate in Spring 2, no contentType settings or additional messageConverters are required on the client side , just:
RestTemplate restTemplate = new RestTemplate();
MyObject myObjectReturn = restTemplate.postForObject(url,myObject,MyObject.class);
and on the server side:
#RestController
#RequestMapping(value = "/endPoint", method = RequestMethod.POST)
public MyObject anyMethodName(#RequestBody MyObject myObject) {
//Do stuff to myObject
return myObject;
}
So I am trying to implement a simple Jersey Client that hits a public API to get movie times etc..
https://api.eventcinemas.co.nz/Api/Movies/GetMovies
I have gone through tutorials on how to do this and have implemented two methods that deserialzse the JSON response into:
A String
An Object (POJOs)
The issue is this: the JSON to String method is working correctly, printing the String to console gives me the expected result. However when trying to deserialize to my Java Objects I am always getting null.
I have tried a few simple things such as different dependency versions, different API calls etc but no luck. To save time I have used an online converter to take the JSON response and populate the necessary POJOs for deserialization, I have taken this to be correct.
Would someone be kind enough to point me in the right direction on why I am always getting null, I feel like its something small or silly that I have missed. Thanks in advance!
So starting with my pom.xml dependencies...
pom.xml
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
My Client is as follows:
MoviesClient:
package nz.co.brownbridge.application;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
public class MoviesClient {
protected MoviesResponse getMovieDetails() {
/*JSON to POJO*/
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("https://api.eventcinemas.co.nz/Api/Movies/GetMovies");
MoviesResponse response = webTarget.request().accept(MediaType.APPLICATION_JSON_TYPE).get(MoviesResponse.class);
return response;
}
protected String getMovieDetailsString() {
/*JSON to String*/
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("https://api.eventcinemas.co.nz/Api/Movies/GetMovies");
String response = webTarget.request().accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
return response;
}
}
and finally the main() class:
Application Class:
package nz.co.brownbridge.application;
public class Application {
public static void main(String[] args) throws InterruptedException {
MoviesClient moviesClient = new MoviesClient();
String stringResponse = moviesClient.getMovieDetailsString();
MoviesResponse pojoResponse = moviesClient.getMovieDetails();
System.out.println("Printing String Response...");
System.out.println();
System.out.println(stringResponse);
System.out.println();
System.out.println();
System.out.println("Printing POJO Response...");
System.out.println();
System.out.println(pojoResponse);
}
}
Would output the following:
Printing String Response...
//super long but correct string response goes here
Printing POJO Response...
ClassPojo [Data = null, Success = null]
I have a simple #RestController and would like to response with both JSON or XML depending on the http header content-type.
Problem: I'm always only getting XML response, never JSON. Of course I'm using Content-Type: application/json as http header.
What might be missing in the following configuration?
#RestController
public void MyServlet {
#RequestMapping(value = "test", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public MyResponse test() {
return new MyResponse();
}
}
#XmlRootElement
public class MyResponse {
private String test = "somevalue";
//getter, setter
}
pom.xml:
<!-- as advised in: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
</dependency>
Interestingly: if I switch the produces statement:
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}),
then I'm always getting JSON out and never XML!
So the question is: why does the first MediaType always have precedence, and the http header is never taken into account?
In your case, you should use Accept header not Content-Type.
On a request, the Accept header is used to request the Content-Type of the response from the server.
On a request the Content-Type is used to define the structure of the request body.
I am trying to build a rest client using jersey 2.13.
The rest endpoint is in : https://gist.githubusercontent.com/richersoon/ff4dd5c5abe414c5ec4c/raw/4ce49c32e57bf071d052f7efa76f332d60308035/user.json
But when I tried to run the application I got:
Exception in thread "main" org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/plain, type=class com.napier.entity.User, genericType=class com.napier.entity.User.
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:173)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:134)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:988)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:833)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:768)
at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:96)
at org.glassfish.jersey.client.ScopedJaxrsResponse.access$001(ScopedJaxrsResponse.java:56)
at org.glassfish.jersey.client.ScopedJaxrsResponse$1.call(ScopedJaxrsResponse.java:77)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:397)
at org.glassfish.jersey.client.ScopedJaxrsResponse.readEntity(ScopedJaxrsResponse.java:74)
at com.napier.service.rest.UsersClient.main(UsersClient.java:20)
Here's the code:
public class UsersClient {
public static void main(String[] args) {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(
UriBuilder.fromUri(
"https://gist.githubusercontent.com/richersoon/ff4dd5c5abe414c5ec4c/raw/4ce49c32e57bf071d052f7efa76f332d60308035/user.json"));
Response response = target.request().accept(MediaType.APPLICATION_JSON).get(Response.class);
User user = response.readEntity(User.class);
System.out.println(user);
}
}
Here's the POJO:
#XmlRootElement
public class User {
private String firstname;
private String lastname;
private String photourl;
... setters and getters...
}
Here's the POM:
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.13</version>
</dependency>
Please guide me because I am totally new to webservices.
Your client is accepting results of media type "application/json", but your REST webservice returns "text/plain". Check this post to see a possible solution: MessageBodyReader not found for media type=application/octet-stream
Seems you are trying to access the wring uri, which is plain text.
I was able to get it to work with this uri, which is the actual .json file
"https://gist.github.com/richersoon/ff4dd5c5abe414c5ec4c#file-user-json"