Grails, property names in embedded object in domain class causing problems - grails

I'm using grails 2.3.4 and I have a domain class that embeds an object. The embedded object has a property called 'version' and it seems that this is conflicting with the 'version'-field automatically added to the database-table by GORM. The result is that the 'version'-field belonging to my embedded object isn't created in the database and as a consequence my application doesn't work properly.
My code looks like this:
class Thing {
String someText
EmbeddedThing embeddedThing
Date someDate
static embedded = ['embeddedThing']
static constraints = {
embeddedThing(unique: true)
}
}
class EmbeddedThing {
String textOfSomeSort
String version
String textOfSomeOtherSort
}
You might think that a quick fix is to rename the 'version'-property of the embedded object but the class belongs to an included sub-project (i.e. a JAR-file) that I'm not allowed to touch since other projects use it. So the solution needs to be done completely within my domain class, or at least in a manner that doesn't change the class of the embedded object.

version is a special column name, you should rename your version field within your EmbeddedThin class

I actually found a solution to this problem by using a Hibernate UserType to represent the EmbeddedThing-class.
My code now looks like this and works perfectly:
Thing.groovy:
import EmbeddedThingUserType
class Thing {
String someText
EmbeddedThing embeddedThing
Date someDate
static embedded = ['embeddedThing']
static mapping = {
version false
embeddedThing type: EmbeddedThingUserType, {
column name: "embedded_thing_text"
column name: "embedded_thing_version"
column name: "embedded_thing_other_text"
}
}
static constraints = {
embeddedThing(unique: true)
}
}
EmbeddedThing.groovy:
class EmbeddedThing {
String textOfSomeSort
String version
String textOfSomeOtherSort
}
EmbeddedThingUserType.groovy:
class EmbeddedThingUserType implements UserType {
int[] sqlTypes() {
return [StringType.INSTANCE.sqlType(),
StringType.INSTANCE.sqlType(),
StringType.INSTANCE.sqlType()]
}
Class returnedClass() {
return EmbeddedThing
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException {
if (resultSet && names) {
return new EmbeddedThing(
textOfSomeSort: resultSet?.getString(names[0] ?: '_missing_textOfSomeSort_'),
version: resultSet?.getString(names[1] ?: '_missing_version_'),
textOfSomeOtherSort: resultSet?.getString(names[2] ?: '_missing_textOfSomeOtherSort_'))
} else {
return null
}
}
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index)
throws HibernateException, SQLException {
if (value != null) {
preparedStatement.setString(index, value?.textOfSomeSort)
preparedStatement.setString(index + 1, value?.version)
preparedStatement.setString(index + 2, value?.textOfSomeOtherSort)
} else {
preparedStatement.setString(index, '_missing_textOfSomeSort_')
preparedStatement.setString(index + 1, '_missing_version_')
preparedStatement.setString(index + 2, '_missing_textOfSomeOtherSort_')
}
}
#Override
public boolean isMutable() {
return false
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x.equals(y)
}
#Override
public int hashCode(Object x) throws HibernateException {
assert (x != null)
return x.hashCode()
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value
}
#Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value
}
#Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached
}
}
Config.groovy:
grails.gorm.default.mapping = {
'user-type'( type: EmbeddedThingUserType, class: EmbeddedThing)
}

Please try version false in your 'static mapping', for the 'EmbeddedThing' class.

Related

Jackson Object mapper how to Serialize object as String which is having nested object?

