TableServicesImpl.java

/*
 * Copyright 2021 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gringlobal.service.impl;

import java.lang.reflect.Field;
import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.CaseUtils;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.gringlobal.compatibility.service.DataviewService;
import org.gringlobal.compatibility.service.impl.DataviewServiceImpl;
import org.gringlobal.custom.validation.javax.CodeValueField;
import org.gringlobal.model.CooperatorOwnedModel;
import org.gringlobal.model.SysTable;
import org.gringlobal.model.SysTableField;
import org.gringlobal.model.SysTableFieldLang;
import org.gringlobal.persistence.SysTableFieldLangRepository;
import org.gringlobal.persistence.SysTableFieldRepository;
import org.gringlobal.persistence.SysTableRepository;
import org.gringlobal.service.SysTableFieldTranslationService;
import org.gringlobal.service.SysTableMappingException;
import org.gringlobal.service.TableServices;
import org.gringlobal.service.filter.SysTableFieldFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ReflectionUtils;

/**
 * The Class TableServicesImpl.
 */
@Service
@Slf4j
public class TableServicesImpl implements TableServices {

	/**
	 * The Class SysDataviewServiceImpl.
	 */
	@Service
	@Transactional(readOnly = true)
	public static class SysTableServiceImpl extends CRUDServiceImpl<SysTable, SysTableRepository> implements SysTableService {

		@Override
		@Transactional
		public SysTable create(SysTable source) {
			return repository.save(source);
		}

		@Override
		@Transactional
		public SysTable update(SysTable updated, SysTable target) {
			target.setId(updated.getId());
			return repository.save(target);
		}

		@Override
		public SysTable load(String tableName) {
			return repository.findByTableName(tableName);
		}
		
	}

//	/**
//	 * The Class SysDataviewServiceImpl.
//	 */
//	@Service
//	@Transactional(readOnly = true)
//	public class SysTableLangServiceImpl extends CRUDServiceImpl<SysTableLang, SysTableLangRepository> implements SysTableLangService {
//		
//	}

	/**
	 * The Class SysDataviewServiceImpl.
	 */
	@Service
	@Transactional(readOnly = true)
	public static class SysTableFieldServiceImpl extends FilteredTranslatedCRUDServiceImpl<SysTableField, SysTableFieldLang, SysTableFieldTranslationService.TranslatedSysTableField, SysTableFieldFilter, SysTableFieldRepository> implements SysTableFieldService {

		@Autowired
		private DataviewService dataviewService;

		@Autowired
		private SysTableService sysTableService;

		public SysTableFieldServiceImpl() {
			super();
		}

		@Override
		public SysTableField get(String tableName, String fieldName) {
			return repository.findByTableNameAndFieldName(tableName, fieldName);
		}

		@Override
		@Transactional
		public SysTableField create(SysTableField source) {
			return repository.save(source);
		}

		@Override
		@Transactional
		public SysTableField createFast(SysTableField source) {
			return repository.save(source);
		}

		@Override
		@Transactional
		public SysTableField update(SysTableField updated, SysTableField target) {
			target.apply(updated);
			return repository.save(target);
		}

		@Override
		@Transactional
		public SysTableField updateFast(SysTableField updated, SysTableField target) {
			target.apply(updated);
			return repository.save(target);
		}

