Selma - Bad custom field to field mapping - java

I have a project that uses Mapstrcut, i want to replace it by Selma, so in the below example i mapped AttachmentRequest to Attachment
public class Attachment {
private SocialReason socialReason;
public SocialReason getSocialReason() { return socialReason; }
public Attachment socialReason(SocialReason socialReason) { this.socialReason = socialReason; return this; }
}
public class SocialReason {
private String value;
public String getValue() { return value; }
public SocialReason value(String value) { this.value = value; return this; }
}
public class AttachmentRequest {
private String socialReason;
public String getSocialReason() { return socialReason; }
public AttachmentRequest socialReason(String socialReason) { this.socialReason = socialReason; return this; }
}
I tried with the bellow code, but i have this error
Bad custom field to field mapping: field out.getSocialReason().value
from destination bean Attachment has no setter !
--> Fix #Field({"socialreason","socialreason.value"})
#Mapper(withIgnoreMissing = IgnoreMissing.ALL)
public interface AttachmentCommandMapper {
#Maps(withCustomFields = { #Field({ "socialReason", "socialReason.value" }) })
Attachment attachmentRequestToAttachment(AttachmentRequest attachmentRequest);
}
Is setters required in the class for the mapping in Selma, because in mapstruct doesn't require setters.

Related

Could not read JSON: Can not construct instance from String value

I have pojo class Ticket
public class Ticket implements Serializable{
#JsonProperty("lineItemStatus")
private String revisionStatus;
public String getRevisionStatus() {
return revisionStatus;
}
public void setRevisionStatus(String revisionStatus) {
this.revisionStatus = revisionStatus;
}
public void setRevisionStatus(RevisionStatus revisionStatus) {
String status = "";
if (revisionStatus != null) {
switch (revisionStatus) {
case added: {
status = "New";
break;
}
case updated: {
status = "Modified";
break;
}
}
}
this.revisionStatus = status;
}
}
Also I have an enum
public enum RevisionStatus {
added {
#Override
public String getName() {
return this.name();
}
},
updated {
#Override
public String getName() {
return this.name();
}
}
public abstract String getName();
}
During GET request , I use setRevisionStatus(RevisionStatus revisionStatus) method and i get response like for example
{"lineItemStatus": "Modified"} which is fine
But problem occurs during PUT request . During PUT request, my requirement is that I should be able to send payloads like for {"lineItemStatus": "Modified"} or {"lineItemStatus": "New"} or {"lineItemStatus": "abc"} , meaning lineItemStatus should be able to accept any String value . I am using #RequestBody Ticket ticket for receiving the payload.
The debugger doesn't come inside the controller and fails at the payload step . How do I handle this error without making any changes inside Enum ?
You can do it by updating your enum:
public enum RevisionStatus {
added("New") {
#Override
public String getName() {
return this.name();
}
},
updated("Modified") {
#Override
public String getName() {
return this.name();
}
};
private String status;
private RevisionStatus(String status) {
this.status = status;
}
public abstract String getName();
/**
* #return the status
*/
public String getStatus() {
return status;
}
#JsonCreator
public static RevisionStatus fromValue(String text) {
if(StringUtils.isNoneBlank(text)) {
for (RevisionStatus b : RevisionStatus.values()) {
if (String.valueOf(b.toString()).equalsIgnoreCase(text.trim())) {
return b;
}
}
}
return null;
}
}
Here we have updated enum to value enum. And fromValue() method with #JsonCreator annotation will bind this String value with the Enum constant.
Additionally this way you won't need that switch cases during get method. As getStatus() method will give you the status value directly. And that will improve your code design also.
Just an additional point here, I don't see any need of this getName() method, as the placed where getName() is called, directly name() can be called. As that is also a public method.

mapstruct wrapper type and generics

