I am facing a issue where when i collecting object from flink flatmap collector than i am not getting value collected correctly. I am getting object reference and its not giving me actual value.
dataStream.filter(new FilterFunction<GenericRecord>() {
#Override
public boolean filter(GenericRecord record) throws Exception {
if (record.get("user_id") != null) {
return true;
}
return false;
}
}).flatMap(new ProfileEventAggregateFlatMapFunction(aggConfig))
.map(new MapFunction<ProfileEventAggregateEmittedTuple, String>() {
#Override
public String map(
ProfileEventAggregateEmittedTuple profileEventAggregateEmittedTupleNew)
throws Exception {
String res=null;
try {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
res= mapper.writeValueAsString(profileEventAggregateEmittedTupleNew);
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
}).print();
public class ProfileEventAggregateFlatMapFunction extends
RichFlatMapFunction<GenericRecord, ProfileEventAggregateEmittedTuple> {
private final ProfileEventAggregateTupleEmitter aggregator;
ObjectMapper mapper = ObjectMapperPool.getInstance().get();
public ProfileEventAggregateFlatMapFunction(String config) throws IOException {
this.aggregator = new ProfileEventAggregateTupleEmitter(config);
}
#Override
public void flatMap(GenericRecord event,
Collector<ProfileEventAggregateEmittedTuple> collector) throws Exception {
try {
List<ProfileEventAggregateEmittedTuple> aggregateTuples = aggregator.runAggregates(event);
for (ProfileEventAggregateEmittedTuple tuple : aggregateTuples) {
collector.collect(tuple);
}
}}
Debug Results:
tuple that i am collecting in collector
tuple = {ProfileEventAggregateEmittedTuple#7880}
profileType = "userprofile"
key = "1152473"
businessType = "keyless"
name = "consumer"
aggregates = {ArrayList#7886} size = 1
0 = {ProfileEventAggregate#7888} "geo_id {geo_id=1} {keyless_select_destination_cnt=1, total_estimated_distance=12.5}"
entityType = "geo_id"
dimension = {LinkedHashMap#7891} size = 1
"geo_id" -> {Integer#7897} 1
key = "geo_id"
value = {Integer#7897} 1
metrics = {LinkedHashMap#7892} size = 2
"keyless_select_destination_cnt" -> {Long#7773} 1
key = "keyless_select_destination_cnt"
value = {Long#7773} 1
"total_estimated_distance" -> {Double#7904} 12.5
key = "total_estimated_distance"
value = {Double#7904} 12.5
This i get in my map function .map(new MapFunction<ProfileEventAggregateEmittedTuple, String>()
profileEventAggregateEmittedTuple = {ProfileEventAggregateEmittedTuple#7935}
profileType = "userprofile"
key = "1152473"
businessType = "keyless"
name = "consumer"
aggregates = {GenericData$Array#7948} size = 1
0 = {ProfileEventAggregate#7950} "geo_id {geo_id=java.lang.Object#863dce2} {keyless_select_destination_cnt=java.lang.Object#7cdb4bfc, total_estimated_distance=java.lang.Object#52e81f57}"
entityType = "geo_id"
dimension = {HashMap#7952} size = 1
"geo_id" -> {Object#7957}
key = "geo_id"
value = {Object#7957}
Class has no fields
metrics = {HashMap#7953} size = 2
"keyless_select_destination_cnt" -> {Object#7962}
key = "keyless_select_destination_cnt"
value = {Object#7962}
Class has no fields
"total_estimated_distance" -> {Object#7963}
Please help me to understand what is happening why i am not getting correct data.
public class ProfileEventAggregateEmittedTuple implements Cloneable, Serializable {
private String profileType;
private String key;
private String businessType;
private String name;
private List<ProfileEventAggregate> aggregates = new ArrayList<ProfileEventAggregate>();
private long startTime;
private long endTime;
public String getProfileType() {
return profileType;
}
public void setProfileType(String profileType) {
this.profileType = profileType;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getBusinessType() {
return businessType;
}
public void setBusinessType(String businessType) {
this.businessType = businessType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ProfileEventAggregate> getAggregates() {
return aggregates;
}
public void addAggregate(ProfileEventAggregate aggregate) {
this.aggregates.add(aggregate);
}
public void setAggregates(List<ProfileEventAggregate> aggregates) {
this.aggregates = aggregates;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public long getEndTime() {
return endTime;
}
public void setEndTime(long endTime) {
this.endTime = endTime;
}
#Override
public ProfileEventAggregateEmittedTuple clone() {
ProfileEventAggregateEmittedTuple clone = new ProfileEventAggregateEmittedTuple();
clone.setProfileType(this.profileType);
clone.setKey(this.key);
clone.setBusinessType(this.businessType);
clone.setName(this.name);
for (ProfileEventAggregate aggregate : this.aggregates) {
clone.addAggregate(aggregate.clone());
}
return clone;
}
public class ProfileEventAggregate implements Cloneable, Serializable {
private String entityType;
private Map<String, Object> dimension =new LinkedHashMap<String, Object>();
private Map<String, Object> metrics = new LinkedHashMap<String, Object>();
public Map<String, Object> getDimension() {
return dimension;
}
public void setDimension(Map<String, Object> dimension) {
this.dimension.putAll(dimension);
}
public void addDimension(String dimensionKey, Object dimensionValue) {
this.dimension.put(dimensionKey, dimensionValue);
}
public Map<String, Object> getMetrics() {
return metrics;
}
public void addMetric(String metricKey, Object metricValue) {
this.metrics.put(metricKey, metricValue);
}
public void setMetrics(Map<String, Object> metrics) {
this.metrics.putAll(metrics);
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
#Override
public ProfileEventAggregate clone() {
ProfileEventAggregate clone = new ProfileEventAggregate();
clone.setEntityType(this.entityType);
clone.getDimension().putAll(this.getDimension());
clone.getMetrics().putAll(this.metrics);
return clone;
}
When you don't enableObjectReuse, objects are copied with your configured serializer (seems to be Avro?).
In your case, you use Map<String, Object> where you cannot infer a plausible schema.
The easiest fix would be to enableObjectReuse. Else make sure your serializer matches your data. So you could add a unit test where you use AvroSerializer#copy and make sure your POJO is properly annotated if you want to stick with Avro reflect or even better go with a schema first approach, where you generate your Java POJO with a Avro schema and use specific Avro.
Let's discuss some alternatives:
Use GenericRecord. Instead of converting it to a Java type, directly access GenericRecord. This is usually the only way when the full record is flexible (e.g. your job takes any input and writes it out to S3).
Denormalize schema. Instead of having some class Event { int id; Map<String, Object> data; } you would use class EventInformation { int id; String predicate; Object value; }. You would need to group all information for processing. However, you will run into the same type issues with Avro.
Use wide-schema. Looking at the previous approach, if the different predicates are known beforehand, then you can use that to craft a wide-schema class Event { int id; Long predicate1; Integer predicate2; ... String predicateN; } where all oft he entries are nullable and most of them are indeed null. To encode null is very cheap.
Ditch Avro. Avro is fully typed. You may want to use something more dynamic. Protobuf has Any to support arbitrary submessages.
Use Kryo. Kryo can serialize arbitrary object trees at the cost of being slower and having more overhead.
If you want to write the data, you also need to think about a solution where the type information is added for proper deserialization. For an example, check out this JSON question. But there are more ways to implement it.
Related
I am trying to read a JSON into the class. Jackson wants to apply a field of a subelement to the element itself, where it of course does not exist.
This is the JSON:
{
"authorizationRequest":{
"scope":["write","read"],
"resourceIds":["metadata"],
"approved":true,
"authorities":[],
"authorizationParameters":{
"scope":"write read",
"response_type":"token",
"redirect_uri":"",
"state":"",
"stateful":"false",
"client_id":"5102686_metadata"
},
"approvalParameters":{},
"state":"",
"clientId":"5102686_metadata",
"redirectUri":"",
"responseTypes":["token"],
"denied":false
},
"credentials":"",
"clientOnly":false,
"name":"testuser"
}
The classes look like the following:
// The main class that I try do deserialize:
public class DeserializedOAuth2Authentication extends OAuth2Authentication{
private String name;
private boolean clientOnly;
private AuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest("", new ArrayList<>());
public DeserializedOAuth2Authentication() {
super(new DefaultAuthorizationRequest("", new ArrayList<>()), null);
}
#Override
#JsonProperty
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
#JsonProperty
public boolean isClientOnly() {
return clientOnly;
}
public void setClientOnly(boolean clientOnly) {
this.clientOnly = clientOnly;
}
#Override
#JsonProperty
public AuthorizationRequest getAuthorizationRequest() {
return authorizationRequest;
}
public void setAuthorizationRequest(AuthorizationRequest authorizationRequest) {
this.authorizationRequest = authorizationRequest;
}
}
AuthorizationRequest is an interface with all the getters for the listed elements; it is configured to be serialized by a DefaultAuthorizationRequest class also containing the respective setters and implementing fileds with corresponding names.
public class DefaultAuthorizationRequest implements AuthorizationRequest, Serializable {
private Set<String> scope = new LinkedHashSet<String>();
private Set<String> resourceIds = new HashSet<String>();
private boolean approved = false;
private Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
private Map<String, String> authorizationParameters = new ConcurrentHashMap<String, String>();
private Map<String, String> approvalParameters = new HashMap<String, String>();
private String resolvedRedirectUri;
public Map<String, String> getAuthorizationParameters() {
return Collections.unmodifiableMap(authorizationParameters);
}
public Map<String, String> getApprovalParameters() {
return Collections.unmodifiableMap(approvalParameters);
}
public String getClientId() {
return authorizationParameters.get(CLIENT_ID);
}
public Set<String> getScope() {
return Collections.unmodifiableSet(this.scope);
}
public Set<String> getResourceIds() {
return Collections.unmodifiableSet(resourceIds);
}
public Collection<GrantedAuthority> getAuthorities() {
return Collections.unmodifiableSet((Set<? extends GrantedAuthority>) authorities);
}
public boolean isApproved() {
return approved;
}
public boolean isDenied() {
return !approved;
}
public String getState() {
return authorizationParameters.get(STATE);
}
public String getRedirectUri() {
return resolvedRedirectUri == null ? authorizationParameters.get(REDIRECT_URI) : resolvedRedirectUri;
}
public Set<String> getResponseTypes() {
return OAuth2Utils.parseParameterList(authorizationParameters.get(RESPONSE_TYPE));
}
public void setRedirectUri(String redirectUri) {
this.resolvedRedirectUri = redirectUri;
}
public void setScope(Set<String> scope) {
this.scope = scope == null ? new LinkedHashSet<String>() : new LinkedHashSet<String>(scope);
authorizationParameters.put(SCOPE, OAuth2Utils.formatParameterList(scope));
}
public void setResourceIds(Set<String> resourceIds) {
this.resourceIds = resourceIds == null ? new HashSet<String>() : new HashSet<String>(resourceIds);
}
public void setApproved(boolean approved) {
this.approved = approved;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities == null ? new HashSet<GrantedAuthority>() : new HashSet<GrantedAuthority>(
authorities);
}
public void setAuthorizationParameters(Map<String, String> authorizationParameters) {
String clientId = getClientId();
Set<String> scope = getScope();
this.authorizationParameters = authorizationParameters == null ? new HashMap<String, String>()
: new HashMap<String, String>(authorizationParameters);
}
public void setApprovalParameters(Map<String, String> approvalParameters) {
this.approvalParameters = approvalParameters == null ? new HashMap<String, String>()
: new HashMap<String, String>(approvalParameters);
}
....
}
On calling read on the above JSON string I get an exception
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "scope" (class de.mvbonline.vlx.auth.oauth2.DeserializedOAuth2Authentication), not marked as ignorable (3 known properties: "name", "authorizationRequest", "clientOnly"])
at [Source: (String)"{ "credentials":"", "clientOnly":false, "authorizationRequest":{ "scope":["write","read"], "resourceIds":["metadata"], "approved":true, "authorities":[], "authorizationParameters":{ "scope":"write read", "response_type":"token", "redirect_uri":"", "state":"", "stateful":"false", "[truncated 316 chars]; line: 1, column: 111] (through reference chain: de.mvbonline.vlx.auth.oauth2.DeserializedOAuth2Authentication["scope"])
Of course the field "scope" is not in the context of DeserializedOAuth2Authentication, but in the context of DefaultAuthorizationRequest. Why is Jackson searching in the wrong class for it?
I am unsing Jackson version 2.12.4
Make sure that DefaultAuthorizationRequest can be serialized and deserialized by Jackson. I guess that they are not for several reasons. Two that I can think of:
You have to let Jackson know how to deserialize DefaultAuthorizationRequest class. One possible solution would be to add a #JsonCreator and #JsonProperty to the class. The same applies to GrantedAuthority class.
DefaultAuthorizationRequest has fields of type Map, which need special attention. See these links on how to convert a JSON String to a Map<String, String> or, if the Map has custom objects, how to deserialize into a HashMap of custom objects
Also, you can take a look at Map Serialization and Deserialization with Jackson
I found my problem.
I formerly mapped my concrete implementation of the interface AuthorizationRequest via a handler:
mapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, ValueInstantiator valueInsta, JsonParser p, String msg) throws IOException {
if(instClass.isAssignableFrom(AuthorizationRequest.class)) {
return new DeserializedAuthorizationRequest();
}
return super.handleMissingInstantiator(ctxt, instClass, valueInsta, p, msg);
}
});
This seems to be definitely not the same as annotating the field with the concrete class. This now works without problems:
public class DeserializedOAuth2Authentication extends OAuth2Authentication{
...
#Override
#JsonProperty("authorizationRequest")
#JsonDeserialize(as = DeserializedAuthorizationRequest.class)
public AuthorizationRequest getAuthorizationRequest() {
return authorizationRequest;
}
public void setAuthorizationRequest(AuthorizationRequest authorizationRequest) {
this.authorizationRequest = authorizationRequest;
}
}
Coming from C++ and currently employed in a Java environment, I was wondering how I would be able to create a mapping of void* and void* in Java in order to create a generic mapping from A to B and from B to A. I am aware that Java doesn't have pointers and references the way C++ does, but am failing to find a method that would still allow this.
An example of what I am trying to achieve:
public class A{
#GenericMapping(1)
private Integer temp1;
}
public class B{
#GenericMapping(1)
private Integer temp2;
}
public class Mapper{
private List<Pair<Integer, Integer>> mapping;
public void map(Object ObjectOfAnyClassButLetsAssumeA, Object ObjectOfAnyClassButLetsAssumeB){
// Get all parameters with GenericMapping above it, get its value
// and match the corresponding value with the value of B
// Resulting in A.temp1 = B.temp2;
}
}
However, if possible I'd rather create a map (like map[A.temp1] = B.temp2) in order to avoid using the #GenericMapping, seeing as that would allow me to not modify the class in any way and still facilitate its mapping.
I think I understand what you want to do here and you can accomplish it with some metadata and Java 8's Lambdas.
What we do is set up a helper class that contains all mappings identified by class and IDs (analogous to your #GenericMapping but without actually annotating the classes) and containing methods for setting and getting the value. It's important that all mappings for the same ID have the same value type or a ClassCastException may be thrown when transferring values.
My example uses three classes where not all mappings apply to all classes.
Here's the code:
public class GenericMappingDemo {
static class A {
private Integer integerA;
private String stringA;
private Float floatA;
public A(final Integer integerA, final String stringA, final Float floatA) {
this.integerA = integerA;
this.stringA = stringA;
this.floatA = floatA;
}
public Integer getIntegerA() {
return integerA;
}
public void setIntegerA(final Integer integerA) {
this.integerA = integerA;
}
public String getStringA() {
return stringA;
}
public void setStringA(final String stringA) {
this.stringA = stringA;
}
public Float getFloatA() {
return floatA;
}
public void setFloatA(final Float floatA) {
this.floatA = floatA;
}
#Override
public String toString() {
return "A{integerA=" + integerA + ", stringA='" + stringA + "', floatA=" + floatA + '}';
}
}
static class B {
private Integer integerB;
private String stringB;
public Integer getIntegerB() {
return integerB;
}
public void setIntegerB(final Integer integerB) {
this.integerB = integerB;
}
public String getStringB() {
return stringB;
}
public void setStringB(final String stringB) {
this.stringB = stringB;
}
#Override
public String toString() {
return "B{integerB=" + integerB + ", stringB='" + stringB + '\'' + '}';
}
}
static class C {
private Float floatC;
private String stringC;
public Float getFloatC() {
return floatC;
}
public void setFloatC(final Float floatC) {
this.floatC = floatC;
}
public String getStringC() {
return stringC;
}
public void setStringC(final String stringC) {
this.stringC = stringC;
}
#Override
public String toString() {
return "C{floatC=" + floatC + ", stringC='" + stringC + "'}";
}
}
static class GenericMapping<C, T> {
final int id;
final Class<C> type;
final Function<C, T> getter;
final BiConsumer<C, T> setter;
public GenericMapping(final int id,
final Class<C> type,
final Function<C, T> getter,
final BiConsumer<C, T> setter) {
this.id = id;
this.type = type;
this.getter = getter;
this.setter = setter;
}
}
static class Mapper {
// All mappings by class and id
private final Map<Class<?>, Map<Integer, GenericMapping<?, ?>>> mappings
= new HashMap<>();
public void addMapping(GenericMapping<?, ?> mapping) {
mappings.computeIfAbsent(mapping.type,
c -> new TreeMap<>()).put(mapping.id, mapping);
}
/**
* Map values from one object to another,
* using any mapping ids that apply to both classes
* #param from The object to transfer values from
* #param to The object to transfer values to
*/
public <From, To> void map(From from, To to) {
Map<Integer, GenericMapping<?, ?>> getters
= mappings.get(from.getClass());
Map<Integer, GenericMapping<?, ?>> setters
= mappings.get(to.getClass());
if (getters == null || setters == null) {
// Nothing to do
return;
}
// Create a set with the ids in both getters and
// setters, i.e. the mappings that apply
Set<Integer> ids = new HashSet<>(getters.keySet());
ids.retainAll(setters.keySet());
// Transfer all mappings
for (Integer id : ids) {
GenericMapping<From, ?> getter
= (GenericMapping<From, ?>) getters.get(id);
GenericMapping<To, ?> setter
= (GenericMapping<To, ?>) setters.get(id);
transfer(from, to, getter, setter);
}
}
private <From, To, V> void transfer(final From from,
final To to, final GenericMapping<From, ?> getter,
final GenericMapping<To, V> setter) {
// This will throw an exception if the mappings are invalid
final V value = (V) getter.getter.apply(from);
setter.setter.accept(to, value);
}
}
public static void main(String[] args) {
final Mapper mapper = new Mapper();
// Mapping definition for class A
mapper.addMapping(new GenericMapping<>(1, A.class,
A::getIntegerA, A::setIntegerA));
mapper.addMapping(new GenericMapping<>(2, A.class,
A::getStringA, A::setStringA));
mapper.addMapping(new GenericMapping<>(3, A.class,
A::getFloatA, A::setFloatA));
// Mapping definition for class B
mapper.addMapping(new GenericMapping<>(1, B.class,
B::getIntegerB, B::setIntegerB));
mapper.addMapping(new GenericMapping<>(2, B.class,
B::getStringB, B::setStringB));
// Mapping definition for class C
mapper.addMapping(new GenericMapping<>(2, C.class,
C::getStringC, C::setStringC));
mapper.addMapping(new GenericMapping<>(3, C.class,
C::getFloatC, C::setFloatC));
// Use the mappings
A a = new A(7, "foo", 3.7f);
B b = new B();
C c = new C();
System.out.printf("A before map: %s%n", a);
System.out.printf("B before map: %s%n", b);
System.out.printf("C before map: %s%n", c);
// This will transfer a.integerA to b.integerB and a.stringA to b.stringB
mapper.map(a, b);
// This will transfer a.stringA to c.stringC and a.floatA to c.floatC
mapper.map(a, c);
System.out.println();
System.out.printf("A after map: %s%n", a);
System.out.printf("B after map: %s%n", b);
System.out.printf("C after map: %s%n", c);
}
}
And the result after running it:
A before map: A{integerA=7, stringA='foo', floatA=3.7}
B before map: B{integerB=null, stringB='null'}
C before map: C{floatC=null, stringC='null'}
A after map: A{integerA=7, stringA='foo', floatA=3.7}
B after map: B{integerB=7, stringB='foo'}
C after map: C{floatC=3.7, stringC='foo'}
Java 7
The same general solution can be used for Java 7, but it will be a lot more verbose. Since Java 7 doesn't have the functional interfaces Function<U, V> and BiConsumer<U, V> you'll need to define these yourself, which isn't that much trouble. It could be argued that they should be defined in Java 8 too so interface and method names makes more sense (e.g. Getter.get and Setter.set).
The big thing is the mapping definitions which will have to use anonymous classes instead of lambdas - lambdas is mostly syntactic sugar for anonymous classes with only one method anyways, but they make the code a lot more readable.
The mapping for a.integerA will look like this in Java 7:
mapper.addMapping(new GenericMapping<>(1, A.class,
new Function<A, Integer>() {
#Override
public Integer apply(final A a1) {
return a1.getIntegerA();
}
},
new BiConsumer<A, Integer>() {
#Override
public void accept(final A a1, final Integer integerA) {
a1.setIntegerA(integerA);
}
}));
You could also have a look at Apache Commons BeanUtils, which also have a quite sophisticated, although explicit (not Annotation-based), Converter API:
http://commons.apache.org/proper/commons-beanutils/javadocs/v1.9.3/apidocs/org/apache/commons/beanutils/Converter.html
http://commons.apache.org/proper/commons-beanutils/javadocs/v1.9.3/apidocs/org/apache/commons/beanutils/ConvertUtilsBean.html
I need to format the output (xml) of a restful service using Jersey according to following scenario
I have a class with key value pair as follows.
#XmlRootElement(name="columnValues")
public class KeyValueDTO {
private String key;
private String val;
#XmlElement(name="column")
public String getKey() {
return key;
}
#XmlElement(name="value")
public String getVal() {
return val;
}
}
Suppose I have list like this which is returned by rest service:
List<KeyValueDTO> mylist = new ArrayList<KeyValueDTO>();
KeyValueDTO dto1 = new KeyValueDTO();
dto1.key = "Name";
dto1.val = "alex";
KeyValueDTO dto2 = new KeyValueDTO();
dto2.key = "Age";
dto2.val = 23
mylist.add(dto1);
mylist.add(dt02);
And I want to generate the output as follow
<Name>alex</Name>
<Age>20</Age>
But currently it is giving following output
<column>Name</column>
<value>alex</column>
<column>Age</column>
<value>20</column>
Can anyone let me know how to achieve this?
You could try using an XmlAdapter:
public class KeyValueAdapter extends XmlAdapter<String, List<KeyValueDTO>> {
#Override
public List<KeyValueDTO> unmarshal(String v) throws Exception {
// Needs implementation
return null;
}
#Override
public String marshal(List<KeyValueDTO> vs) throws Exception {
StringBuffer buffer = new StringBuffer();
for (KeyValueDTO v: vs) {
buffer.append(String.format("<%s>%s</%1$s>", v.key, v.val));
}
return buffer.toString();
}
}
And then add that adapter to your bean:
#XmlRootElement
public static class Wrapper {
#XmlJavaTypeAdapter(KeyValueAdapter.class)
List<KeyValueDTO> dtos;
}
TLDR: I'd like to know how to extend fit.TypeAdaptor so that I can invoke a method that expects parameters as default implementation of TypeAdaptor invokes the binded (bound ?) method by reflection and assumes it's a no-param method...
Longer version -
I'm using fit to build a test harness for my system (a service that returns a sorted list of custom objects). In order to verify the system, I thought I'd use fit.RowFixture to assert attributes of the list items.
Since RowFixture expects the data to be either a public attribute or a public method, I thought of using a wrapper over my custom object (say InstanceWrapper) - I also tried to implement the suggestion given in this previous thread about formatting data in RowFixture.
The trouble is that my custom object has around 41 attributes and I'd like to provide testers with the option of choosing which attributes they want to verify in this RowFixture. Plus, unless I dynamically add fields/methods to my InstanceWrapper class, how will RowFixture invoke either of my getters since both expect the attribute name to be passed as a param (code copied below) ?
I extended RowFixture to bind on my method but I'm not sure how to extend TypeAdaptor so that it invokes with the attr name..
Any suggestions ?
public class InstanceWrapper {
private Instance instance;
private Map<String, Object> attrs;
public int index;
public InstanceWrapper() {
super();
}
public InstanceWrapper(Instance instance) {
this.instance = instance;
init(); // initialise map
}
private void init() {
attrs = new HashMap<String, Object>();
String attrName;
for (AttrDef attrDef : instance.getModelDef().getAttrDefs()) {
attrName = attrDef.getAttrName();
attrs.put(attrName, instance.getChildScalar(attrName));
}
}
public String getAttribute(String attr) {
return attrs.get(attr).toString();
}
public String description(String attribute) {
return instance.getChildScalar(attribute).toString();
}
}
public class MyDisplayRules extends fit.RowFixture {
#Override
public Object[] query() {
List<Instance> list = PHEFixture.hierarchyList;
return convertInstances(list);
}
private Object[] convertInstances(List<Instance> instances) {
Object[] objects = new Object[instances.size()];
InstanceWrapper wrapper;
int index = 0;
for (Instance instance : instances) {
wrapper = new InstanceWrapper(instance);
wrapper.index = index;
objects[index++] = wrapper;
}
return objects;
}
#Override
public Class getTargetClass() {
return InstanceWrapper.class;
}
#Override
public Object parse(String s, Class type) throws Exception {
return super.parse(s, type);
}
#Override
protected void bind(Parse heads) {
columnBindings = new TypeAdapter[heads.size()];
for (int i = 0; heads != null; i++, heads = heads.more) {
String name = heads.text();
String suffix = "()";
try {
if (name.equals("")) {
columnBindings[i] = null;
} else if (name.endsWith(suffix)) {
columnBindings[i] = bindMethod("description", name.substring(0, name.length()
- suffix.length()));
} else {
columnBindings[i] = bindField(name);
}
} catch (Exception e) {
exception(heads, e);
}
}
}
protected TypeAdapter bindMethod(String name, String attribute) throws Exception {
Class partypes[] = new Class[1];
partypes[0] = String.class;
return PHETypeAdaptor.on(this, getTargetClass().getMethod("getAttribute", partypes), attribute);
}
}
For what it's worth, here's how I eventually worked around the problem:
I created a custom TypeAdapter (extending TypeAdapter) with the additional public attribute (String) attrName. Also:
#Override
public Object invoke() throws IllegalAccessException, InvocationTargetException {
if ("getAttribute".equals(method.getName())) {
Object params[] = { attrName };
return method.invoke(target, params);
} else {
return super.invoke();
}
}
Then I extended fit.RowFixture and made the following overrides:
public getTargetClass() - to return my class reference
protected TypeAdapter bindField(String name) throws Exception - this is a protected method in ColumnFixture which I modified so that it would use my class's getter method:
#Override
protected TypeAdapter bindField(String name) throws Exception {
String fieldName = camel(name);
// for all attributes, use method getAttribute(String)
Class methodParams[] = new Class[1];
methodParams[0] = String.class;
TypeAdapter a = TypeAdapter.on(this, getTargetClass().getMethod("getAttribute", methodParams));
PHETypeAdapter pheAdapter = new PHETypeAdapter(fieldName);
pheAdapter.target = a.target;
pheAdapter.fixture = a.fixture;
pheAdapter.field = a.field;
pheAdapter.method = a.method;
pheAdapter.type = a.type;
return pheAdapter;
}
I know this is not a neat solution, but it was the best I could come up with. Maybe I'll get some better solutions here :-)
I have a POJO I need to format as a MultiValueMap. This MultiValueMap will be used as the request in a POST method using the restTemplate class and will be passed along to my web service as contentType application/x-www-form-urlencoded.
Are there any tools or utilities that will do the POJO -> MultiValueMap conversion for me?
sample pojo:
public class SampleDto implements Serializable, Idable, Comparable<SampleDto> {
private static final long serialVersionUID = 1L;
private Integer id;
private Boolean active;
private String lastName;
private List<SurgeonClinicDto> surgeonClinics = new ArrayList<SurgeonClinicDto>();
private List<PromptValueDto> promptValues = new ArrayList<PromptValueDto>();
private Date lastUpdated = new Date();
public SampleDto() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public List<SurgeonClinicDto> getSurgeonClinics() {
return surgeonClinics;
}
public void setSurgeonClinics(List<SurgeonClinicDto> surgeonClinics) {
this.surgeonClinics = surgeonClinics;
}
public List<PromptValueDto> getPromptValues() {
return promptValues;
}
public void setPromptValues(List<PromptValueDto> promptValues) {
this.promptValues = promptValues;
}
public int compareTo(SampleDto o) {
if (getLastName() != null && o.getLastName() != null
&& getLastName().compareTo(o.getLastName()) != 0) {
return getLastName().compareTo(o.getLastName());
}
if (getActive() != null && o.getActive() != null
&& getActive().compareTo(o.getActive()) != 0) {
return getActive().compareTo(o.getActive());
}
if (getLastUpdated() != null && o.getLastUpdated() != null
&& getLastUpdated().compareTo(o.getLastUpdated()) != 0) {
return getLastUpdated().compareTo(o.getLastUpdated());
}
if (getId() != null && o.getId() != null
&& getId().compareTo(o.getId()) != 0) {
return getId().compareTo(o.getId());
}
return 0;
}
}
After MultiValueMap is converted to contentType: application/x-www-form-urlencoded by calling POST on restTemplate object:
id=11752&active=true&lastName=Brownie&
promptValues[0].id=12&promptValues[0].miscPromptId=882&promptValues[0].value=meFirst&
promptValues[1].id=13&promptValues[1].miscPromptId=881&promptValues[1].value=youToo&
surgeonClinics[0].address1=newAddress&surgeonClinics[0].address2=newAddress2&surgeonClinics[0].city=clinic City&
surgeonClinics[0].email=email#clinic1.com&surgeonClinics[0].fax=123.456.7890&surgeonClinics[0].id=33273&
surgeonClinics[0].name=clinic name&surgeonClinics[0].phone=890-098-4567&
surgeonClinics[0].zip=34567&surgeonClinics[0].surgeryCenter1=MySurgeryCenter1&
surgeonClinics[0].surgeryCenter2=MySurgeryCenter2&
surgeonClinics[1].address1=newAddress11&surgeonClinics[1].address2=newAddress22&surgeonClinics[1].city=clinic2 City&
surgeonClinics[1].email=email#clinic2.com&surgeonClinics[1].fax=123.456.7890&surgeonClinics[1].id=33274&
surgeonClinics[1].name=clinic2 name&surgeonClinics[1].phone=890-098-4567&
surgeonClinics[1].zip=34567&
surgeonClinics[1].surgeryCenter1=MySurgeryCenter21&surgeonClinics[1].surgeryCenter2=MySurgeryCenter22
You can use Jackson objectMapper:
MultiValueMap valueMap = new LinkedMultiValueMap<String, Object>();
Map<String, Object> fieldMap = objectMapper.convertValue(requestObject, new TypeReference<Map<String, Object>>() {});
valueMap.setAll(fieldMap);
You can do this with reflection and/or introspection (I never remember the correct name). This is a variation of Serialization, so you might want to look in the Serialization implementation.
Another option is to create an interface similar to this
putlic interface ToMap
{
Map<String, String> toMap();
}
And implement it on the classes in question.
For your pojo it might look like this:
Map<String, String> toMap()
{
int index;
StringBuilder key = new StringBuidler();
Map<String, String> returnValue = new HashMap<String, String>();
returnValue.put("id", id);
returnValue.put("active", active);
returnValue.put("lastName", lastName);
index = 0;
for (SurgeonClinicDto surgeonClinic : surgeonClinics)
{
key.setLength(0);
key.append("surgeonClinic[");
key.append(index);
key.append("].field1");
returnValue.put(key.toString(), surgeonClinic[index].field1);
key.setLength(0);
key.append("surgeonClinic[");
key.append(index);
key.append("].field2");
returnValue.put(key.toString(), surgeonClinic[index].field2);
... more stuff here...
}
return returnValue;
}
If you are looking to send it in the format that you have specified in your question then you may want to do something like what DwB suggested, however I think you may find it easier to convert if you use a more object orientated approach such as JSON, there are lots of libraries to convert between POJO->JSON. Another advantage is that JSON handles stores numbers/string/booleans separately therefore it is easier to convert back to a POJO whereas if you send the data as in your example you will need to convert all the String objects back into what type they are e.g. id will need to be converted String->int and active converted String->boolean.
So in JSON it may look something like this:
dto = {
id : 11752,
active : true,
lastName : "Brownie",
promptValues : [
{id : 12, miscPromptId : 882, value : "meFirst"},
{id : 13, miscPromptId : 881, value : "youToo"}
],
surgeonClinics = [{..etc..}]
}
Obviously you could do something similar with XML, but I like this simple solution when I want to send data simply and all in one line. The JSON libraries are getting better and some can pretty much generate this from your object by reflection.
Anyway just a suggestion.