Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2019 Yak.Works - Licensed under the Apache License, Version 2.0 (the "License")
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package gorm.tools.databinding
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.concurrent.ConcurrentHashMap
import groovy.transform.CompileStatic
import org.apache.commons.lang3.StringUtils
import org.grails.core.artefact.AnnotationDomainClassArtefactHandler
import org.grails.core.artefact.DomainClassArtefactHandler
import org.grails.core.exceptions.GrailsConfigurationException
import org.grails.datastore.gorm.GormEnhancer
import org.grails.datastore.gorm.GormStaticApi
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.PersistentProperty
import org.grails.datastore.mapping.model.types.Association
import org.springframework.context.MessageSource
import org.springframework.core.convert.ConversionService
import org.springframework.core.convert.support.DefaultConversionService
import org.springframework.validation.AbstractBindingResult
import org.springframework.validation.BeanPropertyBindingResult
import org.springframework.validation.BindingResult
import org.springframework.validation.FieldError
import org.springframework.validation.ObjectError
import gorm.tools.utils.GormMetaUtils
import grails.databinding.DataBindingSource
import grails.databinding.SimpleDataBinder
import grails.databinding.SimpleMapDataBindingSource
import grails.databinding.converters.ValueConverter
import grails.databinding.events.DataBindingListener
import grails.gorm.validation.ConstrainedProperty
import grails.gorm.validation.DefaultConstrainedProperty
import grails.util.Environment
import grails.validation.ValidationErrors
import yakworks.commons.lang.ClassUtils
import yakworks.commons.lang.IsoDateUtil
import static gorm.tools.utils.GormMetaUtils.getEntityClass
/**
* Faster data binder for PersistentEntity.persistentProperties. Uses the persistentProperties to assign values from the Map
* Explicitly checks and converts most common property types eg (numbers and dates). Otherwise fallbacks to value converters.
*
* @author Joshua Burnett (@basejump)
* @since 6.1
*/
@SuppressWarnings("EmptyClass")
@CompileStatic
class EntityMapBinder extends SimpleDataBinder implements MapBinder {
/**
* A map that holds lists of properties which should be bound manually by a binder.
* A key represents a domain class and the value is a list with properties.*/
static final Map EXPLICIT_BINDING_LIST = new ConcurrentHashMap()
protected static final Map CLASS_TO_BINDING_INCLUDE_LIST = new ConcurrentHashMap()
protected MessageSource messageSource
boolean trimStrings = true
boolean convertEmptyStringsToNull = true
protected List listeners = []
protected ConversionService springConversionService
EntityMapBinder() {
this.springConversionService = new DefaultConversionService()
// registerConverter new ByteArrayMultipartFileValueConverter()
}
/**
* Binds data from a map on target object.
*
* @param obj The target object to bind
* @param source The data binding source
*/
@Override
void bind(Object obj, DataBindingSource source) {
bind obj, source, null, getBindingIncludeList(obj), null, null
}
/**
* Binds data from a map on target object.
*
* @param obj The target object to bind
* @param source The data binding source
* @param listener DataBindingListener
*/
@Override
void bind(Object obj, DataBindingSource source, DataBindingListener listener) {
bind obj, source, null, getBindingIncludeList(obj), null, listener
}
/**
* Binds data from a map on target object.
*
* @param obj The target object to bind
* @param source The data binding source
* @param filter Only properties beginning with filter will be included in the data binding.
* @param whiteList A list of property names to be included during this
* data binding. All other properties represented in the binding source
* will be ignored
* @param blackList A list of properties names to be excluded during
* this data binding.
* @param listener DataBindingListener
*/
@Override
void bind(Object object, DataBindingSource source, String filter, List whiteList, List blackList, DataBindingListener listener) {
String className = getEntityClass(object).name
BindingResult bindingResult = new BeanPropertyBindingResult(object, className)
doBind object, source, filter, whiteList, blackList, listener, bindingResult
}
@Override
protected void doBind(Object object, DataBindingSource source, String filter, List whiteList, List blackList,
DataBindingListener listener, Object errors) {
EntityMapBindingListener errorHandlingListener = new EntityMapBindingListener(messageSource)
fastBind(object, source, whiteList, errorHandlingListener, errors)
def bindingResult = (BeanPropertyBindingResult) errors
if(bindingResult.hasErrors()) populateErrors(object, bindingResult)
}
/**
* Binds data from a map on target object.
*
* @param args An optional map of options. supports two boolean options
*
*
include The list of properties to include in data binding
*
errorHandling If type conversion error should be handled and Added to Errors
*
*
* @param target The target object to bind
* @param source The data binding source
*/
@Override
void bind(Map args = [:], Object target, Map source) {
//BindAction bindAction = (BindAction) args["bindAction"]
List includeList = (List) args["include"] ?: getBindingIncludeList(target)
Boolean errorHandling = args["errorHandling"] == null ? true : args["errorHandling"]
if (errorHandling) {
bind target, new SimpleMapDataBindingSource(source), null, includeList, null, null
} else {
fastBind target, new SimpleMapDataBindingSource(source), includeList
}
}
/**
* Binds properties which specified in a white list on the given entity.
* In case the white list is empty method takes the list of persistent properties and iterates on them.
*
* @param target a target entity
* @param source a data binding source which contains property values
* @param whiteList a list which contains properties for binding
* @param listener DataBindingListener
* @param errors the errors object to add binding to
*/
void fastBind(Object target, DataBindingSource source, List whiteList = null, DataBindingListener listener =
null, Object errors = null) {
Objects.requireNonNull(target, "Target is null")
if (!source) return
GormStaticApi gormStaticApi = GormEnhancer.findStaticApi(target.getClass() as Class)
PersistentEntity entity = gormStaticApi.gormPersistentEntity
List properties = whiteList ?: entity.persistentPropertyNames
for (String prop : properties) {
PersistentProperty perProp = entity.getPropertyByName(prop)
try {
setProp(target, source, perProp, listener, errors)
} catch (Exception e) {
if (errors) {
addBindingError(target, perProp.name, source.getPropertyValue(perProp.name), e, listener, errors)
} else {
throw e
}
}
}
}
/**
* Quick way to convert a string to basic type such as Date, LocalDate, LocalDateTime and number
* if its a string it trims it and returns a null.
*
* @param sval the string value to parse to typeToConvertTo
* @param typeToConvertTo the Class to try and convert
* @return the converted object, or a NotParsed object if not converted
*/
static Object parseBasicType(String sval, Class typeToConvertTo){
Object valueToAssign = Boolean.FALSE
//do we have tests for this?
if (String.isAssignableFrom(typeToConvertTo)) {
valueToAssign = sval
} else if (Date.isAssignableFrom(typeToConvertTo)) {
valueToAssign = IsoDateUtil.parse(sval)
} else if (LocalDate.isAssignableFrom(typeToConvertTo)) {
valueToAssign = IsoDateUtil.parseLocalDate(sval)
//LocalDate.parse(val, DateTimeFormatter.ISO_DATE_TIME)
} else if (LocalDateTime.isAssignableFrom(typeToConvertTo)) {
valueToAssign = IsoDateUtil.parseLocalDateTime(sval)
} else if (Number.isAssignableFrom(typeToConvertTo)) {
valueToAssign = sval.asType(typeToConvertTo as Class)
} else {
valueToAssign = new UnParsed()
}
return valueToAssign
}
static class UnParsed {
// see good explanation of thread safe static instance stratgey https://stackoverflow.com/a/16106598/6500859
@SuppressWarnings('UnusedPrivateField')
private static class Holder {
private static final UnParsed INSTANCE = new UnParsed();
}
static UnParsed getInstance() {
return Holder.INSTANCE
}
}
/**
* Sets a value to a specified target's property.
*
* @param target a target entity
* @param source a data binding source which contains property values
* @param prop a persistent property which should be filled with the value
* @param listener DataBindingListener
* @param errors the erros object to add the binding errors to
*/
void setProp(Object target, DataBindingSource source, PersistentProperty prop,
DataBindingListener listener = null, Object errors = null) {
String propName = getPropertyNameToUse(source, prop)
if (!propName) return
Object propValue = source.getPropertyValue(propName)
Object valueToAssign = propValue
// if its string this wil get populated
Class> typeToConvertTo = prop.getType() as Class>
//try association first
if (prop instanceof Association) {
bindAssociation(target, valueToAssign, (Association) prop, listener, errors)
}
else if (propValue instanceof String) {
//trim and make null if empty
String sval = processStringValue(propValue)
Object parsedVal = sval
if(sval != null) {
//attempt all other basic conversions, only if value is not null/empty
parsedVal = parseBasicType(sval, typeToConvertTo)
// if its not a basic type then try the conversionHelpers
if (parsedVal instanceof UnParsed) {
if (conversionHelpers.containsKey(typeToConvertTo)) {
List convertersList = conversionHelpers.get(typeToConvertTo)
ValueConverter converter = convertersList?.find { ValueConverter c -> c.canConvert(propValue) }
if (converter) {
parsedVal = converter.convert(propValue)
}
} else if (springConversionService?.canConvert(propValue.getClass(), typeToConvertTo)) {
parsedVal = springConversionService.convert(propValue, typeToConvertTo as Class