I am trying to map JsonNullable<List<ChildRequestTO> to Nullable<List<ChildRequestDO>> (see full code below) with mapstruct 1.4.2.Final and I am facing the following error: error: Nullable<List<ChildRequestDO>> does not have an accessible constructor. If I add a constructor for Nullable like
public Nullable(T value) {
this.value = value;
this.isPresent = true;
}
then I get the following error error: Unmapped target property: "value". Mapping from property "JsonNullable<List<ChildRequestTO>> products" to "Nullable<List<ChildRequestDO>> products".
How do I map complex wrapped types in a generic way?
The following mapping code (part of ChildRequestMapper class and applied in ObjectRequestMapper) solves the problem but I want to solve it in a more generic way:
#Named("mappingHelper")
default Nullable<List<ChildRequestDO>> customMapToDOs(JsonNullable<List<ChildRequestTO>> input) {
if (JsonNullable.undefined().equals(input)) {
return Nullable.undefined();
}
if (input.get() == null) {
return Nullable.of(null);
}
var output= input.get()
.stream()
.map(this::mapToDO)
.collect(Collectors.toList());
return Nullable.of(output);
}
Changing the NullableMapper to the code below does not work/compile because I do not know how to tell mapstruct to look for the appropriate mapper to map from T to X.
public static <T, X> Nullable<X> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
Full code:
#Mapper(
unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {ChildRequestMapper.class, NullableMapper.class}
)
public interface ObjectRequestMapper {
#Mapping(target = "slots", source = "slots", qualifiedByName = "mapToSlotDOs")
ModifyObjectRequestDO mapToDO(ModifyObjectRequestTO input);
}
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface ChildRequestMapper {
ChildRequestDO mapToDO(ChildRequestTO input);
}
public class NullableMapper {
public static <T> Nullable<T> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
}
public class ModifyObjectRequestTO {
private JsonNullable<String> name = JsonNullable.undefined();
private JsonNullable<List<ChildRequestTO>> children = JsonNullable.undefined();
}
public class ModifyObjectRequestDO {
private Nullable<String> name = Nullable.undefined();
private Nullable<List<ChildRequestDO>> children = Nullable.undefined();
}
public class Nullable<T> {
private static final Nullable<?> UNDEFINED = new Nullable<>(null, false);
private final T value;
private final boolean isPresent;
private Nullable(T value, boolean isPresent) {
this.value = value;
this.isPresent = isPresent;
}
public static <T> Nullable<T> undefined() {
#SuppressWarnings("unchecked")
Nullable<T> t = (Nullable<T>) UNDEFINED;
return t;
}
public static <T> Nullable<T> of(T value) {
return new Nullable<T>(value, true);
}
public T get() {
if (!isPresent) {
throw new NoSuchElementException("Value is undefined");
}
return value;
}
public boolean isPresent() {
return isPresent;
}
}

Convert JSON to JAVA Object through play.data.Form