		@Override
		@Transactional
		public SysTableField generateMapping(String tableName, String tableFieldName) throws SysTableMappingException {
			var sysTableField = repository.findByTableNameAndFieldName(tableName, tableFieldName);

			if (sysTableField != null) {
//				repository.delete(sysTableField);
//				sysTableField = null;
			}

			var entityInfo = dataviewService.getEntityInfo(tableName);
			if (entityInfo == null) {
				throw new SysTableMappingException("Table " + tableName + " is not a GGCE entity.");
			}

			var sysTable = sysTableService.load(tableName);
			if (sysTable == null) {
				sysTable = new SysTable();
				sysTable.setTableName(tableName);
				sysTable.setIsEnabled("Y");
				sysTable.setIsReadonly("N");
				sysTable.setAuditsCreated("N");
				sysTable.setAuditsModified("N");
				sysTable.setAuditsOwned("N");
				if (CooperatorOwnedModel.class.isAssignableFrom(entityInfo.getTarget())) {
					sysTable.setAuditsCreated("Y");
					sysTable.setAuditsModified("Y");
					sysTable.setAuditsOwned("Y");
				} else if (AuditedVersionedModel.class.isAssignableFrom(entityInfo.getTarget())) {
					sysTable.setAuditsCreated("Y");
					sysTable.setAuditsModified("Y");
				}
				sysTable = sysTableService.create(sysTable);
			}

			if (sysTableField == null) {
				sysTableField = new SysTableField();
				sysTableField.setTable(sysTable);
				sysTableField.setIsReadonly("N");
				sysTableField.setFieldName(tableFieldName);
			}

			Field targetField = null;
			if (tableFieldName.endsWith("_id")) {
				if (tableFieldName.equalsIgnoreCase(tableName + "_id")) {
					targetField = ReflectionUtils.findField(entityInfo.getTarget(), "id");
				} else {
					String camelFieldName = CaseUtils.toCamelCase(tableFieldName.replaceFirst("_id$", ""), false, '_');
					targetField = ReflectionUtils.findField(entityInfo.getTarget(), camelFieldName);
				}
			} 

			if (targetField == null) {
				String camelFieldName = CaseUtils.toCamelCase(tableFieldName, false, '_');
				targetField = ReflectionUtils.findField(entityInfo.getTarget(), camelFieldName);
				if (targetField == null) {
					throw new SysTableMappingException("Field " + camelFieldName + " for " + tableFieldName + " not found in " + tableName);
				}
			}

			sysTableField.setFieldType(getFieldType(targetField.getType()));
			sysTableField.setFieldPurpose(getFieldPurpose(targetField));
			sysTableField.setIsPrimaryKey(isPrimaryKey(targetField) ? "Y" : "N");
			sysTableField.setIsNullable(isNullable(targetField) ? "Y" : "N");
			sysTableField.setIsForeignKey(isForeignKey(targetField) ? "Y" : "N");
			sysTableField.setIsAutoincrement(isAutoincrement(targetField) ? "Y" : "N");
			sysTableField.setMaxLength(getMaxLength(targetField));
			sysTableField.setDefaultValue(DataviewServiceImpl.NULLVALUE);

			var codeValueField = targetField.getAnnotation(CodeValueField.class);
			if (codeValueField != null) {
				// Annotated with @CodeValueField
				sysTableField.setGuiHint("SMALL_SINGLE_SELECT_CONTROL");
				sysTableField.setGroupName(codeValueField.value());

			} else if (sysTableField.getGuiHint() == null) {
				sysTableField.setGuiHint(getGuiHint(targetField));
			}


			log.warn("Mapping SysTableField {}.{}", sysTableField.getTable().getTableName(), sysTableField.getFieldName());
			return repository.save(sysTableField);
		}

		private String getGuiHint(Field targetField) {
			if (Double.class.isAssignableFrom(targetField.getType())) {
				return "DECIMAL_CONTROL";
			}
			if (Float.class.isAssignableFrom(targetField.getType())) {
				return "DECIMAL_CONTROL";
			}
			if (Number.class.isAssignableFrom(targetField.getType())) {
				return "INTEGER_CONTROL";
			}
			if (Date.class.isAssignableFrom(targetField.getType())) {
				return "DATE_CONTROL";
			}
			if (Calendar.class.isAssignableFrom(targetField.getType())) {
				return "DATE_CONTROL";
			}
			if (targetField.getName().startsWith("is")) {
				return "TOGGLE_CONTROL";
			}
			return "TEXT_CONTROL";
		}

