spring cloud stream kafka 2.0 - StreamListener with condition - apache-kafka

I'm trying to create a consumer using StreamListener annotation and condition attirbute.However , i'm getting the following exception :
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'test'; nested exception is java.lang.NumberFormatException: For input string: "test"
TestListener:
#StreamListener(target=ITestSink.CHANNEL_NAME,condition="payload['test'] == 'test'")
public void test(#Payload TestObj message) {
log.info("message is {}",message.getName());
}
TestObj:
#Data
#ToString(callSuper=true)
public class TestObj {
#JsonProperty("test")
private String test;
#JsonProperty("name")
private String name;
}
can someone assist with this issue?

the payload of the message is not yet converted from the wire format (byte[]) to the desired type. In other words, it has not yet gone through the type conversion process described in the Content Type Negotiation.
So, unless you use a SPeL expression that evaluates raw data (for example, the value of the first byte in the byte array), use message header-based expressions (such as condition = "headers['type']=='dog'").
Example:
#StreamListener(target = Sink.INPUT, condition = "headers['type']=='bogey'")
public void receiveBogey(#Payload BogeyPojo bogeyPojo) {
// handle the message
}
Check the Spring documentation here.

From what you show, it should work. I suggest you remove the condition, then set breakpoint to debug. Then you should be able to know which actual type is.

Related

Mongo Map codec hates ObjectId keys

I have a bit of code I'm using to handle any type of Map, copied from here: MongoDb Map<K, V> codec - Maps MUST have string keys FIX
#Override
public void encode(final BsonWriter writer, final Map<K, T> map, final EncoderContext encoderContext) {
try (var dummyWriter = new BsonDocumentWriter(new BsonDocument())) {
dummyWriter.writeStartDocument();
writer.writeStartDocument();
for (final Map.Entry<K, T> entry : map.entrySet()) {
var dummyId = UUID.randomUUID().toString();
dummyWriter.writeName(dummyId);
keyCodec.encode(dummyWriter, entry.getKey(), encoderContext);
//TODO: could it be simpler by something like JsonWriter?
writer.writeName(dummyWriter.getDocument().asDocument().get(dummyId).asString().getValue());
valueCodec.encode(writer, entry.getValue(), encoderContext);
}
dummyWriter.writeEndDocument();
}
writer.writeEndDocument();
}
This seems to work fine most of the time, except for when the Map is of type Map<ObjectId, *>, causing the following error:
Value expected to be of type STRING is of unexpected type OBJECT_ID
org.bson.BsonInvalidOperationException: Value expected to be of type STRING is of unexpected type OBJECT_ID
on the dummyWriter.getDocument().asDocument().get(dummyId).asString() line, in encode().
I have attempted to work arond this via the following code, but also fails; looks like there is an issue with how the type is written...
BsonValue cur = dummyWriter.getDocument().asDocument().get(dummyId);
if(cur.isObjectId()){
writer.writeName(
dummyWriter.getDocument().asDocument().get(dummyId).asObjectId().getValue().toHexString()
);
} else {
writer.writeName(
dummyWriter.getDocument().asDocument().get(dummyId).asString().getValue()
);
}
An exception occurred when decoding using the AutomaticPojoCodec.
Decoding into a '' failed with the following exception:
Cannot find a public constructor for ''.
I am beginning to suspect this code shouldn't work, and the default codec for Map<String, *> is actually being used where I thought I tested this codec before... What can get my codecs to play nice?

Drools get object from map mvel cast exception

