I have some REST services (consuming and producing application/json) and I use #TypeHint to generate documentation.
Now I have something like this:
import javax.ws.rs.core.Response;
...
#Path("/path")
public class MyClass {
#GET
#TypeHint(MyResponse.class)
public Response getIt() {
MyResponse resp = ... ;
return MyBuilder.build(resp);
}
}
but MyResponse is a wrapper over List<MyType>.
My build method from MyResponse looks like this:
public static Response build(Serializable payload) {
return Response.ok(msr).header(...).build();
}
I want to use directly List<MyType> instead of MyResponse. Which is the best way to use TypeHint in the following code?
#GET
#TypeHint(/* TODO */)
public Response getIt() {
List<MyType> myList = ... ;
return MyBuilder.build(myList);
}
I was thinking to the following options:
#TypeHint(List.class)
#TypeHint(MyType.class)
#TypeHint(List<MyType>.class) -> unfortunately this doesn't work because of Java type erasure.
Question:
Is there a valid alternative for number 3?
Even if the type is a List, number 1 is not useful because my own type has to be annotated with #XmlRootElement and that List is unchangeable (it is from JDK).
There is a workaround for number 2, but it's not quite perfect:
Use number 2 (just to have an available example in the generated HTML documentation - a description for an element that is contained in that list)
Specify that it is a List in Javadoc (E.g.: after the #return word) (it can be emphasized using bold, colors, italics, etc. via HTML tags)
E.g.:
/**
* ...
* #return <strong><font color="blue">List<MyType></font></strong>
*/
Details:
enunciate.version = 1.30.1
Java 7
I have opted for using MyType[].class when using TypeHint instead of List.class. This way the documentation will state "array of MyType" which for my rest-api with json is true.
#TypeHint(value = MyType[].class)
As you know TypeHint is used to give Enunciate a hint about what a JAX-RS resource method returns or accepts as an input parameter.
In your case the return type is being described.
I assume that the ClassReturnedByMyBuildersBuildMethod is a subclass of the javax.ws.rs.core.Response abstract class.
For the code as you showed it what you need to use is the class returned by MyBuilder's build method - #TypeHint(ClassReturnedByMyBuildersBuildMethod.class).
Options 2 #TypeHint(MyType.class) makes no sense. It is not a return type nor an input parameter.
Update 1: with your workaround it can make some sense :)
If you add an input parameter to the getIt method - something like public Response getIt(List<MyType> myList){... you would use option 1(#TypeHint(List.class)) because as you know the org.codehaus.enunciate.jaxrs.TypeHint annotation type element declaration has a Class return type (Class<?> value();) and you cannot use a parameterized type beacuse of the erasure of generic types (the paramterized type share the same class at runtime in this case - List).
But changing the input parameter to getIt(List<MyType> myList) is probably not viable because the list would have to be obtained from the URI ( with javax.ws.rs's #QueryParam or #Context UriInfo ). This question addresses best practices when using list of parameters as input if it may concern you.
Update 2: Option 1 becomes is less viable because of your XmlRootElement constraint.
Update 3: I see no valid alternative for option 3 using the TypeHint annotation as it is.
You will have to go with your custom option 2 workaround.
Related
Got class/interface level and method mapping
#RequestMapping(value = "/post")
public interface PostApi {
//to get /posts
#RequestMapping(value = "s")
ResponseEntity getAll();
}
Basically I want to add character 's' on /post and get /posts, how this is possible
public interface PostApi {
//to get /posts
#RequestMapping(value = "/posts")
ResponseEntity getAll();
#GetMapping(value = "/post/{id}")
ResponseEntity getById(#PathParam Long id);
}
You can't change the context path, instead remove "/post" then use below "/post" and "/posts" on different tasks.
you can not concatenate the path values into a single path string, since they are seen as objects in the URI specification and not multiple just strings.
(if you like RFCs, check this: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 )
so basically your class-level Mapping already sets a hierarchy by design, and the method can only configure a lower level inside that hierarchy
((earlier) spring documentations featured this explanation:
"Method-level mappings are only allowed to narrow the mapping expressed at the class level (if any)."
the only way to work around this is actually to remove the class mapping and add individual mappings to either method (as stated by others).
as a side effect: this makes your code much better readable, so reviewers (or yourself in 3 months) do not have to concat the full path in your mind whily trying to understand the controller
I have implemented a java method with the following signature:
public <T> T getItemValue(String itemName, Class<T> itemType) {
...
}
This allows a client to call the method in the following way to get for example a value of the type String or Integer:
String s = itemCol.getItemValue("_name", String.class);
int i = itemCol.getItemValue("_count", Integer.class);
This kind of method signature is also used by the Config interface of the new microprofile Config 1.3 API.
My question is how - or if - I can call the method with a Type of List of Types like List<String> to get for example a List of String objects.
I was not able to formulate the client call. I tried something like this:
List<String> list = itemCol.getItemValue("_count", List<String.class>);
but this seems not to be the correct syntax.
EDITED:
As a result of the responses below I decided to add a separate method to get a List of a specific type.
public <T> List<T> getItemValueList(String itemName, Class<T> itemType) {
...
}
With this additional method signature a client can decide weather to get a single value or a list of a specific type.
Type Parameters are erased, they don't exist at runtime. There is no such thing as List<String> at runtime, there is only List.
So, the correct way to express a reflective proxy for List<String> is … you can't. You can only express a reflective proxy for List, the same way you do for any other class: List.class.
Not with List<String.class>, no. You need to use a TypeToken to get that sort of generic information. You also need to change your code to work with Type instead of Class.
This is the method:
protected <T> TestPageResult<T> getTestPageResutForRequest(MockHttpServletRequestBuilder request) throws Exception {
String responseJson = mockMvc.perform(request).andReturn().getResponse()
.getContentAsString();
TestPageResult<T> response = getObjectMapper().readValue(responseJson,
new TypeReference<TestPageResult<T>>() {
});
return response;
}
I call it like this:
TestPageResult<SomeDto> pageResult = this.<SomeDto>getTestPageResutForRequest(getRequest());
TestPageResult is:
protected static class TestPageResult<T> {
private List<T> items;
private long totalCount = -1;
public TestPageResult() {
}
//omitted getters and setters
}
The resulting pageResult.getItems() contains a List of LinkedHashMap instead of a list of SomeDto. If I were to just hardcode the SomeDto type in the objectMapper.readValue method I'd get the correct results.
What's the problem?
edit: The suggested duplicated did solve my problem - kind of.
I used:
JavaType type = getObjectMapper().getTypeFactory().constructParametricType(TestPageResult.class, clazz);
TestPageResult<T> response = getObjectMapper().readValue(responseJson, type);
Problem is there is no going around not passing down a Class argument to the method. So the method looks ugly due to both passing a generic type and the same thing as a Class. Obviously you can just not pass the generic now but this way a casting would be required and adding SuppressWarnings and so on.
The problem is erasure. All these <T> parameters don't exist in the compiled code, after they're erased. This means that source new TypeReference<TestPageResult<T>>() looks like new TypeReference<TestPageResult>() once compiled, which is not what you want. (Similar to how a List<String> ends up being a List in compiled code, and it's just compile-time validation that you don't add Integers to your String List.)
I think there's roughly two ways to deal with this (in this case), both of these you already stumbled upon:
Either you create a type that properly represents what you want, such as: new TypeReference<TestPageResult<SomeDto>>(), or class SomeDtoPageResult extends TestPageResult<SomeDto> which you can then use in places like readValue(..., SomeDtoPageResult.class);
Or you create a complete class representation, like you were doing with JavaType
What you really want won't work. Your best bet is to tinker and come up with the cleanest code that solves it. Generics let you express really elaborate structures, and when you serialize an actual instance (nested objects), that comes out just fine, but when the classes need to be introspected at runtime, e.g. for deserialization (your use case) or to build a model (e.g. to generate Swagger docs), this becomes problematic.
To map a certain object with mapstruct I need some custom post processing which needs an additional parameter to do it's work:
#Mapper
public abstract class AlertConfigActionMapper {
#Mappings({ #Mapping(target = "label", ignore = true)})
public abstract AlertConfigActionTO map (AlertConfigAction action, Locale userLanguage);
#AfterMapping
public void setLabel (AlertConfigAction action, #MappingTarget AlertConfigActionTO to, Locale userLanguage) {
for (AlertConfigActionLabel label : action.getAlertConfigActionLabels()) {
if (label.getLanguage().equals(userLanguage)) {
to.setLabel(label.getLabel());
break;
} else if (label.getLanguage().equals(Locale.ENGLISH)) {
to.setLabel(label.getLabel());
}
}
}
}
This works just fine.
The problem starts when I add following method to this mapper:
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, Locale userLanguage);
I need to pass this parameter (userLanguage) as well but mapstruct seems to 'break down' in this case: I generates following code for this part (which naturally gives a compilation error):
#Override
public List<AlertConfigActionTO> mapList(List<AlertConfigAction> actions, Locale userLanguage) {
if ( actions == null && userLanguage == null ) {
return null;
}
List<AlertConfigActionTO> list = new List<AlertConfigActionTO>();
return list;
}
I'm sure it is related to the parameter since if I remove it (from all mapping methods) then the mapList method is generated correctly.
What is needed to be done to allow custom parameters in this case?
What you describe is not possible (yet). Could you open a feature request in our issue tracker? We should provide means of denoting parameters as some sort of "context" which is passed down the call stack.
As a work-around for the time being, you might take a look at using a ThreadLocal which you set before invoking the mapping routine and which you access in your after-mapping customization. It's not elegant - and you need to make sure to clean up the thread local to avoid memory leaks - but it should do the trick.
I know that this question is quiet old, but I run into this issue, and starting at version 1.2 of mapstruct you can resolve it using #Context
So declaring the mapping for the list need to be like this :
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, #Context Locale userLanguage);
Now, you juste need to add another non abstract mapping like this :
public AlertConfigActionTO mapConcrete (AlertConfigAction action, #Context Locale userLanguage){
return map (action, userLanguage);
}
I don't think it is possible. At least not that way. Problem is that you prepare interface/abstract class - and rest is done by the engine. And that engine expects methods with one parameter... There are decorators, but they go the same way. I would try to inject language. Create bean, mark it as session scoped, and find out. With Spring, you would use ScopedProxyMode for that... Not sure how that goes with CDI.
Other option is more workaround, then solution - maybe that AlertConfigAction can pass that information?
I want to use non spring bean class object as parameter for jersey web service class method. But it is giving missing dependency error at build time.
My code is:
#Component
#Path("/abcd")
public class ActorServiceEndpoint {
#POST
#Path("/test/{nonspringBean}")
#Produces(MediaType.APPLICATION_XML)
public void addActor(#PathParam("nonspringBean") MyNonSpringBeanClass nonspringBean){
}
}
The thing is path parameters come in String form. As per the specification, if we want the have a custom type be injected as a #PathParam, the custom class, should have one of three things:
A public static valueOf(String param) that returns the type
A public static fromString(String param) that returns the type
Or a public constructor that accepts a String
Another option implement a ParamConverter. You can see an example here.
If you don't own the class (it's a third-party class that you can't change) then your only option is to use the ParamConverter/ParamConverterProvider pair.
In either of these cases you'll want to construct the instance accordingly by parsing the String either in the constructor or in one of the above mentioned methods. After doing this, the custom type can be made a method parameter with the annotation.
The same holds true for other params, such as #FormParam, #HeaderParam, #QueryParam, etc.
It would help if you gave a bit more details of the error you're getting, but I see two problems with your code snippet:
The correct Spring annotation is #PathVariable, #PathParam is probably from another package. This doesn't apply as I guess you're using JAX-RS, not Spring annotations.
I'm not sure what converters are applied to path variables, but in any case it would need to have one for MyNonSpringBeanClass. I would take a String parameter and then instantiate MyNonSpringBeanClass myself in the function body.