According to the Spring documentation, you can receive all GET parameters in a map like so:
#GetMapping
public final ReturnType getAll(#RequestParam MultiValueMap<String, String> allRequestParams) {
...
}
Is it possible (and how) to also accept a custom Java object instead (for which a custom Converter exists via subclassing GenericConverter) in such a way that the Converter gets the whole request map for construction of its DTO object?
#GetMapping
public final ReturnType getAll(#RequestParam CustomDTO customObj) {
[...]
}
[...]
#Component
public class CustomDTOConverter implements GenericConverter {
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
// Allow MultiValueMap.class to CustomDTO.class
[...]
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final MultiValueMap<String, String> requestMap = (MultiValueMap<String, String>) source;
// Construct CustomDTO from the requestMap
}
}
Trying the above snippet will fail, complaining that there is no parameter customObj present.
For convert get parameters to java object use method parameter without annotations. CustomDto must have setters.
#GetMapping
public final ReturnType getAll(CustomDTO customObj) {
...
}
class CustomDto {
int x;
String s;
public void setX(int x) {
this.x = x;
}
public void setS(String s) {
this.s = s;
}
}
Url example:http://example.com/test?x=123&s=abc
Related
I am using mapstruct to map from one DTO to another. I have multiple default methods , but 2 of them with a return value of String and that uses the same class as the input parameter gives me "Ambiguous mapping methods using java Mapstruct" error.
public class Action implements Serializable {
[...]
private String statusHistory;
[...]
private String propertiesOriginal;
private String propertiesEdited;
// Setter, Getter, ecc..
}
public class ActionDTO implements Serializable {
...
private Map<String, Integer> statusHistory = new HashMap<>();
...
private Object propertiesOriginal;
private Object propertiesEdited;
// Setter, Getter, ecc..
}
#Mapper(componentModel = "spring", uses = {})
public interface ActionMapper extends EntityMapper<ActionDTO, Action> {
default Map toMap(String text) throws IOException {
Map map = new HashMap();
try{
map = new ObjectMapper().readValue(text, new TypeReference<Map<String, Integer>>(){});
} catch (IOException e){
e.printStackTrace();
}
return map;
}
default String fromMap(Map map){
return new JSONObject(map).toString();
}
[...]
default Object toObject(String text) throws IOException {
return new ObjectMapper().readValue(text, Object.class);
}
default String fromObject(Object object) throws IOException {
return new ObjectMapper().convertValue(object, String.class);
}
[...]
}
Error:(16, 7) java: Ambiguous mapping methods found for mapping property "java.lang.String statusHistory" to java.util.Map: java.util.Map toMap(java.lang.String text), java.lang.Object toObject(java.lang.String text).
Both methods qualify Object and Map. You can however select the proper result type to use. Have a look at #BeanMapping#resultType.
If I have a class with a generic like this:
public class ResponseSimple<T> {
private Map<String, Collection<String>> headers;
private int status;
private T body;
}
Then,in other class I have a method which I need to use an instance of this class, but the method passes by param a java.lang.reflect.Type and it's overrided so I can't change the any of the method (name, signature..):
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
//How assign type T using type param??
//¿ResponseSimple<T> responseSimple = new ResponseSimple();?
return null;
}
}
How could I assign the generic type T using the param type (java.lang.reflect.Type)?
I would suggest something like this:
#Override
public <T> T decode(Response response, Class<T> type) throws IOException
{
//How assign type T using type param??
ResponseSimple<T> response = new ResponseSimple<T>();
return response;
}
Then use decode as follows:
.decode(response, NameOfClass.class)
Edit:
If you need to extend your class you could use a static helper function:
public static <T> ResponseSimple<T> createResponse(Class<T> clazz)
{
return new ResponseSimple<>();
}
And use it like this:
public class ResponseEncoder extends GsonDecoder {
public ResponseEncoder() {
super();
}
#Override
public Object decode(Response response, Type type) throws IOException
{
Class<?> clazz = (Class<?>) type;
ResponseSimple<?> response = createResonse(clazz);
return null;
}
}
I hope I understood your question correctly.
To create a new instance of your generic class you need to infer the correct type arguments like this if you want your ResponseSimple<T> to contain java.lang.reflect.Type:
ResponseSimple<Type> response = new ResponseSimple<>();
So, inbetween the <> you need to add the name of the class you want to use.
Also have a look at this: https://docs.oracle.com/javase/tutorial/java/generics/types.html
//EDIT:
As you said you want to infer the type arguments dynamically, what you did works fine for me. The only thing is that you forgot the diamond operator:
#Override
public Object decode(Response response, T type) throws IOException
{
ResponseSimple<T> response = new ResponseSimple<>(); //<> in the second part
return null;
}
I am using JMock-2.6.0. I have a map containing the names of methods and their expected return values.
I want to invoke a method on a mock object created using JMock.
Earlier I was able to this using JMock 1 as it follows following syntax:
mockObj.stubs().method(mymap.getKey()).will(new ReturnStub(mymap.getValue()));
But I am not sure, if there is a way to achieve this using JMock-2.
JMock-2's documentation is insufficient.
I believe this is the documentation you've been looking for:
Match Objects or Methods
Although matchers are normally used to specify acceptable parameter values, they can also be used to specify acceptable objects
or methods in an expectation, using an API syntax similar to that of
jMock 1. To do so, use a matcher where you would normally refer to a
mock object directly in the invocation count clause. Then chain
clauses together to define the expected invocation.
Their example includes:
To allow invocations of any bean property getter on any mock object:
allowing (any(Object.class)).method("get.*").withNoArguments();
For example you can use the following allowing... portion in a loop to achieve a similar result.
An sample test:
Interface:
public interface ThingOneI {
public abstract String getData();
public abstract void setData(String data);
public abstract String getRequest();
public abstract void setRequest(String request);
}
Impl:
public class ThingOne implements ThingOneI {
private String data;
private String request;
public ThingOne() {
}
#Override
public String getData() {
return data;
}
#Override
public void setData(String data) {
this.data = data;
}
#Override
public String getRequest() {
return request;
}
#Override
public void setRequest(String request) {
this.request = request;
}
}
Junit test:
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Before;
import org.junit.Test;
public class ThingOneTest {
Mockery context = new Mockery();
#Before
public void setUp() throws Exception {
}
#Test
public void test() {
ThingOneI thingOne = context.mock(ThingOneI.class);
Map<String, String> methMap = new HashMap<String, String>();
methMap.put("getData", "5");
context.checking(new Expectations() {{
for (Map.Entry<String, String> entry : methMap.entrySet())
allowing(any(ThingOneI.class))
.method(entry.getKey())
.with(any(String.class));
will(returnValue(entry.getValue()));
}
}});
System.out.println(thingOne.getData());
}
}
I'm trying to do something like this :
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(){
return new GsonResponseProcessor<T>();
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor() {
this.typeToken = new TypeToken<T>(){};
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
private void ResponseProcessor getResponseProcessor(){
return ResponseProcessorFactory<List<String>>.newResponseProcessor();
}
Now, whenever I invoke getResponseProcessor(), it doesn't return me the response processor for List<String>. Rather, it returns the default response processor for Object.
I'm sure, I'm missing some concept regarding generic. Can someone explain in detail ?
EDIT :
The real usage is like this :
public BaseRequestWithResponseProcessor<List<Dashboard>> getDashboards(Listener<List<Dashboard>> responseListener, ErrorListener errorListener) {
String url = mBaseUrl + "/dashboard";
ResponseProcessor<List<Dashboard>> responseProcessor = ResponseProcessorFactory.newResponseProcessor();
AuthInfo authInfo = getAuthInfo();
BaseRequestWithResponseProcessor<List<Dashboard>> request = new BaseRequestWithResponseProcessor<List<Dashboard>>(
Method.GET, url, authInfo, null, responseProcessor, responseListener, errorListener);
return request;
}
In the GsonResponseProcessor constructor type erasure has happened and at runtime only one version of the method will exist with the type variable T converted to Object.
In Java only one version of generic methods and classes will exist, the type parameters only exist during compile-time and will be replaced by Object during run-time.
Type tokens must be constructed with a concrete type to capture the type information. This is the whole point with them, to capture type information at a place where the concrete type is known. The token can then be stored in variables and later be used to lookup objects or get hold of the type information with reflection.
The solution here is that the caller of getResponseProcessor who knows the concrete type creates the type token and passes it as a parameter. You could also pass in a Class object if that works in you situation. If you want to use generic classes as tokens however, as in your example with List<Dashboard> you will need a type token.
Something like this:
ResponseProcessor<List<String>> p = ResponseProcessorFactory.newResponseProcessor(new TypeToken<List<String>>() {});
You can work around the type erasure by passing in the class type as method parameter.
public class ResponseProcessorFactory {
public static <T> ResponseProcessor<T> newResponseProcessor(Class<T> type){
return new GsonResponseProcessor<T>(type);
}
}
public class GsonResponseProcessor<T> implements ResponseProcessor<T> {
protected T response;
protected TypeToken typeToken;
public GsonResponseProcessor(Class<T> type) {
this.typeToken = TypeToken.get(type);//depends on the API version
//this.typeToken = new TypeToken<T>(type);
//this.typeToken = TypeToken.of(type);
}
#Override
public void parse(String jsonString) throws JSONException, IOException {
response = GsonHelper.getGsonInstance().fromJson(jsonString, typeToken.getType());
}
public T getResponse() {
return response;
}
}
Have you tried changing the signature to the correct type, too?
private ResponseProcessor<List<String>> getResponseProcessor() {
return ResponseProcessorFactory.newResponseProcessor();
}
How do I make custom object in a restful Response look like a native type. In other words, I don't want to use a getter and setter, but rather have the object marshal and unmarshall like a string. I can use a constructor for unmarshall, but I don't know how to get the object through the Response of the service. When I run the sample, I get this result:
<data>
<nType>123</nType>
<myType/>
<notMyType>
<value>def</value>
</notMyType>
</data>
I am trying to get myType to look like nType. Here is a short code example:
package rscust;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.xml.bind.annotation.XmlRootElement;
#Path("/")
public class Service extends Application {
#XmlRootElement
public static class Data {
public String nType;
public MyType myType;
public NotMyType notMyType;
public Data() {}
}
// custom class does not work in Response
public static class MyType {
private String value;
public MyType() {}
public MyType(String value) {
this.value=value;
}
public String toString() {
return value;
}
}
// works with getter and setter, but I don't want that
public static class NotMyType {
private String value;
public NotMyType() {}
public NotMyType(String value) {
this.setValue(value);
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#GET
#Produces(value={MediaType.APPLICATION_XML})
public Response get(
#QueryParam(value = "ntype")String nType,
#QueryParam(value = "mytype")MyType myType,
#QueryParam(value = "notmytype")NotMyType notMyType
) {
Data data = new Data();
data.nType = nType;
data.myType = myType;
data.notMyType = notMyType;
return Response.ok().entity(data).build();
}
private HashSet<Object> singletons;
public Service() {
super();
singletons = new HashSet<Object>();
singletons.add(this);
}
public Set<Object> getSingletons() {
return singletons;
}
}
The answer is you need to add annotations to the custom class to tell it how to deserialize and serialize the different fields. Your get method looks fine, but the custom classes aren't annotated at all. MyType and NotMyType should also have # XmlRootElement as well as annotations to mark which fields are # Transient and which are # JsonProperty
#XmlRootElement
public static class Data {
#JsonProperty
public String nType;
#JsonProperty
public MyType myType;
#JsonProperty
public NotMyType notMyType;
public Data() {}
}
#XmlRootElement
public static class MyType {
#JsonProperty
private String value;
public MyType() {}
public MyType(String value) {
this.value=value;
}
public String toString() {
return value;
}
}
also, since you made those objects part of Data already, just request a single data object, and then you can pull the others from there.
public Response get( #QueryParam(value = "data")Data d,){
Data d = data.nType;
}
If you don't wont to use getter and setter, than you need set the fields 'public'. Because RESTeasy need access to the fields.