I am using jboss EAP 7.2 and Red Hat Decision Central 7.5.0
I have a custom objects like that
public class Model{
private String id;
private Map<String, Object> map;
// ... getters and setters
}
public class ParameterModel{
private String parameterName;
private BigDecimal maxValue;
private BigDecimal minValue;
private Object value;
// ... getters and setters
}
I have created new "Model" object that has custom "id" attribute and "map" attribute contains <parameterName, ParameterModel> pairs.
I sent it to decision manager and drools side ı want to achieve ParameterModel attributes but I could not.
My rule is below.
package com.rule.test;
import com.test.Model;
import com.test.ParameterModel;
rule "drools1"
when
Model(getId().equals("1"), Integer.parseInt(((ParameterModel)getMap().get("param1")).getValue().toString())>10)
then
System.out.println("Error on " + drools.getRule().getName());
end
The exception is below.
Caused by: [Error: null pointer:
Integer.parseInt(((ParameterModel)getMap().get("param1")).getValue().toString())]
[Near : {... Integer.parseInt(((ParameterMo ....}] In [Rule "drools1"
in com/rule/test/test.drl]
Caused by: java.lang.NullPointerException
at org.mvel2.DataConversion.convert(DataConversion.java:129)
at org.mvel2.ast.TypeCast.getReducedValueAccelerated(TypeCast.java:74)
at org.mvel2.compiler.ExecutableAccessor.getValue(ExecutableAccessor.java:38)
at org.mvel2.ast.Substatement.getReducedValueAccelerated(Substatement.java:44)
at org.mvel2.ast.Union.getReducedValueAccelerated(Union.java:44)
at org.mvel2.compiler.ExecutableAccessor.getValue(ExecutableAccessor.java:38)
at org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer.getMethod(ReflectiveAccessorOptimizer.java:970)
at org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer.compileGetChain(ReflectiveAccessorOptimizer.java:396)
Thank you all.
Drools has a lot of built-in null checking, but if you insist on bypassing it you're going to end up with a lot of errors.
The other kind of interesting Drools has is a special syntax for maps -- get("foo") can be written as this["foo"].
rule "drools1"
when
Model( id == "1", // equivalent to getId().equals("1")
$map: map != null )
// Special syntax for getting stuff from maps:
Map( $param1: this["param1"] != null ) from $map
ParameterModel( $value: value != null ) from $param1
Integer( this > 10 ) from Integer.parseInt($value.toString())
then
System.out.println("Error on " + drools.getRule().getName());
end
Why was your version giving a NPE? Not a clue, but it was also just about unreadable in its original form. All that really can be gleaned from the stack trace was that it was occurring at some implicit conversion step.
Of course this version will also fail if value isn't something whose toString() turns into a String representation of an integer.
Note that if value is actually an Integer type, then you don't need to waste time with the parse int and can just do:
Integer(this > 10) from $value

How to convert SinkRecord to JSON string?

Imagine myAPICreate requires a JSON string.
public void put(Collection<SinkRecord> collection) {
for (SinkRecord record : collection) {
JSONObject recordJson = toJSON(record.value());
String recordJsonString = recordJson.toString();
myAPICreate(recordJsonString);
}
}
toJSON is a helper I have defined which just takes the record and returns a JSONObject.
JSONObject json = new JSONObject()
.put("a", record.getString("a"))
.put("b", record.getString("b"))
.put("c", record.getString("c"));
I feel like I might be doing a lot of redundant work here. Is it necessary to have the code in put convert it to JSON or is there a way to use the converters so that record already comes in as JSON or a JSON string? Then I can just pass myAPICreate(record.value().toString()) without having to manually do it?
When you create a SinkRecord, you have a key & value schema w/ a key and value Object. Those objects should be Struct instances that must be created with the matching Schema
In the Connector configuration, you would then use JSONConverter (or other converter) to get the serialized output

Customising Spring Boot Exception Handling to Prevent Stacktraces Being Returned in Rest Response

How do I configure my spring boot service so that errors such as 500 don't potentially leak implementation details such as stacktraces.
{
"timestamp": "2019/05/01 15:06:17",
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class net.i2p.crypto.eddsa.math.ed25519.Ed25519LittleEndianEncoding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.Collections$UnmodifiableRandomAccessList[0]->........)",
"path": "/api/test"
}
Note: here the stacktrace is in the message and not the exception part of the json.
As you can see I am already formatting the timestamp with:
#Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
private static final String TIMESTAMP = "timestamp";
#Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//Let Spring handle the error first
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
//Format & update timestamp
Object timestamp = errorAttributes.get(TIMESTAMP);
if(timestamp == null) {
errorAttributes.put(TIMESTAMP, dateFormat.format(new Date()));
} else {
errorAttributes.put(TIMESTAMP, dateFormat.format((Date)timestamp));
}
return errorAttributes;
}
}
But I need to handle the message too.
If this 500 was the only error I could just do:
errorAttributes.put("message", "Server error. Contact support.");
However, all the errors go through here and that would override all the messages.
I could check if the status is 500 and only modify it then. However, there are other errors that can be generated that also might leak stacktraces.
Using #RestControllerAdvice seems to require knowing every exception that is generated and having an #ExceptionHandler for each and knowing which status code to respond with.
Is there a cleaner way to handle this?
It may not be the "cleanest" approach, but with projects I've been on we had a "standard format" for our Error Responses across projects, so we had a custom object with the fields that matched our orgs standard (HttpStatus, Reason, ect.) that extended RuntimeException. Then in our controllers, services, repos, ect we would catch exceptions and create this object accordingly and throw the custom one up instead. Based upon where it happened in the app (repo, service, controller, ect.) we could give our own custom verbage to it, but still log out the full exception in our server logs so we could investigate later
For example if we caught an error in our repository we would create our custom error object, set the Reason to DB unavailable (really all the consumer needs to know), set the status to HttpStatus.SERVICE_UNAVAILABLE (we tracked these with reasons and httpstatus with enums to keep status the same across modules), and throw the custom object up to the controller to be returned.
Sorry if this was a longwinded answer that may not give you what you want, I'm not too familiar with how you're trying to do it so figured I'd just give an example of other methods. I'll put some sample code as well
Custom Exception:
data class MyException(
val reason: String,
val httpStatus: HttpStatus? = null
) : RuntimeException(reason)
Method for creation:
fun createApiException(errorCode: ErrorCodeEnum) = MyException(
reason = errorCode.reason,
httpStatus = errorCode.httpStatus,
)
Spring-boot provides us with a standard method to handle exceptions using spring aop concept. You can use the #ControllerAdvice and #Exceptionhandled annotations to handle exceptions from a spring-boot rest endpoint so that a custom exception is always thrown from a rest endpoint with proper error code and error response.
The #ResponseStatus() annotation can be used to customize the response code being thrown.
For example consider the custom exception :
#ResponseStatus(HttpStatus.NOT_FOUND)
public class DataNotFoundException extends RuntimeException {
public DataNotFoundException(String exception) {
super(exception);
}
}
We can throw this error from a rest GET mapping when a data is not found like :
#GetMapping("/trains/{id}")
public Resource<Student> retrieveTrains(#PathVariable long id) {
Optional<Trains> trains = trainRepository.findById(id);
if (!train.isPresent())
throw new DataNotFoundException("id-" + id);
Resource<Trains> resource = new Resource<Trains>(train.get());
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllTrains());
resource.add(linkTo.withRel("all-trains"));
return resource;
}
Default error response provided by Spring Boot contains all the details that are typically needed.
However, you might want to create a framework independent response structure for your organization. In that case, you can define a specific error response structure.
For example :
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
To use this error node we use :
#ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(DataNotFoundException.class)
public final ResponseEntity<ErrorDetails> handleUserNotFoundException(DataNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(DataNotFoundException.class) indicates that this
method would handle exceptions of the specific type.
new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND) - Create an
error response object and return it with a specific Http Status.
For a more generalized exception handler you can define a method that handles exception of the type Exception.class, that way you don't have to know every exception.
Like :
#ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorDetails> handleAllExceptions(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
Reference from : https://www.javaguides.net/2019/02/spring-boot-2-angular-7-crud-example-tutorial.html

