I am trying to replace the standard Jackson as it is described here: http://camel.apache.org/json.html
I am using the following configuration
<bean name="myJsonObjectMapper" class="com.my.app.MyJsonObjectMapper" primary="true"/>
<camel:camelContext id="camel-client">
<camel:template id="camelTemplate"/>
<camel:dataFormats>
<camel:json id="json" library="Jackson" objectMapper="myJsonObjectMapper"/>
</camel:dataFormats>
</camel:camelContext>
MyJsonObjectMapper extends ObjectMapper. I can see it in Spring context, I can autowire and use it. I see that Camel context is started OK, I am using the Camel version 2.20 where this feature is enabled and fixed. However when I am trying to parse JSON I am getting the exceptions which will non-modified ObjectMapper generate.
When debugging I see that JacksonDataFormat is not initialized with customized ObjectMapper but rather creates a new one on doStart() method.
What am I missing on the Camel configuration?
UPD:
I am using that mapper in scope of Camel REST DSL routes in one of the following ways:
restConfiguration().component("servlet").bindingMode(RestBindingMode.json);
rest("/somepath")
.description("blah")
.post("/subpath/")
.type(MyRQ.class)
.outType(MyRS.class)
.route().id("under-test")
.bean(service)
.endRest();
or
restConfiguration().component("servlet");
rest("/somepath")
.description("blah")
.post("/subpath/")
.route().id("under-test")
.unmarshal().json(JsonLibrary.Jackson)
.bean(service)
.marshal().json(JsonLibrary.Jackson)
.endRest();
In both cases I am getting the marshaller error due to the usage of the default marshaller instead of custom one. The routes seem to be started in scpe of the correct context, the same I am configuring ObjectMapper for.
Related
I developed a small application that stores data coming from a device: I chose to store data in JSON format, and the serialization/deserialization of the data works just fine, even if it involves some custom types created by me...but only I work in the IDE (Eclipse, for that matter).
When I export a runnable JAR file though, the deserialization of the data encounters some kind of problem, because the software always throws this exception:
Caused by: java.lang.UnsupportedOperationException: Cannot allocate class LocalDateTime
at com.google.gson.internal.UnsafeAllocator$4.newInstance(UnsafeAllocator.java:104)
at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:225)
... 88 common frames omitted
I thought I'd encounter problems with custom types, not a built-in one. At this point, I discovered two things:
if I use a full JRE 9 to run the JAR file, the exception is not thrown: I double checked the modules included in the custom JRE I created with Jlink.exe, and everything is included correctly. I still want to use a smaller JRE, so I did not investigate further yet (I guess this explains why in the IDE it works perfectly)
I added a custom deserializer to the Gson object (see below), with which I simply manually converted the JSON string into a valid data, and that avoided the exception on the LocalDateTime class...but the exception reappeared simply on another class, this time a custom-made one.
At this point, I guess I can simply add a deserializer for each data type that causes problem, but I'm wondering why the issue won't happen with a full JRE, and why a smaller JRE causes this, even if all the modules required are included. Maybe it's worth mentioning also that I added no custom serializer to the Gson object that saves the data, it is all serialized as per Gson default.
LocalDateTime deserializer:
#Override
public LocalDateTime deserialize(JsonElement json, java.lang.reflect.Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject joDate = json.getAsJsonObject().get("date").getAsJsonObject();
JsonObject joTime = json.getAsJsonObject().get("time").getAsJsonObject();
//JSON example: {"date":{"year":2019,"month":1,"day":9},"time":{"hour":6,"minute":14,"second":1,"nano":0}
return LocalDateTime.of(joDate.get("year").getAsInt(),
joDate.get("month").getAsInt(),
joDate.get("day").getAsInt(),
joTime.get("hour").getAsInt(),
joTime.get("minute").getAsInt(),
joTime.get("second").getAsInt(),
joTime.get("nano").getAsInt());
}
}
Jdeps.deps modules list:
com.google.gson
java.base
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
org.slf4j
After the answer I received, I opened an issue here.
TL;DR
You need a runtime image (e.g. full JDK or something built with jlink) that includes the module jdk.unsupported.
Full Answer
GSON wants to create instances of classes it deserializes without calling any constructors (so nothing gets initialized without GSON saying so). This can't normally be done, but sun.misc.Unsafe offers a way to do this with the method allocateInstance. To that end, GSON needs an instance of sun.misc.Unsafe. The topmost frame in the call stack is from UnsafeAllocator, which uses common trickery to get Unsafe.
The problem is, sun.misc.Unsafe is in module jdk.unsupported, which is present in a full JDK but you won't usually find in runtime images.
When creating your runtime image with jlink, make sure to include the option --add-modules jdk.unsupported and you should be good to go.
Arguably, GSON should declare an optional dependency on jdk.unsupported with requires static.
I have faced the same issue when packing compose a desktop application.
update build.gradle file, add an unsupported module.
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "admin"
packageVersion = "1.0.0"
modules("java.sql")
modules("jdk.unsupported")
}
}
}
I'm doing my first steps with Camel and currently working on writing a simple junit test using jms as a transport.
Here is a code I wrote:
public class FirstMockTest extends CamelTestSupport {
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("jms:topic:quote")
.to("mock:quote");
}
};
}
#Test
public void testMessageCount() throws InterruptedException {
MockEndpoint mockEndpoint = getMockEndpoint("mock:quote");
mockEndpoint.setExpectedMessageCount(1);
template.sendBody("jms:topic:quote", "Camel rocks");
mockEndpoint.assertIsSatisfied();
}
}
Because of missing connectionFactory I got the following exception:
org.apache.camel.FailedToCreateRouteException: Failed to create route route1: Route(route1)[[From[jms:topic:quote]] -> [To[mock:quote]]] because of connectionFactory must be specified
I'm able to fix it adding the following lines to my route:
ConnectionFactory connectionFactory =
new ActiveMQConnectionFactory("vm://localhost?roker.persistent=false");
context.addComponent("jms", JmsComponent.jmsComponent(connectionFactory));
But I don't like I'm adding some components to my context inside the route. Also, If i want to have another route I will need to do it again.
Obviously, there should be another way to tell my test about connection factory.
Thank you in advance!
It's a good idea to define the JMS connection factory outside of your Camel context and, if possible, reuse it. How to do that depends on your component model / execution environment.
If you're using a Java SE version that supports CDI, that would be an obvious choice. You'd define your JMS connection factory as a named component once and inject it everywhere you need it. Have a look at http://camel.apache.org/cdi.html and for testing support at http://camel.apache.org/cdi-testing.html
If you're using Spring, define your connection factory as a spring bean and inject it wherever you need it.
If you're using Java EE on an application server, you'd usually define the JMS connection factory using the mechanisms of that app server. You'd then look up the JMS connection factory using JNDI.
If you're running in an OSGi container, you should define the JMS connection factory in its own bundle and export it as an OSGi service. In the bundle of your Camel context, import that OSGi servide and inject it into the Camel context.
In all above cases you should consider using a pooled JMS connection factory.
For CDI, Spring and OSGi, have a look at: http://activemq.apache.org/maven/5.14.5/apidocs/org/apache/activemq/jms/pool/PooledConnectionFactory.html
For Java EE the way how to set pooling parameters depends on your app server.
Note of caution: for Java SE CDI and Spring there should be only one Camel context per application (you can have many routes, though). So if the JMS connection factory is only used in that one Camel context, there is not much reuse. Despite that I still think it's preferable to define the JMS connection outside of the Camel context in a separate component. It's, well, cleaner.
Since you are writing a junit you can avoid creating a ConnectionFactory if you stub the jms endpoint. You can name the endpoint as stub:jms:topic:quote. Have a look at sample example at link https://github.com/camelinaction/camelinaction2/blob/master/chapter9/mock/src/test/java/camelinaction/FirstMockTest.java
I'm trying to use SerializationConfig.Feature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS but I'm not configuring the mapper myself, relying on annotations exclusively and letting Spring's RestTemplate (de)serialize automatically. Is there a way to enable the aforementioned feature in this scenario (i.e. annotations only)?
NOTE: I'm using Jackson 1.x and can't upgrade due to other libs...
With JAX-RS (like DropWizard) you can actually annotated resource endpoints, using #JacksonFeatures
public class Resource {
#Path("item")
#GET
#JacksonFeatures(serializationEnable={ SerializationFeature.WRAP_ROOT_VALUE })
public Pojo getItem(String id) {
...
}
}
I don't know if Spring exposes similar functionality, but it seems possible it does. And if not, it is something they should be able to add to allow per-endpoint setting/clearing of SerializationFeatures / DeserializationFeatures. So if it is not available, maybe file a feature request for Spring project?
Yes, it is possible.
checkout this link: http://jackson.codehaus.org/1.7.0/javadoc/org/codehaus/jackson/map/annotate/JsonSerialize.html
Example:
#JsonSerialize(using=MySerializer.class,
as=MySubClass.class,
include=JsonSerialize.Inclusion.NON_NULL,
typing=JsonSerialize.Typing.STATIC
)
How to achieve that some properties are ignored in the JSON-output (like XmlTransient for XML-output)?
#GET
#Path("/{companyId}")
#Produces(MediaType.APPLICATION_JSON)
public PortfolioCompany getCompany(#PathParam("companyId") long id);
I've been playing around with the RESTeasy-support in Seam 2.3 deployed as an EAR on a JBoss 7.1. I started by adding the same dependencies to my ejb-project as in the Seam-restbay-example.
It is basically working fine for #Produces(MediaType.APPLICATION_XML), where all properties annotated with #XmlTransient are ignored, in order to prevent some LazyInitialisationExceptions.
But how to achieve this behavior for #Produces(MediaType.APPLICATION_JSON)?
I've read Seam uses Jettison by default, which uses the #XmlTransient annotation for both, XML and JSON (because technically it transforms from XML -> to JSON). But I get a "Caused by: org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer" which indicates that in fact it is using Jackson..?
For Jackson there is the annotation like #JsonIgnore, but having the same maven dependencies like restbay - this "cannot to be resolved to a type".
/**
* #return the contact
*/
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#XmlTransient // working for produces XML but not for JSON
//#JsonIgnore = unknown type
public Contact getContact() {
return contact;
}
Anyone any experiences or hints on that?
thanks
EDIT: Really no one having the need of realizing lazy collections for REST-services with Seam??
After some research:
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property ="#id")
is what apparently would be needed. But that is only provided with Jackson 2.x. But the seam2.3/jboss7 setup is obviously using Jackson 1.9...
The default annotation mode of #XmlType/#XmlRootElement is to capture everyfield, including the lazy initializer from hibernate. Use #XmlAccessorType(XmlAccessType.NONE) in your entities and then individually annotate the fiels with #XmlElement so that only the necessary fields are processeded
Is there any way to instruct Jackson 2 ObjectMapper to use custom Classloader to deserialize JSON strings?
Here is the solution:
ClassLoader myClassLoader = ...
ObjectMapper om = new ObjectMapper();
TypeFactory tf = TypeFactory.defaultInstance()
.withClassLoader(myClassLoader);
om.setTypeFactory(tf);
Take a look at the Jackson Wiki for Modules, a Jackson Module can be used to create custom serialization / deserialization. While the wiki talks about what they can do
Add custom serializers and deserializers
Add mix-in annotations
Override or add AnnotationIntrospector
Access SerializationConfig and DeserializationConfig settings.
It is pretty light on details, just giving a very basic example. The Module JavaDoc is light on details as well. Fortunately the wiki also has a list of existing Jackson Modules on GitHub, which is more helpful as examples if you need to create your own Module from scratch.
To preserve the existing TypeMapper, you might want to do something like this (this is Scala, but same pattern works in Java), where you adjust the current type factory.
def updateClassLoader(cl: ClassLoader): Unit = {
objectMapper.setTypeFactory(objectMapper.getTypeFactory.withClassLoader(cl))
}
This will preserve cached type data from possible prior configuration calls like this:
mapper.registerModule(new Jdk8Module)
.registerModule(new JavaTimeModule)
.registerModule(new DefaultScalaModule)