I have json like following
{"data": [
{
"instance": { ...
"inner"" {....
.............}
}
}]
"isvalid":true
"nextVal" : <some num>
}
and POJO like
class A{
private String data;
private boolean isvalid;
private String nextVal;
//with getter setters and proper jackson annotations
}
These can have variable structure inside data, so with object mapper.read I want to take entire data object in string!
have tried direct serialization to my simple object which obviously gives error and also tried JSONNode
mapper.readValue(jsonString, JsonNode.class);
String content = node.get("data").textValue();
This returns blank
anyway I can achieve that to take entire data object value in string with objectmapper?
I tried toString and returned just fine what I wanted - entire data object as String
JsonNode node = (ObjectNode) mapper.readValue(jsonString, JsonNode.class);
node.get("data").toString();
The reason it returns blank is because, data is an array. You need to deseralise it in to JsonArray. Assuming your JSON structure as below,
{
"data": [
{"instance": {
"inner": {
"id": "1"
}
}
}],
"isvalid": true,
"nextVal": 1
}
This will be deserialised using below code (in JSONNode),
List<JsonNode> list = node.findValues("data");
for(JsonNode n: list){
JsonNode in1 = n.findValue("instance");
JsonNode in2 = in1.findValue("inner");
String abc = in2.findValue("id").textValue();
System.out.println(abc);
}
You need to have the POJO structure as shown above. The data will be list of instance object. instance object will have to have inner object.
Update:
Outer node = mapper.readValue(jsonstr, Outer.class);
The classes which needs to be created would be as shown below.
public class Outer {
private List<Data> data;
Boolean valid;
Integer nextval;
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
public Boolean isValid() {
return valid;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public Integer getNextval() {
return nextval;
}
public void setNextval(Integer nextval) {
this.nextval = nextval;
}
}
public class Data {
Instance instance;
public Instance getInstance() {
return instance;
}
public void setInstance(Instance instance) {
this.instance = instance;
}
}
public class Instance {
private Inner inner;
public Inner getInner() {
return inner;
}
public void setInner(Inner inner) {
this.inner = inner;
}
}
public class Inner {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

Polymorphism with swagger not working as expected

I am using springfox version 2.9.2 and swagger annotations 1.5.x. The ApiModel annotations support the discriminator, subTypes and parent attribute which are required to make polymorphism work but I am not seeing the correct apidocs generated to enable polymorphism.
Here is my annotated code.
#RestController
#RequestMapping("/api/vehicles")
public class VehicleController {
private static final Logger LOGGER = LoggerFactory.getLogger(VehicleController.class);
#PostMapping(consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
void post(#RequestBody Vehicle anyVehicle) {
LOGGER.info("Vehicle : {}", anyVehicle);
}
}
#ApiModel(discriminator = "type", subTypes = {Car.class, Bike.class})
public class Vehicle {
String brand;
String type;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
#ApiModel(parent = Vehicle.class)
public class Car extends Vehicle {
int noOfDoors;
boolean powerWindows;
public int getNoOfDoors() {
return noOfDoors;
}
public void setNoOfDoors(int noOfDoors) {
this.noOfDoors = noOfDoors;
}
public boolean isPowerWindows() {
return powerWindows;
}
public void setPowerWindows(boolean powerWindows) {
this.powerWindows = powerWindows;
}
}
#ApiModel(parent = Vehicle.class)
public class Bike extends Vehicle {
boolean pillion;
public boolean isPillion() {
return pillion;
}
public void setPillion(boolean pillion) {
this.pillion = pillion;
}
}
When the docs get generated is basically shows one endpoint which handles a POST request and takes in a Vehicle as the model.
Is what I am doing here supposed to work? Can someone point me to a working example of this with SpringFox that I can look at?
Support for discriminator is not available in Swagger UI yet. You can follow these issues for status updates:
Discriminator does not switch schema
subTypes not displayed in model

Vaadin Validate date, not empty

Im trying to write validation in Vaadin but I don't understand how to check if date field is empty
I wrote something like this
#Override
public void setConfiguration(EditorConfiguration editorConfiguration) {
boolean required = ((DateFieldConfiguration) editorConfiguration).isRequired();
if (required == true) {
setRequiredIndicatorVisible(true);
addValueChangeListener(event -> validate(event.getSource().getDefaultValidator(), event.getValue()));
}
}
private void validate(Validator<LocalDate> defaultValidator, LocalDate localDate) {
binder.forField(this).withValidator(validator).asRequired("Mandatory").bind(s -> getValue(),
(b, v) -> setValue(v));
}
I have achived a validation with a text field:
String Validator code
public class VaadinStringEditor extends TextField implements HasValueComponent<String> {
/**
*
*/
private static final long serialVersionUID = 6271513226609012483L;
private Binder<String> binder;
#PostConstruct
public void init() {
setWidth("100%");
binder = new Binder<>();
}
#Override
public void initDefaults() {
setValue("");
binder.validate();
}
#Override
public void setConfiguration(EditorConfiguration editorConfiguration) {
Validator<String> validator = ((TextFieldConfiguration) editorConfiguration).getValidator();
if (validator != null) {
binder.forField(this).withValidator(validator).asRequired("Mandatory").bind(s -> getValue(),
(b, v) -> setValue(v));
}
and I valid it here:
question.setEditorConfiguration(new TextFieldConfiguration(textRequiredValidator()));
Validator:
private Validator<String> textRequiredValidator() {
return Validator.from(v -> v != null && StringUtils.trimAllWhitespace((String) v).length() != 0,
, "Not empty");
}
You should use com.vaadin.ui.DateField for LocalDate values. Have a look at the following example.
Example bean:
public class MyBean {
private LocalDate created;
public LocalDate getCreated() {
return created;
}
public void setCreated(LocalDate created) {
this.created = created;
}
}
Editor
DateField dateField = new DateField("Date selector");
binder.forField(dateField)
.bind(MyBean::getCreated, MyBean::setCreated);
If for some reason you would like to have com.vaadin.ui.TextField for editing date, then you need to set converter like this:
Binder<MyBean> binder = new Binder<>();
TextField textDateField = new TextField("Date here:");
binder.forField(textDateField)
.withNullRepresentation("")
.withConverter(new StringToLocalDateConverter())
.bind(MyBean::getCreated, MyBean::setCreated);
Converter implementation:
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
#Override
public Result<LocalDate> convertToModel(String userInput, ValueContext valueContext) {
try {
return Result.ok(LocalDate.parse(userInput));
} catch (RuntimeException e) {
return Result.error("Invalid value");
}
}
#Override
public String convertToPresentation(LocalDate value, ValueContext valueContext) {
return Objects.toString(value, "");
}
}
Note that this converter does not utilise ValueContext object that contains information that should be taken into account in more complex cases. For example, user locale should be handled.

Adapt field to store to database

Say I have a field content that is a json. I would like to store it in database so that my domain class keeps only the 1 field only. (It's more of a brain task ;-)
class MyDomain{
def content
static constraints = {
content nullable: false, blank: false, sqlType: "text" // adapter from JSON to String??
}
def beforeInsert(){
content = content.toString()
}
def beforeUpdate(){
content = content.toString()
}
def afterInsert(){
content = JSON.parse(content) as JSON
}
def afterUpdate(){
content = JSON.parse(content) as JSON
}
def onLoad(){
content = JSON.parse(content) as JSON
}
}
I want my domain object to expose only content so I don't want to use another field like String contentAsText because it would be visible outside.
In the whole GORM documentation I haven't found a thing how to manage it. I've tried beforeValidate()/beforeInsert() and onLoad() methods but no luck...
How can I adapt the value before it gets persisted?
You can define a custom hibernate user-type for JSONElement as described here: https://stackoverflow.com/a/28655708/607038
In domain class constraints:
static constraints = {
content type: JSONObjectUserType
}
User Type Class:
import org.grails.web.json.JSONObject
import org.hibernate.HibernateException
import org.hibernate.engine.spi.SessionImplementor
import org.hibernate.type.StandardBasicTypes
import org.hibernate.usertype.EnhancedUserType
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Types
class JSONObjectUserType implements EnhancedUserType, Serializable {
private static final int[] SQL_TYPES = [Types.VARCHAR]
#Override
public int[] sqlTypes() {
return SQL_TYPES
}
#Override
public Class returnedClass() {
return JSONObject.class
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true
}
if (x == null || y == null) {
return false
}
JSONObject zx = (JSONObject) x
JSONObject zy = (JSONObject) y
return zx.equals(zy)
}
#Override
public int hashCode(Object object) throws HibernateException {
return object.hashCode()
}
#Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Object jsonObject = StandardBasicTypes.STRING.nullSafeGet(resultSet, names, session, owner)
if (jsonObject == null) {
return null
}
return new JSONObject((String) jsonObject)
}
#Override
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (value == null) {
StandardBasicTypes.STRING.nullSafeSet(preparedStatement, null, index, session)
} else {
JSONObject jsonObject = (JSONObject) value
StandardBasicTypes.STRING.nullSafeSet(preparedStatement, jsonObject.toString(), index, session)
}
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value
}
#Override
public boolean isMutable() {
return false
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value
}
#Override
public Object assemble(Serializable cached, Object value) throws HibernateException {
return cached
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original
}
#Override
public String objectToSQLString(Object object) {
throw new UnsupportedOperationException()
}
#Override
public String toXMLString(Object object) {
return object.toString()
}
#Override
public Object fromXMLString(String string) {
return new JSONObject(string)
}
}
class MyDomain{
JSONElement content
static constraints = {
content nullable: false, blank: false, sqlType: "text" // adapter from Map to String??
}
def setContent(String textContent){
content = JSON.parse(textContent)
}
}
I had to do 2 things.
replace def content with JSON content so that it gets persisted, see Grails Domain Constructor is not Groovy Constructor
Convert a json string back to json via def setContent().
As content is JSONElement use JSONObject and JSONArray as concrete classes.

spring-data-elastic Id field not populated on reads when using CustomEntityMapper

Elasticversion - 1.7.6
springboot - 1.3.5
Using spring-data-elasticsearch I have created a custom JSON mapping as advised elsewhere in order to support Java8 new datetime fields.
This works fine - but breaks reading entities from the repository as the id field no longer gets populated.
CustomConfig:
#Bean
#Autowired
public ElasticsearchTemplate elasticsearchTemplate(Client client) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return new ElasticsearchTemplate(client, new CustomEntityMapper(objectMapper));
}
public class CustomEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
public CustomEntityMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
Sample Entity :
#Document(indexName = "scanner", type = "Entry")
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Entry {
#Id
private String id;
#Field(type= FieldType.String)
private String path;
#Field(type = FieldType.Date, format = DateFormat.date_time )
private OffsetDateTime created;
}
Note - that when I remove the CustomEntityMapper the id field is returned. I have traced the spring-data-elasticsearch code,
and identified that it fails to resolve the Id field from the elastic response in DefaultResultMapper.setPersistentId since
the mappingContext is null.
private <T> void setPersistentEntityId(T result, String id, Class<T> clazz) {
if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) {
PersistentProperty<ElasticsearchPersistentProperty> idProperty = mappingContext.getPersistentEntity(clazz).getIdProperty();
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
Method setter = idProperty.getSetter();
if (setter != null) {
try {
setter.invoke(result, id);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
}
Has anyone experienced this issue? How can I support a CustomEntityMapper without breaking the Id resolution?
upgrading to spring boot 1.4.1-RELEASE resolved the issue

Resources