Getting invalid SpanContext in mongo extension for java OpenTelemetry automatic instrumentation - open-telemetry

I am writing an extension of OpenTelemetry to input traceId as comment in mongo query. For this I have put an Advice on the find method in com.mongodb.client.MongoCollection. Inside the Advice when I call spanContext.isValid() it returns false, also spanId and traceId have all zeros in them.
MongoQueryInstrumentation.java
public class MongoQueryInstrumentation implements TypeInstrumentation {
#Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("com.mongodb.client.MongoCollection"));
}
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("find").and(takesArgument(0, Bson.class)).and(ElementMatchers.isPublic()),
AdvicesFind.class.getName());
}
#SuppressWarnings("unused")
public static class AdvicesFind {
#Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(#Advice.Return(readOnly = false) FindIterable<?> result) {
SpanContext spanContext = Java8BytecodeBridge.currentSpan().getSpanContext();
System.out.println("traceId:" + spanContext.getTraceId());
System.out.println("VALID :" + spanContext.isValid());
result.comment(spanContext.getTraceId());
}
}
}
MongoInstrumentationModule.java
#AutoService(InstrumentationModule.class)
public final class MongoInstrumentationModule extends InstrumentationModule {
public MongoInstrumentationModule() {
super("mongo-ext", "mongo-4.0");
}
#Override
public int order() {
return 1;
}
#Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("com.mongodb.internal.async.SingleResultCallback");
}
#Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new MongoQueryInstrumentation());
}
}
OUTPUT:
traceId:00000000000000000000000000000000
VALID :false
I am exporting the logs to zipkin, there db.statement is
{"find": "sampleCollection", "filter": {"title": "MongoDB"}, "comment": "00000000000000000000000000000000", "$db": "myDb", "lsid": {"id": {"$binary": {"base64": "nOpqMCwHRWe2h+qmVEgGIQ==", "subType": "04"}}}}
I tried doing similar thing in JDBC by adding a comment containing traceId and spanId and there it worked as expected. There I patched sendQueryString method of NativeProtocolInstrumentation.
There Java8BytecodeBridge.currentSpan().getSpanContext() returned a valid spanId but in mongo it does not.

Related

Getting a '404 Not Found' error, when testing serialised JSON-B output

