I have a test http server which returns some jackson annotated classes and one method returns raw string.
Jackson annotated classes are serialized well into json strings but string returned by getUserName() is not json serialized.
Retrofit client I am using expects the string returned by getUserName() method to be json serialized and throws an exception during deserialization.
Is there some annotation I can use to tell retrofit to not deserialize the string returned by this method.
===== Test Http Server =====
#Path("v1/testserver")
public class TestServer {
#GET
#Path("username")
public String getUserName() {
return "Dummy user name";
}
.........
}
===== Test Http Client using Retrofit =====
public interface RetrofitTestService {
#GET("username")
Call<String> getUserName();
}
public class TestClient {
public static void main(String[] args) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/v1/testserver/")
.addConverterFactory(JacksonConverterFactory.create())
.build();
RetrofitTestService service = retrofit.create(RetrofitTestService.class);
Call<String> userName = service.getUserName();
try {
System.out.printf("UserName: " + userName.execute().body());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Using .addConverterFactory(ScalarsConverterFactory.create()) did help.
I was using wrong version of converter-scalars artifact before.
Related
I have an application running on Payara 4 using a custom GSON JSON adapter. I would like to migrate to Payara 5 (5.191) and start using JSON-B. In our current application we can control the JSON output using annotations on a resource.
For example using #Summarize:
#GET
#Path("summary/{encryptedId}")
#Produces(MediaType.APPLICATION_JSON)
#Summarize
public Address findSummarized(#PathParam("encryptedId") String encryptedId) {
return super.find(encryptedId);
}
it will cause a different GSON configuration to be used in our #Provider:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class GsonProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
public GsonProvider() {
gson = getGson(EntityAdapter.class);
gsonSummary = getGson(EntitySummaryAdapter.class);
}
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? gsonSummary : gson).toJson(object));
printWriter.flush();
}
}
}
I want to do something similar in the new JSON-B setup. I annotated our entities with #JsonbTypeSerializer(MySerializer.class), so I would like to be able to detect from within the serializer what it should do: either create a full serialized JSON object, or a summary.
What I hoped to do is set a property in the JsonbConfig, like so:
JsonbConfig config = new JsonbConfig()
.setProperty("com.myCompany.jsonb.summarize", true);
and read it in the serializer using #Context (just guessing that this might work here), like so:
#Context
private JsonbConfiguration config;
.. but that's not. Is there any way to access JAX-RS resource annotations from a JsonbSerializer?
You could accomplish a similar goal using two separate Jsonb instances in your JAX-RS provider class like so:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class JsonbProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
private static final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntityAdapter()));
private static final Jsonb jsonbSummary = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntitySummaryAdapter()));
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? jsonbSummary : jsonb).toJson(object));
printWriter.flush();
}
}
}
In the end I opted to create summaries from within my entities and drop the annotation on my REST resources. It was a bit of work, but I think it has been worth it.
I created a Summarizable interface and added a default method there to create a simple map summary of any entity, based on a extended version of the PropertyVisibilityStrategy we created for the full version of the entities.
public interface Summarizable {
public default Map<String, Object> toSummary() {
SummaryPropertyVisibilityStrategy summaryStrategy = new SummaryPropertyVisibilityStrategy();
Map<String, Object> summary = new LinkedHashMap<>();
ReflectionUtils.getFields(this.getClass())
.stream()
.filter(summaryStrategy::isVisible)
.map(f -> new AbstractMap.SimpleEntry<>(f.getName(), summarize(f)))
.filter(e -> e.getValue() != null)
.forEach(e -> summary.put(e.getKey(), e.getValue()));
return summary;
}
public default Object summarize(final Field field) {
Object value = ReflectionUtils.getValueJsonb(this, field);
return value != null && Stream.of(ManyToOne.class, OneToOne.class).anyMatch(field::isAnnotationPresent)
? value.toString()
: value;
}
}
public static Object getValueJsonb(final Object object, final Field field) {
field.setAccessible(true);
JsonbTypeAdapter adapterAnnotation = field.getAnnotation(JsonbTypeAdapter.class);
try {
Object value = field.get(object);
return adapterAnnotation == null
? value
: adapterAnnotation.value().newInstance().adaptToJson(value);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
I made an ExceptionMapper to catch and log all exceptions, like:
#Provider
public class CatchAllExceptionsMapper implements ExceptionMapper<Throwable> {
private static final Logger LOG = LoggerFactory.getLogger(CatchAllExceptionsMapper.class);
#Override
public Response toResponse(Throwable exception) {
LOG.error("Exception not catched!", exception);
return Response.serverError().build();
}
}
It catches the Exceptions my code throws, but if I send a Request with a JSON value that throws an IllegalStateException at my object's creation, this ExceptionMapper is ignored and I get a 400 Bad Request Response.
Funny thing is this Response is not the traditional Tomcat HTML formatted Response, its just plain text. It say just:
Cannot construct instance of `com.example.vo.AutoValue_Customer$Builder`, problem: First name is null or empty. at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 14, column: 1]
I thought this might be something short-circuiting Jersey, but my #PreMatching ContainerRequestFilter is executed beforehand, so I really have no idea why the 400 Response is not the traditional HTML one from Tomcat.
Why is this happening? What can I do to catch this and return my own Response?
As stated by Paul Samsotha in the comments, JacksonFeature from the jersey-media-json-jackson package define some ExceptionMappers, like JsonMappingException and JsonParseException. The solution is to create our own, register them within the ResourceConfig and register JacksonFeature last, otherwise it won't work.
e.g.
#Provider
#Priority(1) // hack for overriding other implementations.
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException exception) {
return Response.status(Status.BAD_REQUEST).build();
}
}
#Provider
#Priority(1) // hack for overriding other implementations.
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
#Override
public Response toResponse(JsonParseException exception) {
return Response.status(Status.BAD_REQUEST).build();
}
}
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(CatchAllExceptionsMapper.class);
register(JsonMappingExceptionMapper.class);
register(JsonParseExceptionMapper.class);
register(JacksonFeature.class);
}
}
I have asp.net core 2.1 application along with HangFire 1.6.17. HangFire is configured to execute a background job at certain interval. The background job calls external API using HttpClient. If the http call fails, then the method throws custom exception with metadata. Idea is hangfire will log the exception with metadata. I followed best-practices-for-exceptions to create exception
public class MyHttpRequestException : Exception
{
public string Content { get; private set; }
public string RequestUri { get; private set; }
public string HttpResponse { get; private set; }
public MyHttpRequestException()
{
}
public MyHttpRequestException(string message)
: base(message)
{
}
public MyHttpRequestException(string message, Exception innerException)
: base(message, innerException)
{
}
public MyHttpRequestException(string message, string content, string httpResponse, string requestUri)
: base(message)
{
Content = content;
RequestUri = requestUri;
HttpResponse = httpResponse;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(base.ToString());
sb.AppendLine();
sb.AppendLine();
sb.AppendLine("Content");
sb.AppendLine(Content);
sb.AppendLine("RequestUri");
sb.AppendLine(RequestUri);
sb.AppendLine("HttpResponse");
sb.AppendLine(this.HttpResponse);
return sb.ToString();
}
}
I also have extension method for HttpResponseMessage which ensures API request is successful, and if not throws MyHttpRequestException
public static class HttpResponseMessageExtensions
{
public static async Task EnsureSuccessStatusCodeAsync(this HttpResponseMessage response)
{
if (response.IsSuccessStatusCode)
{
return;
}
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var httpResponse = response.ToString();
var requestUri = response.RequestMessage.RequestUri.ToString()
if (response.Content != null)
response.Content.Dispose();
throw new MyHttpRequestException("Error while making http request.", content, httpResponse, requestUri);
}
}
Here is my background Job which is invoked by Hangfire recurring job scheduler
public async Task DoSomething(string url)
{
var response = await _httpClient.GetAsync(url)
await response.EnsureSuccessStatusCodeAsync();
// do something here if everything is okay
}
Issue
When EnsureSuccessStatusCodeAsync method throws MyHttpRequestException then Hangfire logs the exception as expected, and i see that in HangFire's dashboard. However Hangfire only logs Exception message and stack trace. I don't see my custom properties are being logged ( ie. Content, RequestUri, HttpResponse)
In clssic .NET we use SerializationInfo like this SO post
How do i create a custom exception in .NET Core so metadata will also get logged?
Note:
When the MyHttpRequestException gets thrown i noticed exception's ToString() method is getting called
however, i dont see whatever ToString() returns is getting logged by Hangfire.
I dont know if this is hangfire issue, or i need to implement MyHttpRequestException is different way.
The stack trace that you see in Dashboard is formatted. You can see here and here.
Because this custom stack trace format you can see your custom properties.
I try to use a custom Jackson serializer, but unfortunately the serializer will not be triggered.
In my Application I added Jackson:
JacksonJaxbJsonProvider jacksonProvider = new JacksonJaxbJsonProvider();
jacksonProvider.setMapper( mapper );
s.add( jacksonProvider );
return s;
I added a serializer for my class TestType:
#JsonSerialize(using = TestSerializer.class)
public class TestType {
private String test;
public String getTest(){
if (test==null || test.isEmpty()){
test="test";
}
return test;
}
public void setTest(String test) {
this.test = test;
}
}
public class TestSerializer extends JsonSerializer<TestType> {
#Override
public void serialize(TestType value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("special serialization", value.getTest());
jgen.writeEndObject();
}
}
The API:
#GET
#Produces({"application/json; charset=UTF-8"})
public Response getTest(#HeaderParam( RestHelper.HEADER_PARAM_ORIGIN ) String origin, #Context HttpServletRequest request, #Context HttpHeaders headers) {
TestType test = new TestType();
test.setTest("test");
return Response.status(200).entity( test ).build();
}
Did I miss any step to register the serializer?
Regards,
hyperion
That looks ok. Just makes sure you are using Jackson 2.x annotations (com.fasterxml.jackson) with Jackson 2.x ObjectMapper. Since annotation names of 1.x are the same (but Java package differs), they will not work, despite looking identical (only import statement in sources differing).
Note, too, that you can implement JsonSerializable which would remove the need to use #JsonSerialize annotation here.
Consider the following very basic WCF service implementation:
public enum TransactionStatus
{
Success = 0,
Error = 1
}
public class TransactionResponse
{
public TransactionStatus Status { get; set; }
public string Message { get; set; }
}
[ServiceContract]
[XmlSerializerFormat]
public interface ITestService
{
[OperationContract]
TransactionResponse DoSomething(string data);
}
public class TestService : ITestService
{
public TransactionResponse DoSomething(string data)
{
var result = ProcessData(data); // may throw InvalidOperationException
return new TransactionResponse()
{
Status = TransactionStatus.Success,
Message = result
};
}
private string ProcessData(string data)
{
if (data = "foobar")
throw new InvalidOperationException();
return data;
}
}
In the instance that the DoSomething method does throw an InvalidOperationException, I would like to intercept the fault and return a TransactionResponse object, rather than have WCF raise a FaultException with the client. How can I do this without surrounding each method body in a huge try catch statement? Is there some where I can hook into? Can I do this with some sort of attribute or something? An example of how I would like to handle it can be demonstrated using ASP.NET MVC:
public class ApiController : BaseController
{
protected override void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
var message = HttpContext.IsDebuggingEnabled ? ex.ToString() : ex.Message;
_logger.Error("Error processing request for controller {0}, action {1}",
filterContext.RequestContext.RouteData.Values["controller"],
filterContext.RequestContext.RouteData.Values["action"]);
_logger.Error(ex.ToString());
filterContext.ExceptionHandled = true;
filterContext.Result = ToXml(new ApiResult(false)
{
Message = message
});
}
// ...
}
Using the above method in MVC, I can ensure that no matter which controller action throws an exception, I can handle it and return an appropriately formatted ActionResult containing the necessary info. Is there a way to do this kind of thing with WCF?
Check out the WCF IErrorHandler interface - it allows you to centrally define one way in your service implementation to catch all exceptions and either swallow them, or convert them to WCF-friendly SOAP exceptions. This will make sure the channel between the client and the server isn't faulted, e.g. it can still be used after this call failed.
I don't understand why you'd want to "catch" the SOAP faults and convert those to something else, though.... nor do I know of any support that WCF would give you. The basic assumption is: catch .NET exceptions and convert them into interoperable SOAP faults