		@Override
		public String getDeclaredFieldName(Field targetField) {
			var joinAnno = targetField.getAnnotation(JoinColumn.class);
			if (joinAnno != null && joinAnno.name() != null) {
				return joinAnno.name();
			}
			var columnAnno = targetField.getAnnotation(Column.class);
			if (columnAnno != null && columnAnno.name() != null) {
				return columnAnno.name();
			}
			return null;
		}

		@Override
		public int getMaxLength(Field targetField) {
			var columnAnno = targetField.getAnnotation(Column.class);
			if (columnAnno != null) {
				return columnAnno.length();
			}
			return 0;
		}

		@Override
		public boolean isAutoincrement(Field targetField) {
			return targetField.getAnnotation(GeneratedValue.class) != null;
		}

		@Override
		public boolean isForeignKey(Field targetField) {
			return targetField.getAnnotation(ManyToOne.class) != null;
		}

		@Override
		public boolean isNullable(Field targetField) {
			if (targetField.getType().isPrimitive()) {
				return false;
			}
			
			if (targetField.getAnnotation(NotNull.class) != null) {
				return false;
			}

			var columnAnno = targetField.getAnnotation(Column.class);
			if (columnAnno != null) {
				return columnAnno.nullable();
			}

			var joinAnno = targetField.getAnnotation(JoinColumn.class);
			if (joinAnno != null) {
				return joinAnno.nullable();
			}

			return true;
		}

		@Override
		public boolean isPrimaryKey(Field targetField) {
			return targetField.getAnnotation(Id.class) != null;
		}

		@Override
		public String getFieldPurpose(Field targetField) {

			// Map field to fieldPurpose
			if (targetField.getAnnotation(Id.class) != null) {
				return "PRIMARY_KEY";
			} else if (targetField.getAnnotation(LastModifiedDate.class) != null) {
				return "AUTO_DATE_MODIFY";
			} else if (targetField.getAnnotation(CreatedDate.class) != null) {
				return "AUTO_DATE_CREATE";
			} else if (targetField.getAnnotation(CreatedBy.class) != null) {
				return "AUTO_ASSIGN_CREATE";
			} else if (targetField.getAnnotation(LastModifiedBy.class) != null) {
				return "AUTO_ASSIGN_MODIFY";
			} else if (targetField.getName().equals("ownedBy")) {
				return "AUTO_ASSIGN_OWN";
			} else if (targetField.getName().equals("ownedDate")) {
				return "AUTO_DATE_OWN";
			}

			return "DATA";
		}

		@Override
		public String getFieldType(Class<?> type) {
			if (type == Integer.class) {
				return "INTEGER";
			} else if (type == String.class) {
				return "STRING";
			} else if (type == Long.class) {
				return "LONG";
			} else if (type == Date.class) {
				return "DATETIME";
			} else if (type == Calendar.class) {
				return "DATETIME";
			} else if (type == Instant.class) {
				return "DATETIME";
			} else if (type == UUID.class) {
				return "GUID";
			} else if (AuditedVersionedModel.class.isAssignableFrom(type)) {
				return "LONG";
			} else if (CooperatorOwnedModel.class.isAssignableFrom(type)) {
				return "INTEGER";
			}

			log.error("Unmanaged SysTableField type {}", type.getName());
			return null;
		}

	}

	@Component
	public static class SysTableFieldTranslationSupport extends BaseTranslationSupport<SysTableField, SysTableFieldLang, SysTableFieldTranslationService.TranslatedSysTableField, SysTableFieldFilter, SysTableFieldLangRepository> implements SysTableFieldTranslationService {

		public SysTableFieldTranslationSupport() {
			super();
		}

		@Override
		protected TranslatedSysTableField toTranslated(SysTableField e, String title, String description) {
			return TranslatedSysTableField.from(e, title, description);
		}
	}

//	/**
//	 * The Class SysDataviewServiceImpl.
//	 */
//	@Service
//	@Transactional(readOnly = true)
//	public class SysTableFieldLangServiceImpl extends CRUDServiceImpl<SysTableFieldLang, SysTableFieldLangRepository> implements SysTableFieldLangService {
//		
//	}

}