I'm trying to build a simple JSON-B program, which converts a java object to a JSON string. The programme also uses the #JsonbProperty annotation on the pojo, to map an unmatched json property to the java object.
The string is then sent to a JMS queue, for a JMS consumer to pick up. I'm using a bash test script, to print the serialised json output in the console. The test script and .json files have been provided, and i'm just assuming they're correct.
When I run the bash script I'm getting a '404 Not Found' error, with no output in the console. I've attached the java code, the .json file, the test scripts and the error messages.
Any help would be greatly appreciated.
Thanks in advance..
Bash Script
curl -X POST -H "Content-Type: application/json" -d #order.json http://localhost:8080/hsports-catalog-jax/hsports/api/order
Bash Script Terminal Error
[21:27] ~/IdeaProjects/JEE8_Essential_Training/HSports_CatalogProject/jaxrs_module/src/resources Marc's Mac >> ./test.sh
<html><head><title>Error</title></head><body>404 - Not Found</body></html>[21:27] ~/IdeaProjects/JEE8_Essential_Training/HSports_CatalogProject/jaxrs_module/src/resources Marc's Mac >>
JAX-RS Endpoint
#RequestScoped
#Path("/order")
#Produces("application/json")
#Consumes("application/json")
public class OrderEndpoint {
// injects JMS producer from ejb module
#Inject
private JmsService jmsService;
// method for placing an order
// accepts Order object JAX-RS resource
// method will convert it to json
#POST
public void placeOrder(Order order) {
Jsonb jsonb = JsonbBuilder.create(); // json builder object
String json = jsonb.toJson(order); // converts order object to json representation
System.out.println(json);
jmsService.send(json);
}
}
JMS Producer
#ApplicationScoped
public class JmsService {
// injects JMS queue we want to send the message to
#Resource(mappedName = "java:/jms/queue/HsportsQueue") // JNDI name
private Queue hsportsQueue;
#Inject
#JMSConnectionFactory("java:/ConnectionFactory")
private JMSContext context;
// code that sends the message to the consumer
public void send(String message) {
try {
TextMessage textMessage = context.createTextMessage(message); // message object
context.createProducer().send(hsportsQueue, textMessage); // producer object
System.out.println("Message sent to JMS queue"); // console println
} catch (Exception e) {
e.printStackTrace();
}
}
}
JMS Consumer
#MessageDriven(
activationConfig = {
#ActivationConfigProperty(
propertyName = "destination", propertyValue = "/jms/queue/HsportsQueue"),
#ActivationConfigProperty(
propertyName = "destinationType", propertyValue = "javax.jms.Queue")
},
mappedName = "/jms/queue/HsportsQueue")
public class JmsConsumerBean implements javax.jms.MessageListener {
public JmsConsumerBean() {
}
// defines what the consumer does, when msg is received from jms queue
#Override
public void onMessage(Message message) {
System.out.println("Message received, JMS Consumer message-driven bean");
try {
System.out.println(message.getBody(String.class));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
Java Pojo
public class Order {
private Long orderId;
private String storeName;
private Customer customer; // new class created
private List<InventoryItem> items;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public List<InventoryItem> getItems() {
return items;
}
// maps json-inventoryItems to java-item
#JsonbProperty("inventoryItems")
public void setItems(List<InventoryItem> items) {
this.items = items;
}
}
JSON File Content
{
"orderId": 1,
"storeName": "Franklin Park",
"customer": {
"customerId": 1,
"firstName": "Kevin",
"lastName": "Bowersox"
},
"inventoryItems": [
{
"inventoryItemId": 1,
"catalogItemId": 1,
"name": "Sneakers",
"quantity": 4
}
]
}
Ah! Problem solved. I had the wrong module name in the URI...idiot!

unable to bind request parameters to the object in spring mvc?

Hi I am having an angular ui which consumes rest api's provided by a spring boot application. from the angular ui i am issuing a GET rest api call , however the request parameters are not getting binded to the object. the following is my GET request.
curl -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjYW1wYWlnbm1hbmFnZXJAbG9jYWxob3N0IiwiYXV0aCI6IlJPTEVfQ0FNUEFJR05fTUFOQUdFUiIsImV4cCI6MTU1ODE4MzAyM30.OHSqVZ5c9-44SyyB_ykFqf9xC-06UvSv-F7UYLvrrK_YNJrqF3Mvuv8zvTrBqdMXRMBdCQNmitVQ38zdZxj3Tg" http://localhost:8080/api/campaigns/unpaginated?statuses=357632f0-1afd-4af2-a8f2-3b964884bfb3&statuses=2f02e5f0-2d56-4583-a9db-f962becbd5f9&accounts=e15965cf-ffc1-40ae-94c4-b450ab190222
The following is my RestController named CampaignResource & request method
getAllCampaignsUnpaginated
#RestController
#RequestMapping("/api")
public class CampaignResource {
/**
* GET /campaigns : get all the campaigns unpaginated.
*
* #return the ResponseEntity with status 200 (OK) and the list of campaigns in body
*/
#GetMapping("/campaigns/unpaginated")
#Timed
#Secured({AuthoritiesConstants.GLOBAL_ADMIN, AuthoritiesConstants.ACCOUNT_ADMIN, AuthoritiesConstants.CAMPAIGN_MANAGER, AuthoritiesConstants.TEAM_MEMBER})
public ResponseEntity<List<DropdownDTO>> getAllCampaignsUnpaginated(CampaignFilterRequest filter) {
log.debug("REST request to get all Campaigns");
return ResponseEntity.ok().body(campaignService.findAll(filter));
}
}
the following is my CampaignFilterRequest class to which i want to bind my request parameters .
import com.google.common.collect.Lists;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
public class CampaignFilterRequest {
private ZonedDateTime minStartDate;
private ZonedDateTime maxEndDate;
private List<UUID> types = Lists.newArrayList();
private List<UUID> createdBy = Lists.newArrayList();
private List<UUID> statuses = Lists.newArrayList();
private List<UUID> accounts = Lists.newArrayList();
public ZonedDateTime getMinStartDate() {
return minStartDate;
}
public void setMinStartDate(ZonedDateTime minStartDate) {
this.minStartDate = minStartDate;
}
public ZonedDateTime getMaxEndDate() {
return maxEndDate;
}
public void setMaxEndDate(ZonedDateTime maxEndDate) {
this.maxEndDate = maxEndDate;
}
public List<UUID> getStatuses() {
return statuses;
}
public void addStatus(UUID status) {
this.statuses.add(status);
}
public List<UUID> getTypes() {
return types;
}
public void setTypes(List<UUID> types) {
this.types = types;
}
public void addType(UUID type) {
this.types.add(type);
}
public List<UUID> getCreatedBy() {
return createdBy;
}
public void setCreatedBy(List<UUID> createdBy) {
this.createdBy = createdBy;
}
public void addCreatedBy(UUID createdBy) {
this.createdBy.add(createdBy);
}
public List<UUID> getAccounts() {
return accounts;
}
public void addAccount(UUID accounts) {
this.accounts.add(accounts);
}
public void setAccounts(List<UUID> accounts) {
this.accounts = accounts;
}
}
I am able to put a debug on the getAllCampaignsUnpaginated and i can see the statuses and accounts are empty . !!!
appreciate any help
thanks a lot.
You need setter methods for the collections as a collection object instead of a per object basis. You have
public void addStatus(UUID status) {
this.statuses.add(status);
}
But spring doesn't know how to set the uuids, if you add a setter for the entire collection it will work, for example
public void setStatuses(List<UUID> statuses) {
this.statuses = statuses;
}
Adding to this i would also suggest you create a constructor which contains all of the fields of the class that you want to set. That way you don't need setters and the class will contain less boilerplate.

Spring Rest -> Hibernate entity to JSON

I am creating REST API using spring framework. My entity is based on one table and REST API is supposed to be invoked using POST operation with below JSON structure. Can someone explain me how to map the entity class so that it can consume below-shown json.
Since my entity is based on only one table, I am not able to understand how can it create nested json objects for same table properties.
{
"process_ar_receipt": {
"message_header": {
"source_system_guid": "DDED-DBCD-REV-E1F4343DB3434",
"source_system": "MeSo_TravelAds"
},
"receipt_header": {
"customer_number": "123",
"source_receipt_number": "TESTRCPT_1523",
}
}
}
you could use Gson to convert the json to a DTO
https://jarroba.com/gson-json-java-ejemplos/
pseudo code
assuming your Entity class as
#Entity(name="foo")
class Data{
#Id
private String source_system_guid;
#Column
private String source_system;
#Column
private String customer_number;
#Column
private String source_receipt_number;
public Data() {}
public Data(String ssId, String sourceSystm, String custNum, String srcRcptNum) {
this.source_system_guid = ssId;
this.source_system = sourceSystm;
this.customer_number = custNum;
this.source_receipt_number = srcRcptNum;
}
public String getSource_system_guid() {
return source_system_guid;
}
public void setSource_system_guid(String source_system_guid) {
this.source_system_guid = source_system_guid;
}
public String getSource_system() {
return source_system;
}
public void setSource_system(String source_system) {
this.source_system = source_system;
}
public String getCustomer_number() {
return customer_number;
}
public void setCustomer_number(String customer_number) {
this.customer_number = customer_number;
}
public String getSource_receipt_number() {
return source_receipt_number;
}
public void setSource_receipt_number(String source_receipt_number) {
this.source_receipt_number = source_receipt_number;
}
}
Now since your DTO/BO i.e. Data Transfer Object or Business Object is different from the actual entity we will create the required BO object as below
class DataTO{
#JsonProperty("process_ar_receipt")
private ReceiptTO receiptTO=new ReceiptTO();
public ReceiptTO getReceiptTO() {
return receiptTO;
}
public void setReceiptTO(ReceiptTO receiptTO) {
this.receiptTO = receiptTO;
}
}
class ReceiptTO{
#JsonProperty("message_header")
private MessageHeader messageHeder = new MessageHeader();
#JsonProperty("receipt_header")
private ReceiptHeader receiptHeder = new ReceiptHeader();
public MessageHeader getMessageHeder() {
return messageHeder;
}
public void setMessageHeder(MessageHeader messageHeder) {
this.messageHeder = messageHeder;
}
public ReceiptHeader getReceiptHeder() {
return receiptHeder;
}
public void setReceiptHeder(ReceiptHeader receiptHeder) {
this.receiptHeder = receiptHeder;
}
}
class MessageHeader{
#JsonProperty("source_System_Guid")
private String sourceSystemId;
#JsonProperty("system_Id")
private String systemId;
public String getSourceSystemId() {
return sourceSystemId;
}
public void setSourceSystemId(String sourceSystemId) {
this.sourceSystemId = sourceSystemId;
}
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
}
class ReceiptHeader{
#JsonProperty("customer_number")
private String customerNumber;
#JsonProperty("source_rcpt_number")
private String sourceReceiptNumber;
public String getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
public String getSourceReceiptNumber() {
return sourceReceiptNumber;
}
public void setSourceReceiptNumber(String sourceReceiptNumber) {
this.sourceReceiptNumber = sourceReceiptNumber;
}
}
The #JsonProperty annotation is imported from org.codehaus.jackson.annotate.JsonProperty; i.e from jackson jar
Now a Simple Test class to demo DTO/BO back and forth Entity conversion
public class Test{
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
List<Data> datas = new ArrayList<Data>();
datas.add(new Data("DDED-DBCD-REV-E1F4343DB3434","MeSo_TravelAds","123","TESTRCPT_1523"));
datas.add(new Data("ADED-EWQD-REV-E1F4343YG3434","FooSo_MusicAds","132","TESTRCPT_1523"));
datas.add(new Data("YDED-YUTR-REV-E1F43UIDB3434","BarSo_HealthAds","143","TESTRCPT_1523"));
List<DataTO> dataTOs = new ArrayList<DataTO>();
for (Data data : datas) {
DataTO dataTO = new DataTO();
dataTO.getReceiptTO().getMessageHeder().setSourceSystemId(data.getSource_system_guid());
dataTO.getReceiptTO().getMessageHeder().setSystemId(data.getSource_system());
dataTO.getReceiptTO().getReceiptHeder().setCustomerNumber(data.getCustomer_number());
dataTO.getReceiptTO().getReceiptHeder().setSourceReceiptNumber(data.getSource_receipt_number());
dataTOs.add(dataTO);
}
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(dataTOs);
System.out.println(str);
}
}
This will give you below result
[
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"DDED-DBCD-REV-E1F4343DB3434",
"system_Id":"MeSo_TravelAds"
},
"receipt_header":{
"customer_number":"123",
"source_rcpt_number":"TESTRCPT_1523"
}
}
},
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"ADED-EWQD-REV-E1F4343YG3434",
"system_Id":"FooSo_MusicAds"
},
"receipt_header":{
"customer_number":"132",
"source_rcpt_number":"TESTRCPT_1523"
}
}
},
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"YDED-YUTR-REV-E1F43UIDB3434",
"system_Id":"BarSo_HealthAds"
},
"receipt_header":{
"customer_number":"143",
"source_rcpt_number":"TESTRCPT_1523"
}
}
}
]
similarly the other conversion
String input = "{ \r\n" +
" \"process_ar_receipt\":{ \r\n" +
" \"message_header\":{ \r\n" +
" \"source_System_Guid\":\"ADED-EWQD-REV-E1F4343YG3434\",\r\n" +
" \"system_Id\":\"FooSo_MusicAds\"\r\n" +
" },\r\n" +
" \"receipt_header\":{ \r\n" +
" \"customer_number\":\"132\",\r\n" +
" \"source_rcpt_number\":\"TESTRCPT_1523\"\r\n" +
" }\r\n" +
" }\r\n" +
" }";
DataTO dataTO = mapper.readValue(input, DataTO.class);
System.out.println(dataTO.getReceiptTO().getMessageHeder().getSourceSystemId());
System.out.println(dataTO.getReceiptTO().getMessageHeder().getSystemId());
System.out.println(dataTO.getReceiptTO().getReceiptHeder().getCustomerNumber());
System.out.println(dataTO.getReceiptTO().getReceiptHeder().getSourceReceiptNumber());
this will print
ADED-EWQD-REV-E1F4343YG3434
FooSo_MusicAds
132
TESTRCPT_1523
You dont have to use the mapper code you can directly add the jackson converter as HttpMessageConverted which will convert the JSON to java object automatically
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
... other configurations
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}