Use GuidRepresentation.Standard with MongoDB

I am implementing a custom IBsonSerializer with the official MongoDB driver (C#). I am in the situation where I must serialize and deserialize a Guid.
If I implement the Serialize method as follow, it works:
public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
BsonBinaryData data = new BsonBinaryData(value, GuidRepresentation.CSharpLegacy);
bsonWriter.WriteBinaryData(data);
}
However I don't want the Guid representation to be CSharpLegacy, I want to use the standard representation. But if I change the Guid representation in that code, I get the following error:
MongoDB.Bson.BsonSerializationException: The GuidRepresentation for the writer is CSharpLegacy, which requires the subType argument to be UuidLegacy, not UuidStandard.
How do I serialize a Guid value using the standard representation?
Old question but in case someone finds it on google like I did...
Do this once:
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
For example, in a Web Application/Web API, your Global.asax.cs file is best place to add it once
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
//Other code...below
}
}
If you don't want to modify the global setting BsonDefaults.GuidRepresentation (and you shouldn't, because modifying globals is a bad pattern), you can specify the setting when you create your collection:
IMongoDatabase db = ???;
string collectionName = ???;
var collectionSettings = new MongoCollectionSettings {
GuidRepresentation = GuidRepresentation.Standard
};
var collection = db.GetCollection<BsonDocument>(collectionName, collectionSettings);
Then any GUIDs written to the collection will be in the standard format.
Note that when you read records from the database, you will get a System.FormatException if the GUID format in the database is different from the format in your collection settings.
It looks like what's happening is when you are not explicitly passing the GuidRepresentation to BsonBinaryData constructor, it defaults to passing GuidRepresentation.Unspecified and that ultimately maps to GuidRepresentation.Legacy (see this line in the source)
So you need to explicitly pass the guidRepresentation as a third argument to BsonBinaryData set to GuidRepresentation.Standard.
edit: As was later pointed out, you can set BsonDefaults.GuidRepresentation = GuidRepresentation.Standard if that's what you always want to use.