Spring MVC binding an enum to an input - spring

I'm having trouble creating an edit page. One of the inputs should be bound to part of the object model which is an enum:
public class BonusFormData implements Serializable {
private static final long serialVersionUID = 2L;
#JsonSerialize(
using = BonusTypeSerializer.class
)
#JsonDeserialize(
using = BonusTypeDeserializer.class
)
private BonusType bonusType;
.......
public class BonusCampaignRequest extends CampaignRequest implements Serializable {
private static final long serialVersionUID = 319551486808177424L;
...
private BonusFormData bonusFormData;
...
Then , I bind it to my model like so:
BonusCampaignRequest cr = loadCampaignRequest( campaignId, partnerId );
model.addAttribute("campaignRequest", cr);
When I attempt to bind an input to the bonusType field, it throws this error:
Caused by: java.lang.NumberFormatException: For input string: "undefined"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.valueOf(Integer.java:766)
at org.springframework.util.NumberUtils.parseNumber(NumberUtils.java:193)
at org.springframework.beans.propertyeditors.CustomNumberEditor.setAsText(CustomNumberEditor.java:113)
...
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "undefined"
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:115)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:78)
.....
Here's the BonusType class:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.gameaccount.isight.bonusengine.bonus.domain;
public enum BonusType {
NONE(0, "None"),
FIXED_BONUS(1, "Fixed Bonus"),
CASHBACK_BONUS(2, "Cashback Bonus"),
DEPOSIT_MATCH(3, "Deposit Match"),
PURCHASE_OFFER_BONUS(4, "Purchase Offer Bonus"),
FREE_SPINS(5, "Free spins"),
SPORTSBOOK(6, "Sportsbook Bonus"),
WIDGETS(7, "Widgets Bonus"),
UNKNOWN(999, "Unknown");
private final int value;
private final String description;
private BonusType(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return this.value;
}
public static BonusType fromInt(Integer fromValue) {
if (fromValue == null) {
return NONE;
} else {
switch(fromValue) {
case 0:
return NONE;
case 1:
return FIXED_BONUS;
case 2:
return CASHBACK_BONUS;
case 3:
return DEPOSIT_MATCH;
case 4:
return PURCHASE_OFFER_BONUS;
case 5:
return FREE_SPINS;
case 6:
return SPORTSBOOK;
case 7:
return WIDGETS;
default:
return UNKNOWN;
}
}
}
public static BonusType fromDescription(String description) {
BonusType[] var1 = values();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
BonusType bonusType = var1[var3];
if (bonusType.description.equalsIgnoreCase(description)) {
return bonusType;
}
}
return NONE;
}
public String getDescription() {
return this.description;
}
}
How do I bind an input field to an enum type within my data model?

Related

Return the built Enum to JSON

I have an object and an enum for it. When I give away an object, I want my enum inside the object to be displayed as an object with the name and value attributes without using DTO, or to be partially used. I want json to build this object itself (enum with name and value), and I give only the object in which this enum is contained.
public enum MyType {
TT("Time Tu"), TD("Time dust");
MyType(String value) {
this.value = value;
}
private String value;
public String getValue() {
return value;
}
#Override
public String toString() {
return value;
}
Here is the DTO, it may be necessary (get/set/constructor auto generated by Intellij Idea)
public class MyTypeWrapper {
private String name;
private String value;
}
#Entity
public class MyObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
private String number;
........
private MyType myType;
........
}
Perhaps serialization/deserialization is needed? How to do it?
It should go something like this:
{
.....
myType: {
"name: "TT",
"value: "TD"
},
.....
}
Perhaps this is a piece of the solution? But I'm not sure that it will work, and it's not clear how to serialize
public enum MyType {
......
#JsonValue
private MyTypeWrapper getWrapper()
return new MyTypeWrapper(this.name, this.value)
}
......
}
This turned out to be the solution
public enum MyType {
......
#JsonValue
private MyTypeWrapper getWrapper()
return new MyTypeWrapper(this.name, this.value)
}
......
}

How to send Enum data inside request object