I have no idea what to do with this problem.
I have a JSON object to assist in value input through POST (using Play Framework) that structures like this:
{
"start_absolute": 1403185486254,
"end_absolute": 1403185486254,
"metrics": [
{
"name": "parts",
"tags": [
{
"key":"machine",
"value":"10"
}
],
"sampling":
{
"value": 1,
"unit": "minutes"
}
}
]
}
And in server side i try to process the info like this:
Form<QueryForm> queryForm = Form.form(QueryForm.class).bindFromRequest();
if (queryForm.hasErrors()) {
return badRequest(queryForm.errorsAsJson());
}
QueryForm queryInfo = queryForm.get();
the classes used to define the JSON object in java
public class QueryForm {
private Date start_absolute;
private Date end_absolute;
private List<MetricForm> metrics= Lists.newArrayList();
public Date getStart_absolute() {
return start_absolute;
}
public void setStart_absolute(long start_absolute) {
this.start_absolute = new Date(start_absolute);
}
public Date getEnd_absolute() {
return end_absolute;
}
public void setEnd_absolute(long end_absolute) {
this.end_absolute =new Date(end_absolute);
}
public List<MetricForm> getMetrics() {
return metrics;
}
public void setMetrics(List<MetricForm> metrics) {
this.metrics = metrics;
}
#Override
public String toString() {
return "QueryForm[start_absolute="+start_absolute+", end_absolute="+end_absolute+", metrics="+metrics+"]";
}
.
public class MetricForm {
private String name;
private List<Tag> tags= Lists.newArrayList();
private RelativeTimeForm sampling= new RelativeTimeForm(1,"milliseconds");
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags.addAll(tags);
}
public RelativeTimeForm getSampling() {
return sampling;
}
public void setSampling(int val, String unit) {
this.sampling.setUnit(unit);
this.sampling.setVal(val);
}
#Override
public String toString() {
return "MetricForm[name="+name+", tags="+tags+",sampling="+sampling+"]";
}
.
public class Tag {
private String key;
private String value;
public Tag(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
return String.format(
"Tag[key=%s, value='%s']",
key, value
);
}
}
.
public class RelativeTimeForm {
private int value;
private String unit;
public RelativeTimeForm (int val, String unit){
this.setValue(val);
this.setUnit(unit);
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
#Override
public String toString() {
return "RelativeTimeForm[value="+value+", unit="+unit+"]";
}
I'm not experienced working with play.data.Form but in previous ocasions with simpler objects (Using primitive data types and String) this worked fine but when i tried to POST to the play application (Header: Content-Type = application/json; charset=utf-8) it occurs an Internal Server Error:
play.api.Application$$anon$1: Execution exception [[InvalidPropertyException:
Invalid property 'metrics[0].tags[0]' of bean class [model.QueryForm]: Illegal
attempt to get property 'tags' threw exception; nested exception is
org.springframework.beans.NullValueInNestedPathException: Invalid property
'metrics[0].tags' of bean class [model.QueryForm]: Could not instatiate property
path:
java.lang:InstatiationException: [model.Tag]
Any one can help with this? I canĀ“t figure out what to do. Is it to complex of an object to convert from JSON? Do i need to add some JSON Annotations (not currently using them)?
I have no clue how to fix this
UPDATE: I narrowed it down to the Tag class. for some reason i'm not able to instatiate Tag through JSON. I even tried setting a single Tag instance in the same level as the dates and it gives the same error (It work for Sampling)
RESOLVED:
Spring binding exception when a form is submitted
The answer is in the post above. I have to set an empty construtor in Tag class for it to work.
public Tag(){
}
The exception means, that one or more attributes aren't instantiated.
private List<MetricForm> metrics = new ...;
private List<Tag> tags = new ...;
private RelativeTimeForm sampling = new ...;
should do it.
In the case you get problems to read in your time values: Instead of using Date type directly to read in, I would use long to read in the time values and then (internally) convert them to Date objects.

Robospice Cached Object is Always null

For some reason pulling a cached object back from the cache in Robospice is always null. Is there something I'm doing wrong?
getSpiceManager().execute(cardRequest, Card.class.getName(),
DurationInMillis.ONE_DAY, new CardRequestListener());
Is how it's executed. The spice manager is created as follows:
mSpiceManager = new SpiceManager(JacksonGoogleHttpClientSpiceService.class);
And the card class is as follows:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"iosThumbHighRes",
"iosThumb",
"iosLargeHiRes",
"iosLargeHighRes",
"iosLarge"
})
public class Card {
#JsonProperty("iosThumbHighRes")
private String iosThumbHighRes;
#JsonProperty("iosThumb")
private String iosThumb;
#JsonProperty("iosLargeHiRes")
private String iosLargeHiRes;
#JsonProperty("iosLargeHighRes")
private String iosLargeHighRes;
#JsonProperty("iosLarge")
private String iosLarge;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("iosThumbHighRes")
public String getIosThumbHighRes() {
return iosThumbHighRes;
}
#JsonProperty("iosThumbHighRes")
public void setIosThumbHighRes(String iosThumbHighRes) {
this.iosThumbHighRes = iosThumbHighRes;
}
#JsonProperty("iosThumb")
public String getIosThumb() {
return iosThumb;
}
#JsonProperty("iosThumb")
public void setIosThumb(String iosThumb) {
this.iosThumb = iosThumb;
}
#JsonProperty("iosLargeHiRes")
public String getIosLargeHiRes() {
return iosLargeHiRes;
}
#JsonProperty("iosLargeHiRes")
public void setIosLargeHiRes(String iosLargeHiRes) {
this.iosLargeHiRes = iosLargeHiRes;
}
#JsonProperty("iosLargeHighRes")
public String getIosLargeHighRes() {
return iosLargeHighRes;
}
#JsonProperty("iosLargeHighRes")
public void setIosLargeHighRes(String iosLargeHighRes) {
this.iosLargeHighRes = iosLargeHighRes;
}
#JsonProperty("iosLarge")
public String getIosLarge() {
return iosLarge;
}
#JsonProperty("iosLarge")
public void setIosLarge(String iosLarge) {
this.iosLarge = iosLarge;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
#Override
public boolean equals(Object other) {
return EqualsBuilder.reflectionEquals(this, other);
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperties(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
Is there something else I need to set?
Thanks, Graeme
The Google Http Client uses the #Key annotation. You are using Jackson annotation which is not supported by Googe Http Java Client, as it provides an abstraction layer over all serialisation solutions (gson/jackson).

How do i create a string enum types without overriding toString method to be used in Mule Studio?

I'm working with Mule Studio in this, and I actually was wondering the possibility to add the drop down list with a "custom" string name. The case applies for MIME Types (ContentTypes) names. (i.e: text/css, text/javascript, and so)
Made an structure like this:
public enum HttpContentType {
TEXT_PLAIN
{
public String toString() {
return "text/plain";
}
},
TEXT_CSS
{
public String toString() {
return "text/css";
}
},
TEXT_JS
{
public String toString() {
return "text/javascript";
}
},
TEXT_XML
{
public String toString() {
return "text/xml";
}
},
};
To then be used like this as a Configurable attribute:
/**
* Connection Content Types
*/
#Configurable
#Placement(order=1,group="Configuration",tab="HTTP Configuration")
private HttpContentType contentType;
But of course that when reading that from the Mule Studio it will be a Drop Down list with names such as: TEXT_CSS, TEXT_XML, TEXT_JAVASCRIPT ( http://puu.sh/3vLbd.png ) and so instead of text/css, text/xml, text/javascript.
How can i achieve this result?
A far better way to achieve this would be to provide a field in your enum, and provide an appropriate constructor:
public enum HttpContentType {
TEXT_PLAIN("text/plain"),
TEXT_CSS("text/css");
private String value;
HttpContentType(String value) {
this.value = value;
}
public String toString() {
return this.value;
}
public static HttpContentType getByValue(String value){
for (final HttpContentType element : EnumSet.allOf(HttpContentType.class)) {
if (element.toString().equals(value)) {
return element;
}
}
return null;
}
}
One option is to provide a different method to get the MIME string.
public interface IMimeTypeProvider {
public String toMimeType();
}
public enum HttpContentType implements IMimeTypeProvider {
TEXT_PLAIN
{
public String toMimeType() {
return "text/plain";
}
},
...
public String toMimeType() {
return "text/plain";
}
}
You might also consider adding a toDisplayString() method. The enumerator name is not necessarily a good display name, even in a single locale.
public enum HttpContentType {
TEXT_PLAIN("text/plain"), TEXT_CSS("text/css");
private String code;
HttpContentType(String code) {
this.code = code;
}
public String toString() {
return code;
}
public static HttpContentType getEnum(String code) {
if (code.equals("text/plain")) {
return TEXT_PLAIN;
} else if (code.equals("text/css")) {
return TEXT_CSS;
} else {
return null;
}
}
}

Categories

Resources