My current task is to build an application using OSGI Enroute (http://enroute.osgi.org/) and Angular (though we elected to use Angular2/4 instead of the bundled AngularJS).
So far so good. I have a REST Java application which is responding to various requests from the Angular front-end but I'm currently running into an issue. In order to make development easier I am serving the Angular code on port 4200 and the back-end is listening on port 8080. CORS is working so I am able to send and receive requests while building the code. This may or may not be related to the issue.
The issue is when responding with a DTO with String content in excess of 21 characters the value is getting 'compressed.' I noticed this when attempting to use the value I received (a UUID) as a key for a subsequent GET request. Checking the DTO class I have confirmed that the toString() method does indeed call a private compress method where it will take any string longer than 21 characters and return something akin to this nine...last nine which tends to make it difficult to re-obtain a UUID from ... {"uuid":"95b90155-...ee5c02200", "name":"My Object"}...
So ... given something like this:
import org.osgi.dto.DTO;
public final class MyDTO extends DTO
{
public String uuid;
public String name;
}
and a REST application like this:
#RequireBootstrapWebResource(resource="css/bootstrap.css")
#RequireWebserverExtender
#RequireConfigurerExtender
#Component(name="web", propery={"debug=true"})
public final class MyApplication implements REST
{
private boolean debug = false;
public MyDTO getMe(RESTRequest request)
{
MyDTO dto = new MyDTO();
dto.name = "My Object";
dto.uuid = UUID.randomUUID().toString();
return dto;
}
#SuppressWarnings("unused")
#Activate
void activate(ComponentContext component, BundleContext bundle,
Map<String, Object> config)
{
if ("true".equals(config.get("debug"))
{
debug = true;
}
}
}
what am I missing in order to avoid this value 'compression' in my JSON responses?
Things I have tried
(The one that works) overriding the toString() method provided by DTO. This works but doesn't seem like it is the best solution. I would then have to override the toString() for anything that might have a string value in excess of 21 characters. The documentation indicates that the intent is for debugging, which likely means I'm not returning the proper type?
Setting the request's _response()'s content type to application/json: the result I see in the Chrome Web console is still a compressed string
I wrote the DTO.toString methods. It is clearly documented that the format of the output is not specified and that it is for use as a debugging tool and not for serialization. This is is why the impl "compresses" strings.
If you need to serialize a DTO, you need to use code for that purpose. See https://github.com/osgi/osgi.enroute/blob/master/osgi.enroute.base.api/src/osgi/enroute/dto/api/DTOs.java for an API that can convert DTOs to a format like JSON.
Related
I recently stumbled upon a problem that I could not find any references to although it's very unlikely nobody ever had this problem before.
Maybe someone can give me a heads-up on where to look.
Using JAX-RS to build a REST service running on JakartaEE 8 I created a method that's supposed to handle PUT requests from the client. This method is given a DTO object which, amongst other things, contains a HashMap that uses Long variables as keys.
This works just fine so far and the method accepts the input given by the clients PUT request.
However when I check for the type of the variables that the keyset() method return for the HashMap it is not of type Long but instead of type String which obviously leads to problems later on.
Here is a shortened version of the code in question:
#PUT
#Path("edituser")
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Optional<DTOobject> editUser(#QueryParam("user_id") Long user_id, DTOobject dto_object) {
for (Object i : dto_object.getOtherDTO_map().keySet()) {
LOGGER.log(Level.INFO, "HashMap key of type: {0}", i.getClass().getName());
// This will return java.lang.String as the type for the individual keys.
}
// do something and return something
}
public class DTOobject {
// some other vars
private HashMap<Long, OtherDTO> otherDTO_map;
// getters and setters
}
I'm writing an API Gateway that must route requests based on a MAC address. Example of endpoints:
/api/v2/device/AABBCCDDEEFF
/api/v2/device/AABBCCDDEEFF/metadata
/api/v2/device/search?deviceId=AABBCCDDEEFF
I've written a Custom Predicate Factory that extracts the MAC address, performs the necessary logic to determine what URL the MAC address should be routed to, then stores that information on the ServerWebExchange attributes.
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
// Fields/Constructors Omitted
private static final String IP_ATTRIBUTE = "assignedIp";
private static final String MAC_ATTRIBUTE = "mac";
#Override
public Predicate<ServerWebExchange> apply(Config config) {
return (ServerWebExchange exchange) -> {
String mac = exchange.getAttributes().get(MAC_ATTRIBUTE);
if(mac == null){
mac = extractMacAddress(exchange);
}
if(!exchange.getAttributes().contains(IP_ATTRIBUTE)){
exchange.getAttributes().put(IP_ATTRIBUTE, findAssignedIp(mac);
}
return config.getRouteIp().equals(exchange.getAttribute(IP_ATTRIBUTE));
});
}
// Config Class & utility methods omitted
}
NOTE: This implementation is greatly simplified for brevity
With this implementation I'm able to guarantee that the MAC is extracted only once and the logic determining what URL the request belongs to is performed only once. The first call to the predicate factory will extract and set that information on ServerWebExchange Attributes and any further calls to the predicate factory will detect those attributes and use them to determine if they match.
This works, but it isn't particularly neat. It would be much easier and simpler if I could somehow set the Exchange Attributes on every single request entering the gateway BEFORE the application attempts to match routes. Then the filter could be a simple predicate that checks for equality on the Exchange Attributes.
I've read through the documentation several times, but nothing seems to be possible. Filters are always scoped to a particular route and run only after a route matches. It might be possible to make the first route be another Predicate that executes the necessary code, sets the expected attributes and always returns false, but can I guarantee that this predicate is always run first? It seems like there should be support for this kind of use case, but I cannot for the life of me find a way that doesn't seem like a hack. Any ideas?
Use a WebFilter instead of a GatewayFilter or a GlobalFilter. They are applied only after the predicates chain. Whereas WebFilter works as an interceptor.
#Component
public class CustomRoutePredicateFactory implements WebFilter, Ordered {
private static final String IP_ATTRIBUTE = "assignedIp";
private static final String MAC_ATTRIBUTE = "mac";
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String mac = (String) exchange.getAttributes().computeIfAbsent(MAC_ATTRIBUTE, key -> extractMacAddress(exchange));
exchange.getAttributes().computeIfAbsent(IP_ATTRIBUTE, key -> findAssignedIp(mac));
return chain.filter(exchange);
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
I think your approach makes sense since you want it to run before filters.
Have you considered using a GlobalFilter with an order set on it? You can ensure it's always the first filter to run. You can also modify the URL in the ServerWebExchange by mutating the request and setting the GATEWAY_REQUEST_URL_ATTR attribute on the exchange.
Take a look at the PrefixPathGatewayFilterFactory for an example of how to change the URI being routed to.
You can set an order on the Global filter by implementing the org.springframework.core.Ordered interface.
That being said, it still feels a little like a hack but it's an alternative approach.
i think it may help you that overriding the class RoutePredicateHandlerMapping.
see: org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler
I want to create a web service using C#. In web service I have a web method that accept list of a specific class:
[DataContract]
public class CompositeType
{
string stringValue = "Hello ";
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
[DataMember]
public List<Product> Products { get; set; }
}
[DataContract]
public class Product
{
[DataMember]
public int PID { get; set; }
[DataMember]
public string PName { get; set; }
}
and my web method:
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
I want to publish this service using BasicHttpBinding that Java users can call it too. Now since the Java programmer is not around me, I wanted to ask those who have the experience to do this :
1) Can Java programmers call my web method that accept List<Product>?
2)Should I change List to Array?
Thanks for you contribution
Presumably your HTTP API serializes this as JSON (or maybe XML). In either case, libraries such as Jackson can handle it just fine, and most REST clients will even handle that part automatically. Standards compliance is the rule, and so as long as your List<Product> is converted to/from a regular JSON array, everything should work smoothly.
JSON doesn't have separate list types, just the plain array, so either array or list-based serialization should be equivalent.
As a note, most APIs use either camelCase or snake_case for properties, so your property names (in JSON) would be expected to be stringValue, products, pid, and pName.
As with .Net client calling WCF using the Svcutil tool, most Java users use the asis2 library which is a webservice engine to invoke web service.
WebService is a specification that any service that implements it can be called WebService. they use SOAP message based on XML to communicate. they use WSDL to describe the service details, which is used for generating the client proxy class. The reason why WCF can be called across service boundaries by various platforms is that it is also a web service. Although there may be different data types on various platforms, as long as we specify how to represent it in XML and how to serialize it, the service can be called correctly by others platforms, By default, List is specified to be serialized using a one-dimensional array.
Say I have a Jersey Resource somewhat similar to this:
#Path("/test")
public class TestResource{
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response test(List<JSONRepresentation> json){
//some logic that gives different responses, all just Strings
}
}
And a RestyGWT Service that consumes it like this:
#Path("api/test")
public interface TestService extends RestService{
#POST
public void test(List<JSONRepresentation> json, MethodCallback<String> callback);
}
The thing is that, when I try to access the Resource using the Service, if the List isn't null or empty I get an Internal Server Error that doesn't have anything to do with the server code because I can't even debug the test method logic.
However, when the List is null or empty, the Resource's logic does what it should and I can debug it without any problems.
The contents of the JSONRepresentation don't really seem to matter for this problem.
I have no idea why this is even happening and I couldn't really find any similar questions around, any help would be really appreciated.
If you want to send Strings back to the client, change your code to:
#Path("/test")
public class TestResource{
#POST
#Produces(MediaType.APPLICATION_JSON)
public String test(List<JSONRepresentation> json){
//some logic that gives different responses, all just Strings
}
}
and change the code of your resource to:
#Path("api/test")
public interface TestService extends RestService{
#POST
public void test(List<JSONRepresentation> json, TextCallback callback);
}
}
hope that helps.
Ok, I somehow figured out why the error happened and how to avoid it.
The thing is, the problem was actually in my json representation, I had defined it with private variables and getters and setters for each one of them (I have some sets and instances of other json representations on it along with other String and primitive variables)
The thing is that, for some reason I'd really want to know more about of, if a variable with a type of another json representation is set as private, this error happens
So, I just set that variable as public and everything worked fine, the odd part is that Collections of another json representation classes work fine even as private variables (primitives, String, and Date work fine like that too).
My service:
#POST
public String setData(#QueryParam("id") Long is, MyObject payload) {
...
}
or
#POST
public String setData(#PathParam("id") Long is, MyObject payload) {
...
}
My interceptor on the server:
Object read(MessageBodyReaderContext context) throws IOException, WebApplicationException {
Class mypayloadtype = context.getType;
InputStream mypayloadinpustream = context.getInputStream();
Long myidparam = ???????? // how to get the query or path param here?
}
EDIT: To be a bit more concrete:
What I'd like to do is to grab the XML and store it based on the parameters in a separate audit system. Maybe PreProcessInterceptor / PostProcessInterceptor are the better choices?
Any hints or alternative ways to get the param when the xml is still available for preprocessing?
Miguel
I just stumbled over the same problem today. I needed the #PathParams and #QueryParams in the read() method and ended up with something like this:
public class MyInterceptor implements PreProcessInterceptor, MessageBodyReaderInterceptor
{
private static ThreadLocal<UriInfo> uri = new ThreadLocal<UriInfo>();
public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
{
uri.set(request.getUri);
...
}
public Object read(MessageBodyReaderContext context)
{
String param = uri.get().getPathParameters().getFirst("myidparam");
...
}
}
Although when thinking about it now - I'm not quite sure, if just using PreProcessInterceptor/PostProcessInterceptor will also do the trick for my (and maybe your) problem. I'll have another look tomorrow.
I am not an expert on the topic but to me it seems as if the MessageBodyReaderContext interface does not really know if it is on the server or the client side, so it cannot expose the request or its parameters / path parts etc.
So as far as I know this is not possible.
If your code knows that it lives on the server side of the rest
communication, maybe you can use a servlet filter to store the request
in a ThreadLocal and then access it from there while the request is
handled, somewhat similar to RequestContextFilter / RequestContextHolder from the spring framework? (Then the request object does not know anything about the annotations of your service, but instead one has to extract the information manually from the request. This means to have the same information in two places, so there has to be a better solution ...)
Edit: after looking at some examples I get the vague feeling that if you want to read the input stream to create an object and add path parameters to it, MessageBodyReaderInterceptor is simply not the way to go. Instead set up a MessageBodyReader which constructs the object from the request body data, and this then will be passed into the public String setData(#PathParam("id") Long is, MyObject payload), assuming that this method is annotated with a #Consumes which matches the #ConsumeMime annotation for the MessageBodyReader. There you might be able in the setData to set the missing id on the object read from the request body. Some examples related to this seem to be here: How to get full REST request body using Jersey? (but for Jersey, not jBoss :-/)
However I am not sure if that works for you, and I also feel I completely overestimated my ability to answer this question appropriately, so I hope someone more knowledgeable comes in with a better solution.