I have created an enum like the below to store static dropdown value.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Days {
MONDAY(1,"Monday"), TUESDAY(2,"Tuesday"),THURSDAY(4,"Thursday"), FRIDAY(5,"Friday"), SATURDAY(6,"Saturday"),
SUNDAY(7,"Sunday");
private final Integer key;
private final String value;
Days(Integer key, String value) {
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public String getValue() {
return value;
}
}
I have written a below endpoint to list enum data as a JSON response
#GetMapping("/getenums")
public List<Days> getenums() {
return Arrays.asList(Days.values());
}
which results in an array of JSON Objects like below
[{"key":1,"value":"Monday"},{"key":2,"value":"Tuesday"},{"key":4,"value":"Thursday"},{"key":5,"value":"Friday"},{"key":6,"value":"Saturday"},{"key":7,"value":"Sunday"}]
I have sample domain like below
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String name;
private Days day;
private Month month;
}
Below is my controller,
#PostMapping("/user")
public User createInstructor(#RequestBody User user) {
return userRepository.save(user);
}
While trying to save the user using the below request data getting bad request how can I resolve this
{
"name": "Pradeep",
"day": {
"key": 1,
"value": "Monday"
},
"month": "JUNE"
}
If you really want to return the complete enum object in your REST API then one solution could be adding a custom deserializer to it:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Days {
MONDAY(1,"Monday"), TUESDAY(2,"Tuesday"),THURSDAY(4,"Thursday"), FRIDAY(5,"Friday"), SATURDAY(6,"Saturday"),
SUNDAY(7,"Sunday");
private final Integer key;
private final String value;
Days(Integer key, String value) {
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public String getValue() {
return value;
}
#JsonCreator
public static Days fromObject(Map<String, Object> obj) {
if (obj != null && obj.containsKey("key") && obj.containsKey("value")) {
Integer key = obj.get("key");
String value = obj.get("value");
if (key != null) {
for (Days day : Days.values()) {
if (key.equals(e.getKey())) {
return day;
} else {
return null;
}
}
}
return null;
}
return null;
}
}
Additionally, you might want to tell Hibernate how to store your Enum in the database. You can use #Enumerated(EnumType.STRING) for that as follows:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String name;
#Enumerated(EnumType.STRING)
private Days day;
private Month month;
}
This will store it as a String. The other option is ORDINAL, which will persist the Enum as an Integer.

Genson not using my custom Converter for inner structure in JerseyTest