Converter from #PathVariable DomainObject to String? (using ControllerLinkBuilder.methodOn)

I'm trying to call Spring's ControllerLinkBuilder.methodOn() with a non-String type, which always fails. And I don't know which kind of Converter to use and where to register it.
Here's my Controller:
#RestController
#RequestMapping("/companies")
class CompanyController {
#RequestMapping(value="/{c}", method=RequestMethod.GET)
void getIt(#PathVariable Company c) {
System.out.println(c);
Link link = linkTo(methodOn(getClass()).getIt(c));
}
}
The System.out.println(c) works well. My Company Domain object get's fetched from DB. (I'm using DomainClassConverter)
But the other way doesn't work: ConverterNotFoundException: No converter found capable of converting from type #PathVariable Company to type String
Do I just need a Converter<Company, String>? And where should I register it? I tried something within the addFormatters(FormatterRegistry registry) method of WebMvcConfigurationSupport, but it did just display the same error. But after all I'm not sure what exactly I tried...
I had the same issue, it is a bug. If you don't want to do copy & paste on every controller you can try something like this in your WebMvcConfigurationSupport. It works for me.
#Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service = (DefaultFormattingConversionService) field.get(null);
for (Converter<?, ?> converter : beanFactory.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
Found a "solution". It requires a lot copy & paste from Spring's classes, but at least it works!
Basically I had to copy org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor and change two lines:
class AnnotatedParametersParameterAccessor {
...
static class BoundMethodParameter {
// OLD: (with this one you can't call addConverter())
// private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
// NEW:
private static final FormattingConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
...
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
...
// ADD:
CONVERSION_SERVICE.addConverter(new MyNewConverter());
}
...
}
This class get's used by ControllerLinkBuilderFactory. So I had to copy & paste that, too.
And this one get's used by ControllerLinkBuilder. Also copy & paste.
My Converter just does myDomainObject.getId().toString():
public class MyNewConverter implements Converter<Company, String> {
#Override
public String convert(Company source) {
return source.getId().toString();
}
}
Now you can use the copy&pasted ControllerLinkBuilder inside the controller and it works as expected!
I developed a framework to render links in spring hateoas and it supports annotated parameters (#PathVariable and #RequestParam) and arbitrary parameters types.
In order to render these arbitrary types you have to create a spring bean that implements com.github.osvaldopina.linkbuilder.argumentresolver.ArgumentResolver interface.
The interface has 3 methods:
public boolean resolveFor(MethodParameter methodParameter)
Is used to determine if the ArgumentResolver can be used to deal with the methodParameter. For example:
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
Defines that this ArgumentResover will be used for UserDefinedType.
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter)
Is used to include in the uriTemplate associated with the method the proper template parts. For example:
#Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
adds 2 query parameters (value1 and value2) to the uri template.
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames)
Sets in the template the values for the template variables. For example:
#Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
gets the UserDefinedType instance and use it to sets the templates variables value1 and value2 defined in augmentTemplate method.
A ArgumentResolver complete example would be:
#Component
public class UserDefinedTypeArgumentResolver implements ArgumentResolver {
#Override
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
#Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
#Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
}
and for the following link builder:
linksBuilder.link()
.withRel("user-type")
.fromControllerCall(RootRestController.class)
.queryParameterForUserDefinedType(new UserDefinedType("v1", "v2"));
to the following method:
#RequestMapping("/user-defined-type")
#EnableSelfFromCurrentCall
public void queryParameterForUserDefinedType(UserDefinedType userDefinedType) {
}
would generate the following link:
{
...
"_links": {
"user-type": {
"href": "http://localhost:8080/user-defined-type?value1=v1&value2=v2"
}
...
}
}
full config in spring boot. same as Franco Gotusso's answer just provide more detail.
```
/**
* This configuration file is to fix bug of Spring Hateoas.
* please check https://github.com/spring-projects/spring-hateoas/issues/118.
*/
#Component
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private ApplicationContext applicationContext;
#Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc."
+ "AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service =
(DefaultFormattingConversionService) field.get(null);
for (Formatter<?> formatter : applicationContext
.getBeansOfType(Formatter.class).values()) {
service.addFormatter(formatter);
}
for (Converter<?, ?> converter : applicationContext
.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
```

