package net.techreadiness.service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import net.techreadiness.persistence.AuditedBaseEntity;
import net.techreadiness.persistence.AuditedBaseEntityWithExt;
import net.techreadiness.persistence.dao.EntityDAO;
import net.techreadiness.persistence.dao.EntityDAO.EntityTypeCode;
import net.techreadiness.persistence.dao.EntityDataTypeDAO;
import net.techreadiness.persistence.dao.EntityFieldDAO;
import net.techreadiness.persistence.domain.EntityFieldDO;
import net.techreadiness.persistence.domain.OptionListValueDO;
import net.techreadiness.service.common.ValidationError;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.converters.DateConverter;
import org.apache.commons.beanutils.converters.DateTimeConverter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.drools.runtime.StatefulKnowledgeSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public abstract class BaseServiceWithValidationImpl extends BaseServiceImpl implements BaseServiceWithValidation {
private Logger log = LoggerFactory.getLogger(BaseServiceWithValidationImpl.class);
@Inject
protected EntityFieldDAO entityFieldDAO;
@Inject
private RuleService ruleService;
public BaseServiceWithValidationImpl() {
DateTimeConverter dtConverter = new DateConverter();
// TODO This list of patterns will need to be database driven in the future
// Also this kind of thing could be handled in the validation layer as well. Do the convert, validate the converted
// Date Obj etc....
String[] patterns = { "MM/dd/yyyy", "M/d/yyyy", "M/dd/yyyy", "MM/d/yyyy", "yyyy-MM-dd", "yyyy-M-d", "yyyy-M-dd",
"yyyy-MM-d", "yyyy-MM-dd HH:mm:ss.SSS" };
dtConverter.setPatterns(patterns);
ConvertUtils.register(dtConverter, java.util.Date.class);
}
public List<ValidationError> performCrossFieldValidation(ServiceContext context,
final Map<String, String> extAttributes, Long scopeId, EntityTypeCode entityType) {
List<ValidationError> errorList = new ArrayList<>();
if (extAttributes != null) {
StatefulKnowledgeSession ksession = ruleService.getRuleSessionForScopePath(context, scopeId, entityType);
if (ksession != null) {
ksession.setGlobal("errorList", errorList);
ksession.insert(extAttributes);
ksession.fireAllRules();
ksession.dispose();
}
}
return errorList;
}
@Override
public List<ValidationError> performValidation(Map<String, String> extAttributes, Long scopeId,
EntityDAO.EntityTypeCode code) {
List<ValidationError> errorList = Lists.newArrayList();
List<EntityFieldDO> entityFieldDOs = entityFieldDAO.findByScopePathAndType(scopeId, code);
if (extAttributes != null) {
// iterate through the map, and check for existence and valid use
for (EntityFieldDO entityFieldDO : entityFieldDOs) {
String attCode = entityFieldDO.getCode();
String attValue = extAttributes.get(attCode);
EntityDataTypeDAO.EntityDataTypeCodes dtCode = EntityDataTypeDAO.EntityDataTypeCodes.valueOf(entityFieldDO
.getEntityDataType().getCode().toUpperCase());
switch (dtCode) {
case STRING:
case DATE:
case DATETIME: {
if (validateRequired(entityFieldDO, attValue, errorList)) {
if (validateLength(entityFieldDO, attValue, errorList)) {
validateRegEx(entityFieldDO, attValue, errorList);
}
}
break;
}
case NUMBER: {
if (validateRequired(entityFieldDO, attValue, errorList)) {
if (validateLength(entityFieldDO, attValue, errorList)) {
if (validateNumeric(entityFieldDO, attValue, errorList)) {
validateRegEx(entityFieldDO, attValue, errorList);
}
}
}
break;
}
case BOOLEAN: {
validateRequired(entityFieldDO, attValue, errorList);
validateRegEx(entityFieldDO, attValue, errorList);
}
}
if (entityFieldDO.getOptionList() != null) {
String newValue = validateOption(entityFieldDO, attValue, errorList);
extAttributes.put(attCode, newValue);
}
}
}
return errorList;
}
private String validateOption(EntityFieldDO entityFieldDO, String attValue, List<ValidationError> errorList) {
if (StringUtils.isBlank(attValue)) {
return null;
}
for (OptionListValueDO listVal : entityFieldDO.getOptionList().getOptionListValues()) {
if (listVal.getValue().equalsIgnoreCase(attValue)) {
return listVal.getValue();
}
}
String errorMessage = messageSource.getMessage("validation.optionList", new Object[] { entityFieldDO.getName(),
attValue }, null);
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), errorMessage,
"validation.optionList", errorMessage));
return attValue;
}
private boolean validateNumeric(EntityFieldDO entityFieldDO, String attValue, List<ValidationError> errorList) {
if (StringUtils.isNotBlank(attValue) && !StringUtils.isNumeric(attValue)) {
String errorMessage = messageSource.getMessage("validation.numeric", new Object[] { entityFieldDO.getName() },
null);
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), errorMessage,
"validation.numeric", errorMessage));
return false;
}
return true;
}
protected void validateRegEx(EntityFieldDO entityFieldDO, String attValue, List<ValidationError> errorList) {
String regex = entityFieldDO.getRegex();
String value = attValue != null ? attValue : "";
if (StringUtils.isNotBlank(regex) && StringUtils.isNotBlank(value)) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(value);
if (!matcher.matches()) {
String msg = messageSource.getMessage("validation.regex", new Object[] { entityFieldDO.getName() }, null);
if (StringUtils.isNotBlank(entityFieldDO.getRegexDisplay())) {
msg = entityFieldDO.getName() + " " + entityFieldDO.getRegexDisplay();
}
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), msg, "validation.regex",
msg));
}
}
}
private boolean validateLength(EntityFieldDO entityFieldDO, String attValue, List<ValidationError> errorList) {
boolean isValid = true;
String value = attValue != null ? attValue : "";
if (StringUtils.isBlank(value)) {
return true;
}
if (entityFieldDO.getMinLength() != null) {
if (entityFieldDO.getMinLength().intValue() > value.length()) {
String errorMessage = messageSource.getMessage("validation.minLength",
new Object[] { entityFieldDO.getName(), entityFieldDO.getMinLength() }, null);
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), errorMessage,
"validation.minLength", errorMessage));
isValid = false;
}
}
if (entityFieldDO.getMaxLength() != null) {
if (entityFieldDO.getMaxLength().intValue() < value.length()) {
String errorMessage = messageSource.getMessage("validation.maxLength",
new Object[] { entityFieldDO.getName(), entityFieldDO.getMaxLength() }, null);
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), errorMessage,
"validation.maxLength", errorMessage));
isValid = false;
}
}
return isValid;
}
protected boolean validateRequired(EntityFieldDO entityFieldDO, String attValue, List<ValidationError> errorList) {
if (entityFieldDO.getRequired()) {
if (StringUtils.isBlank(attValue)) {
String errorMessage = entityFieldDO.getName() + " is required.";
errorList.add(new ValidationError(entityFieldDO.getCode(), entityFieldDO.getName(), errorMessage,
"validation.required", errorMessage));
return false;
}
}
return true;
}
protected void copyExtFieldsToCore(ServiceContext context, AuditedBaseEntityWithExt entity) {
copyMapFieldsToEntity(context, entity, entity.getExtAttributes());
}
protected void copyMapFieldsToEntity(ServiceContext context, AuditedBaseEntity entity, Map<String, String> map) {
Map<String, String> entityFields = entity.getAsMap();
Map<String, Method> methods = Maps.newHashMap();
for (Method method : entity.getClass().getMethods()) {
methods.put(method.getName(), method);
}
Iterator<Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, String> entry = iterator.next();
String key = entry.getKey();
String value = entry.getValue();
if (entityFields.containsKey(key)) {
try {
String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
if (methods.containsKey(methodName)) {
Method method = methods.get(methodName);
Class<?>[] parameters = method.getParameterTypes();
if (ArrayUtils.isNotEmpty(parameters) && parameters.length == 1) {
Object convertedValue;
if (value == null) {
convertedValue = null;
} else if (Number.class.isAssignableFrom(parameters[0]) && StringUtils.isEmpty(value)) {
convertedValue = null;
} else {
convertedValue = ConvertUtils.convert(value, parameters[0]);
}
method.invoke(entity, convertedValue);
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
log.warn("Unable to copy map entry: {}, to member variable on entity: {}.", key, entity.getClass());
}
}
}
}
protected EntityFieldDO getEntityFieldByScopeAndTypeAndCode(Long scopeId, EntityDAO.EntityTypeCode type, String code) {
return entityFieldDAO.findByScopeAndTypeAndCode(scopeId, type, code);
}
public void setEntityFieldDAO(EntityFieldDAO entityFieldDAO) {
this.entityFieldDAO = entityFieldDAO;
}
public void setRuleService(RuleService ruleService) {
this.ruleService = ruleService;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}