I have a class hierarchy that is somewhat like below, with a custom Converter
The FieldValueConverter#deserialize is NOT being called in my JerseyTest. Instead, it uses the default GensonJsonConverter which complains it can't find the appropriate constructor. (Caused by: com.owlike.genson.JsonBindingException: No constructor has been found for type class com.searchdata.actions.api.FieldValue)
How do I get it to be used?
Registration
The converter for the FieldValues (see below), which I register in a Jersey Application like this:
Genson genson = new GensonBuilder()
.withBundle(new JAXBBundle())
.withConverter(new FieldValueConverter(), FieldValue.class)
.setSkipNull(true)
.create();
register(new GensonJaxRSFeature().use(genson));
FieldValueConverter
public class FieldValueConverter implements Converter<FieldValue> {
private static final Logger LOG = LoggerFactory.getLogger(FieldValueConverter.class);
public void serialize(FieldValue fieldValue, ObjectWriter writer, Context ctx) throws Exception {
LOG.info("Serializing fieldValue:{}", fieldValue);
writer.beginObject();
writer.writeString("type", fieldValue.getType().name())
.writeString("value", fieldValue.getValue().toString())
.writeString("field", fieldValue.getField());
writer.endObject();
LOG.info("..Done!", fieldValue);
}
/* You don't have to worry for the object being null here, if it is null Genson will
handle it for you. */
public FieldValue deserialize(ObjectReader reader, Context ctx) throws Exception {
LOG.info("Deserializing fieldValue...");
reader.beginObject();
String stringValue=null;
FieldType type= FieldType.STRING;
String fieldKey= null;
while (reader.hasNext()) {
reader.next();
if ("type".equals(reader.name())) {
type = FieldType.valueOf(reader.valueAsString());
} else if ("field".equals(reader.name())) {
fieldKey = reader.valueAsString();
} else if ("value".equals(reader.name())) {
stringValue = reader.valueAsString();
} else {
reader.skipValue();
}
}
Item
public class Item
{
#Schema(name = "id", description = "The id of an item")
private String id;
#Schema(name = "values", description = "The fields with values for this action")
private List<FieldValue> values;
}
FieldValue
#Schema(name = "FieldValue")
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class FieldValue {
#Schema(name = "field", description = "The technical name of the field")
private String field;
#Schema(name = "type", description = "The type of the field")
private FieldType type;
#Schema(name = "value", description = "The value of a field", oneOf = {Integer.class, String.class, Date.class, Double.class})
private Serializable value;
public FieldValue(final String field, final String string) {
setField(field);
setValue(string);
setType(FieldType.STRING);
}
public FieldValue(final String field, final Long number) {
setField(field);
setValue(number);
setType(FieldType.LONG);
}

How do I insert values of elements that are part of the EmbeddedId in JPA?

I have a case where I need to execute an insert statement via createNativeQuery. I have an entity list I'm looping through in order to set the properties accordingly from another bean class, and then persist that data to the oracle database.
The problem I am facing is persisting the data that is part of the embeddedId (item, loc, weekstart, type, forecastId, insertTS). I need to persist that data for the new records to be inserted into the database. When I try to set the values from the POJO bean to my set method for the properties of my entity bean, nothing happens. Below is my code for setting the values of the properties from the POJO bean to my entity bean, along with my persistence method and the insert query being executed:
Validation class where validation occurs beforehand (missing to get the point) that includes the setting of my entity properties from the POJO bean:
List <InsertPromoData> insertPromos = new ArrayList<InsertPromoData>();
promo.forEach(record -> {
if (record.getErrorList().size() == 0) {
rowsSuccessful++;
Util.writeSuccessToFile(templateCd, successFile, record, successFields);
try {
InsertPromoData insertData = new InsertPromoData();
insertData.getId().setItem(record.getItem());
insertData.getId().setLoc(record.getLoc());
insertData.getId().setWeekStart(record.getWeek_Start_Date());
insertData.setNumberOfWeeks(record.getNumber_Of_Weeks());
insertData.getId().setType(record.getType());
insertData.getId().setForecastId(record.getForecast_ID());
insertData.setQty(record.getUnits());
insertPromos.add(insertData);
}
catch (Exception e) {
logger.error("Error with setting insertPromolist from promo list values and the error is " + e.getMessage());
}
}
else {
if (rowsFailure == 0) {
Util.writeHeaderToFile(templateCd, errorFile);
}
rowsFailure++;
Util.writeErrorToFile(templateCd, errorFile, record, record.getErrorList());
}
});
errorFile.close();
successFile.close();
OracleImpl.insertPromoData(insertPromos);
POJO bean (promo is the variable representing this list of beans in validation class above):
public class PromoBean extends ErrorListBean
{
public String Item;
public String Loc;
public String Week_Start_Date;
public String Units;
public String Forecast_ID;
public String Type;
public String Number_Of_Weeks;
public String getItem() {
return Item;
}
public void setItem(String item) {
Item = item;
}
public String getLoc() {
return Loc;
}
public void setLoc(String loc) {
Loc = loc;
}
public String getWeek_Start_Date() {
return Week_Start_Date;
}
public void setWeek_Start_Date(String week_Start_Date) {
Week_Start_Date = week_Start_Date;
}
public String getNumber_Of_Weeks() {
return Number_Of_Weeks;
}
public void setNumber_Of_Weeks(String number_Of_Weeks) {
Number_Of_Weeks = number_Of_Weeks;
}
public String getType() {
return Type;
}
public void setType(String type) {
Type = type;
}
public String getForecast_ID() {
return Forecast_ID;
}
public void setForecast_ID(String forecast_ID) {
Forecast_ID = forecast_ID;
}
public String getUnits() {
return Units;
}
public void setUnits(String units) {
Units = units;
}
}
Embeddable class representing the composite primary key of the table:
#Embeddable
public class PromoID implements Serializable {
#Column(name = "ITEM")
private String item;
#Column(name = "LOC")
private String loc;
#Column(name = "WK_START")
private String weekStart;
#Column(name = "TYPE")
private String type;
#Column(name = "FCSTID")
private String forecastId;
#Column(name = "U_TIMESTAMP")
private String insertTS;
public PromoID() {
}
public PromoID (String item, String loc, String weekStart, String type, String forecastId, String insertTS) {
this.item = item;
this.loc = loc;
this.weekStart = weekStart;
this.type = type;
this.forecastId = forecastId;
this.insertTS = insertTS;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public String getWeekStart() {
return weekStart;
}
public void setWeekStart(String weekStart) {
this.weekStart = weekStart;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getForecastId() {
return forecastId;
}
public void setForecastId(String forecastId) {
this.forecastId = forecastId;
}
public String getInsertTS() {
return insertTS;
}
public void setInsertTS(String insertTS) {
this.insertTS = insertTS;
}
//hashcode and equals methods
Persistence Bean:
#Entity
#Table(name = "U_USER_PROMO")
public class InsertPromoData {
#EmbeddedId
private PromoID id;
#Column(name="NUMBER_OF_WEEKS")
String numberOfWeeks;
#Column(name="QTY")
String qty;
#Id
#AttributeOverrides(
{
#AttributeOverride(name = "item",column = #Column(name="ITEM")),
#AttributeOverride(name = "loc", column = #Column(name="LOC")),
#AttributeOverride(name = "weekStart", column = #Column(name="WK_START")),
#AttributeOverride(name = "type", column = #Column(name="TYPE")),
#AttributeOverride(name = "forecastId", column = #Column(name="FCSTID"))
}
)
public PromoID getId() {
return id;
}
public void setId(PromoID id) {
this.id = id;
}
public String getNumberOfWeeks() {
return numberOfWeeks;
}
public void setNumberOfWeeks(String numberOfWeeks) {
this.numberOfWeeks = numberOfWeeks;
}
public String getQty() {
return qty;
}
public void setQty(String qty) {
this.qty = qty;
}
}
DAO class method to execute the update (entitymanagerfactory emf already initialized):
public static void insertPromoData(List<InsertPromoData> insertData) {
logger.debug("Execution of method insertPromoData in Dao started");
System.out.println("Size of the insertData list is " + insertData.size());
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
System.out.println("Beginning transaction for insertPromoData");
Query query = em.createNativeQuery(env.getProperty("insertPromoUploadData"));
for (InsertPromoData promoData : insertData) {
query.setParameter("item", promoData.getId().getItem());
query.setParameter("location", promoData.getId().getLoc());
query.setParameter("wkStart", promoData.getId().getWeekStart());
query.setParameter("numberOfWeeks", promoData.getNumberOfWeeks());
query.setParameter("type", promoData.getId().getType());
query.setParameter("fcstId", promoData.getId().getForecastId());
query.setParameter("quantity", promoData.getQty());
query.executeUpdate();
}
em.getTransaction().commit();
}
catch(Exception e) {
logger.error("Exception in beginning transaction");
e.printStackTrace();
}
finally {
em.clear();
em.close();
}
logger.debug("Execution of method insertPromoData in Dao ended");
}
Query in properties file:
insertPromoUploadData = INSERT INTO {h-schema}U_USER_PROMO (ITEM, LOC, WK_START, NUMBER_OF_WEEKS, TYPE, FCSTID, QTY, U_TIMESTAMP) VALUES (:item, :location, TO_DATE(:wkStart,'MM DD YYYY'), :numberOfWeeks, :type, :fcstId, :quantity, SYSDATE)
My list size from my DAO class is returning as 0 once I begin the transaction and not sure why it is empty. Is there a reason that it is empty? I'm trying to persist each of the fields to the database (including the composite key fields) via insert query. Any help appreciated.
After looking into this for hours, I finally came to the conclusion that the simplest way to executeUpdate() without running into issues due to my current #EmbeddedId/#Embeddable logic was to change it to use #IdClass for my composite PK class, and annotate the fields from the PK in my entity with #Id. This allowed my data to be persisted to the database. Another slight difference was adding the insertTS in my entity class and annotating with #Id and generating getters/setters. This was necessary for JPA to recognize all the properties being referenced that I am wanting to persist, though I am persisting insertTS using SYSDATE function from the oracle DB instead of utilizing the get/set methods and setting to the current time from the java side.
I am sure there is a way to use #EmbeddedId/#Embeddable logic and be able to persist the fields that are part of the EmbeddedId, however, this I found to be a more simplistic way of doing it without further complexity in the code.

Hibernate CompositeUserType mapping has wrong number of columns

I am new to Hibernate. Writing a CompositeUserType. When I run the code I am getting error.
property
mapping has wrong number of columns:
Please help me what am I missing?
My CompositeUserType goes as follows
public class EncryptedAsStringType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String[] { "stockId", "stockCode", "stockName","stockDescription" };
}
#Override
public Type[] getPropertyTypes() {
//stockId, stockCode,stockName,modifiedDate
return new Type[] {
Hibernate.INTEGER, Hibernate.STRING, Hibernate.STRING,Hibernate.STRING
};
}
#Override
public Object getPropertyValue(final Object component, final int property)
throws HibernateException {
Object returnValue = null;
final Stock auditData = (Stock) component;
if (0 == property) {
returnValue = auditData.getStockId();
} else if (1 == property) {
returnValue = auditData.getStockCode();
} else if (2 == property) {
returnValue = auditData.getStockName();
} return returnValue;
}
#Override
public void setPropertyValue(final Object component, final int property,
final Object setValue) throws HibernateException {
final Stock auditData = (Stock) component;
}
#Override
public Object nullSafeGet(final ResultSet resultSet,
final String[] names,
final SessionImplementor paramSessionImplementor, final Object paramObject)
throws HibernateException, SQLException {
//owner here is of type TestUser or the actual owning Object
Stock auditData = null;
final Integer createdBy = resultSet.getInt(names[0]);
//Deferred check after first read
if (!resultSet.wasNull()) {
auditData = new Stock();
System.out.println(">>>>>>>>>>>>"+resultSet.getInt(names[1]));
System.out.println(">>>>>>>>>>>>"+resultSet.getString(names[2]));
System.out.println(">>>>>>>>>>>>"+resultSet.getString(names[3]));
System.out.println(">>>>>>>>>>>>"+resultSet.getString(names[4]));
}
return auditData;
}
#Override
public void nullSafeSet(final PreparedStatement preparedStatement,
final Object value, final int property,
final SessionImplementor sessionImplementor)
throws HibernateException, SQLException {
if (null == value) {
} else {
final Stock auditData = (Stock) value;
System.out.println("::::::::::::::::::::::::::::::::"+auditData.getStockCode());
System.out.println("::::::::::::::::::::::::::::::::"+auditData.getStockDescription());
System.out.println("::::::::::::::::::::::::::::::::"+auditData.getStockId());
System.out.println("::::::::::::::::::::::::::::::::"+auditData.getStatus());
}
}
My Domain class Stock has five attributes. (stockId,stockCode,StockName,Status , Stock
Description)
I need to declare the field Stock description as Composite field Type.
private Integer stockId;
private String stockCode;
private String stockName;
private String status;
private String stockDescription;
//Constructors
#Column(name = "STOCK_CC", unique = true, nullable = false, length = 20)
#Type(type="com.mycheck.EncryptedAsStringType")
#Columns(columns = { #Column(name="STOCK_ID"),
#Column(name="STOCK_CODE"),
#Column(name="STOCK_NAME")
})
public String getStockDescription() {
return stockDescription;
}
}
When I try to execute a insert for Stock. I am getting the error Error creating bean with name
'sessionFactory' defined in class path resource [spring/config/../database/Hibernate.xml]:
Invocation of init method failed. nested exception is org.hibernate.MappingException:
property mapping has wrong number of columns: com.stock.model.Stock.stockDescription type:
com.mycheck.EncryptedAsStringType
Where am I going wrong ?
One can extract the answer from the code samples and the comments to the original question, but to save everyone some reading, I've compiled a quick summary.
If you declare a CompositeUserType that maps a type to n columns, you have to declare n columns in #Columns besides the #Type annotation. Example:
public class EncryptedAsStringType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String[] { "stockId", "stockCode", "stockName","stockDescription" };
}
// ...
}
This CompositeUserType maps to 4 separate columns, therefore 4 separate #Column annotations have to be declared:
#Type(type="com.mycheck.EncryptedAsStringType")
#Columns(columns = {
#Column(name="STOCK_ID"),
#Column(name="STOCK_CODE"),
#Column(name="STOCK_NAME"),
#Column(name="STOCK_DESCRIPTION")
})
public String getStockDescription() {
return stockDescription;
}
That's it and Hibernate is happy.

Resources