smartgwt listgrid RestDataSource not populating

Im new using this front end framework application...
I recently started to work with smartgwt and i'm bulding a new application with a Spring MVC integration.
I'm using a ListGrid with a RestDataSource (Consume the Rest service with mvc:annotation-driven for plain JSON)
I can see that the servaice gets consuming properly perhaps my grid is never shown with the data in it.
Can someone help me here ?
Here's my ListGrid class
public class ListGrid extends com.smartgwt.client.widgets.grid.ListGrid {
private final SpringJSONDataSource springJSONDataSource;
public ListGrid(List<DataSourceField> fields) {
this(new PatientDataSource(fields));
}
public ListGrid(SpringJSONDataSource springJSONDataSource) {
this.springJSONDataSource = springJSONDataSource;
init();
}
private void init() {
setAutoFetchData(true);
setAlternateRecordStyles(true);
setEmptyCellValue("???");
setDataPageSize(50);
setDataSource(springJSONDataSource);
}
}
Now there's the DataSource implmentation
public abstract class SpringJSONDataSource extends RestDataSource {
protected final HTTPMethod httpMethod;
public SpringJSONDataSource(List<DataSourceField> fields) {
this(fields, HTTPMethod.POST);
}
public SpringJSONDataSource(List<DataSourceField> fields, HTTPMethod httpMethod) {
this.httpMethod = httpMethod;
setDataFormat(DSDataFormat.JSON);
addDataSourceFields(fields);
setOperationBindings(getFetch());
addURLs();
}
private void addURLs() {
if(getUpdateDataURL() != null)
setUpdateDataURL(getUpdateDataURL());
if(getRemoveDataURL() != null)
setRemoveDataURL(getRemoveDataURL());
if(getAddDataURL() != null)
setAddDataURL(getAddDataURL());
if(getFetchDataURL() != null)
setFetchDataURL(getFetchDataURL());
}
private void addDataSourceFields(List<DataSourceField> fields) {
for (DataSourceField dataSourceField : fields) {
addField(dataSourceField);
}
}
protected abstract OperationBinding getFetch();
protected abstract OperationBinding getRemove();
protected abstract OperationBinding getAdd();
protected abstract OperationBinding getUpdate();
public abstract String getUpdateDataURL();
public abstract String getRemoveDataURL();
public abstract String getAddDataURL();
public abstract String getFetchDataURL();
}
The class PatientDataSource that extends SpringJSONDataSource
public class PatientDataSource extends SpringJSONDataSource {
public PatientDataSource(List<DataSourceField> fields) {
super(fields);
setPrettyPrintJSON(true);
}
#Override
protected OperationBinding getFetch() {
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.POSTMESSAGE);
DSRequest fetchProps = new DSRequest();
fetchProps.setHttpMethod(httpMethod.toString());
fetch.setRequestProperties(fetchProps);
return fetch;
}
#Override
public String getFetchDataURL() {
return "/spring/fetchPatients";
}
#Override
protected OperationBinding getRemove() {
return null;
}
#Override
public String getRemoveDataURL() {
return null;
}
#Override
protected OperationBinding getAdd() {
return null;
}
#Override
public String getAddDataURL() {
return null;
}
#Override
protected OperationBinding getUpdate() {
return null;
}
#Override
public String getUpdateDataURL() {
return null;
}
}
My spring controller PatientControler
#Controller
public class PatienController {
Logger logger = Logger.getLogger(PatienController.class);
#Autowired
private PatientServices patientServices;
#RequestMapping(value = "/patientTest", method = RequestMethod.GET)
#ResponseBody
public Object getTest()
{
return patientServices.getAllPatients();
}
#RequestMapping(value = "/fetchPatients", method = RequestMethod.POST)
#ResponseBody
public Object getAllPatients()
{
return patientServices.getAllPatients();
}
}
PatientServiceImpl
public class PatientServicesImpl implements PatientServices {
public List<Patient> getAllPatients() {
List<Patient> patients = new ArrayList<Patient>();
Patient patient;
for(int i = 0 ; i < 500 ; i++){
patient = new Patient();
patient.setDateOfBirth(new Date());
patient.setFirstName("Joe");
patient.setMiddleName("Moe");
patient.setLastName("Blow");
patient.setLastConsultation(new Date());
patient.setSex(Sex.M);
patients.add(patient);
}
return patients;
}
}
*Im Really stuck right now i've been looking for all type of answers .... but so far nothing worked when i tried to override the transformResponse from my RestDataSource impentation the parameter "data" as an OBJECT, returns me an array [object Object],[object Object],[object Object],[object Object],[object Object] *
The Data which is transferred from the RestDataSource has a specific format which is described in the JavaDoc of the RestDataSource
Your server must understand the request and send back a valid response.
At the moment your example doesn't seem to honour the contract.
To debug the traffic send to and from your server you can use the SmartClient-Console. You can open it by a browser bookmark like this:
javascript:isc.showConsole()
Of cause you need to deploy this console by adding the following module to your gwt.xml
<inherits name="com.smartclient.tools.SmartClientTools"/>
Now go to the RPC Tab and check Track-RPCs

Resources